package com.mortals.xhx.base.system.idgenerator.service.impl;

import com.alibaba.fastjson.JSON;
import com.mortals.xhx.base.system.idgenerator.dao.IdgeneratorDao;
import com.mortals.xhx.base.system.idgenerator.model.IdgeneratorEntity;
import com.mortals.xhx.base.system.idgenerator.service.IdgeneratorService;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * <p>Title: id生成器</p>
 * <p>Description: IdgeneratorServiceImpl service接口 </p>
 * <p>Copyright: Copyright &reg;  </p>
 * <p>Company: </p>
 *
 * @author
 * @version 1.0.0
 */
@Service("idgeneratorService")
public class IdgeneratorServiceImpl implements IdgeneratorService {

    private Log log = LogFactory.getLog(IdgeneratorServiceImpl.class);

    @Resource
    private IdgeneratorDao idgeneratorDao;

    private ConcurrentHashMap<IdGeneratorKey, SequenceId> seqIdMap = new ConcurrentHashMap<>();

    /**
     * 默认步长，即一次取多少个ID
     */
    private static final Long DEFAULT_STEP = 50L;


    /**
     * <B>Description</B> 生成器key枚举 <br />
     *
     * @author
     * @createTime 2016年3月16日 上午10:07:19
     */
    @SuppressWarnings("rawtypes")
    public static enum IdGeneratorKey {

        /**
         * 生成各基础信息表的version字段值(保证所有分表ID全局唯一) <br/>
         * 生成规则：1开始
         *
         * @return 返回Long类型
         */
        INFO_VERSION_KEY(50L, "getInfoVersion", null),


        ATTEND_KEY(1L, "attend", null),
        REVIEW_KEY(1L, "review", null),
        COMPLAIN_KEY(1L, "complain", null),
        GOWORK_KEY(1L, "gowork", null),
        EFFECT_KEY(1L, "effect", null),
        OTHER_KEY(1L, "other", null),

        USER_KEY(10000L, "user", null),

        /**
         * 空，测试用
         */
        DUMMY(DEFAULT_STEP, "", null),
        ;

        /**
         * 步长
         */
        private final Long step;
        /**
         * 生成ID方法名
         */
        private final String methodName;
        /**
         * 生成ID所需参数因子类型
         */
        private final Class[] factorsClazz;

        private IdGeneratorKey(Long step, String methodName, Class[] factorsClazz) {
            this.step = step;
            this.methodName = methodName;
            this.factorsClazz = factorsClazz;
        }

        public Long getStep() {
            return step;
        }

        public String getMethodName() {
            return methodName;
        }

        public Class[] getFactorsClazz() {
            return factorsClazz;
        }

    }

