package kr.co.swh.lecture.opensource.jsoup.news;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;


public abstract class A_CrawlClient implements ICrawlService{

	protected transient ObjectMapper mapper;
	protected transient CountDownLatch articleBreakLatch;
	
	protected String domainName, startDate, endDate;
	protected String[] categories;
	protected Integer sleepTime;
	
	protected List<String> cacheNewsId;
	protected final String autoModeTag = "~";
	protected boolean autoMonitor;
	protected boolean continueCrawl;
	protected String monitoringStartDate;					//	모니터링 시작 날짜
	protected String currentStartDate;
	
	//	final value
	protected final int monitoringNewPageBlockNum = 10;		//	모니터링 페이지 임계치
	protected final int fastModeSleepTime = 0;				//	milisecond
	protected final int safeModeSleepTime = 500;			//	milisecond
	
	//	pause
	protected String lastProcessId;
	
	
	public A_CrawlClient(String domain, String startDate, String endDate, String[] category, boolean fastMode)
	{
		this.mapper = new ObjectMapper();
		this.articleBreakLatch = new CountDownLatch(1);
		
		this.domainName = domain.toLowerCase();
		this.startDate = startDate;
		this.endDate = endDate;
		this.categories = category;
		this.currentStartDate = startDate;
		
		this.monitoringStartDate = currentDate();
		this.cacheNewsId = Lists.newArrayList();
		
		if(fastMode) sleepTime = fastModeSleepTime;
		else sleepTime = safeModeSleepTime;
		
		//	현 시스템 날짜와 종료날짜가 동일하다면, 하루전 종료일짜까지 수집한 후, 현 시스템 날짜는 모니터링을 통해 실시간으로 수집, 날짜가 지나면 종료
		//	'~' 표시인 경우 계속 수집
		if(endDate.equals(this.autoModeTag)) {
			this.continueCrawl = true;
		}

		//	종료날짜가 현 시스템 날짜보다 같거나 크다면, 현 시스템 날짜로 종료날짜를 등록하고 모니터링
		if(this.continueCrawl || compareToDates(monitoringStartDate, endDate) >= 0) {
			this.autoMonitor = true;
		}
	}
	
	
	@Override
	public void crawlling() throws Exception{
		//	최초 스타트 일짜로 수행
		fetch(startDate);
		
		//	장시간 기사를 수집할 시, 패치날짜의 간격이 커짐으로써, 모니터링 대상 날짜가 늘어나는 것을 방지하기 위해 한번 더 패치를 수행
		String oldMonitoringStartDate = monitoringStartDate;
		String currentSystemDate = currentDate();
		if(updateSystemDateMonitoringDate(currentSystemDate)){
			fetch(oldMonitoringStartDate);
		}
		
		//	모니터링 모드일 경우 수행
		if(autoMonitor) monitor();
	}
	
	protected String currentDate(){
		return new SimpleDateFormat("yyyyMMdd").format(new Date(System.currentTimeMillis()));
	}
	
	protected boolean updateSystemDateMonitoringDate(String systemDate){
		//	수집 시작한 날짜와 현 시스템의 날짜가 다른 경우,
		if(!monitoringStartDate.equals(systemDate)) {
			//	시스템 수집날짜를 현 시간으로 설정하여, 모니터링 대상 날짜로 지정
			monitoringStartDate = systemDate;
			return true;
		}
		return false;
	}
	
	protected boolean isContinueCrawl(){
		if(continueCrawl) return true;
		else{
			//	모니터링 날짜가 종료날짜보다 커지는 경우는 돌면 안됨. 
			if(compareToDates(endDate, monitoringStartDate) > 0) return false;
			else return true;
		}
	}
	
	protected List<String> getFetchDateList(String sourceDate){
		List<String> fetchDateList = null;
		
		//	패치대상 날짜들은 현 시스템 날짜보다 하루 전 까지만 수행(모니터링 대상 날짜로부터)
		if(autoMonitor) {
			fetchDateList = getStringFormatDateList(sourceDate, monitoringStartDate);
			fetchDateList.remove(fetchDateList.size()-1);
		}
		//	모니터링이 필요없는 경우
		else fetchDateList = getStringFormatDateList(sourceDate, endDate);
		return fetchDateList;
	}
	
