package thyyxxk.webserver.utils;

import lombok.extern.slf4j.Slf4j;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * @description: IdCard functions
 * @author: DingJie
 * @create: 2021-04-01 11:42:13
 **/
@Slf4j
public class IdCardUtil {
    /**
     * 每位加权因子
     * */
    private static final int[] POWER = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
    private static final int MAX_LENGTH = 18;
    private static final int MIN_LENGTH = 15;

    /**
     * 验证所有的身份证的合法性
     */
    public static boolean isValidatedIdCard(String idCard) {
        if (StringUtil.isBlank(idCard)) {
            return false;
        }
        if (idCard.length() == MIN_LENGTH) {
            idCard = convertIdCarBy15bit(idCard);
        }
        if (StringUtil.isBlank(idCard)) {
            return false;
        }
        return isValidate18IdCard(idCard);
    }

    private static boolean isValidate18IdCard(String idCard) {
        // 非18位为假
        if (idCard.length() != MAX_LENGTH) {
            return false;
        }
        // 获取前17位
        String idCard17 = idCard.substring(0, 17);
        // 获取第18位
        String idCard18Code = idCard.substring(17, 18);
        char[] c;
        String checkCode;
        // 是否都为数字
        if (isDigital(idCard17)) {
            c = idCard17.toCharArray();
        } else {
            return false;
        }
        int[] bit;
        bit = convertCharToInt(c);
        int sum17;
        sum17 = getPowerSum(bit);
        // 将和值与11取模得到余数进行校验码判断
        checkCode = getCheckCodeBySum(sum17);
        if (null == checkCode) {
            return false;
        }
        // 将身份证的第18位与算出来的校码进行匹配，不相等就为假
        return idCard18Code.equalsIgnoreCase(checkCode);
    }

    /**
     * 将15位的身份证转成18位身份证
     */
    private static String convertIdCarBy15bit(String idCard) {
        String idCard17;
        // 非15位身份证
        if (idCard.length() != MIN_LENGTH) {
            return null;
        }
        if (isDigital(idCard)) {
            // 获取出生年月日
            String birthday = idCard.substring(6, 12);
            Date birthdate = null;
            try {
                birthdate = new SimpleDateFormat("yyMMdd").parse(birthday);
            } catch (ParseException e) {
                log.error("15位身份证转18位解析出生日期出错", e);
            }
            if (null == birthdate) {
                return null;
            }
            Calendar cday = Calendar.getInstance();
            cday.setTime(birthdate);
            String year = String.valueOf(cday.get(Calendar.YEAR));
            idCard17 = idCard.substring(0, 6) + year + idCard.substring(8);
            char[] c = idCard17.toCharArray();
            String checkCode;
            int[] bit;
            // 将字符数组转为整型数组
            bit = convertCharToInt(c);
            int sum17;
            sum17 = getPowerSum(bit);
            // 获取和值与11取模得到余数进行校验码
            checkCode = getCheckCodeBySum(sum17);
            // 获取不到校验位
            if (null == checkCode) {
                return null;
            }
            // 将前17位与第18位校验码拼接
            idCard17 += checkCode;
        } else { // 身份证包含数字
            return null;
        }
        return idCard17;
    }

    /**
     * 数字验证
     */
    private static boolean isDigital(String str) {
        return str != null && !"".equals(str) && str.matches("^[0-9]*$");
    }

    /**
     * 将身份证的每位和对应位的加权因子相乘之后，再得到和值
     */
    private static int getPowerSum(int[] bit) {
        int sum = 0;
        if (POWER.length != bit.length) {
            return sum;
        }
        for (int i = 0; i < bit.length; i++) {
            for (int j = 0; j < POWER.length; j++) {
                if (i == j) {
                    sum = sum + bit[i] * POWER[j];
                }
            }
        }
        return sum;
    }

    /**
     * 将和值与11取模得到余数进行校验码判断
     */
    private static String getCheckCodeBySum(int sum17) {
        String checkCode;
        switch (sum17 % 11) {
            case 10:
                checkCode = "2";
                break;
            case 9:
                checkCode = "3";
                break;
            case 8:
                checkCode = "4";
                break;
            case 7:
                checkCode = "5";
                break;
            case 6:
                checkCode = "6";
                break;
            case 5:
                checkCode = "7";
                break;
            case 4:
                checkCode = "8";
                break;
            case 3:
                checkCode = "9";
                break;
            case 2:
                checkCode = "x";
                break;
            case 1:
                checkCode = "0";
                break;
            case 0:
            default:
                checkCode = "1";
                break;
        }
        return checkCode;
    }

    /**
     * 将字符数组转为整型数组
     */
    private static int[] convertCharToInt(char[] c) throws NumberFormatException {
        int[] a = new int[c.length];
        int k = 0;
        for (char temp : c) {
            a[k++] = Integer.parseInt(String.valueOf(temp));
        }
        return a;
    }

    public static int getAgeByIdCard(String idCard) {
        String birthDay = idCard.substring(6, 14);
        String time = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String yearStr = time.split("-")[0];
        String monthStr = time.split("-")[1];
        String dayStr = time.split("-")[2];
        String yearBirthStr = birthDay.substring(0, 4);
        String monthBirthStr = birthDay.substring(4, 6);
        String dayBirthStr = birthDay.substring(6);
        int year = Integer.parseInt(yearStr);
        int yearBirth = Integer.parseInt(yearBirthStr);
        if (year - yearBirth <= 0) {
            return 0;
        }
        int age = year - yearBirth;
        int month = Integer.parseInt(monthStr);
        int monthBirth = Integer.parseInt(monthBirthStr);
        if (month - monthBirth > 0) {
            return age;
        }
        if (month - monthBirth < 0) {
            return --age;
        }
        int day = Integer.parseInt(dayStr);
        int dayBirth = Integer.parseInt(dayBirthStr);
        if (day - dayBirth >= 0) {
            return age;
        }
        return --age;
    }
}
