package cokr.xit.fims.crdn.receive.eqpmnt;

import java.io.File;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import cokr.xit.applib.Hangul;
import cokr.xit.base.code.CommonCode;
import cokr.xit.fims.cmmn.CodeConverter;
import cokr.xit.fims.mngt.service.bean.TaskProcessor;
import cokr.xit.foundation.AbstractComponent;
import cokr.xit.foundation.data.DataObject;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;

abstract public class LayoutParser extends AbstractComponent {
	private Map<String, List<CommonCode>> allCode;
	LayoutDescriptor descriptor = new LayoutDescriptor();
	protected int tempGroupSeq = 1;
	protected DataObject before = new DataObject();
	protected String groupingType;
	protected Need need;
	protected TaskProcessor taskProcessor;

	public LayoutParser() {
		allCode = new HashMap<>();
		allCode.put("PHOTO_TYPE", List.of(
			newCommonCode("1","입차사진"),
			newCommonCode("2","주차사진"),
			newCommonCode("3","위반사진"),
			newCommonCode("4","출차사진")
		));
		allCode.put("FILE_STATUS", List.of(
			newCommonCode("S","정상등록자료"),
			newCommonCode("E","삭제자료")
		));
		allCode.put("CRDN_CN", List.of(
			newCommonCode("00","사용안함"),
			newCommonCode("01","일반차량(전기차)"),
			newCommonCode("02","장기주차(전기차)")
		));
	}

	public static CommonCode newCommonCode(String code, String value) {
		CommonCode commonCode = new CommonCode();
		commonCode.setCode(code);
		commonCode.setValue(value);
		return commonCode;
	}

	public int getTempGroupSeq() {
		return this.tempGroupSeq;
	}

	public void setTempGroupSeq(int tempGroupSeq) {
		this.tempGroupSeq = tempGroupSeq;
	}

	public void setTaskProcessor(TaskProcessor taskProcessor) {
		this.taskProcessor = taskProcessor;
	}

	static class Need {
		boolean fileNameAnalyze;
		boolean contentAnalyze;
		boolean checkChangeCrackdown;

		Need(boolean fileNameAnalyze, boolean contentAnalyze, boolean checkChangeCrackdown){
			this.fileNameAnalyze = fileNameAnalyze;
			this.contentAnalyze = contentAnalyze;
			this.checkChangeCrackdown = checkChangeCrackdown;
		}
	}

	String[] allItem = {
		"FILE_LAYOUT_ID",
		"FILE_GROUP_ID",
		"FILE_GROUP_TYPE","FILE_PATH","FILE_NAME","FILE_LAST_MODIFIED","FILE_SIZE","FILE_EXTENSION","LINK_ENT_NM",
		"FILE_SEQ","PHOTO_CNT","PHOTO_TYPE_CD","PHOTO_TYPE_NM","FILE_STATUS_CD","FILE_STATUS_NM",
		"VHRNO",
		"CRDN_TIMESTAMP","CRDN_YMD_TM","CRDN_YMD","CRDN_TM","CRDN_END_TM","CRDN_BGNG_TM",
		"CRDN_PLC","CRDN_ROAD_NM","CRDN_STDG_NM","GPS_X","GPS_Y","CRDN_SPAREA_NM","CRDN_SPAREA_CD",
		"BZENTY_CD","TEAM_ID","EQPMNT_CD","CRDN_SE_CD","CRDN_SE_NM",
		"MOSC_X","MOSC_Y","PLATE_WIDTH","PLATE_HEIGHT",
		"VLTN_CD","VLTN_NM","CRDN_CN_CD","CRDN_CN_NM",
		"CAR_VELOCITY"
	};

	public DataObject createParsedDataObject() {
		DataObject dataObject = new DataObject();
		for (int i = 0; i < allItem.length; i++) {
			dataObject.put(allItem[i], "");
		}
		return dataObject;
	}

	public void addCommonCode(Map<String, List<CommonCode>> commonCode) {
		allCode.putAll(commonCode);
	}

	public void setDescriptor(LayoutDescriptor descriptor) {
		this.descriptor = descriptor;
	}

