/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.common.job.impl;

import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import com.crunii.micro.common.job.ScanQueue;
import com.crunii.micro.common.job.common.JobLock;
import com.crunii.micro.common.utils.DateUtil;
import com.crunii.micro.common.utils.StopUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * @author 田平 create 2020年3月25日下午2:23:01
 *
 */
@Slf4j
public abstract class AbstractScanJob<T extends ScanQueue> implements InitializingBean, DisposableBean {

	private int defaultCorePoolSize = 8;

	private int defaultMaxPoolSize = 64;

	private int defaultMaxScanSize = 200;

	private int defaultMaxDealTimes = 1;

	private boolean defaultUseMultiThread = true;

	protected JobLock jobLock = new JobLock();

	protected ExecutorService es;

	protected int getMaxDealTimes() {
		return defaultMaxDealTimes;
	}

	protected int getCorePoolSize() {
		return defaultCorePoolSize;
	}

	protected int getMaxPoolSize() {
		return defaultMaxPoolSize;
	}

	protected int getMaxScanSize() {
		return defaultMaxScanSize;
	}

	protected boolean isUseMultiThread() {
		return defaultUseMultiThread;
	}

	public void afterPropertiesSet() throws Exception {
		if (defaultUseMultiThread) {
			es = new ThreadPoolExecutor(getCorePoolSize(), getMaxPoolSize(), 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(getMaxScanSize()), new ThreadPoolExecutor.DiscardPolicy());
		}
	}

	public void destroy() throws Exception {
		StopUtil.stopES(es);
	}

	protected String getJobName() {
		return getClass().getSimpleName();
	}

	protected abstract List<T> getWaitScanQueue(int max);

	protected abstract void doBusiness(T queue) throws Exception;

	protected abstract void doBusinessFailure(T queue, Exception e);

	protected abstract void doBusinessFailureLessDealTimes(T queue, Exception e);

	protected abstract boolean updateDoingState(T queue);

	/**
	 * <pre>
	 * 处理程序异常终止，状态一直在执行中,
	 * 可以单独的项目执行异常程序终止造成的队列表状态一直处于中间状态
	 * </pre>
	 */
	protected abstract void resetDoingState();

	protected void execScan() {
		int maxScanSize = getMaxScanSize();
		int doingSize = jobLock.lockSize();
		int realMaxScanSize = maxScanSize - doingSize;
		if (realMaxScanSize <= 0) {
			log.warn("{} maxScanSize:{} already in doing so cancel this scan!", getJobName(), maxScanSize);
			return;
		}
		long start = System.currentTimeMillis();
		List<T> queues = getWaitScanQueue(realMaxScanSize);
		log.info("{} maxScanSize:{},realMaxScanSize:{},doingSize:{},realSize:{},usedTime:{}", getJobName(), maxScanSize, realMaxScanSize, doingSize, queues.size(), DateUtil.getUseTime(start));
		if (isUseMultiThread()) {
			queues.forEach(queue -> {
				es.execute(new Runnable() {
					@Override
					public void run() {
						doJobQueue(queue);
					}
				});
			});
		} else {
			queues.forEach(queue -> {
				doJobQueue(queue);
			});
		}
	}

	private void doJobQueue(T queue) {
		String lockId = queue.getLockId();
		Date start = new Date();
		boolean lockSucc = jobLock.lock(lockId);
		if (lockSucc) {
			try {
				boolean succ = updateDoingState(queue);
				if (succ) {
					queue.setJobDoing();
					queue.setDealTimes(queue.getDealTimes() + 1);
					doBusiness(queue);
					log.info("{} id:{},lockId:{} success!usedTime:{} ms", getJobName(), queue.getId(), lockId, DateUtil.getUseTime(start));
				} else {
					log.warn("{} id:{},lockId:{} updateDoingState failure!usedTime:{} ms", getJobName(), queue.getId(), lockId, DateUtil.getUseTime(start));
				}
			} catch (Exception e) {
				queue.setErrorDesc(e);
				if (queue.getDealTimes() < getMaxDealTimes()) {
					doBusinessFailureLessDealTimes(queue, e);
					log.warn("{} id:{},lockId:{} currDealTimes:{} failure less than maxDealTimes:{}!usedTime:{} ms",
							getJobName(), queue.getId(), lockId, queue.getDealTimes(), getMaxDealTimes(), DateUtil.getUseTime(start), e);
				} else {
					doBusinessFailure(queue, e);
					log.error("{} id:{},lockId:{} failure!usedTime:{} ms", getJobName(), queue.getId(), lockId, DateUtil.getUseTime(start), e);
				}
			} finally {
				jobLock.unlock(lockId);
			}
		} else {
			log.warn("{} {} already in doing!", getJobName(), queue.getLockId());
		}
	}

}
