package kr.gmtc.tss.main;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import kr.gmt.so.state.StateManager;
import kr.gmt.so.state.model.SystemState;
import kr.gmtc.tss.data.DataInsertRunnable;
import kr.gmtc.tss.data.ParseTargetRunnable;
import kr.gmtc.tss.data.TargetManageRunnable;
import kr.gmtc.tss.data.vo.CctvTrackVO;
import kr.gmtc.tss.db.BatchInsertManager;
import kr.gmtc.tss.filelog.DataLogger;
import kr.gmtc.tss.filelog.MessageBodyLogger;
import kr.gmtc.tss.kafka.KafkaTopicReader;
import kr.gmtc.tss.netty.StringClient;
import kr.gmtc.tss.sqllog.SqlLogger;
import kr.gmtc.tss.util.ArrayBlockingLoggingQueue;
import kr.gmtc.tss.util.FileUtil;

@EnableScheduling
@Component
public class MainServer implements InitializingBean {

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Value("${kafka.bootstrapServers}")
	private String bootstrapServers;
	
	@Value("${kafka.topic}")
	private String topic;
	
	@Value("${file-zip-path}")
	private String fileZipPath;
	
	@Value("${file-zip-bak-path}")
	private String fileZipBakPath;
	
	@Value("${file-make-time}")
	private int fileMakeTime;
	
	@Value("${data-zip-path}")
	private String dataZipPath;
	
	@Value("${data-zip-bak-path}")
	private String dataZipBakPath;
	
	@Value("${data-make-time}")
	private int dataMakeTime;

	@Value("${flag.database}")
	private boolean flagDataBase;
	
	@Value("${flag.datafile}")
	private boolean flagDataFile;
	
	@Value("${flag.file}")
	private boolean flagFile;

	@Value("${client.server.ip:127.0.0.1}")
	private String tcpServerIp="127.0.0.1";

	@Value("${client.reconn-time-sec:3000}")
	private long reconnTime=3000;

	@Value("${client.queue-count:10000}")
	private int queueCount=100000;

	@Value("${dynamic-dedupl.set-size:10000}")
	private int dynamicSetSize;

	@Value("${dynamic-dedupl.set-remove-unit-size:500}")
	private int dynamicRemoveUnitSize;
	
	@Value("${spring.db.batch-insert-size:1000}")
	private int batchInsertSize;
	
	@Value("${process.queue-chunk-size:1000}")
	private int queueChunkSize;
	
	@Autowired
	@Qualifier("batchSqlSessionFactory")
	public SqlSessionFactory sqlSessionFactory;
	private SqlSession session;
	
	public static String FILE_LOG_NAME;
	public static String DATA_FILE_NAME;

	@Value("${selep-time-ms.parse-target:50}")
	private long parseTargetSleepMs;
	
	@Value("${selep-time-ms.target-manage:50}")
	private long targetManageSleepMs;
	
	@Value("${selep-time-ms.data-insert:50}")
	private long dataInsertSleepMs;
	
	@Value("${selep-time-ms.sql-logger:50}")
	private long sqlLoggerSleepMs;
	
	@Value("${classification}")
	private String classification;
	
	@Value("${swclassification}")
	private String swclassification;
	
	@Value("${logclassification}")
	private String logclassification;
	
	StringClient client;
	KafkaTopicReader kafkaTopicReader;
	ParseTargetRunnable parserRunnable;
	DataLogger dataLoggerRunnable;
	TargetManageRunnable targetManageRunnable;
	SqlLogger sqlLoggerRunnable;
	BatchInsertManager batchInsertManager;
	DataInsertRunnable dataInsertRunnable;
	MessageBodyLogger messageBodyLogger;
	
	List<Thread> threadList = new ArrayList<Thread>();
	
	Queue<String> rcvQueue;
	Queue<CctvTrackVO> sqlLogTrackQueue;
	Queue<CctvTrackVO> sqlLogTrackLastQueue;
	
	// track 위치정보 저장
	Queue<CctvTrackVO> trackQueue;
	Queue<String> dataFileQueue;
	Queue<CctvTrackVO> trackLastQueue;
	
	Queue<CctvTrackVO> messageBodyQueue;
	
	@Bean(name = "rcvQueue")
	public Queue<String> getRcvQueue() {
		return rcvQueue;
	}
	
	@Bean(name = "trackQueue")
	public Queue<CctvTrackVO> getTrackQueue() {
		return trackQueue;
	}
	
	@Bean(name = "dataFileQueue")
	public Queue<String> getDataFileQueue() {
		return dataFileQueue;
	}
	
	@Bean(name = "trackLastQueue")
	public Queue<CctvTrackVO> getTrackLastQueue() {
		return trackLastQueue;
	}
	