	/**장비 단속 연계 파일에서 단속정보를 추출한다.<br />
	 * @param fileList 연계파일리스트
	 * @return 단속정보
	 */
	public List<DataObject> parsing(List<Path> fileList){
		String taskSeCd = descriptor.getTaskSeCd();

		return fileList.stream()
			.sorted((item1, item2) -> item1.toFile().getName().compareTo(item2.toFile().getName())) //파일명으로 정렬(파싱 전)
			.map(item -> {
	        	DataObject dataObject = createParsedDataObject()
	        		.set("FILE_GROUP_TYPE", groupingType)
	        		.set("FILE_LAYOUT_ID", descriptor.getFileLayoutId());

	        	File file = item.toFile();
	        	setFileDefaultInfo(dataObject, file);

	        	if (need.fileNameAnalyze) {
	        		String noExtensionName = FilenameUtils.removeExtension(dataObject.string("FILE_NAME"));
		        	analyzeFileName(dataObject, noExtensionName);
		        	refineValue(dataObject);
	        	}

	        	if (need.contentAnalyze) {
	        		analyzeFileContent(dataObject, file);
	            	refineValue(dataObject);
	        	}

	        	//단속구분코드 보정
				String crdnSeCd = taskProcessor.getCrdnSeCd(taskSeCd, descriptor.getEnterpriseName(), dataObject);
				dataObject.put("CRDN_SE_CD", crdnSeCd);

	        	if (!before.isEmpty()) {
	        		if (need.checkChangeCrackdown) {
	        			boolean isChangeTempGroup = isChangeCrackdown(dataObject, before);
	                	if (isChangeTempGroup) {
	                		tempGroupSeq++;
	                	}
	        		} else {
	        			tempGroupSeq++;
	        		}
	        	}

	        	return before = dataObject.set("TEMP_GROUP_ID", tempGroupSeq);
        })
		.toList();
	}

	public List<DataObject> parse(String workPath, List<MultipartFile> uploads){
		List<File> files = uploads.stream()
			.map(upload -> {
				try {
					String filename = upload.getOriginalFilename();
					File file = new File(workPath + "/" + filename);
					upload.transferTo(file);
					return file;
				} catch (Exception e) {
					throw runtimeException(e);
				}
			})
			.toList();
		return parse(files);
	}

	public List<DataObject> parse(List<File> files){
		String taskSeCd = descriptor.getTaskSeCd();

		return files.stream()
			.sorted((file1, file2) -> file1.getName().compareTo(file2.getName())) //파일명으로 정렬(파싱 전)
			.map(file -> {
				String filename = file.getName();
	        	DataObject dataObject = createParsedDataObject()
	        		.set("FILE_GROUP_TYPE", groupingType)
	        		.set("FILE_LAYOUT_ID", descriptor.getFileLayoutId())
	        		.set("FILE_PATH", filename)
	        		.set("FILE_NAME", filename)
	        		.set("FILE_EXTENSION", FilenameUtils.getExtension(filename));

	        	if (need.fileNameAnalyze) {
	        		String noExtensionName = FilenameUtils.removeExtension(dataObject.string("FILE_NAME"));
		        	analyzeFileName(dataObject, noExtensionName);
		        	refineValue(dataObject);
	        	}

	        	if (need.contentAnalyze) {
	        		try {
		        		analyzeFileContent(dataObject, file);
		            	refineValue(dataObject);
		            	dataObject.set("file", file);
					} catch (Exception e) {
						throw runtimeException(e);
					}
	        	}

	        	//단속구분코드 보정
				String crdnSeCd = taskProcessor.getCrdnSeCd(taskSeCd, descriptor.getEnterpriseName(), dataObject);
				dataObject.put("CRDN_SE_CD", crdnSeCd);

	        	if (!before.isEmpty()) {
	        		if (need.checkChangeCrackdown) {
	        			boolean isChangeTempGroup = isChangeCrackdown(dataObject, before);
	                	if (isChangeTempGroup) {
	                		tempGroupSeq++;
	                	}
	        		} else {
	        			tempGroupSeq++;
	        		}
	        	}

	        	return before = dataObject.set("TEMP_GROUP_ID", tempGroupSeq);
        })
		.toList();
	}

	/**파일에서 파일관련 기본정보를 추출한다.<br />
	 * @param dataObject 정보, File 파일
	 * @return
	 */
	private void setFileDefaultInfo(DataObject dataObject, File file) {
    	String fileName = file.getName();

    	dataObject
    		.set("FILE_PATH", file.getPath())
    		.set("FILE_NAME", fileName)
    		.set("FILE_LAST_MODIFIED", new SimpleDateFormat("yyyy-MM-dd / HH:mm:ss").format(new Date(file.lastModified())))
    		.set("FILE_SIZE", file.length() + " byte")
    		.set("FILE_EXTENSION", FilenameUtils.getExtension(fileName));

    	boolean isUserUpload = file.compareTo(new File(descriptor.getLinkFileLocation()+File.separator+fileName)) == 0;
    	if (!isUserUpload)
    		dataObject.put("LINK_ENT_NM", descriptor.getLinkEnterpriseName());
	}

