package com.ywt.outpatient.core.utils;

import com.ywt.biz.common.util.Checker;
import com.ywt.biz.common.util.DateUtil;
import com.ywt.biz.common.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

public final class IdCardUtil {
    private static Logger logger = LoggerFactory.getLogger(IdCardUtil.class);

    private IdCardUtil() {
    }

    // 身份证前17位数字本体码对应的加权因子
    private static final int[] WEIGHT_LIST = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};

    // 身份证第18位数字本体码（校验码）
    private static final char[] CHECK_CODE_LIST = new char[]{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};

    // 男性数字位
    private static final int[] MALE_DIGIT = new int[]{1, 3, 5, 7, 9};

    // 女性数字位
    private static final int[] FEMALE_DIGIT = new int[]{0, 2, 4, 6, 8};

    private static final String ID_CARD_NO_PATTERN = "^([1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}[0-9Xx])$";

    // 日期格式
    public static final String DATE_PATTERN1 = "yyyyMMdd";
    public static final String DATE_PATTERN2 = "yyyy-MM-dd";

    /**
     * 计算校验码
     * return 18th id card number
     *
     * @param idCardNo
     * @return
     */
    public static char calculateCheckCode(String idCardNo) {
        int sum = 0;

        for (int i = 0, len = idCardNo.length(); i < len; i++) {
            char c = idCardNo.charAt(i);
            int digit = Integer.parseInt(String.valueOf(c));
            ;
            sum += digit * WEIGHT_LIST[i];
        }

        int mod = sum % 11;

        return CHECK_CODE_LIST[mod];
    }

    /**
     * 校验是否为合法的身份证号码
     *
     * @param idCardNo
     * @return
     */
    public static boolean verify(String idCardNo) {
        if (StringHelper.isNullOrWhiteSpace(idCardNo)) {
            return false;
        }

        int len = idCardNo.length();

        if (len != 15 && len != 18) {
            return false;
        }

        if (!isIdCardNoFormat(idCardNo)) {
            return false;
        }

        if (len == 18) {
            String checkCode = String.valueOf(calculateCheckCode(idCardNo.substring(0, len - 1)));
            String c = String.valueOf(idCardNo.charAt(len - 1));

            if (!checkCode.toUpperCase().equals(c.toUpperCase())) {
                return false;
            }
        }

        return true;
    }

    /**
     * 根据身份证出生日期计算当前周岁
     * <p>
     * 《最高人民法院关于审理未成年人刑事案件具体应用法律若干问题的解释》第二条明确规定，
     * “周岁”，按照公历的年月日计算，从周岁生日的第二天起算。
     * <p>
     *
     * @param idCardNo 身份证号码
     * @return
     */
    public static int getCurrentAge(String idCardNo) {
        Date date = getBirthday(idCardNo);
        if (date == null) {
            return -1;
        }
        return getCurrentAgeCommon(date);
    }

    /**
     * 根据身份证出生日期计算当前周岁
     * <p>
     * 《最高人民法院关于审理未成年人刑事案件具体应用法律若干问题的解释》第二条明确规定，
     * “周岁”，按照公历的年月日计算，从周岁生日的第二天起算。
     * <p>
     *
     * @param birthday 出生日期
     * @return
     */
    public static int getCurrentAgeByBirthday(String birthday) {
        return getCurrentAgeByBirthday(birthday, DATE_PATTERN1);
    }

    public static int getCurrentAgeByBirthday(String birthday, String pattern) {
        if (Checker.isNone(birthday)) {
            return -1;
        }
        Date date = DateUtil.parseDate(birthday, pattern);
        if (date == null) {
            return -1;
        }
        return getCurrentAgeCommon(date);
    }

    /**
     * 根据身份证出生日期计算当前周岁
     * <p>
     * 《最高人民法院关于审理未成年人刑事案件具体应用法律若干问题的解释》第二条明确规定，
     * “周岁”，按照公历的年月日计算，从周岁生日的第二天起算。
     * <p>
     *
     * @param date 日期
     * @return
     */
    public static int getCurrentAgeCommon(Date date) {
        Calendar birthday = Calendar.getInstance();
        birthday.setTime(date);
        int year = birthday.get(Calendar.YEAR);
        int month = birthday.get(Calendar.MONTH);
        int day = birthday.get(Calendar.DAY_OF_MONTH);

        Calendar current = Calendar.getInstance();
        int currentYear = current.get(Calendar.YEAR);
        int currentMonth = current.get(Calendar.MONTH);
        int currentDay = current.get(Calendar.DAY_OF_MONTH);

        int age = currentYear - year;

        if (age <= 0) {
            return 0;
        }
        if (currentMonth < month || (currentMonth == month && currentDay <= day)) {
            age--;
        }
        return age < 0 ? 0 : age;
    }

    /**
     * 根据身份证号码读取性别
     *
     * @param idCardNo
     * @return
     */
    public static String getSex(String idCardNo) {
        String errMessage = "未知";
        try {
            if (StringHelper.isNullOrEmpty(idCardNo)) return errMessage;
            int len = idCardNo.length();
            if (len < 2) {
                return errMessage;
            }
            char ch = idCardNo.charAt(len - 2);
            int digit = Integer.parseInt(String.valueOf(ch));
            return digit % 2 == 0 ? "女" : "男";
        } catch (Exception e){
            logger.error("getSexWithNoCheckIdCard() :idCardNo{}, {}", idCardNo, e.getMessage(), e);
            return errMessage;
        }
    }

    /**
     * 根据身份证号码读取性别
     *
     * @param idCardNo
     * @return (1 - 男 ， 2 - 女)
     */
    public static int getSexInt(String idCardNo) {
        if (StringHelper.isNullOrEmpty(idCardNo)) return 0;
        checkIdCardNo(idCardNo);
        int len = idCardNo.length();
        char ch = idCardNo.charAt(len - 2);
        int digit = Integer.parseInt(String.valueOf(ch));

        return digit % 2 == 0 ? 2 : 1;
    }

    /**
     * 从身份证号获取出生日期
     *
     * @param idCardNo
     * @return
     */
    public static Date getBirthday(String idCardNo) {
        if (StringHelper.isNullOrEmpty(idCardNo)) return null;
        checkIdCardNo(idCardNo);

        int len = idCardNo.length();
        String birthday;

        if (len == 18) {
            birthday = idCardNo.substring(6, 14);
        } else {
            birthday = "19" + idCardNo.substring(6, 12);
        }

        return DateUtil.parseDate(birthday, "yyyyMMdd");
    }

    /**
     * 从身份证获取年份
     *
     * @param idCardNo
     * @return
     */
    public static int getYear(String idCardNo) {
        checkIdCardNo(idCardNo);


        int len = idCardNo.length();

        if (len == 18) {
            return Integer.parseInt(idCardNo.substring(6, 10));
        }

        return Integer.parseInt("19" + idCardNo.substring(6, 8));
    }

    /**
     * 从身份证获取月份
     *
     * @param idCardNo
     * @return
     */
    public static int getMonth(String idCardNo) {
        checkIdCardNo(idCardNo);

        int len = idCardNo.length();

        if (len == 18) {
            return Integer.parseInt(idCardNo.substring(10, 12));
        }

        return Integer.parseInt(idCardNo.substring(8, 10));
    }

    /**
     * 从身份证获取日期
     *
     * @param idCardNo
     * @return
     */
    public static int getDate(String idCardNo) {
        checkIdCardNo(idCardNo);

        int len = idCardNo.length();
        if (len == 18) {
            return Integer.parseInt(idCardNo.substring(12, 14));
        }
        return Integer.parseInt(idCardNo.substring(10, 12));
    }

    /**
     * 是否为身份证格式
     *
     * @param idCardNo
     * @return
     */
    private static boolean isIdCardNoFormat(String idCardNo) {
        return Pattern.matches(ID_CARD_NO_PATTERN, idCardNo);
    }

    private static String checkIdCardNo(String idCardNo) {
        if (!verify(idCardNo)) {
            throw new IllegalArgumentException(String.format("身份证号无效：%s", idCardNo));
        }
        return idCardNo;
    }

    /**
     * 隐藏部分身份证号（eg:425***********2273）
     *
     * @param idCardNo
     * @return
     */
    public static String getPartIdNo(String idCardNo) {
        if (idCardNo == null || idCardNo.equals("")) return "";
        int len = idCardNo.length();
        String str1 = idCardNo.substring(0, 3);
        String str3 = idCardNo.substring(len - 4, len);
        String str2 = "";
        int xLen = len - 7;
        for (int i = 0; i < xLen; i++) {
            str2 += "*";
        }
        String idNoStr = str1 + str2 + str3;
        return idNoStr;
    }

    /**
     * 台湾居民来往大陆通行证
     * 规则： 新版8位或18位数字 或 旧版9位数字 + 英文字母 或 8位数字 + 英文字母
     * 样本： 12345678
     */
    public static Boolean isTWCard(String card) {
        String reg = "^\\d{8}|^[a-zA-Z0-9]{10}|^[a-zA-Z0-9]{9}|^\\d{18}$";
        if (card.matches(reg) == false) {
            //台湾居民来往大陆通行证号码不合格
            return false;
        } else {
            //校验通过
            return true;
        }
    }

    /**
     * 台湾身份首字母对应数字
     */
    public static Map<String, Integer> twFirstCode = new HashMap<String, Integer>();
    /**
     * 香港身份首字母对应数字
     */
    public static Map<String, Integer> hkFirstCode = new HashMap<String, Integer>();

    static {
        twFirstCode.put("A", 10);
        twFirstCode.put("B", 11);
        twFirstCode.put("C", 12);
        twFirstCode.put("D", 13);
        twFirstCode.put("E", 14);
        twFirstCode.put("F", 15);
        twFirstCode.put("G", 16);
        twFirstCode.put("H", 17);
        twFirstCode.put("J", 18);
        twFirstCode.put("K", 19);
        twFirstCode.put("L", 20);
        twFirstCode.put("M", 21);
        twFirstCode.put("N", 22);
        twFirstCode.put("P", 23);
        twFirstCode.put("Q", 24);
        twFirstCode.put("R", 25);
        twFirstCode.put("S", 26);
        twFirstCode.put("T", 27);
        twFirstCode.put("U", 28);
        twFirstCode.put("V", 29);
        twFirstCode.put("X", 30);
        twFirstCode.put("Y", 31);
        twFirstCode.put("W", 32);
        twFirstCode.put("Z", 33);
        twFirstCode.put("I", 34);
        twFirstCode.put("O", 35);
        hkFirstCode.put("A", 1);
        hkFirstCode.put("B", 2);
        hkFirstCode.put("C", 3);
        hkFirstCode.put("R", 18);
        hkFirstCode.put("U", 21);
        hkFirstCode.put("Z", 26);
        hkFirstCode.put("X", 24);
        hkFirstCode.put("W", 23);
        hkFirstCode.put("O", 15);
        hkFirstCode.put("N", 14);
    }

    /**
     * 验证台湾身份证号码
     * 共10位
     * 计算校验码的步骤：1、英文转成数字，其个位数字*9+十位数
     * 2、中间八位数字，各数字从左到右依次乘以8、7、6、5....2、1
     * 3、计算步骤1和2的和sum
     * 4、计算10-sum%10 ,其结果就是检查码，若余数为0，则检查码就是0
     *
     * @param idCard 身份证号码
     * @return 验证码是否符合
     */
    public static boolean validateTWCard(String idCard) {
        //第一位是字母（表示地区代码），通过map找到对应的数字代码
        String start = idCard.substring(0, 1);
        //第二位数字表示性别，男生为1，女生为2
        String mid = idCard.substring(1, 9);
        //最后一位是检查码,由第一位和8个数字的组合计算得到校验和
        String end = idCard.substring(9, 10);
        Integer iStart = twFirstCode.get(start);

        Integer sum = iStart / 10 + (iStart % 10) * 9;
        char[] chars = mid.toCharArray();
        Integer iflag = 8;
        for (char c : chars) {
            sum = sum + Integer.valueOf(c + "") * iflag;
            iflag--;
        }
        return (sum % 10 == 0 ? 0 : (10 - sum % 10)) == Integer.valueOf(end) ? true : false;
    }

    /**
     * 港澳居民来往内地通行证
     * 规则： H/M + 10位或6位数字
     * 例：H1234567890
     */
    public static Boolean isHKCard(String card) {
        String reg = "^([A-Z]\\d{6,10}(\\(\\w{1}\\))?)$";
        if (card.matches(reg) == false) {
            //港澳居民来往内地通行证号码不合格
            return false;
        } else {
            //校验通过
            return true;
        }
    }

    /**
     * 验证香港身份证号码(存在Bug，部份特殊身份证无法检查)
     * <p>
     * 如：身份证号码是：C668668（9）
     * 香港身份证号码由三部分组成：一个英文字母；6个数字；括号及0-9中的任一个数字，或者字母A。
     * 括号中的数字或字母A，是校验码，用于检验括号前面的号码的逻辑正确性。
     * <p>
     * 身份证前2位为英文字符，如果只出现一个英文字符则表示第一位是空格，对应数字58, 前2位英文字符A-Z分别对应数字10-35
     * 最后一位校验码为0-9的数字加上字符"A"，"A"代表10
     * 将身份证号码全部转换为数字，分别对应乘9、8...1相加的总和，整除11则证件号码有效
     *
     * @param idCard 身份证号码
     * @return 验证码是否符合
     */
    public static boolean validateHKCard(String idCard) {
        String card = idCard.replaceAll("[\\(|\\)]", "");
        Integer sum = 0;
        if (card.length() == 9) {
            sum = (Integer.valueOf(card.substring(0, 1).toUpperCase()
                    .toCharArray()[0]) - 55)
                    * 9
                    + (Integer.valueOf(card.substring(1, 2).toUpperCase()
                    .toCharArray()[0]) - 55) * 8;
            card = card.substring(1, 9);
        } else {
            sum = 522 + (Integer.valueOf(card.substring(0, 1).toUpperCase()
                    .toCharArray()[0]) - 55) * 8;
        }
        String mid = card.substring(1, 7);
        String end = card.substring(7, 8);
        char[] chars = mid.toCharArray();
        Integer iflag = 7;
        for (char c : chars) {
            sum = sum + Integer.valueOf(c + "") * iflag;
            iflag--;
        }
        if (end.toUpperCase().equals("A")) {
            sum = sum + 10;
        } else {
            sum = sum + Integer.valueOf(end);
        }
        return (sum % 11 == 0) ? true : false;
    }


    /**
     * 根据身份证号码读取性别
     *
     * @param idCardNo
     * @return
     */
    public static String getSexWithNoCheckIdCard(String idCardNo) {
        String errMessage = "未知";
        try {
            if (StringHelper.isNullOrEmpty(idCardNo)) return errMessage;
            int len = idCardNo.length();
            if (len < 2) {
                return errMessage;
            }
            char ch = idCardNo.charAt(len - 2);
            int digit = Integer.parseInt(String.valueOf(ch));
            return digit % 2 == 0 ? "女" : "男";
        } catch (Exception e){
            logger.error("getSexWithNoCheckIdCard() :idCardNo{}, {}", idCardNo, e.getMessage(), e);
            return errMessage;
        }

    }

    /**
     * 根据身份证号码读取性别
     *
     * @param idCardNo
     * @return (1 - 男 ， 2 - 女)
     */
    public static int getSexIntWithNoCheckIdCard(String idCardNo) {
        if (StringHelper.isNullOrEmpty(idCardNo)) return 0;
        int len = idCardNo.length();
        char ch = idCardNo.charAt(len - 2);
        int digit = Integer.parseInt(String.valueOf(ch));

        return digit % 2 == 0 ? 2 : 1;
    }
}