	@Bean(name = "sqlLogTrackQueue")
	public Queue<CctvTrackVO> getSqlLogTrackQueue() {
		return sqlLogTrackQueue;
	}

	@Bean(name = "sqlLogTrackLastQueue")
	public Queue<CctvTrackVO> getSqlLogTrackLastQueue() {
		return sqlLogTrackLastQueue;
	}
	
	@Bean(name = "messageBodyQueue")
	public Queue<CctvTrackVO> getMessageBodyQueue() {
		return messageBodyQueue;
	}
	
	@Autowired
    private FileUtil fileUtil;

	@Autowired
    private StateManager stateMgr;
	// private boolean swBeforeStatus = true;
	
	public static int LAST_DATA_CNT = 0;
	
	@PostConstruct
	public void init() {

		stateMgr.updateState();

		logger.info("DataQueue Count : "+queueCount);
		logger.info("flagDataBase : "+flagDataBase);
		logger.info("flagDataFile : "+flagDataFile);
		logger.info("flagFile : "+flagFile);
		
		rcvQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		sqlLogTrackQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		sqlLogTrackLastQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		trackQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		dataFileQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		trackLastQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		messageBodyQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		
		// LAST TABLE 데이터 확인
		DBLastDataStatus();
		
		// 최초 한번만 실행  - 로그파일이 없을 시 생성
		if (flagFile) FILE_LOG_NAME = fileUtil.createLogFile(fileZipPath, fileZipBakPath, fileMakeTime);		// rest log file
		if (flagDataFile) DATA_FILE_NAME = fileUtil.createLogFile(dataZipPath, dataZipBakPath, dataMakeTime);		// logstash data file log
	}
	
	@PreDestroy
	public void destroy() {
//		parserRunnable;
//		targetManageRunnable;
//		sqlLoggerRunnable;
//		batchInsertManager;
//		dataInsertRunnable.s;
//		client.isInterrupted();
		logger.info("Pre destroy called!!");
		client.interrupt();
		dataInsertRunnable.setRunning(false);
		sqlLoggerRunnable.setRunning(false);
		parserRunnable.setRunning(false);
		//targetManageRunnable.setRunning(false);
//		sqlSessionFactory.ge
//		batchInsertManager.setRunning(false);
		
//		for (int i = 0; i < threadList.size(); i++) {
//			try {
//				
//				logger.info("["+threadList.get(i).getClass().getName() +"/"+threadList.get(i).getName()+"] thread destroy");
//				threadList.get(i).interrupt();	
//			} catch (Exception e) {
//				logger.error("Thread interrupt error - ",e);
//				
//			}
//			
//		}
	}
	

	@Override
	public void afterPropertiesSet() throws Exception {
		if(flagDataBase) startDataInsertThread();
		if(flagDataFile) startDataLoggerThread();
		if(flagFile) startMessageBodyThread();
		
		startKafkaThread();
		startParserThread();
		startSqlLoggerThread();
	}

	@Scheduled(cron = "${process.status-check-cron}")
	private void chkThreadStatus() throws InterruptedException{

		stateMgr.updateState();

		boolean running = true;
		boolean swNewStatus = stateMgr.isActive();

		// // 이중화 Active / Standby 상태 변경 체크
		// if(swBeforeStatus != swNewStatus){

		// 	parserRunnable.setRunning(swNewStatus);
		// 	dataInsertRunnable2.setRunning(swNewStatus);
		// 	dataLoggerRunnable.setRunning(swNewStatus);
		// 	sqlLoggerRunnable.setRunning(swNewStatus);
		// 	messageBodyLogger.setRunning(swNewStatus);

		// }

		// 해당 프로그램이 Active일때 Thread Interrupt 유무 체크
		if(swNewStatus){

			for(Thread ct : threadList ){
				if(ct.isInterrupted()){
					running = false;
					break;
				}
			}

			if(running){
				stateMgr.updateState(SystemState.Normal);
			}else{
				stateMgr.updateState(SystemState.Error);
			}
		}

		// swBeforeStatus = stateMgr.isActive();

		// logger.info("swBeforeStatus : " + swBeforeStatus);
	}
	
	@Scheduled(cron = "${process.file-backup-cron}")
	public void fileRecv() throws Exception{
		if (flagFile) {
			/* 파일 압축 */
			fileUtil.createZipFile(fileZipPath, fileZipBakPath, fileMakeTime);
		}
	}
	
	@Scheduled(cron = "${process.data-backup-cron}")
	public void dataFileRecv() throws Exception{
		if (flagDataFile) {
			/* 파일 압축 */
			fileUtil.createZipFile(dataZipPath, dataZipBakPath, dataMakeTime);
		}
	}
	