	/**파일명을 분석한다.<br />
	 * @param dataObject
	 * @return
	 */
	public void analyzeFileName(DataObject dataObject, String noExtensionName) {
		String fileNameItmesJSONString = descriptor.getFileNameItmes();

		try {
			JSONParser jsonParser = new JSONParser(-1);
			JSONArray jsonArray = (JSONArray)jsonParser.parse(fileNameItmesJSONString);

			String[] fileNameSplit = {};
			if (descriptor.getFileNameSeperator().equals("byte")) {
				fileNameSplit = smartSplit(noExtensionName, jsonArray);
			} else {
				fileNameSplit = smartSplit(noExtensionName, descriptor.getFileNameSeperator());
			}

			for (int i = 0; i < jsonArray.size(); i++) {
				JSONObject jsonObject = (JSONObject)jsonArray.get(i);
				String key = jsonObject.keySet().iterator().next();

				if (fileNameSplit.length > i) {
					dataObject.put(key, fileNameSplit[i]);
				} else {
					dataObject.put(key, "");
				}
			}
		} catch (ParseException e) {
			throw new RuntimeException("파일명 분석 오류."+e);
		}
	}

	/**파일내용을 분석한다.<br />
	 * @param dataObject
	 * @return
	 */
	abstract public void analyzeFileContent(DataObject dataObject, File file);

	/**파일명이나 파일내용을 파싱한 후 데이터를 정제한다.<br />
	 * @param dataObject
	 * @return
	 */
	public void refineValue(DataObject dataObject) {
		//공백 제거
		for (int i = 0; i < allItem.length; i++)
			dataObject.put(allItem[i],dataObject.string(allItem[i]).trim());

		//단속일시 설정
		String crdnTimestamp = dataObject.string("CRDN_TIMESTAMP");
		if (!isEmpty(crdnTimestamp)) {
			dataObject.put("CRDN_YMD", crdnTimestamp.substring(0, 8));
			dataObject.put("CRDN_TM", crdnTimestamp.substring(8, 14));
			dataObject.put("CRDN_YMD_TM", crdnTimestamp.substring(0, 14));
    	} else {
			String crdnYmdTm = dataObject.string("CRDN_YMD_TM");
			if (!isEmpty(crdnYmdTm)) {
				dataObject.put("CRDN_YMD", crdnYmdTm.substring(0, 8));
				dataObject.put("CRDN_TM", crdnYmdTm.substring(8, 14));
				dataObject.put("CRDN_TIMESTAMP", crdnYmdTm + "000");
			} else {
				String crdnYmd = dataObject.string("CRDN_YMD");
				if (!isEmpty(crdnYmd)){
					dataObject.put("CRDN_YMD_TM", crdnYmd + dataObject.string("CRDN_TM"));
					dataObject.put("CRDN_TIMESTAMP", crdnYmd + dataObject.string("CRDN_TM") + "000");
				}
			}
		}

		//숫자형 값에서 불필요한 0제거
		String[] numberValues = {"PHOTO_CNT", "GPS_X", "GPS_Y", "MOSC_X", "MOSC_Y", "PLATE_WIDHT", "PLATE_HEIGHT"};
		refineNumberValue(dataObject, numberValues);

		//코드변환
		CodeConverter codeConverter = new CodeConverter(allCode);

		//위반내용
		String vltnGroup = taskProcessor.getVltnCdGrp(descriptor.getTaskSeCd());
		if (!isEmpty(vltnGroup))
			codeConverter.fillIfEmpty(dataObject, vltnGroup, "VLTN_CD", "VLTN_NM");

		//특별구역
		codeConverter.fillIfEmpty(dataObject, "FIM007", "CRDN_SPAREA_CD", "CRDN_SPAREA_NM");
		codeConverter.fillIfEmpty(dataObject, "PHOTO_TYPE", "PHOTO_TYPE_CD", "PHOTO_TYPE_NM");
		codeConverter.fillIfEmpty(dataObject, "FILE_STATUS", "FILE_STATUS_CD", "FILE_STATUS_NM");
		codeConverter.fillIfEmpty(dataObject, "CRDN_CN", "CRDN_CN_CD", "CRDN_CN_NM");
	}

