/**
 * bobotuan.com Inc.
 * Copyright (c) 2004-2020 All Rights Reserved.
 */
package com.qs.mp.system.service.id;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qs.mp.common.utils.LogUtil;
import com.qs.mp.framework.service.AbstractBizLockService;
import com.qs.mp.system.domain.SnowflakeDatacenter;
import com.qs.mp.system.service.ISnowflakeDatacenterService;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.util.CollectionUtils;

/**
 * ID生成器配置
 *
 * @author chunping.zhongcp
 * @version $Id: IdWorkerConfig.java, v 0.1 2020/5/27 chunping.zhongcp Exp $
 */
@Configuration
public class IdWorkerConfig {

    protected final    Logger logger            = LoggerFactory.getLogger(IdWorkerConfig
            .class);

    /** workerId和datacenterId最大长度，（注意这里不是下标） */
    private static final long   WORKERID_MAX_SIZE = 32;

    /** id绑定最大容量 */
    private static final long ID_ALL_SIZE = WORKERID_MAX_SIZE * WORKERID_MAX_SIZE;

    /** 应用名，避免同一台服务器两个app都是用id生成器导致冲突 */
    private @Value("${server.servlet.context-path}")
    String contextPath;

    @Value("${server.datacenterId}")
    private Long datacenterId;

    @Autowired
    private ISnowflakeDatacenterService snowflakeDatacenterService;

    @Autowired
    RedisLockRegistry redisLockRegistry;

    private static final String LOCK_KEY = "newWorkerIdAndDatecenterId";

    @PostConstruct
    private void init() {
        // 去除首尾的/
        contextPath = StringUtils.strip(contextPath, "/");
    }

    @Bean
    public SnowflakeIdWorker bizIdWorker() throws Exception {

        // 0-31
        Long workerId = null;
        //Long datacenterId = null;
        LinkedHashSet<String> localMacAddrIPv4Set = getLocalMacAddrIPv4AppendStr(contextPath);

        // 查询绑定的mac地址
        List<String> macAddrList = new ArrayList<>();
        macAddrList.addAll(localMacAddrIPv4Set);
        QueryWrapper<SnowflakeDatacenter> queryWrapper = new QueryWrapper<SnowflakeDatacenter>();
        queryWrapper.lambda().in(SnowflakeDatacenter::getMacAddress, macAddrList);
        List<SnowflakeDatacenter> snowflakeDatacenterList = snowflakeDatacenterService.list(queryWrapper);

        if (!CollectionUtils.isEmpty(snowflakeDatacenterList)) {
            SnowflakeDatacenter snowflakeDatacenter = snowflakeDatacenterList.get(0);
            long[] workerIdAndDatecenterId = new long[2];
            idToWorkerIdAndDatecenterId(snowflakeDatacenter.getDataId(),
                    workerIdAndDatecenterId);
            workerId = workerIdAndDatecenterId[0];
            //datacenterId = workerIdAndDatecenterId[1];

            String macAddress = snowflakeDatacenter.getMacAddress();
            LogUtil.info(logger,"id生成器已经绑定mac地址={0},将使用workerId={1},datacenterId={2}",
                    new Object[]{macAddress, workerId, datacenterId});

            SnowflakeDatacenter updateDatacenter = new SnowflakeDatacenter();
            updateDatacenter.setDataId(snowflakeDatacenter.getDataId());
            updateDatacenter.setHostName(InetAddress.getLocalHost().getHostName());
            snowflakeDatacenterService.updateById(updateDatacenter);
        }

        if (workerId == null || datacenterId == null) {
            // 未绑定到数据中心!!!!!!!!!!!!自动注册绑定机器//加锁
            long[] workerIdAndDatecenterId = new long[2];
            new AbstractBizLockService<Integer>() {
                @Override protected Integer process(Object... param) {
                    // 需要加锁的代码
                    LinkedHashSet<String> localMacAddrIPv4Set = (LinkedHashSet<String>) param[0];
                    long[] workerIdAndDatecenterId = (long[]) param[1];

                    QueryWrapper<SnowflakeDatacenter> queryWrapper1 = new QueryWrapper<SnowflakeDatacenter>();
                    queryWrapper1.lambda().orderByAsc(SnowflakeDatacenter::getDataId);
                    List<SnowflakeDatacenter> aLLSnowflakeDatacenterList = snowflakeDatacenterService.list(queryWrapper1);
                    // 找出空闲位置id
                    Long insertId = null;
                    if (CollectionUtils.isEmpty(aLLSnowflakeDatacenterList)) {
                        insertId = 1L;
                    } else {
                        for (int i = 1; i <= ID_ALL_SIZE; i++) {
                            if (aLLSnowflakeDatacenterList.size() >= i
                                && aLLSnowflakeDatacenterList.get(i - 1).getDataId() == i) {
                                continue;
                            } else {
                                insertId = (long) i;
                                break;
                            }
                        }
                    }
                    if (insertId == null) {
                        throw new RuntimeException("Snowflake！！！机器绑定已被占满！！！！");
                    }
                    idToWorkerIdAndDatecenterId(insertId, workerIdAndDatecenterId);

                    // 绑定机器id
                    SnowflakeDatacenter snowflakeDatacenter = new SnowflakeDatacenter();
                    snowflakeDatacenter.setDataId(insertId);
                    snowflakeDatacenter.setMacAddress(localMacAddrIPv4Set.iterator().next());
                    try {
                        snowflakeDatacenter.setHostName(InetAddress.getLocalHost().getHostName());
                    } catch (UnknownHostException e) {
                        LogUtil.error(logger, "获取本地IP地址失败");
                    }
                    snowflakeDatacenter.setCreateTime(new Date());
                    snowflakeDatacenter.setUpdateTime(new Date());
                    snowflakeDatacenterService.save(snowflakeDatacenter);

                    LogUtil.info(logger, "初始化Datacenter:{0}", new Object[] { snowflakeDatacenter });
                    return 0;
                }
            }.processWithLock(LOCK_KEY, redisLockRegistry, new Object[]{localMacAddrIPv4Set,
                                                     workerIdAndDatecenterId});
            workerId = workerIdAndDatecenterId[0];
            //datacenterId = workerIdAndDatecenterId[1];

        }

        return new SnowflakeIdWorker(workerId, datacenterId);
    }

