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.vo.AlarmVO;
import kr.gmtc.tss.data.vo.StandStatusVO;
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.alarm}")
	private String topicAlarm;
	
	@Value("${kafka.topic.status}")
	private String topicStatus;
	
	@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 FILE_STAND_LOG_NAME;
	public static String FILE_SITTN_LOG_NAME;
	
//	public static String DATA_FILE_NAME;
	public static String DATA_STAND_FILE_NAME;
	public static String DATA_SITTN_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;
	ParseTargetRunnable parserRunnable;
	DataLogger dataLoggerRunnable;
	SqlLogger sqlLoggerRunnable;
	BatchInsertManager batchInsertManager;
	DataInsertRunnable dataInsertRunnable;
	MessageBodyLogger messageBodyLogger;
	KafkaTopicReader kafkaTopicReader;
	
	List<Thread> threadList = new ArrayList<Thread>();
	
	Queue<String> rcvStandStatusQueue;
	Queue<String> rcvAlarmQueue;
	
	Queue<StandStatusVO> standStatusQueue;
	Queue<String> dataStandStatusFileQueue;
	
	Queue<AlarmVO> alarmQueue;
	Queue<String> dataAlarmQueueFileQueue;
	
	Queue<StandStatusVO> sqlLogStandStatusQueue;
	Queue<AlarmVO> sqlLogAlarmQueue;
	
	Queue<StandStatusVO> messageBodyStandStatusQueue;
	Queue<AlarmVO> messageBodyAlarmQueue;
	
	@Bean(name = "rcvStandStatusQueue")
	public Queue<String> getRcvStandStatusQueue() {
		return rcvStandStatusQueue;
	}
	
	@Bean(name = "rcvAlarmQueue")
	public Queue<String> getRcvAlarmQueue() {
		return rcvAlarmQueue;
	}
	
	@Bean(name = "standStatusQueue")
	public Queue<StandStatusVO> getStandStatusQueue() {
		return standStatusQueue;
	}
	
	@Bean(name = "alarmQueue")
	public Queue<AlarmVO> getAlarmQueue() {
		return alarmQueue;
	}
	
	@Bean(name = "dataStandStatusFileQueue")
	public Queue<String> getDataStandStatusFileQueue() {
		return dataStandStatusFileQueue;
	}
	
	@Bean(name = "dataAlarmQueueFileQueue")
	public Queue<String> getDataAlarmQueueFileQueue() {
		return dataAlarmQueueFileQueue;
	}
	
	@Bean(name = "sqlLogStandStatusQueue")
	public Queue<StandStatusVO> getSqlLogStandStatusQueue() {
		return sqlLogStandStatusQueue;
	}

	@Bean(name = "sqlLogAlarmQueue")
	public Queue<AlarmVO> getSqlLogAlarmQueue() {
		return sqlLogAlarmQueue;
	}
	
	@Bean(name = "messageBodyStandStatusQueue")
	public Queue<StandStatusVO> getMessageBodyStandStatusQueue() {
		return messageBodyStandStatusQueue;
	}
	
	@Bean(name = "messageBodyAlarmQueue")
	public Queue<AlarmVO> getMessageBodyAlarmQueue() {
		return messageBodyAlarmQueue;
	}
	
	@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);
		
		rcvAlarmQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		rcvStandStatusQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		
		sqlLogStandStatusQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		sqlLogAlarmQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		
		standStatusQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		alarmQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		
		dataStandStatusFileQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		dataAlarmQueueFileQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		
		messageBodyStandStatusQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		messageBodyAlarmQueue=new ArrayBlockingLoggingQueue<>(queueCount);
		
		// LAST TABLE 데이터 확인
		// DBLastDataStatus();
		
		// 최초 한번만 실행  - 로그파일이 없을 시 생성
		if (flagFile) {
			FILE_STAND_LOG_NAME = fileUtil.createLogFile(fileZipPath, fileZipBakPath, fileMakeTime, "STAND");		
			FILE_SITTN_LOG_NAME = fileUtil.createLogFile(fileZipPath, fileZipBakPath, fileMakeTime, "SITTN");		
		}
		
		if (flagDataFile) {
			DATA_STAND_FILE_NAME = fileUtil.createLogFile(dataZipPath, dataZipBakPath, dataMakeTime, "STAND");		// logstash data file log
			DATA_SITTN_FILE_NAME = fileUtil.createLogFile(dataZipPath, dataZipBakPath, dataMakeTime, "SITTN");		// 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();
		
		
		startIcAnalyzeStandStatusThread();
		startIcAnalyzeAlarmThread();
		
		startIcAnalyzeStandStatusParserThread();
		startIcAnalyzeAlarmParserThread();
		
		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);
			FILE_STAND_LOG_NAME = fileUtil.createLogFile(fileZipPath, fileZipBakPath, fileMakeTime, "STAND");		
			FILE_SITTN_LOG_NAME = fileUtil.createLogFile(fileZipPath, fileZipBakPath, fileMakeTime, "SITTN");
		}
	}
	
	@Scheduled(cron = "${process.data-make-cron}")
	public void dataFileMake() throws Exception{
		if (flagDataFile) {
			// DATA_FILE_NAME = fileUtil.createLogFile(dataZipPath, dataZipBakPath, dataMakeTime);
			DATA_STAND_FILE_NAME = fileUtil.createLogFile(dataZipPath, dataZipBakPath, dataMakeTime, "STAND");		// logstash data file log
			DATA_STAND_FILE_NAME = fileUtil.createLogFile(dataZipPath, dataZipBakPath, dataMakeTime, "SITTN");		// logstash data file log
		}
	}
	
	private void checkQueueSize() {
		
		if(rcvAlarmQueue.size()==queueCount) {
			logger.error("rcvAlarmQueue is Full!!");
		}
		if(rcvStandStatusQueue.size()==queueCount) {
			logger.error("rcvStandStatusQueue is Full!!");
		}
		
		if(standStatusQueue.size()==queueCount) {
			logger.error("standStatusQueue is Full!!");
		}
		if(alarmQueue.size()==queueCount) {
			logger.error("alarmQueue is Full!!");
		}
		
		if(dataStandStatusFileQueue.size()==queueCount) {
			logger.error("dataStandStatusFileQueue is Full!!");
		}
		if(dataAlarmQueueFileQueue.size()==queueCount) {
			logger.error("dataAlarmQueueFileQueue is Full!!");
		}
		
		if(sqlLogStandStatusQueue.size()==queueCount) {
			logger.error("sqlLogStandStatusQueue is Full!!");
		}
		if(sqlLogAlarmQueue.size()==queueCount) {
			logger.error("sqlLogAlarmQueue is Full!!");
		}
		
		if(messageBodyStandStatusQueue.size()==queueCount) {
			logger.error("messageBodyStandStatusQueue is Full!!");
		}
		if(messageBodyAlarmQueue.size()==queueCount) {
			logger.error("messageBodyAlarmQueue is Full!!");
		}
	}

	public void startIcAnalyzeAlarmThread() {
		KafkaTopicReader topicReader = new KafkaTopicReader(bootstrapServers, topicAlarm, rcvAlarmQueue);
		this.kafkaTopicReader=topicReader;
		
		Thread thread = new Thread(topicReader);
		thread.start();
		
		threadList.add(thread);
	}
	
	public void startIcAnalyzeAlarmParserThread() {
		ParseTargetRunnable parserRunnable = new ParseTargetRunnable(rcvAlarmQueue, alarmQueue, dataAlarmQueueFileQueue, messageBodyAlarmQueue, queueChunkSize, parseTargetSleepMs, flagFile, flagDataBase, flagDataFile, true, classification, swclassification, logclassification);
		this.parserRunnable=parserRunnable;
		Thread thread = new Thread(parserRunnable);
		thread.start();
		threadList.add(thread);
		parserRunnable.getCountAndSetZero();
	}
	
	public void startIcAnalyzeStandStatusThread() {
        KafkaTopicReader topicReader = new KafkaTopicReader(bootstrapServers, topicStatus, rcvStandStatusQueue);
        this.kafkaTopicReader=topicReader;
        
        Thread thread = new Thread(topicReader);
        thread.start();
        
        threadList.add(thread);
	}
	
	public void startIcAnalyzeStandStatusParserThread() {
		ParseTargetRunnable parserRunnable = new ParseTargetRunnable(rcvStandStatusQueue, standStatusQueue, dataStandStatusFileQueue, messageBodyStandStatusQueue, 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, sqlLogStandStatusQueue, sqlLogAlarmQueue);
		this.batchInsertManager=batchInsertManager;
		
		DataInsertRunnable dataInsertRunnable = new DataInsertRunnable(standStatusQueue, alarmQueue, batchInsertSize, batchInsertManager, dataInsertSleepMs);
		this.dataInsertRunnable = dataInsertRunnable;
		
		Thread thread = new Thread(dataInsertRunnable);
		thread.start();

		threadList.add(thread);
	}
	
	public void startDataLoggerThread() {
		DataLogger dataLoggerRunnable = new DataLogger(dataStandStatusFileQueue, dataAlarmQueueFileQueue, queueChunkSize, sqlLoggerSleepMs);
		this.dataLoggerRunnable = dataLoggerRunnable;
		
		Thread thread = new Thread(dataLoggerRunnable);
		thread.start();
		
		threadList.add(thread);
	}
	
	public void startSqlLoggerThread() {
		SqlLogger sqlLoggerRunnable = new SqlLogger(sqlLogStandStatusQueue, sqlLogAlarmQueue, queueChunkSize, sqlLoggerSleepMs);
		this.sqlLoggerRunnable = sqlLoggerRunnable;
		
		Thread thread = new Thread(sqlLoggerRunnable);
		thread.start();
		
		threadList.add(thread);
	}
	
	public void startMessageBodyThread() {
		MessageBodyLogger messageBodyLogger = new MessageBodyLogger(messageBodyStandStatusQueue, messageBodyAlarmQueue, 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();
	}
}