	protected List<String> getStringFormatDateList(String sourceDate, String targetDate){
		List<String> fetchDateList = new ArrayList<String>();
		int startYear = Integer.parseInt(sourceDate.substring(0, 4));
		int startMonth = Integer.parseInt(sourceDate.substring(4, 6));
		int startDay = Integer.parseInt(sourceDate.substring(6, 8));
		int endYear = Integer.parseInt(targetDate.substring(0, 4));
		int endMonth = Integer.parseInt(targetDate.substring(4, 6));
		int endDay = Integer.parseInt(targetDate.substring(6, 8));
		
		int startTotalDay = totalDayFromCalendar(startYear, startMonth, startDay);
		int endTotalDay = totalDayFromCalendar(endYear, endMonth, endDay);
		
		for(; startTotalDay <= endTotalDay; startTotalDay++){
			int startEndDay = endDayFromTotalDay(startYear, startMonth);
			if(startDay > startEndDay) {
				startMonth++;
				startDay = 1;
			}
			if(startMonth > 12){
				startYear++;
				startMonth = 1;
				startDay = 1;
			}
			String fetchDate = startYear + StringUtils.leftPad(String.valueOf(startMonth), 2, "0") + StringUtils.leftPad(String.valueOf(startDay), 2, "0");
			fetchDateList.add(fetchDate);
			startDay++;
		}
		return fetchDateList;
	}
	
	private int compareToDates(String sourceDate, String targetDate){
		int startYear = Integer.parseInt(sourceDate.substring(0, 4));
		int startMonth = Integer.parseInt(sourceDate.substring(4, 6));
		int startDay = Integer.parseInt(sourceDate.substring(6, 8));
		int endYear = Integer.parseInt(targetDate.substring(0, 4));
		int endMonth = Integer.parseInt(targetDate.substring(4, 6));
		int endDay = Integer.parseInt(targetDate.substring(6, 8));
		
		int startTotalDay = totalDayFromCalendar(startYear, startMonth, startDay);
		int endTotalDay = totalDayFromCalendar(endYear, endMonth, endDay);
		
		return endTotalDay - startTotalDay;
	}
	
	public static int totalDayFromCalendar(int year, int month, int day){
		int totaldays;
		totaldays = 365 * (year - 1);
		for (int i = 1; i < year; i++) {
			if (i % 4 == 0 && i % 100 != 0 || i % 400 == 0)
				totaldays++;
		}
		// totaldays = 365 * (year-1) + (year-1)/4 - (year-1)/100 + (year-1)/400
		int premonth = month - 1;
		if (premonth >= 1)
			totaldays += 31;
		if (premonth >= 2)
			totaldays += 28;
		if (premonth >= 3)
			totaldays += 31;
		if (premonth >= 4)
			totaldays += 30;
		if (premonth >= 5)
			totaldays += 31;
		if (premonth >= 6)
			totaldays += 30;
		if (premonth >= 7)
			totaldays += 31;
		if (premonth >= 8)
			totaldays += 31;
		if (premonth >= 9)
			totaldays += 30;
		if (premonth >= 10)
			totaldays += 31;
		if (premonth >= 11)
			totaldays += 30;
		if (month > 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))
			totaldays++;
		totaldays++;

//		int day = totaldays % 7;
		
		totaldays = totaldays + day;
		return totaldays;
	}
	
	public static int endDayFromTotalDay(int year, int month){
		int lastday;
		switch (month) {
		case 2:
			lastday = 28;
			if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
				lastday = 29;
			else
				lastday = 28;
			break;
		case 4:
		case 6:
		case 9:
		case 11:
			lastday = 30;
			break;
		default:
			lastday = 31;
		}
		return lastday;
	}
	
	
	public synchronized void shutDown(){
		this.articleBreakLatch.countDown();
	}
	
	
	public abstract void fetch(String sourceDate) throws Exception;
	public abstract void monitor();
}