	/** dataObject 내에서 숫자형 데이터 항목을 정제한다.<br />
	 * @param dataObject, items 숫자형 데이터 이름
	 * @return
	 */
	public void refineNumberValue(DataObject dataObject, String[] items) {
		for (int i = 0;i < items.length;i++) {
			String itemValue = dataObject.string(items[i]);
			if (!isEmpty(itemValue)) {
				//소수부 0제거
				if (itemValue.contains(".")) {
					boolean end = false;
					while (!end) {
						String removed = StringUtils.removeEnd(itemValue, "0");
						if (removed.equals(itemValue)) {
							end = true;
						} else {
							itemValue = removed;
						}
					}

					if (itemValue.endsWith(".")){
						itemValue = itemValue + "0";
					}
				}

				//정수부 0제거
				if (itemValue.startsWith("0") && !itemValue.equals("0") && !itemValue.startsWith("0.")) {
					itemValue = itemValue.replaceAll("^0 + ", "");
					if (itemValue.equals("") || itemValue.startsWith(".")) {
						itemValue = "0" + itemValue;
					}
				}

				dataObject.put(items[i], itemValue);
			}
		}
	}

	/**현재 연계 파일의 정보가 이전 연계 파일의 정보와 다른 단속건인지 판단한다.<br />
	 * @param currentItem 현재 연계 파일 정보, beforeItem 이전 연계 파일 정보
	 * @return 개별 단속자료 여부
	 */
	public boolean isChangeCrackdown(DataObject currentItem, DataObject beforeItem) {
		boolean isSameItemChange = false;
		String sameItems = descriptor.getSameItemsInFileGroup();
		String[] sameItemsSplit = sameItems.split(",",-1);
		for (int i = 0; i < sameItemsSplit.length; i++) {
			if (!currentItem.string(sameItemsSplit[i]).equals(beforeItem.string(sameItemsSplit[i]))) {
				isSameItemChange = true;
			}
		}
		if (isSameItemChange)
			return true;

		String increaseItem = descriptor.getIncreaseItemInFileGroup();
		if (increaseItem != null && !increaseItem.equals("")) {
			String increaseType = descriptor.getIncreaseTypeInFileGroup();

			if (increaseType.equals("number")){
				if (currentItem.number(increaseItem).intValue() <= beforeItem.number(increaseItem).intValue())
					return true;
			} else if (increaseType.equals("alphabet")) {
				if (currentItem.string(increaseItem).charAt(0) <= beforeItem.string(increaseItem).charAt(0))
					return true;
			}
		}

		return false;
	}

	/**파일명이나 파일내용을 특정 구분자로 자른다.<br />
	 * @param string 문자열, sep 구분자
	 * @return
	 */
	public String[] smartSplit(String string, String sep) {
		if (sep.equals("LineBreak")) {
			return string.split("\r?\n|\r");
		} else {
			return string.split(Matcher.quoteReplacement(sep));
		}
	}

	/**파일명이나 파일내용을 바이트로 자른다.<br />
	 * @param string 문자열, sep 구분자
	 * @return
	 */
	public String[] smartSplit(String string, JSONArray jsonArray) {
		Hangul hangul = new Hangul(2);

		List<Integer> byteNums = new ArrayList<Integer>();
		for (int i = 0; i < jsonArray.size(); i++) {
			JSONObject jsonObject = (JSONObject)jsonArray.get(i);
			String key = jsonObject.keySet().iterator().next();
			int byteNum = Integer.parseInt((String)jsonObject.get(key));
			byteNums.add(byteNum);
		}

		String[] result = new String[byteNums.size()];

		int cursor = 1;
		for (int i = 0; i < byteNums.size(); i++) {
			int byteNum = byteNums.get(i);
			result[i] = hangul.substringByBytes(string, cursor, cursor+(byteNum-1));
			cursor += byteNum;
		}

		return result;
	}

	/**레이아웃 설정 정보의 byte를 합계를 반환한다.<br />
	 * @param jsonArray 레이아웃 설정정보
	 * @return
	 */
	public int getSumByte(JSONArray jsonArray){
		int result = 0;
		for (int i = 0; i < jsonArray.size(); i++) {
			JSONObject jsonObject = (JSONObject)jsonArray.get(i);
			String key = jsonObject.keySet().iterator().next();
			int byteNum = Integer.parseInt((String)jsonObject.get(key));
			result += byteNum;
		}
		return result;
	}
}