package org.diagbot.graph.encryption;

import java.security.MessageDigest;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 基础加密组件
 *
 * @author Mark Huang
 * @version 1.0
 * @created 26/02/2019
 */
public abstract class Coder {
    public static final String KEY_SHA = "SHA-256";
    public static final String KEY_MD5 = "MD5";

    // 用来将字节转换成 16 进制表示的字符
    private static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    private static String reg = "^[a-zA-Z]+$";
    private static Pattern patreg = Pattern.compile(reg);
    private static String regnum = "^\\d+$";
    private static Pattern patnum = Pattern.compile(regnum);

    /**
     * MAC算法可选以下多种算法
     *
     * <pre>
     * HmacMD5
     * HmacSHA1
     * HmacSHA256
     * HmacSHA384
     * HmacSHA512
     * </pre>
     */
    public static final String KEY_MAC = "HmacMD5";

    /**
     * BASE64解密
     *
     *
     * @return
     * @throws Exception
     *
    public static byte[] decryptBASE64(String key) throws Exception {
        return (new BASE64Decoder()).decodeBuffer(key);
    }

    /**
     * BASE64加密
     *
     *
     * @return
     * @throws Exception
     *
    public static String encryptBASE64(byte[] key) throws Exception {
        return (new BASE64Encoder()).encodeBuffer(key);
    }

    /**
     * MD5加密
     *
     *
     * @return
     * @throws Exception
     *
    public static byte[] encryptMD5(byte[] data) throws Exception {

        MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
        md5.update(data);

        return md5.digest();

    }

    /**
     * SHA加密
     *
     *
     * @return
     * @throws Exception
     *
    public static byte[] encryptSHA(byte[] data) throws Exception {

        MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
        sha.update(data);

        return sha.digest();
    }

    /**
     * 初始化HMAC密钥
     *
     * @return
     * @throws Exception
     *
    public static String initMacKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);

        SecretKey secretKey = keyGenerator.generateKey();
        return encryptBASE64(secretKey.getEncoded());
    }

    /**
     * HMAC加密
     *
     *
     *
     * @return
     * @throws Exception
     *
    public static byte[] encryptHMAC(byte[] data, String key) throws Exception {

        SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);
        Mac mac = Mac.getInstance(secretKey.getAlgorithm());
        mac.init(secretKey);

        return mac.doFinal(data);

    }

    /**
     * 将加密后的数据转换成16进制
     *
     *
     *
    public static String getHash(byte[] encryptbytes) {

        StringBuilder sb = new StringBuilder();
        StringBuilder hex = new StringBuilder();

        try {
            for (byte b : encryptbytes) {
                int iRet = b;
                if (iRet < 0) {
                    iRet += 256;
                }
                int iD1 = iRet / 16;
                int iD2 = iRet % 16;

                sb.append(hexDigits[iD1] + "" + hexDigits[iD2]);

                if (b<0) {
                    hex.append(String.format("%02x", b));
                }
                else {
                    hex.append(String.format("%02X", b));
                }
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
//            return sb.toString();
            return hex.toString();
        }
    }


    /**
     * 将16进制的加密数据转换成byte数组
     *
     *
     *
    public static byte[] deHash(String hash) {

        byte[] bytes = new byte[hash.substring(1).length()/2];
        String a,b;
        try {
            for (int i=0; i<bytes.length; i++) {
                a = hash.substring(i*2,i*2+1);
                b = hash.substring(i*2+1, i*2+2);

                /*
                int iRet = b;
                if (iRet < 0) {
                    iRet += 256;
                }
                int iD1 = iRet / 16;
                int iD2 = iRet % 16;

                sb.append(hexDigits[iD1] + "" + hexDigits[iD2]);

                if (b<0) {
                    hex.append(String.format("%02x", b));
                }
                else {
                    hex.append(String.format("%02X", b));
                }
                *
            }
            System.out.println();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            return bytes;
        }
    }
    */
    /**
     * 加密
     * @param token
     * @return
     */
    public static String Encrypt(String token) {
        String hash = null;

        try {
            if (!isEng(token)) {
                hash = Coder.StringToUTF8(token);
            }
            else {
                hash = Coder.StringToA(token);
            }
            hash = Base64.encodeBase64String(hash.getBytes("utf-8"));
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            return hash;
        }
    }

