package com.meituan.robust.utils;

import com.meituan.robust.Constants;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.NotFoundException;

import static com.meituan.robust.Constants.ORIGINCLASS;

/**
 * Created by mivanzhang on 16/11/25.
 */

public class JavaUtils {

//    public static void removeJarFromLibs() {
//        File file;
//        for (String libName : LIB_NAME_ARRAY) {
//            file = new File(AutoPatchTransform.ROBUST_DIR + libName);
//            if (file.exists()) {
//                file.delete();
//            }
//        }
//    }

    public static Object getMapFromZippedFile(String path) {
        File file = new File(path);
        Object result = null;
        try {
            if (file.exists()) {
                FileInputStream fileIn = new FileInputStream(file);
                GZIPInputStream gzipIn = new GZIPInputStream(fileIn);
                ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                int count;
                byte[] data = new byte[1024];
                while ((count = gzipIn.read(data, 0, 1024)) != -1) {
                    byteOut.write(data, 0, count);
                }
                ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
                ObjectInputStream oi = new ObjectInputStream(byteIn);
                result = oi.readObject();
                fileIn.close();
                gzipIn.close();
                oi.close();
            } else {
                throw new RuntimeException("getMapFromZippedFile error,file doesn't exist ,path is " + path);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("getMapFromZippedFile from " + path + "  error ");
        }
        return result;
    }

    public static int copy(InputStream input, OutputStream output) throws IOException {
        long count = copyLarge(input, output);
        return count > 2147483647L ? -1 : (int) count;
    }

    private static long copyLarge(InputStream input, OutputStream output) throws IOException {
        byte[] buffer = new byte[4096];
        long count = 0L;

        int n1;
        for (boolean n = false; -1 != (n1 = input.read(buffer)); count += (long) n1) {
            output.write(buffer, 0, n1);
        }

        return count;
    }

    public static String fileMd5(File file) {
        if (!file.isFile()) {
            return "";
        }
        MessageDigest digest = null;
        byte[] buffer = new byte[4096];
        int len;
        try {
            digest = MessageDigest.getInstance("MD5");
            FileInputStream inputStream = new FileInputStream(file);
            while ((len = inputStream.read(buffer, 0, 1024)) != -1) {
                digest.update(buffer, 0, len);
            }
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }

        BigInteger bigInt = new BigInteger(1, digest.digest());
        return bigInt.toString(16);
    }

    public static String getWrapperClass(String typeName) {
        String warpperType = typeName;
        switch (typeName) {
            case "boolean":
                warpperType = "java.lang.Boolean";
                break;
            case "byte":
                warpperType = "java.lang.Byte";
                break;
            case "char":
                warpperType = "java.lang.Character";
                break;
            case "double":
                warpperType = "java.lang.Double";
                break;
            case "float":
                warpperType = "java.lang.Float";
                break;
            case "int":
                warpperType = "java.lang.Integer";
                break;
            case "long":
                warpperType = "java.lang.Long";
                break;
            case "short":
                warpperType = "java.lang.Short";
                break;
            default:
                break;
        }
        return warpperType;
    }

    public static String wrapperToPrime(String typeName) {
        String warpperType = "";
        switch (typeName) {
            case "boolean":
                warpperType = ".booleanValue()";
                break;
            case "byte":
                warpperType = ".byteValue()";
                break;
            case "char":
                warpperType = ".charValue()";
                break;
            case "double":
                warpperType = ".doubleValue()";
                break;
            case "float":
                warpperType = ".floatValue()";
                break;
            case "int":
                warpperType = ".intValue()";
                break;
            case "long":
                warpperType = ".longValue()";
                break;
            case "short":
                warpperType = ".shortValue()";
                break;
            default:
                break;
        }
        return warpperType;
    }

    public static String getParameterValue(int length) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            stringBuilder.append("var" + i);
            if (i != length - 1) {
                stringBuilder.append(",");
            }
        }
        return stringBuilder.toString();
    }

    public static String getParameterSignure(CtMethod ctMethod) {
        StringBuilder methodSignure = new StringBuilder();
        try {
            for (int i = 0; i < ctMethod.getParameterTypes().length; i++) {
                methodSignure.append(ctMethod.getParameterTypes()[i].getName());
                methodSignure.append(" var" + i);
                if (i != ctMethod.getParameterTypes().length - 1) {
                    methodSignure.append(",");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return methodSignure.toString();
    }

    public static String getRealParamtersBody() {
        StringBuilder realParameterBuilder = new StringBuilder();
        realParameterBuilder.append("public  Object[] " + Constants.GET_REAL_PARAMETER + " (Object[] args){");
        realParameterBuilder.append("if (args == null || args.length < 1) {");
        realParameterBuilder.append(" return args;");
        realParameterBuilder.append("}");
        realParameterBuilder.append(" Object[] realParameter = (Object[]) java.lang.reflect.Array.newInstance(args.getClass().getComponentType(), args.length);");
        realParameterBuilder.append("for (int i = 0; i < args.length; i++) {");
        realParameterBuilder.append("if (args[i] instanceof Object[]) {");
        realParameterBuilder.append("realParameter[i] =" + Constants.GET_REAL_PARAMETER + "((Object[]) args[i]);");
        realParameterBuilder.append("} else {");
        realParameterBuilder.append("if (args[i] ==this) {");
        realParameterBuilder.append(" realParameter[i] =this." + ORIGINCLASS + ";");
        realParameterBuilder.append("} else {");
        realParameterBuilder.append(" realParameter[i] = args[i];");
        realParameterBuilder.append(" }");
        realParameterBuilder.append(" }");
        realParameterBuilder.append(" }");
        realParameterBuilder.append("  return realParameter;");
        realParameterBuilder.append(" }");
        return realParameterBuilder.toString();
    }

    public static boolean isInnerClassInModifiedClass(String className, CtClass modifedClass) {
        //only the inner class directly defined in modifedClass
        int index = className.lastIndexOf('$');
        if (index < 0) {
            return false;
        }
        return className.substring(0, index).equals(modifedClass.getName());
    }

    public static CtClass addPatchConstruct(CtClass patchClass, CtClass sourceClass) {
        try {
            CtField originField = new CtField(sourceClass, ORIGINCLASS, patchClass);
            patchClass.addField(originField);
            StringBuilder patchClassConstruct = new StringBuilder();
            patchClassConstruct.append(" public Patch(Object o) {");
            patchClassConstruct.append(ORIGINCLASS + "=(" + sourceClass.getName() + ")o;");
            patchClassConstruct.append("}");
            CtConstructor constructor = CtNewConstructor.make(patchClassConstruct.toString(), patchClass);
            patchClass.addConstructor(constructor);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
        return patchClass;
    }

    public
    static boolean isMethodSignureContainPatchClassName(String name, List<String> modifiedClassNameList) {
        for (String classname : modifiedClassNameList) {
            if (name.startsWith(classname)) {
                return true;
            }
        }
        return false;
    }

    public static void printMap(Map<String, ?> memberMappingInfo) {
        if (memberMappingInfo == null) {
            return;
        }
        for (String key : memberMappingInfo.keySet())
            System.out.println("key is   " + key + "  value is    " + memberMappingInfo.get(key));
        System.out.println("");
    }

    public static void printList(List<String> list) {
        if (list == null) {
            return;
        }
        for (String key : list)
            System.out.println("key is   " + key);
        System.out.println("");
    }


    public static String getFullClassNameFromFile(String path) {
        if (path.indexOf("classout") > 0) {
            return path.substring(path.indexOf("classout") + "classout".length() + 1, path.lastIndexOf(".smali")).replace(File.separatorChar, '.');
        }
        if (path.indexOf("main") > 0) {
            return path.substring(path.indexOf("main") + "main".length() + 1, path.lastIndexOf(".class")).replace(File.separatorChar, '.');
        }
        throw new RuntimeException("can not analysis " + path + "  get full class name error!!");
    }

    public static String eradicatReturnType(String name) {
        int blankIndex = name.indexOf(" ");
        if (blankIndex != -1) {
            //method with return type
            return name.substring(blankIndex + 1);
        } else {
            return name;
        }
    }

    public static String getJavaMethodSignure(CtMethod ctMethod) throws NotFoundException {
        StringBuilder methodSignure = new StringBuilder();
        methodSignure.append(ctMethod.getName());
        methodSignure.append("(");
        for (int i = 0; i < ctMethod.getParameterTypes().length; i++) {
            methodSignure.append(ctMethod.getParameterTypes()[i].getName());
            if (i != ctMethod.getParameterTypes().length - 1) {
                methodSignure.append(",");
            }
        }
        methodSignure.append(")");
        return methodSignure.toString();
    }

}