package com.brilliance.mda.runtime.mda.util;

import com.brilliance.mda.runtime.mda.I18n;
import com.brilliance.mda.runtime.mda.IStream;
import com.brilliance.mda.runtime.mda.impl.Argument;
import com.brilliance.mda.runtime.mda.impl.I18NImpl;
import com.brilliance.mda.runtime.mda.impl.StreamImpl;
import com.esotericsoftware.kryo.kryo5.Kryo;
import com.esotericsoftware.kryo.kryo5.util.Pool;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.*;
import java.lang.annotation.Annotation;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Semaphore;

import static com.brilliance.mda.runtime.mda.Constants.*;

public class MdaUtils {

    private static final Logger log = LoggerFactory.getLogger(MdaUtils.class);

    static final Format format = new DecimalFormat("#.######");
    static final Format dateFormat = new SimpleDateFormat("yyyy/MM/dd");
    static final Format timeFormat = new SimpleDateFormat("HH:mm");


    public static boolean autoConvert = false;

    static final Map<String, Semaphore> mutexMap = new HashMap();

    static final HashMap<String, Properties> pMap = new HashMap();

    public static synchronized void close() {
        pMap.clear();
    }

    private static synchronized Properties getProperties(String name)
            throws FileNotFoundException, IOException {
        Properties properties = (Properties) pMap.get(name);
        if (properties == null) {
            properties = new Properties();
            //properties.load(new FileInputStream(new File(name)));
            properties.load(MdaUtils.class.getClassLoader().getResourceAsStream(name));
            pMap.put(name, properties);
        }
        return properties;
    }

    public static Object invoke(Object object, String methodName, Object... args) {
        return invoke(object, methodName, null, args);
    }

    public static Object invoke(Object object, String methodName, Class[] classes, Object[] args) {
        try {
            if (classes == null) {
                classes = new Class[args.length];
                for (int i = 0; i < args.length; i++)
                    classes[i] = args[i].getClass();
            }
            if ((object instanceof String))
                return MethodUtils.invokeStaticMethod(Class.forName((String) object), methodName, args, classes);
            return MethodUtils.invokeMethod(object, methodName, args, classes);
        } catch (Exception e) {
            log.info("invoke errr.", e);
        }
        return null;
    }