    @Override
    public Long getLongId(IdGeneratorKey key, Object... factorInfo) {
        try {
            Method method = this.getClass().getDeclaredMethod(key.getMethodName(), key.getFactorsClazz());
            return (Long) method.invoke(this, factorInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String getStringId(IdGeneratorKey key, Object... factorInfo) {
        try {
            Method method = this.getClass().getDeclaredMethod(key.getMethodName(), key.getFactorsClazz());
            return (String) method.invoke(this, factorInfo);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<Long> getLongIdList(IdGeneratorKey key, Object... factorInfo) {
        try {
            Method method = this.getClass().getDeclaredMethod(key.getMethodName() + "Batch", key.getFactorsClazz());
            return (List<Long>) method.invoke(this, factorInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private Long getInfoVersion() {
        synchronized (IdGeneratorKey.INFO_VERSION_KEY) {
            return getNextSequenceId(IdGeneratorKey.INFO_VERSION_KEY, null);
        }
    }

    private Long attend() {
        synchronized (IdGeneratorKey.ATTEND_KEY) {
            return getNextSequenceId(IdGeneratorKey.ATTEND_KEY, null);
        }
    }

    private Long review() {
        synchronized (IdGeneratorKey.REVIEW_KEY) {
            return getNextSequenceId(IdGeneratorKey.REVIEW_KEY, null);
        }
    }

    private Long complain() {
        synchronized (IdGeneratorKey.COMPLAIN_KEY) {
            return getNextSequenceId(IdGeneratorKey.COMPLAIN_KEY, null);
        }
    }

    private Long gowork() {
        synchronized (IdGeneratorKey.GOWORK_KEY) {
            return getNextSequenceId(IdGeneratorKey.GOWORK_KEY, null);
        }
    }

    private Long effect() {
        synchronized (IdGeneratorKey.EFFECT_KEY) {
            return getNextSequenceId(IdGeneratorKey.EFFECT_KEY, null);
        }
    }

    private Long other() {
        synchronized (IdGeneratorKey.OTHER_KEY) {
            return getNextSequenceId(IdGeneratorKey.OTHER_KEY, null);
        }
    }

    private Long user() {
        synchronized (IdGeneratorKey.USER_KEY) {
            return getNextSequenceId(IdGeneratorKey.USER_KEY, null);
        }
    }

    // *******************************************************************************

    /**
     * 获取表唯一ID,
     *
     * @param key      id类型
     * @param startSeq 从指定SEQ开始
     * @return
     */
    private Long getNextSequenceId(IdGeneratorKey key, Long startSeq) {
        Long seqId = getNextSeqId(key, startSeq);
        // 失败后，最大重复10次获取
        for (int i = 0; (seqId <= 0 && i < 10); i++) {
            seqIdMap.remove(key);
            // 重置为空，从数据库获取最新值
            seqId = getNextSeqId(key, null);
        }
        if (seqId <= 0) {
            throw new RuntimeException("多应用服务器争用程度太高，请稍后再试");
        }
        return seqId;
    }

    private Long getNextSeqId(final IdGeneratorKey key, final Long startSeq) {
        SequenceId idSequence = seqIdMap.get(key);
        if (idSequence == null) {
            idSequence = new SequenceId();
            idSequence.setStep(key.getStep());
            SequenceId returnSeqId = seqIdMap.putIfAbsent(key, idSequence);
            if (returnSeqId != null) {
                idSequence = returnSeqId;
            }
        }
        if (startSeq != null || idSequence.getStartSeq() >= idSequence.getFinishSeq()) {
            if (updateTableIdMaxValue(key, startSeq, idSequence) <= 0) {
                return 0L;
            }
        }
        Long id = idSequence.next();
        return id;
    }


    private List<Long> getNextSequenceIdBatch(IdGeneratorKey key, Long startSeq, Integer num) {
        List<Long> seqId = getNextSeqIdBatch(key, startSeq, num);
        // 失败后，最大重复10次获取
        for (int i = 0; (seqId.size() <= 0 && i < 10); i++) {
            seqIdMap.remove(key);
            // 重置为空，从数据库获取最新值
            seqId = getNextSeqIdBatch(key, null, num);
        }
        if (seqId.size() == 0) {
            throw new RuntimeException("多应用服务器争用程度太高，请稍后再试");
        }
        return seqId;
    }

    private List<Long> getNextSeqIdBatch(final IdGeneratorKey key, final Long startSeq, Integer num) {
        SequenceId idSequence = seqIdMap.get(key);
        if (idSequence == null) {
            idSequence = new SequenceId();
            idSequence.setStep(num.longValue());
            SequenceId returnSeqId = seqIdMap.putIfAbsent(key, idSequence);
            if (returnSeqId != null) {
                idSequence = returnSeqId;
            }
        }
        if (startSeq != null || idSequence.getStartSeq() >= idSequence.getFinishSeq()
                || idSequence.getStartSeq() + idSequence.getStep() > idSequence.getFinishSeq()) {
            if (updateTableIdMaxValueBatch(key, startSeq, idSequence, num) <= 0) {
                return new ArrayList<>();
            }
        }
        List<Long> idList = new ArrayList<>();
        for (int i = 0; i < num.intValue(); i++) {
            idList.add(idSequence.next());
        }
        return idList;
    }


    /**
     * 更新最新ID到数据表
     *
     * @param key
     * @param startSeq
     * @param idSequence
     * @return
     * @createTime 2016年3月16日 上午10:09:00
     * @author
     */
    private int updateTableIdMaxValue(final IdGeneratorKey key, final Long startSeq, final SequenceId idSequence) {
        IdgeneratorEntity entity = new IdgeneratorEntity();
        entity.setIdType(key.name());
        List<IdgeneratorEntity> list = idgeneratorDao.getList(entity);
        IdgeneratorEntity idgeneratorEntity = list.get(0);
        long sseq = idgeneratorEntity.getIdMaxValue();
        if (startSeq != null && idgeneratorEntity.getIdMaxValue() < startSeq) {
            sseq = startSeq;
        }
        idSequence.setStartSeq(sseq);
        idSequence.setFinishSeq(idSequence.getStartSeq() + idSequence.getStep());
        idgeneratorEntity.setIdMaxValue(idSequence.getFinishSeq());
        idgeneratorEntity.setGmtModify(new Date());
        if (log.isDebugEnabled()) {
            log.debug("idgenerator update table idSequence value:" + JSON.toJSONString(idSequence));
            log.debug("idgenerator update table set value:" + JSON.toJSONString(idgeneratorEntity));
        }
        IdgeneratorEntity condition = new IdgeneratorEntity();
        condition.setId(idgeneratorEntity.getId());
        condition.setVersionNum(idgeneratorEntity.getVersionNum());
        idgeneratorEntity.setVersionNum(idgeneratorEntity.getVersionNum() + 1);
        int result = idgeneratorDao.update(idgeneratorEntity, condition);
        if (result > 0) {
            return result;
        } else {
            log.warn("version locked, retry it.....");
            return updateTableIdMaxValue(key, null, idSequence);
        }
    }


    private int updateTableIdMaxValueBatch(final IdGeneratorKey key, final Long startSeq, final SequenceId idSequence,
                                           Integer num) {
        synchronized (IdGeneratorKey.DUMMY) {
            IdgeneratorEntity entity = new IdgeneratorEntity();
            entity.setIdType(key.name());
            List<IdgeneratorEntity> list = idgeneratorDao.getList(entity);
            IdgeneratorEntity idgeneratorEntity = list.get(0);
            long sseq = idgeneratorEntity.getIdMaxValue();
            if (startSeq != null && idgeneratorEntity.getIdMaxValue() < startSeq) {
                sseq = startSeq;
            }
            idSequence.setStartSeq(sseq);
            idSequence.setFinishSeq(idSequence.getStartSeq() + num.longValue());
            idgeneratorEntity.setIdMaxValue(idSequence.getFinishSeq());
            idgeneratorEntity.setGmtModify(new Date());
            if (log.isDebugEnabled()) {
                log.debug("idgenerator update table idSequence value:" + JSON.toJSONString(idSequence));
                log.debug("idgenerator update table set value:" + JSON.toJSONString(idgeneratorEntity));
            }
            IdgeneratorEntity condition = new IdgeneratorEntity();
            condition.setId(idgeneratorEntity.getId());
            condition.setVersionNum(idgeneratorEntity.getVersionNum());
            idgeneratorEntity.setVersionNum(idgeneratorEntity.getVersionNum() + 1);
            int result = idgeneratorDao.update(idgeneratorEntity, condition);
            if (result > 0) {
                return result;
            } else {
                log.warn("version locked, retry it.....");
                return updateTableIdMaxValueBatch(key, null, idSequence, num);
            }
        }
    }

    private class SequenceId {

        /**
         * 步长
         */
        private Long step = 100L;

        /**
         * 开始ID
         */
        private volatile AtomicLong startSeq = new AtomicLong(1);

        /**
         * 结束ID
         */
        private volatile Long finishSeq = 0L;

        public Long next() {
            return startSeq.getAndIncrement();
        }

        public Long getStep() {
            return step;
        }

        public void setStep(Long step) {
            this.step = step;
        }

        public Long getStartSeq() {
            return startSeq.get();
        }

        public void setStartSeq(Long startSeq) {
            this.startSeq = new AtomicLong(startSeq);
        }

        public Long getFinishSeq() {
            return finishSeq;
        }

        public void setFinishSeq(Long finishSeq) {
            this.finishSeq = finishSeq;
        }
    }

}