package com.myzmds.ecp.core.uid.leaf;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;

import com.myzmds.ecp.core.uid.baidu.utils.NamingThreadFactory;

/**
 * @类名称 SegmentServiceImpl.java
 * @类描述 <pre>Segment 策略id生成实现类</pre>
 * @作者 庄梦蝶殇 [email protected]
 * @创建时间 2018年9月6日 下午4:28:36
 * @版本 1.0.2
 *
 * @修改记录
 * <pre>
 *     版本                       修改人 		修改日期 		 修改内容描述
 *     ----------------------------------------------
 *     1.0.0    庄梦蝶殇    2018年09月06日             
 *     1.0.1    庄梦蝶殇    2019年02月28日             更改阈值碰撞时重复执行问题
 *     1.0.2    庄梦蝶殇    2019年03月06日             突破并发数被step限制的bug
 *     ----------------------------------------------
 * </pre>
 */
public class SegmentServiceImpl implements ISegmentService {
    
    /**
     * 线程名-心跳
     */
    public static final String THREAD_BUFFER_NAME = "leaf_buffer_sw";
    
    private static ReentrantLock lock = new ReentrantLock();
    
    /**
     * 创建线程池
     */
    private ExecutorService taskExecutor;
    
    /**
     * 两段buffer
     */
    private volatile IdSegment[] segment = new IdSegment[2];
    
    /**
     * 缓冲切换标识(true-切换,false-不切换)
     */
    private volatile boolean sw;
    
    /**
     * 当前id
     */
    private AtomicLong currentId;
    
    private JdbcTemplate jdbcTemplate;
    
    /**
     * 业务标识
     */
    private String bizTag;
    
    /**
     * 异步标识(true-异步,false-同步)
     */
    private boolean asynLoadingSegment = true;
    
    /**
     * 异步线程任务
     */
    FutureTask<Boolean> asynLoadSegmentTask = null;
    
