package org.cf.apkfile.utils;

import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializer;
import gnu.trove.map.TIntIntMap;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.TObjectLongMap;
import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import gnu.trove.map.hash.TObjectLongHashMap;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

public class Utils {
    private static final int SHA1_BUFF_LENGTH = 0x1000;
    private final static char[] HEX_CHARS = "0123456789abcdef".toCharArray();

    public static Map<Object, Integer> convertToJava(TObjectIntMap map) {
        Map<Object, Integer> javaMap = new HashMap<>();
        for (Object key : map.keySet()) {
            javaMap.put(key, map.get(key));
        }
        return javaMap;
    }

    public static Map<Integer, Integer> convertToJava(TIntIntMap map) {
        Map<Integer, Integer> javaMap = new HashMap<>();
        for (int key : map.keys()) {
            javaMap.put(key, map.get(key));
        }
        return javaMap;
    }

    public static int leToBeI(int num) {
        return ((num << 24) & 0xff000000) | ((num << 8) & 0x00ff0000) | ((num >> 8) & 0x0000ff00) | ((num >> 24) & 0x000000ff);
    }

    public static short leToBeS(short num) {
        return (short) (((num >> 8) & 0x00ff) | ((num << 8) & 0xff00));
    }

    public static <E> Collection<E> makeCollection(Iterable<E> iter) {
        Collection<E> list = new LinkedList<E>();
        for (E item : iter) {
            list.add(item);
        }
        return list;
    }

    public static void rollUp(TIntIntMap dest, TIntIntMap src) {
        for (int key : src.keys()) {
            int value = src.get(key);
            dest.adjustOrPutValue(key, value, value);
        }
    }

    public static void rollUp(TObjectIntMap dest, TObjectIntMap src) {
        for (Object key : src.keySet()) {
            int value = src.get(key);
            dest.adjustOrPutValue(key, value, value);
        }
    }

    public static void rollUp(TObjectLongMap dest, TObjectLongMap src) {
        for (Object key : src.keySet()) {
            long value = src.get(key);
            dest.adjustOrPutValue(key, value, value);
        }
    }

    public static void updateAccessorCounts(TObjectIntMap<String> counts, int[] accessFlags) {
        for (int accessFlag : accessFlags) {
            if (Modifier.isPublic(accessFlag)) {
                counts.adjustOrPutValue("public", 1, 1);
            }
            if (Modifier.isProtected(accessFlag)) {
                counts.adjustOrPutValue("protected", 1, 1);
            }
            if (Modifier.isPrivate(accessFlag)) {
                counts.adjustOrPutValue("private", 1, 1);
            }
            if (Modifier.isFinal(accessFlag)) {
                counts.adjustOrPutValue("final", 1, 1);
            }
            if (Modifier.isInterface(accessFlag)) {
                counts.adjustOrPutValue("interface", 1, 1);
            }
            if (Modifier.isNative(accessFlag)) {
                counts.adjustOrPutValue("native", 1, 1);
            }
            if (Modifier.isStatic(accessFlag)) {
                counts.adjustOrPutValue("static", 1, 1);
            }
            if (Modifier.isStrict(accessFlag)) {
                counts.adjustOrPutValue("strict", 1, 1);
            }
            if (Modifier.isSynchronized(accessFlag)) {
                counts.adjustOrPutValue("synchronized", 1, 1);
            }
            if (Modifier.isTransient(accessFlag)) {
                counts.adjustOrPutValue("transient", 1, 1);
            }
            if (Modifier.isVolatile(accessFlag)) {
                counts.adjustOrPutValue("volatile", 1, 1);
            }
            if (Modifier.isAbstract(accessFlag)) {
                counts.adjustOrPutValue("abstract", 1, 1);
            }
        }
    }

    public static String getComponentBase(String classDescriptor) {
        /*
         * Because a class type reference may be an array, e.g.:
         * [Lcom/google/android/gms/internal/zzvk$zza$zza;->clone()Ljava/lang/Object;
         *
         * It's necessary to determine the "base" class when checking if a local class.
         */
        int index = 0;
        while (classDescriptor.charAt(index) == '[') {
            index += 1;
        }
        if (index == 0) {
            return classDescriptor;
        } else {
            return classDescriptor.substring(index);
        }
    }

    public static GsonBuilder getTroveAwareGsonBuilder() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        JsonSerializer<TObjectIntMap> objectIntMapJsonSerializer = (src, typeOfSrc, context) -> {
            JsonObject jsonMerchant = new JsonObject();
            for (Object key : src.keys()) {
                int value = src.get(key);
                jsonMerchant.addProperty(key.toString(), value);
            }
            return jsonMerchant;
        };
        gsonBuilder.registerTypeAdapter(TObjectIntMap.class, objectIntMapJsonSerializer);
        gsonBuilder.registerTypeAdapter(TObjectIntHashMap.class, objectIntMapJsonSerializer);

        JsonSerializer<TObjectLongMap> objectLongMapJsonSerializer = (src, typeOfSrc, context) -> {
            JsonObject jsonMerchant = new JsonObject();
            for (Object key : src.keys()) {
                long value = src.get(key);
                jsonMerchant.addProperty(key.toString(), value);
            }
            return jsonMerchant;
        };
        gsonBuilder.registerTypeAdapter(TObjectLongMap.class, objectLongMapJsonSerializer);
        gsonBuilder.registerTypeAdapter(TObjectLongHashMap.class, objectLongMapJsonSerializer);

        JsonSerializer<TIntIntMap> intIntMapJsonSerializer = (src, typeOfSrc, context) -> {
            JsonObject jsonMerchant = new JsonObject();
            for (int key : src.keys()) {
                int value = src.get(key);
                jsonMerchant.addProperty(Integer.toHexString(key), value);
            }
            return jsonMerchant;
        };
        gsonBuilder.registerTypeAdapter(TIntIntMap.class, intIntMapJsonSerializer);
        gsonBuilder.registerTypeAdapter(TIntIntHashMap.class, intIntMapJsonSerializer);

        return gsonBuilder;
    }

    public static byte[] readFully(InputStream is) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        byte[] buffer = new byte[0xffff];
        for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
            os.write(buffer, 0, len);
        }

        return os.toByteArray();
    }

    public static byte[] sha1(InputStream input) throws NoSuchAlgorithmException, IOException {
        MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        byte[] buff = new byte[SHA1_BUFF_LENGTH];
        int remaining = 0;
        while ((remaining = input.read(buff)) != -1) {
            sha1.update(buff, 0, remaining);
        }

        return sha1.digest();
    }

    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int i = 0; i < bytes.length; i++) {
            int v = bytes[i] & 0xff;
            hexChars[i * 2] = HEX_CHARS[v >>> 4];
            hexChars[i * 2 + 1] = HEX_CHARS[v & 0x0f];
        }
        return new String(hexChars);
    }

}