    public static String getPropertyValue(String fileName, String key) {
        try {
            return getProperties(fileName).getProperty(key);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return null;
    }


    public static final void closeQuietly(Closeable closeable) {
        try {
            if (closeable != null)
                closeable.close();
        } catch (Exception localException) {
        }
    }


    public static final String toString(Object object) {
        if ((object == null) || ((object instanceof String)))
            return (String) object;
        try {
            if ((object instanceof IStream)) {
                Optional<String> rs = ((IStream) object).getRows().stream().reduce((a, b) -> a + "\r\n" + b);
                if (rs.isPresent())
                    return rs.get();
                return "";
            }

            return object.toString();
        } catch (Exception e) {

        }
        return null;
    }

    public static final String getI18nStr(String name, String key) {
        return "/" + name + "/" + key + "/";
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (Exception localException) {
        }
    }

    public static boolean isEmpty(Object object) {
        if (object == null)
            return true;
        if ((object instanceof String))
            return !StringUtils.hasLength(((String) object).trim());
        if ((object instanceof Date))
            return object.equals(NULLDATE) || object.equals(NULLDATETIME);
        if ((object instanceof StringBuffer))
            return isEmpty(((StringBuffer) object).toString());
//        if ((object instanceof IModule))
//            return ((IModule)object).isEmpty();
        if ((object instanceof IStream))
            return ((IStream) object).isEmpty();
        if ((object instanceof Number))
            return ((Number) object).doubleValue() == 0.0D;
        if ((object instanceof Collection))
            return ((Collection) object).isEmpty();
        if ((object instanceof Map))
            return ((Map) object).isEmpty();
        if ((object instanceof Argument))
            return isEmpty(((Argument) object).getValue());
        return false;
    }

    public static boolean isBigDecimalEmpty(BigDecimal b){
        BigDecimal zero = BigDecimal.ZERO;
        if(b.compareTo(zero) == 0){
            return true;
        }
        return false;
    }

    public static Object getProperty(Object object, String propertyName) {
        try {
            return PropertyUtils.getProperty(object, propertyName);
        } catch (Exception e) {
            log.info("getProperty", e);
        }
        return null;
    }

    //
    public static void setProperty(Object object, String propertyName, Object value) {
        try {
//            if ((object instanceof IModule))
//                ((IDatafield)((IModule)object).get(propertyName)).setValue(value);
//            else
            PropertyUtils.setProperty(object, propertyName, value);
        } catch (Exception e) {
            log.info("setProperty", e);
        }
    }

    public static boolean clear(Object object) {
        if (object == null)
            return true;

        else if ((object instanceof Collection))
            ((Collection) object).clear();
        else if ((object instanceof Map))
            ((Map) object).clear();
        else if ((object instanceof IStream))
            ((IStream) object).close();
        else if ((object instanceof Argument)) {
            if (isEmpty(((Argument) object).value)) {
                return true;
            }
            ((Argument) object).value = zero(((Argument) object).value.getClass());
        } else
            throw new IllegalArgumentException("MdaUtils.clear不支持的变量类型" + object.getClass());
        return false;
    }

    public static Object zero(Class<?> clazz) {
        if (clazz == int.class || clazz == Integer.class)
            return 0;
        if (clazz == float.class || clazz == Float.class)
            return 0f;
        if (clazz == long.class || clazz == Long.class)
            return 0l;
        if (clazz == double.class || clazz == Double.class)
            return 0d;
        if (clazz == char.class || clazz == Character.class)
            return (char) 0;
        if (clazz == byte.class || clazz == Byte.class)
            return (byte) 0;
        if (clazz == Date.class)
            return null;
        if (clazz == BigDecimal.class) {
            return new BigDecimal("0");
        }
        if (clazz == StreamImpl.class || clazz == IStream.class)
            return new StreamImpl();
        if (clazz == String.class)
            return NULLSTR;
        return null;

    }


    public static final int getFormatLength(String format, int length) {
        int index = format.indexOf('.');
        String str = "";
        if (index < 0) {
            for (int i = 0; i < length; i++)
                str = str + "1";
        } else {
            int bits = format.length() - index;
            for (int i = 0; i < length - bits + 1; i++)
                str = str + "1";
            str = str + ("".equals(str) ? "0." : ".");
            for (int i = 0; i < bits; i++)
                str = str + "1";
        }
        str = new DecimalFormat(format).format(new BigDecimal(str));
        return str.length();
    }

    public static final String combineStyle(String oldStyle, String style) {
        if (isEmpty(oldStyle))
            return style;
        if (isEmpty(style))
            return oldStyle;
        return combineStyle(new StringBuffer(oldStyle), style).toString();
    }

    public static final StringBuffer combineStyle(StringBuffer oldStyle, String style) {
        if (isEmpty(oldStyle))
            return oldStyle.append(style);
        if (isEmpty(style)) {
            return oldStyle;
        }
        oldStyle.insert(0, style + ";");
        String[] attributes = oldStyle.toString().split("\\;");
        oldStyle = new StringBuffer();
        List list = new ArrayList();
        for (String attribute : attributes) {
            String[] pairs = attribute.split("\\:");
            if (pairs.length != 2)
                continue;
            String name = pairs[0].trim();
            if (list.contains(name))
                continue;
            list.add(name);
            oldStyle.append(name).append(":").append(pairs[1]).append(";");
        }

        return oldStyle;
    }

    public static final String getWebType(IStream stream) {
        String type = stream.getType();
        return isEmpty(type) ? "pdf" : type;
    }

    public static final String getWebFileName(String browserType, IStream stream) {
        try {
            String name = stream.getName();
            if (isEmpty(name))
                name = "default." + getWebType(stream);
            if (!autoConvert)
                return name;
            log.debug("Type of browser is:\t" + browserType);
            if ((isEmpty(browserType)) || (browserType.contains("MSIE")))
                return URLEncoder.encode(name, "UTF-8");
            return new String(name.getBytes(), "ISO8859-1");
        } catch (Exception e) {
        }
        return "default." + getWebType(stream);
    }

    public static boolean equals(String value1, String value2) {
        if (value1 == null)
            return value2 == null;
        return value1.equals(value2);
    }

    public static void exitRule() {
//        throw new RuleExitException("终止当前Rule");
        return;
    }

    public static String encryptPassword(String srcPassword) {
        return new String(DigestUtils.sha1(DigestUtils.md5(srcPassword)));
    }

    public static String toJson(Object dataMap) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(dataMap);
        } catch (Exception e) {
            log.error("转换JSON异常", e);
        }
        return "";
    }

    public static String toJsonForGidTxt(Object dataMap, Locale locale) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            //重置JSONFormat
            mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
                @Override
                protected <A extends Annotation> A _findAnnotation(Annotated ann, Class<A> annoClass) {
                    if (!ann.hasOneOf(new Class[]{JsonFormat.class})) {
                        return super._findAnnotation(ann, annoClass);
                    }
                    return null;
                }
            });
            if (locale.equals(Locale.ENGLISH)) {
                SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MMMMyyyy", Locale.ENGLISH);
                mapper.setDateFormat(dateFormat);
            } else if (locale.equals(Locale.CHINESE)) {
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
                mapper.setDateFormat(dateFormat);
            }
            return mapper.writeValueAsString(dataMap);
        } catch (Exception e) {
            log.error("转换JSON异常", e);
        }
        return "";
    }

    public static <T> T fromJson(String json, Class<T> clazz) {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.convertValue(json, clazz);
    }

    public static String toString(int str, int len) {
        if (len >= 0) {
            return Strings.lpad(String.valueOf(str), Math.abs(len), ' ');
        } else {
            String padStr = Strings.lpad(String.valueOf(str), Math.abs(len), '0');
            if (padStr.length() > -len) {
                padStr = padStr.substring(padStr.length() + len);
            }
            return padStr;
        }
    }

    public static String toString(double str, int len) {
        return toString((int) str, len);
    }

    public static String toString(BigDecimal str, int len) {
        return toString(str.intValue(), len);
    }

    //    public static String formatModuleList(IBaseObject moudle){