    private void idToWorkerIdAndDatecenterId(Long id, long[] array) {
        array[0] = id / WORKERID_MAX_SIZE;
        array[1] = id - array[0] * WORKERID_MAX_SIZE - 1;
    }

    /**
     *
     * @desc 获取本机所有IPV4的mac地址
     * @return
     * @throws Exception
     */
    public LinkedHashSet<String> getLocalMacAddrIPv4AppendStr(String appName)
            throws Exception {
        LinkedHashSet<String> macSet = new LinkedHashSet<>();
        LinkedHashSet<String> ipSet = new LinkedHashSet<>();

        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
        while (networkInterfaces.hasMoreElements()) {
            NetworkInterface networkInterface = networkInterfaces.nextElement();
            Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
            byte[] mac = networkInterface.getHardwareAddress();

            if (mac != null && mac.length > 0) {
                while (inetAddresses.hasMoreElements()) {
                    InetAddress inetAddress = inetAddresses.nextElement();
                    if (inetAddress != null && inetAddress instanceof Inet4Address) {// IPV4
                        macSet.add(printHexBinary(mac) + "-" + appName);
                        ipSet.add(inetAddress.getHostAddress());
                        break;
                    }
                }
            }

        }
        LogUtil.info(logger, "本机所有IPV4网卡mac地址为{0},mac地址对应ip地址为{1}", new Object[]{macSet, ipSet});

        return macSet;
    }

    private static final char[] hexCode = "0123456789ABCDEF".toCharArray();

    public String printHexBinary(byte[] data) {
        StringBuilder r = new StringBuilder(data.length * 2);
        for (byte b : data) {
            r.append(hexCode[(b >> 4) & 0xF]);
            r.append(hexCode[(b & 0xF)]);
        }
        return r.toString();
    }
}