    public SegmentServiceImpl(JdbcTemplate jdbcTemplate, String bizTag) {
        this.jdbcTemplate = jdbcTemplate;
        if (taskExecutor == null) {
            taskExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new NamingThreadFactory(THREAD_BUFFER_NAME));
        }
        this.bizTag = bizTag;
        // 获取第一段buffer缓冲
        segment[0] = doUpdateNextSegment(bizTag);
        setSw(false);
        // 初始id
        currentId = new AtomicLong(segment[index()].getMinId());
    }
    
    @Override
    public Long getId() {
        // 1.0.1 fix:uid:ecp-190227001 #1(github)更改阈值(middle与max)lock在高速碰撞时的可能多次执行
        // 下一个id
        Long nextId = null;
        if (segment[index()].getMiddleId().equals(currentId.longValue()) || segment[index()].getMaxId().equals(currentId.longValue())) {
            try {
                lock.lock();
                // 阈值50%时,加载下一个buffer
                if (segment[index()].getMiddleId().equals(currentId.longValue())) {
                    thresholdHandler();
                    nextId = currentId.incrementAndGet();
                }
                if (segment[index()].getMaxId().equals(currentId.longValue())) {
                    fullHandler();
                    nextId = currentId.incrementAndGet();
                }
            } finally {
                lock.unlock();
            }
        }
        nextId = null == nextId ? currentId.incrementAndGet() : nextId;
        // 1.0.2 fix:uid:ecp-190306001 突破并发数被step限制的bug
        return nextId <= segment[index()].getMaxId() ? nextId : getId();
    }
    
    /**
     * 阈值处理,是否同/异步加载下一个buffer的值(即更新DB)
     */
    private void thresholdHandler() {
        if (asynLoadingSegment) {
            // 异步处理-启动线程更新DB,由线程池执行
            asynLoadSegmentTask = new FutureTask<>(new Callable<Boolean>() {
                @Override
                public Boolean call()
                    throws Exception {
                    final int currentIndex = reIndex();
                    segment[currentIndex] = doUpdateNextSegment(bizTag);
                    return true;
                }
            });
            taskExecutor.submit(asynLoadSegmentTask);
        } else {
            // 同步处理,直接更新DB
            final int currentIndex = reIndex();
            segment[currentIndex] = doUpdateNextSegment(bizTag);
        }
    }
    
    /**
     * buffer使用完时切换buffer。
     */
    public void fullHandler() {
        if (asynLoadingSegment) {
            // 异步时,需判断 异步线程的状态(是否已经执行)
            try {
                asynLoadSegmentTask.get();
            } catch (Exception e) {
                e.printStackTrace();
                // 未执行,强制同步切换
                segment[reIndex()] = doUpdateNextSegment(bizTag);
            }
        }
        // 设置切换标识
        setSw(!isSw());
        // 进行切换
        currentId = new AtomicLong(segment[index()].getMinId());
    }
    
    /**
     * 获取下一个buffer的索引
     */
    private int reIndex() {
        return isSw() ? 0 : 1;
    }
    
    /**
     * 获取当前buffer的索引
     */
    private int index() {
        return isSw() ? 1 : 0;
    }
    
    /**
     * @方法名称 doUpdateNextSegment
     * @功能描述 <pre>更新下一个buffer</pre>
     * @param bizTag 业务标识
     * @return 下一个buffer的分段id实体
     */
    private IdSegment doUpdateNextSegment(String bizTag) {
        try {
            return updateId(bizTag);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    private IdSegment updateId(String bizTag)
        throws Exception {
        String querySql = "select step, max_id, last_update_time, current_update_time from id_segment where biz_tag=?";
        String updateSql = "update id_segment set max_id=?, last_update_time=?, current_update_time=now() where biz_tag=? and max_id=?";
        final IdSegment currentSegment = new IdSegment();
        this.jdbcTemplate.query(querySql, new String[] {bizTag}, new RowCallbackHandler() {
            @Override
            public void processRow(ResultSet rs)
                throws SQLException {
                Long step = null;
                Long currentMaxId = null;
                step = rs.getLong("step");
                currentMaxId = rs.getLong("max_id");
                Date lastUpdateTime = new Date();
                if (rs.getTimestamp("last_update_time") != null) {
                    lastUpdateTime = (java.util.Date)rs.getTimestamp("last_update_time");
                }
                Date currentUpdateTime = new Date();
                if (rs.getTimestamp("current_update_time") != null) {
                    currentUpdateTime = (java.util.Date)rs.getTimestamp("current_update_time");
                }
                currentSegment.setStep(step);
                currentSegment.setMaxId(currentMaxId);
                currentSegment.setLastUpdateTime(lastUpdateTime);
                currentSegment.setCurrentUpdateTime(currentUpdateTime);
            }
        });
        Long newMaxId = currentSegment.getMaxId() + currentSegment.getStep();
        int row = this.jdbcTemplate.update(updateSql, new Object[] {newMaxId, currentSegment.getCurrentUpdateTime(), bizTag, currentSegment.getMaxId()});
        if (row == 1) {
            IdSegment newSegment = new IdSegment();
            newSegment.setStep(currentSegment.getStep());
            newSegment.setMaxId(newMaxId);
            return newSegment;
        } else {
            // 递归,直至更新成功
            return updateId(bizTag);
        }
    }
    
    public void setTaskExecutor(ExecutorService taskExecutor) {
        this.taskExecutor = taskExecutor;
    }
    
    private boolean isSw() {
        return sw;
    }
    
    private void setSw(boolean sw) {
        this.sw = sw;
    }
    
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    @Override
    public void setBizTag(String bizTag) {
        this.bizTag = bizTag;
    }
    
    public void setAsynLoadingSegment(boolean asynLoadingSegment) {
        this.asynLoadingSegment = asynLoadingSegment;
    }
}