//        String result = "V20";
//        return  result;
//
//    }
//    public static String formatModule(IBaseObject moudle){
//        String result = "V20";
//        Queue<IModule> queue = new LinkedList<>();
//        queue.add(moudle);
//        while (!queue.isEmpty()){
//            IModule poll = queue.poll();
//        }
//        return  result;
//    }

    public static int compareTo(Object o1, Object o2) {
        if (o1 instanceof Boolean) {
            int var1 = o1.equals(Boolean.TRUE) ? 1 : 0;
            return compareTo(var1, o2);
        }
        if (o2 instanceof Boolean) {
            int var2 = o2.equals(Boolean.TRUE) ? 1 : 0;
            return compareTo(o1, var2);
        }

        //拆箱展开
        if (o1 instanceof Argument) {
            o1 = ((Argument) o1).value;
        }
        //拆箱展开2
        if (o2 instanceof Argument) {
            o2 = ((Argument) o2).value;
        }
        if (o1 == null && o2 == null) return 0;
        if (o1 == null) {
            if ("".equals(o2)) {
                return 0;
            }
            return -1;
        }
        if (o2 == null) {
            if ("".equals(o1)) {
                return 0;
            }
            return 1;
        }
        if (o1 instanceof String && o2 instanceof String) {
            //先做空判断，使 " " == ""
            if (isEmpty(((String) o1).trim()) && isEmpty(((String) o2).trim()))
                return 0;
            return Strings.trimEnd((String) o1, " ").compareTo(Strings.trimEnd((String) o2, " "));
        }

        if (o1 instanceof String)
            return Strings.trimEnd((String) o1, " ").compareTo(Strings.trimEnd((String) o2, " "));
        else if (o1 instanceof BigDecimal && o2 instanceof BigDecimal) {
            return (((BigDecimal) o1).compareTo((BigDecimal) o2));
        }else if (o1 instanceof BigDecimal && o2 instanceof Number) {
            BigDecimal o2decimal = null;
            if (o2 instanceof Double) {
                o2decimal = new BigDecimal(Double.toString((Double) o2));
            } else if (o2 instanceof Long) {
                o2decimal = new BigDecimal(Long.toString((Long) o2));
            } else if (o2 instanceof Float) {
                o2decimal = new BigDecimal(Float.toString((Float) o2));
            } else
                o2decimal = new BigDecimal(o2.toString());

            return ((BigDecimal) o1).compareTo(o2decimal);
        } else if (o1 instanceof Number && o2 instanceof BigDecimal) {
            return -compareTo(o2, o1);
        }else if (o1 instanceof Number && o2 instanceof Number) {
            double i1 = ((Number) o1).doubleValue();
            double i2 = ((Number) o2).doubleValue();
            if (i1 > i2)
                return 1;
            else if (i1 == i2)
                return 0;
            else
                return -1;
        }
//        else if (o1 instanceof TIMESTAMP && o2 instanceof TIMESTAMP) {
//            try {
//                return compareTo(((TIMESTAMP) o1).timestampValue(), ((TIMESTAMP) o2).timestampValue());
//            } catch (Exception throwables) {
//                throw new IllegalArgumentException("不支持的比较类型！");
//            }
//        }
        else if (o1 instanceof Date)
            return (((Date) o1).compareTo((Date) o2));
        else if (o1 instanceof Byte)
            return (((Byte) o1).compareTo((Byte) o2));
        else if (o1 instanceof Character)
            return (((Character) o1).compareTo((Character) o2));
        else if (o1 instanceof Short)
            return (((Short) o1).compareTo((Short) o2));
        else if (o1 instanceof Long)
            return (((Long) o1).compareTo((Long) o2));
        else if (o1 instanceof Float)
            return (((Float) o1).compareTo((Float) o2));
        else if (o1 instanceof Double)
            return (((Double) o1).compareTo((Double) o2));
        else if (o1 instanceof IStream && o2 instanceof IStream) {
            IStream is1 = (IStream) o1;
            IStream is2 = (IStream) o2;
            if (is1.getRows().size() == 0 && is2.getRows().size() == 0)
                return 0;
            if (is1.getRows().size() > is2.getRows().size())
                return 1;
            else if (is1.getRows().size() < is2.getRows().size())
                return -1;
            else {
                for (int i = 0; i < is1.getRows().size(); i++) {
                    int diff = is1.getRows().get(i).compareTo(is2.getRows().get(i));
                    if (diff != 0)
                        return diff;
                }
                return 0;
            }
        } else {
            throw new IllegalArgumentException("不支持的比较类型！");
        }
    }

    private static I18n i18N = new I18NImpl();

    public static String getI18NString(String moduleName, String key) {
        Locale locale = Systems.getLocale();

        String desp = i18N.getString(locale, moduleName, key);
        if (Strings.isEmpty(desp) || desp.startsWith("???")) {
            if (locale == Locale.SIMPLIFIED_CHINESE)
                desp = i18N.getString(Locale.US, moduleName, key);
            else if (locale == Locale.US)
                desp = i18N.getString(Locale.SIMPLIFIED_CHINESE, moduleName, key);
        }
        return desp;
    }


    /**
     * 用于克隆时使用的Kryo实例
     */
    private static volatile Pool<Kryo> kryoPool = new Pool<Kryo>(true, false, 20) {
        @Override
        protected Kryo create() {
            Kryo kryo = new Kryo();
            kryo.setReferences(true);
            kryo.setRegistrationRequired(false);
            return kryo;
        }
    };

    /**
     * 基于Kryo实现的深度clone函数
     *
     * @param src
     * @param <T>
     * @return
     */
    public static <T> T deepClone(T src) {
        Kryo kryo = null;
        try {
            kryo = kryoPool.obtain();
            return kryo.copy(src);
        } finally {
            if (kryo != null) {
                kryoPool.free(kryo);
            }
        }
    }

    public static boolean fileExists(String fileName) {
        return new File(fileName).exists();
    }

    public static boolean fileMkdirs(String filePath) {
        return new File(filePath).mkdirs();
    }

    public static boolean fileMove(String sourcefileNameOrPath, String targetFileOrPathName) {
        try {
            File sourceFile = new File(sourcefileNameOrPath);
            File targetFile = new File(targetFileOrPathName);
            if (targetFile.exists()) {
                if (sourceFile.isDirectory())
                    FileUtils.moveDirectoryToDirectory(sourceFile, targetFile, false);
                else if (targetFile.isDirectory())
                    FileUtils.moveFileToDirectory(sourceFile, targetFile, false);
                else {
                    FileUtils.moveFile(sourceFile, targetFile);
                }

            } else if (sourceFile.isDirectory())
                FileUtils.moveDirectoryToDirectory(sourceFile, targetFile, true);
            else {
                FileUtils.moveFile(sourceFile, targetFile);
            }
            return true;
        } catch (Exception e) {
            log.error("fileMove error", e);
        }
        return false;
    }

    public static boolean fileCopy(String sourceFileName, String targetFileName) {
        try {
            File sourceFile = new File(sourceFileName);
            File targetFile = new File(targetFileName);
            if (targetFile.exists()) {
                if (sourceFile.isDirectory())
                    FileUtils.copyDirectoryToDirectory(sourceFile, targetFile);
                else if (targetFile.isDirectory())
                    FileUtils.copyFileToDirectory(sourceFile, targetFile);
                else {
                    FileUtils.copyFile(sourceFile, targetFile);
                }

            } else if (sourceFile.isDirectory()) {
                targetFile.mkdirs();
                FileUtils.copyDirectory(sourceFile, targetFile);
            } else {
                FileUtils.copyFile(new File(sourceFileName), mkdirs(targetFileName));
            }
            return true;
        } catch (Exception e) {
            log.error("fileCopy error", e);
        }
        return false;
    }

    private static File mkdirs(String fileName) {
        File file = new File(fileName);
        if (!file.getParentFile().exists())
            file.getParentFile().mkdirs();
        return file;
    }

    public static String catPath(String path, String subPath) {
        return new File(new File(path), subPath).getAbsolutePath();
    }

    public static Date today() {
        return new Date();
    }

    public static String fileName(String fileName) {
        return FilenameUtils.getName(fileName);
    }

    public static final void saveContent(String content, String fileName, String encoding) {
        try {
            IOUtils.write(content, new FileOutputStream(fileName), encoding);
        } catch (Exception e) {
            log.error("Load content error.", e);
        }
    }
}
