package cokr.xit.fims.base.service.bean;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Resource;

import org.apache.catalina.connector.ClientAbortException;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import com.fasterxml.jackson.databind.ObjectMapper;

import cokr.xit.fims.base.SseEntity;
import cokr.xit.foundation.component.AbstractBean;

@Component("sseBean")
public class SseBean extends AbstractBean {
	private static final long ONE_HOUR =  60 * 1000;
	private String cursor;
	private ConcurrentHashMap<String, SseEntity> sseMap = new ConcurrentHashMap<>();
	private static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");


	@Resource(name="objectMapper")
	private ObjectMapper objectMapper;

	/** SSE키를 등록한다.
	 * @param sseKey
	 * @return SseEmitter
	 */
	public SseEmitter start(String sseKey) {
		SseEmitter emitter = new SseEmitter(-1L);
		sseMap.put(sseKey, new SseEntity(emitter));
		return emitter;
	}

	/** 작업할 SSE키를 설정한다.
	 * @param sseKey
	 * @return SseBean
	 */
	public SseBean setCursor(String sseKey) {
		this.cursor = sseKey;
		return this;
	}

	/** 클라이언트에 메시지를 전송한다.
	 * @param msg 메시지
	 * @return SseBean
	 */
	public void send(Object msg) {
		if(isEmpty(this.cursor))
			return;
		SseEntity se = sseMap.get(this.cursor);
		if (se == null)
			return;

		try {
			se.getEmitter().send(msg);
		} catch (ClientAbortException ex) {
			if (se != null && se.getEmitter() != null) {
				se.getEmitter().complete();
				se.setEmitter(null);
				sseMap.remove(this.cursor);
			}
		} catch (Exception e) {
			e.printStackTrace();
			if (se != null && se.getEmitter() != null) {
				se.getEmitter().complete();
				se.setEmitter(null);
				sseMap.remove(this.cursor);
			}
		}
	}

	/** 클라이언트에 종료 메시지를 전송한다.
	 * @param
	 * @return
	 */
	public void end() {
		if(isEmpty(this.cursor))
			return;
		SseEntity se = sseMap.get(this.cursor);
		if (se == null)
			return;

		String msg = "FIN";
		try {
			se.getEmitter().send(msg);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (se != null && se.getEmitter() != null) {
				se.getEmitter().complete();
				se.setEmitter(null);
				sseMap.remove(this.cursor);
			}
		}
	}

	@Scheduled(cron = "0 0 */1 * * *")
	public void clearSseEmitter() {
		long now = System.currentTimeMillis() - ONE_HOUR;
		for (String key: sseMap.keySet()) {
			System.out.println("key: "+key);
			SseEntity se = sseMap.get(key);
			if (now > se.getCreatedAt()) {
				System.out.println("clear old ssemitter "+key+" : "+format.format(se.getCreatedAt()));
				if (se.getEmitter() != null)
					se.setEmitter(null);
				sseMap.remove(key);
			}
		}
	}

	public SseBean setProgress(String key, Object value) {
		if(isEmpty(this.cursor))
			return this;

		SseEntity se = sseMap.get(this.cursor);
		se.getProgress().set(key, value);
		return this;
	}

	public SseBean increaseProgress(String key) {
		if(isEmpty(this.cursor))
			return this;

		SseEntity se = sseMap.get(this.cursor);
		int i = se.getProgress().number(key).intValue();
		se.getProgress().set(key, i+1);
		return this;
	}

	public SseBean increaseProgress(String key, int num) {
		if(isEmpty(this.cursor))
			return this;

		SseEntity se = sseMap.get(this.cursor);
		int i = se.getProgress().number(key).intValue();
		se.getProgress().set(key, i+num);

		return this;
	}

	public void sendProgress() {
		if(isEmpty(this.cursor))
			return;
		SseEntity se = sseMap.get(this.cursor);
		if (se == null)
			return;

		try {
			se.getEmitter().send(objectMapper.writeValueAsString(se.getProgress()));
		} catch (Exception e) {
			e.printStackTrace();
			if (se != null && se.getEmitter() != null) {
				se.getEmitter().complete();
				se.setEmitter(null);
				sseMap.remove(this.cursor);
			}
		}
	}
}