/*
 *  Copyright (c) 2001,2020 Alex Leigh, All Rights Reserved
 *
 *  THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 *  notice above does not evidence any actual or intended publication of such
 *  source code.
 *
 *  Use, disclosure, copying, distribution, or transmission of this code without
 *  express permission is prohibited.
 */

package lc.mecha.util;

import lc.mecha.math.IntIntervals;
import org.apache.commons.lang.StringUtils;

import java.math.BigDecimal;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.UnknownHostException;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
import java.util.regex.Pattern;

/**
 * Snavig - "Change an object's shape"
 *
 * @author Alex Leigh
 * @since mk14 mod0 (CRYSTAL)
 */

final public class Snavig {

    public static Number convertBigToLongDouble(BigDecimal bd) {
        if (bd.stripTrailingZeros().scale() <= 0) {
            return bd.longValue();
        } else {
            return bd.doubleValue();
        }
    }

    public static boolean equals(Object o1, Object o2) {
        return (o1 == null ? o2 == null : o1.equals(o2));
    }


    /**
     * This method has similar mechanics to <code>String.valueOf()</code> except that in cases where the
     * <code>value</code> is <code>null</code>, <code>null</code> instead of <code>"null"</code> is returned.
     */
    public static String convertToString(Object value) {
        if (value == null) return null;

        // If we find a Date we convert it into an Instant so that we get an ISO time string instead of Java's
        // ancient format.
        if (value instanceof Date) {
            return ((Date) value).toInstant().toString();
        }

        return value.toString();
    }

    public static String convertToLowercaseString(Object value) {
        if (value == null) return null;

        if (value instanceof Date) {
            return ((Date) value).toInstant().toString().toLowerCase();
        }

        return value.toString().toLowerCase();
    }

    public static Pattern convertToPattern(Object value) {
        String s = Snavig.convertToString(value);
        if (s == null) return null;
        return Pattern.compile(s);
    }

    public static BigDecimal convertToBigDecimal(Object value) {
        if (value == null) return null;
        if (value instanceof BigDecimal) return (BigDecimal) value;
        if (value instanceof Double) return BigDecimal.valueOf((double) value);
        if (value instanceof Long) return BigDecimal.valueOf((long) value);

        String s;

        if (value instanceof String) {
            s = (String) value;
        } else {
            s = String.valueOf(value);
        }

        if (s == null || s.equals("null") || s.equals("NaN")) return null;
        return new BigDecimal(s);
    }

    public static Boolean convertToBoolean(Object value) {
        if (value == null) return false;
        if (value instanceof Boolean) return (Boolean) value;
        return Boolean.parseBoolean(String.valueOf(value));
    }

    public static int safeConvertToInteger(Object value) {
        try {
            Integer x = convertToInteger(value);
            return x == null ? 0 : x;
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        return 0;
    }

    public static Integer convertToInteger(Object value) {
        if (value == null) return null;
        if (value instanceof Integer) return (Integer) value;
        return Integer.parseInt(String.valueOf(value).trim());
    }

    public static Double convertToDouble(Object value) {
        if (value == null) return (double) 0;
        if (value instanceof Double) return (Double) value;
        String s = String.valueOf(value);
        if (s == null || s.isEmpty()) return (double) 0;
        return Double.valueOf(s.replace("%", ""));
    }


    public static Instant convertToInstant(Object value) {
        if (value == null) return null;
        // This is very unlikely to occur, since we convert them on setter
        if (value instanceof Instant) return (Instant) value;

        // This is far more likely
        if (value instanceof Date) return ((Date) value).toInstant();

        String strValue = String.valueOf(value);
        if (strValue != null) {
            // ISO8601 format
            try {
                return Instant.parse((CharSequence) value);
            } catch (DateTimeParseException e) {
                //e.printStackTrace();
            }

            // Date & 24-hour time in assumed UTC
            final DateTimeFormatter FMT = new DateTimeFormatterBuilder()
                    .appendPattern("yyyy-MM-dd HH:mm:ss")
                    .toFormatter()
                    .withZone(ZoneId.of("UTC"));

            return FMT.parse(strValue, Instant::from);
        }

        throw new IllegalStateException("Called to return an Instant but no conversion exists");
    }

    public static Duration convertToDuration(Object value) {

        if (value == null) return null;

        if (value instanceof Duration)
            return ((Duration) value);

        // ISO8601 format
        if (value instanceof String)
            return Duration.parse((CharSequence) value);

        throw new IllegalStateException("Called to return a Duration but no conversion exists");
    }

    public static UUID convertToUUID(Object value) {
        if (value == null) return null;
        if (value instanceof UUID) return (UUID) value;
        return UUID.fromString(String.valueOf(value));
    }

    public static Date convertToDate(Object value) {
        if (value == null) return null;
        if (value instanceof Date) return (Date) value;
        if (value instanceof Instant) return Date.from((Instant) value);
        throw new IllegalStateException("No conversion exists to Date from " + value.getClass());
    }

    public static Locale convertToLocale(Object value) {
        if (value == null) return null;
        if (value instanceof Locale) return (Locale) value;
        return Locale.forLanguageTag(String.valueOf(value));
    }

    public static Inet4Address convertToInet4Address(Object value) {
        if (value == null) return null;
        if (value instanceof Inet4Address) return (Inet4Address) value;
        try {
            return (Inet4Address) Inet4Address.getByName(value.toString());
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

    public static IntIntervals convertToIntIntervals(Object value) {
        if (value == null) return null;
        if (value instanceof IntIntervals) return (IntIntervals) value;

        String sVal = null;

        if (value instanceof String) {
            sVal = (String) value;
        } else {
            sVal = value.toString();
        }

        if (StringUtils.isBlank(sVal)) return null;

        return new IntIntervals(sVal);
    }

    public static Inet6Address convertToInet6Address(Object value) {
        if (value == null) return null;
        if (value instanceof Inet6Address) return (Inet6Address) value;

        try {
            return (Inet6Address) Inet6Address.getByName(value.toString());
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

    public static Float convertToFloat(Object value) {
        if (value == null) return null;
        return Float.parseFloat(String.valueOf(value));
    }

}