	// 매 시간 10분 간격 으로 실행
	@Scheduled(cron = "${process.file-make-cron}")
	public void fileMake() throws Exception{
		if (flagFile) {
			FILE_LOG_NAME = fileUtil.createLogFile(fileZipPath, fileZipBakPath, fileMakeTime);
		}
	}
	
	@Scheduled(cron = "${process.data-make-cron}")
	public void dataFileMake() throws Exception{
		if (flagDataFile) {
			DATA_FILE_NAME = fileUtil.createLogFile(dataZipPath, dataZipBakPath, dataMakeTime);
		}
	}
	
	private void checkQueueSize() {
		if(rcvQueue.size()==queueCount) {
			logger.error("rcvQueue is Full!!");
		}
		if(trackQueue.size()==queueCount) {
			logger.error("trackQueue is Full!!");
		}
		if(dataFileQueue.size()==queueCount) {
			logger.error("dataFileQueue is Full!!");
		}
		if(trackLastQueue.size()==queueCount) {
			logger.error("trackLastQueue is Full!!");
		}
		if(sqlLogTrackQueue.size()==queueCount) {
			logger.error("sqlLogTrackQueue is Full!!");
		}
		if(sqlLogTrackLastQueue.size()==queueCount) {
			logger.error("sqlLogTrackLastQueue is Full!!");
		}
		if(messageBodyQueue.size()==queueCount) {
			logger.error("messageBodyQueue is Full!!");
		}
	}
	
	public void startKafkaThread() {
		KafkaTopicReader topicReader = new KafkaTopicReader(bootstrapServers, topic, rcvQueue);
		this.kafkaTopicReader=topicReader;
		
		Thread thread = new Thread(topicReader);
		thread.start();
		
		threadList.add(thread);
	}
	
	public void startParserThread() {
		ParseTargetRunnable parserRunnable = new ParseTargetRunnable(rcvQueue, trackQueue, trackLastQueue, dataFileQueue, messageBodyQueue, queueChunkSize, parseTargetSleepMs ,flagFile, flagDataBase, flagDataFile, classification, swclassification, logclassification);
		this.parserRunnable=parserRunnable;
		Thread thread = new Thread(parserRunnable);
		thread.start();
		threadList.add(thread);
		parserRunnable.getCountAndSetZero();
	}
	
	public void startDataInsertThread() {
		BatchInsertManager batchInsertManager = new BatchInsertManager(sqlSessionFactory, sqlLogTrackQueue,sqlLogTrackLastQueue);
		this.batchInsertManager=batchInsertManager;
		
		DataInsertRunnable dataInsertRunnable = new DataInsertRunnable(trackQueue, trackLastQueue, batchInsertSize, batchInsertManager, dataInsertSleepMs, topic);
		this.dataInsertRunnable = dataInsertRunnable;
		
		Thread thread = new Thread(dataInsertRunnable);
		thread.start();

		threadList.add(thread);
	}
	
	public void startDataLoggerThread() {
		DataLogger dataLoggerRunnable = new DataLogger(dataFileQueue, queueChunkSize, sqlLoggerSleepMs);
		this.dataLoggerRunnable = dataLoggerRunnable;
		
		Thread thread = new Thread(dataLoggerRunnable);
		thread.start();
		
		threadList.add(thread);
	}
	
	public void startSqlLoggerThread() {
		SqlLogger sqlLoggerRunnable = new SqlLogger(sqlLogTrackQueue, sqlLogTrackLastQueue, queueChunkSize, sqlLoggerSleepMs);
		this.sqlLoggerRunnable = sqlLoggerRunnable;
		
		Thread thread = new Thread(sqlLoggerRunnable);
		thread.start();
		
		threadList.add(thread);
	}
	
	public void startMessageBodyThread() {
		MessageBodyLogger messageBodyLogger = new MessageBodyLogger(messageBodyQueue, queueChunkSize, sqlLoggerSleepMs);
		this.messageBodyLogger = messageBodyLogger;
		
		Thread thread = new Thread(messageBodyLogger);
		thread.start();
		
		threadList.add(thread);
	}
		
	public void DBLastDataStatus() {
		openSession(false);
		
		String sql = "SELECT COUNT(*) FROM SACP_TRACK_LAST "; 

		PreparedStatement ps = null;
		Connection con = null;
		
		con = session.getConnection();
		
		try {
			if(con.isClosed() || !con.isValid(1)) {
				closeSession();				
				openSession(true);
				con = session.getConnection();
			}
			con.setAutoCommit(false);
			ps = con.prepareStatement(sql);
			ResultSet rs = ps.executeQuery();

			while (rs.next()) {
				LAST_DATA_CNT = rs.getInt(1);
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	public void openSession(boolean forceOpen) {
		if(session==null||forceOpen)
			session = sqlSessionFactory.openSession();
	}
	
	public void closeSession() {
		if(session!=null)
			session.close();
	}
}
