/*-
 * ~~~~~~licensing~~~~~~
 * atlasmap-entaxy-services
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
/*
 * Copyright (C) 2017 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.atlasmap.converters;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.CharBuffer;
import java.text.NumberFormat;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.regex.Pattern;

import io.atlasmap.api.AtlasConversionException;
import io.atlasmap.spi.AtlasConversionConcern;
import io.atlasmap.spi.AtlasConversionInfo;
import io.atlasmap.spi.AtlasConverter;
import io.atlasmap.v2.FieldType;

public class CharSequenceConverter implements AtlasConverter<CharSequence> {

    private static final Pattern TRUE_PATTERN = Pattern.compile("true|t|yes|y", Pattern.CASE_INSENSITIVE);

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.DECIMAL,
            concerns = AtlasConversionConcern.FORMAT)
    public BigDecimal toBigDecimal(CharSequence value) throws AtlasConversionException {
        try {
            return value != null ? new BigDecimal(value.toString()) : null;
        } catch (NumberFormatException e) {
            throw new AtlasConversionException(String
                    .format("String %s cannont be converted to a BigDecimal as it is not in a valid format", value));
        }
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.BIG_INTEGER,
            concerns = AtlasConversionConcern.FORMAT)
    public BigInteger toBigInteger(CharSequence value) throws AtlasConversionException {
        try {
            return value != null ? new BigInteger(value.toString()) : null;
        } catch (NumberFormatException e) {
            throw new AtlasConversionException(String
                    .format("String %s cannont be converted to a BigInteger as it is not in a valid format", value));
        }
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.BOOLEAN, concerns = AtlasConversionConcern.CONVENTION)
    public Boolean toBoolean(CharSequence value, String sourceFormat, String targetFormat) {
        if (value == null) {
            return null;
        }

        // string expression of true?
        Pattern pattern;
        if (sourceFormat != null && !sourceFormat.isEmpty()) {
            pattern = Pattern.compile(sourceFormat, Pattern.CASE_INSENSITIVE);
        } else {
            pattern = TRUE_PATTERN;
        }
        if (pattern.matcher(value).matches()) {
            return Boolean.TRUE;
        }

        // then try C like numeric translation
        try {
            Number n = NumberFormat.getInstance().parse(value.toString());
            if (n.intValue() == 0) {
                return Boolean.FALSE;
            }
            return Boolean.TRUE;
        } catch (ParseException e) {
            e.getMessage(); // ignore
        }

        // false by default
        return Boolean.FALSE;
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.BYTE, concerns = {
            AtlasConversionConcern.RANGE, AtlasConversionConcern.FORMAT, AtlasConversionConcern.FRACTIONAL_PART})
    public Byte toByte(CharSequence value) throws AtlasConversionException {
        if (value == null) {
            return null;
        }
        try {
            return Byte.parseByte(value.toString());
        } catch (NumberFormatException nfex) {
            try {
                BigDecimal bd = new BigDecimal(value.toString());
                if (bd.compareTo(new BigDecimal(Byte.MIN_VALUE)) < 0
                        || bd.compareTo(new BigDecimal(Byte.MAX_VALUE)) > 0) {
                    throw new AtlasConversionException(String
                            .format("String %s is greater than Byte.MAX_VALUE  or less than Byte.MIN_VALUE", value));
                }
                return bd.byteValue();
            } catch (NumberFormatException nfe2) {
                throw new AtlasConversionException(String
                        .format("String %s cannont be converted to a Byte as it is not in a numerical format", value));
            }
        }
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.CHAR, concerns = AtlasConversionConcern.RANGE)
    public Character toCharacter(CharSequence value) throws AtlasConversionException {
        if (value == null) {
            return null;
        }
        // empty or greater than 1 char String throws Exception
        if (value.toString().isEmpty() || value.length() > 1) {
            throw new AtlasConversionException(
                    String.format("String '%s' is either empty or greater than one character long", value));
        } else if (value.charAt(0) < Character.MIN_VALUE || value.charAt(0) > Character.MAX_VALUE) {
            throw new AtlasConversionException(String
                    .format("String %s is greater than Character.MAX_VALUE  or less than Character.MIN_VALUE", value));
        }
        return value.charAt(0);
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.DATE_TIME)
    public Date toDate(CharSequence date, String sourceFormat, String targetFormat) {

        DateTimeFormatter formater = sourceFormat != null ? DateTimeFormatter.ofPattern(sourceFormat)
                : DateTimeFormatter.ISO_ZONED_DATE_TIME;
        return Date.from(ZonedDateTime.parse(date, formater).toInstant());
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.DOUBLE, concerns = {
            AtlasConversionConcern.FORMAT, AtlasConversionConcern.RANGE })
    public Double toDouble(CharSequence value) throws AtlasConversionException {
        if (value == null) {
            return null;
        }

        String str = value.toString();
        double parsedDouble = 0.0d;
        try {
            parsedDouble = Double.parseDouble(str);
        } catch (NumberFormatException nfe) {
            throw new AtlasConversionException(nfe);
        }

        double absParsedDouble = Math.abs(parsedDouble);
        if (absParsedDouble == 0.0d) {
            return parsedDouble;
        }
        if (absParsedDouble < Double.MIN_VALUE || absParsedDouble > Double.MAX_VALUE) {
            throw new AtlasConversionException(
                    String.format(
                            "String %s is greater than Double.MAX_VALUE  or less than Double.MIN_VALUE",
                            str));
        }

        return parsedDouble;
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.FLOAT, concerns = {
            AtlasConversionConcern.FORMAT, AtlasConversionConcern.RANGE })
    public Float toFloat(CharSequence value) throws AtlasConversionException {
        if (value == null) {
            return null;
        }

        String str = value.toString();
        float parsedFloat = 0.0f;
        try {
            parsedFloat = Float.parseFloat(str);
        } catch (NumberFormatException nfe) {
            throw new AtlasConversionException(nfe);
        }

        float absParsedFloat = Math.abs(parsedFloat);
        if (absParsedFloat == 0.0f) {
            return parsedFloat;
        }

        if (absParsedFloat < Float.MIN_VALUE || absParsedFloat > Float.MAX_VALUE) {
            throw new AtlasConversionException(
                    String.format(
                            "String %s is greater than Float.MAX_VALUE  or less than Float.MIN_VALUE",
                            str));
        }

        return Float.valueOf(str);
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.INTEGER, concerns = {
            AtlasConversionConcern.FORMAT, AtlasConversionConcern.RANGE, AtlasConversionConcern.FRACTIONAL_PART })
    public Integer toInteger(CharSequence value) throws AtlasConversionException {
        if (value == null) {
            return null;
        }

        String str = value.toString();
        Integer i = null;
        try {
            i = Integer.parseInt(str);
        } catch (NumberFormatException nfe) {
            try {
                BigDecimal bd = new BigDecimal(str);
                if (bd.compareTo(new BigDecimal(Integer.MIN_VALUE)) < 0
                        || bd.compareTo(new BigDecimal(Integer.MAX_VALUE)) > 0) {
                    throw new AtlasConversionException(String
                            .format("String %s is greater than Integer.MAX_VALUE  or less than Integer.MIN_VALUE", str));
                }
                i = bd.intValue();
            } catch (NumberFormatException nfe2) {
                throw new AtlasConversionException(nfe);
            }
        }

        return i;
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.DATE)
    public LocalDate toLocalDate(CharSequence value) {
        return value != null ? LocalDate.parse(value) : null;
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.TIME)
    public LocalTime toLocalTime(CharSequence value) {
        return value != null ? LocalTime.parse(value) : null;
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.DATE_TIME)
    public LocalDateTime toLocalDateTime(CharSequence value) {
        return value != null ? LocalDateTime.parse(value) : null;
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.LONG, concerns = {
            AtlasConversionConcern.FORMAT, AtlasConversionConcern.RANGE, AtlasConversionConcern.FRACTIONAL_PART })
    public Long toLong(CharSequence value) throws AtlasConversionException {
        if (value == null) {
            return null;
        }

        String str = value.toString();
        Long l = null;
        try {
            l = Long.parseLong(str);
        } catch (NumberFormatException nfe) {
            try {
                BigDecimal bd = new BigDecimal(str);
                if (bd.compareTo(new BigDecimal(Long.MIN_VALUE)) < 0
                        || bd.compareTo(new BigDecimal(Long.MAX_VALUE)) > 0) {
                    throw new AtlasConversionException(String
                            .format("String %s is greater than Long.MAX_VALUE  or less than Long.MIN_VALUE", value));
                }
                l = bd.longValue();
            } catch (NumberFormatException nfe2) {
                throw new AtlasConversionException(nfe);
            }
        }

        return l;
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.SHORT, concerns = {
            AtlasConversionConcern.FORMAT, AtlasConversionConcern.RANGE, AtlasConversionConcern.FRACTIONAL_PART })
    public Short toShort(CharSequence value) throws AtlasConversionException {
        if (value == null) {
            return null;
        }

        String str = value.toString();
        Short shortty = null;
        try {
            shortty = Short.parseShort(str);
        } catch (NumberFormatException nfe) {
            try {
                BigDecimal bd = new BigDecimal(str);
                if (bd.compareTo(new BigDecimal(Short.MIN_VALUE)) < 0
                        || bd.compareTo(new BigDecimal(Short.MAX_VALUE)) > 0) {
                    throw new AtlasConversionException(String
                            .format("String %s is greater than Short.MAX_VALUE  or less than Short.MIN_VALUE", str));
                }
                shortty = bd.shortValue();
            } catch (NumberFormatException nfe2) {
                throw new AtlasConversionException(nfe2);
            }
        }
        return shortty;
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.STRING)
    public CharBuffer toCharBuffer(CharSequence value, String sourceFormat, String targetFormat) {
        if (value == null) {
            return null;
        }
        return CharBuffer.wrap(value);
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.STRING)
    public CharSequence toCharSequence(CharSequence value, String sourceFormat, String targetFormat) {
        if (value == null) {
            return null;
        }
        return new String(value.toString());
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.STRING)
    public String toString(CharSequence value, String sourceFormat, String targetFormat) {
        if (value == null) {
            return null;
        }
        return new String(value.toString());
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.STRING)
    public StringBuffer toStringBuffer(CharSequence value, String sourceFormat, String targetFormat) {
        if (value == null) {
            return null;
        }
        return new StringBuffer(value);
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.STRING)
    public StringBuilder toStringBuilder(CharSequence value, String sourceFormat, String targetFormat) {
        if (value == null) {
            return null;
        }
        return new StringBuilder(value);
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.NUMBER, concerns = {
            AtlasConversionConcern.FORMAT })
    public Number toNumber(CharSequence value) throws AtlasConversionException {
        if (value == null || value.toString().trim().isEmpty()) {
            return null;
        }
        String str = value.toString();
        if (str.matches("\\d+")) {
            return new BigInteger(str);
        }
        try {
            return new BigDecimal(str);
        } catch (NumberFormatException e) {
            throw new AtlasConversionException(e);
        }
    }

    @AtlasConversionInfo(sourceType = FieldType.STRING, targetType = FieldType.DATE_TIME_TZ)
    public ZonedDateTime toZonedDateTime(CharSequence value) {
        return value != null ? ZonedDateTime.parse(value) : null;
    }

}