    /**
     * 解密
     * @param code
     * @return
     */
    public static String Decrypt(String code) {
        String result = null;

        try {
            byte[] bytes = Base64.decodeBase64(code);
            result = new String(bytes, "utf-8");
            if (isNum(result)) {
                result = Coder.AToString(result);
            }
            else {
                result = Coder.UTF8ToString(result);
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            return result;
        }
    }

    public static Map<String, String> DecryptNode(Map<String, String> node) {
        Map<String, String> result = new HashMap<>();
        JSONObject val = new JSONObject();
        JSONObject nobj;

        try {
            for (String key:node.keySet()) {
                String okey = Decrypt(key);
                System.out.println(okey);
                nobj = JSONObject.parseObject(node.get(key));

                for (String k:nobj.keySet()) {
                    System.out.println(Decrypt(k));
                    val = new JSONObject();
                    String valkey = Decrypt(k);
                    val.put(valkey, new JSONObject());

                    JSONObject sobj = nobj.getJSONObject(k);
                    for (String l:sobj.keySet()) {
                        val.getJSONObject(valkey).put(l, Decrypt(sobj.getString(l)));
                    }
                }
                result.put(okey, val.toJSONString());
            }

        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            return result;
        }
    }

    public static Map<String, String> DecryptPath(Map<String, String> path) {
        Map<String, String> result = new HashMap<>();
        JSONObject content, map;

        try {
            result.put("path", "");
            content = JSONObject.parseObject(path.get("path"));
            for (String key:content.keySet()) {
                System.out.println(key);
                JSONObject subcont = content.getJSONObject(key);

                String s_str = subcont.getString("start");

                JSONObject obj = JSONObject.parseObject(s_str);
                map = new JSONObject();
                for (String k:obj.keySet()) {
                    String n_name = Decrypt(k);
                    JSONObject n_cont = JSONObject.parseObject(obj.get(k).toString());
                    map.put(n_name, new JSONObject());
                    map.getJSONObject(n_name).put("name", Decrypt(n_cont.getString("name")));
                    map.getJSONObject(n_name).put("label", Decrypt(n_cont.getString("label")));
                }
                System.out.println(map);

                System.out.println(content.getJSONObject(key).getString("end"));
                System.out.println(content.getJSONObject(key).getString("relation"));
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            return result;
        }
    }


    /**
     * 汉字 转换为对应的 UTF-8编码
     * @param s 木
     * @return E69CA8
     */
    public static String StringToUTF8(String s) {
        if (s == null || s.equals("")) {
            return null;
        }
        StringBuffer sb = new StringBuffer();
        try {
            char c;
            for (int i = 0; i < s.length(); i++) {
                c = s.charAt(i);
                if (c >= 0 && c <= 255) {
                    sb.append(c);
                } else {
                    byte[] b;
                    b = Character.toString(c).getBytes("utf-8");
                    for (int j = 0; j < b.length; j++) {
                        int k = b[j];
                        //转换为unsigned integer  无符号integer
					/*if (k < 0)
						k += 256;*/
                        k = k < 0? k+256:k;
                        //返回整数参数的字符串表示形式 作为十六进制（base16）中的无符号整数
                        //该值以十六进制（base16）转换为ASCII数字的字符串
                        sb.append(Integer.toHexString(k).toUpperCase());

                        // url转置形式
                        // sb.append("%" +Integer.toHexString(k).toUpperCase());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }


    /**
     * UTF-8编码 转换为对应的 汉字
     *
     * @param s E69CA8
     * @return 木
     */
    public static String UTF8ToString(String s) {
        if (s == null || s.equals("")) {
            return null;
        }
        try {
            s = s.toUpperCase();
            int total = s.length() / 2;
            //标识字节长度
            int pos = 0;
            byte[] buffer = new byte[total];
            for (int i = 0; i < total; i++) {
                int start = i * 2;
                //将字符串参数解析为第二个参数指定的基数中的有符号整数。
                buffer[i] = (byte) Integer.parseInt(s.substring(start, start + 2), 16);
                pos++;
            }
            //通过使用指定的字符集解码指定的字节子阵列来构造一个新的字符串。
            //新字符串的长度是字符集的函数，因此可能不等于子数组的长度。
            return new String(buffer, 0, pos, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return s;
    }

    //字符串转换为ASCII
    public static String StringToA(String content){
        String result = "";
        int max = content.length();
        for (int i=0; i<max; i++){
            char c = content.charAt(i);
            int b = (int)c;
            b = (b<100)?b*10:b;
//            System.out.println(b);
            result = result + b;
        }
        return result;
    }

    public static String AToString(String token) {
        String result = "";
        String val = "";
        int ascii = 0;
        String first = "";
        if (token.length()%3 ==0) {
            for (int i = 0; i < token.length() / 3; i++) {
                val = token.substring(i*3,i*3+3);
                first = String.valueOf(val.toCharArray()[0]);
                if (val.toCharArray()[2] == '0' && Integer.parseInt(first) >= 6) {
                    val = val.substring(0,2);
                }
                ascii = Integer.parseInt(val);
                result += (char)ascii;
            }
//            System.out.println(result);
        }
        return result;
    }

    public static Boolean isEng(String token) {
        Boolean match = false;
        Matcher matcher;
        try {
            matcher = patreg.matcher(token);
            match = matcher.find();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            return match;
        }
    }

    public static Boolean isNum(String token) {
        Boolean match = false;
        try {
            Matcher matcher = patnum.matcher(token);
            match = matcher.find();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            return match;
        }
    }
}
