package cokr.xit.fims.cmmn;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.TextStyle;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.imageio.ImageIO;

import org.apache.commons.io.FilenameUtils;
import org.springframework.core.io.ClassPathResource;

import cokr.xit.foundation.AbstractComponent;
import cokr.xit.foundation.data.DataObject;
import cokr.xit.foundation.util.DateFormats;

public class CmmnUtil extends AbstractComponent {

	public static List<DataObject> getDataObjectListFromMap(Map<String,?> map, String key) {
		Object obj = map.get(key);
		if (obj == null)
			return null;

		ArrayList<DataObject> dataObjectList = new ArrayList<>();
		if (obj instanceof ArrayList<?> list) {
			for (Object o : list) {
				if (o instanceof DataObject dataobject) {
					dataObjectList.add(dataobject);
				}
			}
		}
		return dataObjectList;
	}

	/**
     * HTML엔티티코드 문자열을 특수문자로 치환한다.
     * @param HTML엔티티코드 문자열
     * @return 치환된 특수문자
     */
	public static String escapeHTMLEntity(String str) {
		return str.replaceAll("&lt;", "<")
			.replaceAll("&gt;", ">")
			.replaceAll("&amp;", "&")
			.replaceAll("&quot;", "\\\"")
			.replaceAll("#39;", "'");
	}

	public static String spacedJoin(String... strings) {
		String result = "";
		for (String str : strings) {
			if (str == null) {
				str = "";
			}
			if (result.equals("") || str.equals("")) {
				result += str;
			} else {
				result += " " + str;
			}
		}
		return result;
	}

	/**
     * 문자열 안의 모든 문자가 숫자인지 테스트한다.
     * @param 문자열
     * @return boolean
     */
	public static boolean isNumeric(String str) {
		String REGEXP_PATTERN_NUMBER = "^[\\d]*$";
		return Pattern.matches(REGEXP_PATTERN_NUMBER, str);
	}

	/**
     * 문자열 안의 모든 문자가 한글인지 테스트한다.
     * @param 문자열
     * @return boolean
     */
	public static boolean isKorean(String str) {
		String REGEXP_PATTERN_KOR = "^[가-힣]*$";
		return Pattern.matches(REGEXP_PATTERN_KOR, str);
	}

	/**
     * 순서를 숫자에서 알파벳문자로 변환한다.
     * @param index 순서번호, 대소문자여부(대문자일경우 true, 소문자일경우 false), startFromZero 0부터 시작 여부(0부터 시작시 true, 1부터 시작시 false)
     * @return 알파벳대문자(A~Z)
     */
	public static String indexToAlphabet(int index, boolean isUpper, boolean startFromZero) {
		int A = isUpper ? 65 : 97;
		int Z = isUpper ? 90 : 122;

		int charCode = (A-1);

		charCode += index;

		if (startFromZero) {
			charCode++;
		}

		if (charCode < A || charCode > Z) {
			return "";
		}

		return ""+(char)charCode;
	}

	/**
     * 확장자가 포함된 파일명에 추가 문자열을 더한 파일명을 반환한다.
     * @param orgnFileName 원본파일명, append 추가 문자열
     * @return 변환된 파일명
     */
	public static String appendFileName(String orgnFileName, String append) {
		return FilenameUtils.removeExtension(orgnFileName) + append + "." + FilenameUtils.getExtension(orgnFileName);
	}

	/**
     * 모자이크 처리된 이미지파일을 생성한다.
     * @param source 원본파일, target 결과파일, mosaicInfos 모자이크 정보
     * @return
     */
	public static void createMaskedImage(InputStream source, File target, List<DataObject> mosaicInfos) {

		BufferedImage buffer_black_image = new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR);
		String extension = FilenameUtils.getExtension(target.getName());
		try {

			BufferedImage buffer_original_image = ImageIO.read(source);
			Graphics2D graphic = buffer_original_image.createGraphics();
			for (DataObject mosaicInfo : mosaicInfos){
				int mosaicX = toInt(mosaicInfo.get("X_AXS"));
				int mosaicY = toInt(mosaicInfo.get("Y_AXS"));
		    	int mosaicWidth = toInt(mosaicInfo.get("MOSC_LEN"));
		    	int mosaicHeight = toInt(mosaicInfo.get("MOSC_HGT"));
		    	graphic.drawImage(buffer_black_image, mosaicX, mosaicY, mosaicWidth, mosaicHeight, null);
			}

			ImageIO.write(buffer_original_image, extension, target);
		} catch (IOException e) {
			throw new RuntimeException("이미지 모자이크 처리 중 오류가 발생하였습니다.");
		}

	}

	 /**
     * 하위 폴더가 비워져 있으면 삭제한다.
     * @param file 대상 폴더, removeSelf 대상 폴더까지 삭제할 지 여부
     * @return 삭제된 폴더수
     */
    public static int deleteEmptyDir(File file, boolean removeSelf) {
        if (!file.isDirectory()) return 0;

        int delCnt=0;

        for (File subFile : file.listFiles()) {
            if (subFile.isDirectory()) {
                delCnt+=deleteEmptyDir(subFile, true);
            }
        }

        if (removeSelf) {
        	if (file.listFiles().length==0) {
        		if (!file.delete()) {
        			throw new RuntimeException("파일 삭제 중 오류가 발생하였습니다.");
        		}
                delCnt++;
            }
        }

        return delCnt;
    }

    /**
     * 폴더 경로에 파일명이 동일한 파일이 여러 개 있을 경우 수정일자가 가장 마지막인 파일만 남기고 삭제한다.
     * @param root 경로
     * @return 삭제된 폴더수
     */
    public static void removeDuplicateFileName(String root) {
    	List<Path> fileList = null;
    	try (Stream<Path> pathStream = Files.walk(Paths.get(root))){

    		fileList = pathStream.filter(Files::isRegularFile).toList();
    	} catch (IOException e) {
    		throw new RuntimeException("파일 조회 오류."+e);
		}

    	if (fileList != null && !fileList.isEmpty()) {
    		Map<String, List<Path>> byFileName = fileList.stream().collect(Collectors.groupingBy(item -> item.toFile().getName()));

    		Iterator<String> keys = byFileName.keySet().iterator();

            while( keys.hasNext() ){
                String key = keys.next();
                List<Path> sameNameFileList = byFileName.get(key);
                if (sameNameFileList.size() < 2) {
                	continue;
                }

                Path latest = sameNameFileList.stream()
    			         .max(Comparator.comparing(item -> item.toFile().lastModified()))
    		             .orElseThrow();

                for (Path sameNameFile : sameNameFileList) {
                	if (!sameNameFile.equals(latest)) {
                		if (!sameNameFile.toFile().delete()) {
                			throw new RuntimeException("[F]파일 삭제 오류");
                		}
                	}
                }
            }
    	}
    }

    /**
     * 리소스 파일을 파일 공통 저장 경로로 복사한다.
     * @param staticResourcePath 리소스 파일 경로, replace 기존 생성 파일 덮어쓰기 여부
     * @return
     */
    public static String copyStaticResource(String staticResourcePath, boolean replace) {
    	try {

    		String folderPath = FilenameUtils.getFullPathNoEndSeparator(staticResourcePath);
    		String storageRoot = "files";
    		ClassPathResource cps = new ClassPathResource(staticResourcePath);
    		String fileName = cps.getFilename();

    		File newFile = new File(storageRoot + "/" + folderPath + "/" + fileName);
    		if (replace && newFile.exists()) {
    			return storageRoot + "/" + folderPath + "/" + fileName;
    		}

    		File folder = new File(storageRoot + "/" + folderPath);
    		if (!folder.exists()) {
    			folder.mkdirs();
    		}
			InputStream is = cps.getInputStream();

			Files.copy(is, newFile.toPath(), StandardCopyOption.REPLACE_EXISTING);

			return storageRoot + "/" + folderPath + "/" + fileName;

		} catch (IOException e) {
			throw new RuntimeException(e);
		}

    }


    /**
     * 현재 날짜와 지정한 날짜와의 차이(일)를 반환한다.
     * @param dayStr 지정한 날짜
     * @return 현재 날짜와의 차이 일수
     */
    public static int compareToday(String dayStr) {

		SimpleDateFormat yyyyMMddFormat = new SimpleDateFormat("yyyyMMdd");
		Date curDate = new Date();
		String strCurrentDate = yyyyMMddFormat.format(curDate);

		if (dayStr.length() == 8) {
			return Integer.parseInt(strCurrentDate) - Integer.parseInt(dayStr);
		} else if (dayStr.length() == 14) {
			return Integer.parseInt(strCurrentDate) - Integer.parseInt(dayStr.substring(0,8));
		} else {
			throw new RuntimeException("날짜 형식 오류");
		}
	}

    /**
     * 지정한 날짜에 일수를 더한 날짜를 반환한다.
     * @param yyyyMMdd 지정한 날짜, add 추가 일수
     * @return 계산된 날짜
     */
	public static String addDay(String yyyyMMdd, int add) {

		SimpleDateFormat yyyyMMddFormat = new SimpleDateFormat("yyyyMMdd");

		Calendar cal = Calendar.getInstance();

		try {
			Date dt = yyyyMMddFormat.parse(yyyyMMdd);
			cal.setTime(dt);
		} catch (Exception e){
			throw new RuntimeException(e);
		}

		cal.add(Calendar.DATE,  add);

		return yyyyMMddFormat.format(cal.getTime());
	}

	public static String dateNFormat(String dateStr, String pattern) {
		if (dateStr.length() == 8) {
			return CmmnUtil.date8Format(dateStr, pattern);
		} else if (dateStr.length() == 12) {
			return CmmnUtil.date12Format(dateStr, pattern);
		} else if (dateStr.length() == 14) {
			return CmmnUtil.date14Format(dateStr, pattern);
		} else {
			return dateStr;
		}
	}

	public static String date8Format(String dateStr, String pattern) {
		DateFormats dataFormats = new DateFormats();
		Date date = dataFormats.parse("yyyyMMdd", dateStr);
		return dataFormats.format(pattern, date);
	}
	public static String date12Format(String dateStr, String pattern) {
		DateFormats dataFormats = new DateFormats();
		Date date = dataFormats.parse("yyyyMMddHHmm", dateStr);
		return dataFormats.format(pattern, date);
	}
	public static String date14Format(String dateStr, String pattern) {
		DateFormats dataFormats = new DateFormats();
		Date date = dataFormats.parse("yyyyMMddHHmmss", dateStr);
		return dataFormats.format(pattern, date);
	}

	/**'-'로 구분된 우편번호를 반환한다.
	 * @param zipCode '-'이 없는 우편번호
	 * @return '-'로 구분된 우편번호
	 */
	public static String zipCodeFormat(String zipCode) {
		if (zipCode != null && zipCode.length() == 6) {
			return zipCode.substring(0,3)+"-"+zipCode.substring(3);
		}
		return zipCode;
	}

	/**'-'로 구분된 등기번호를 반환한다.
	 * @param rgNo '-'이 없는 등기번호
	 * @return '-'로 구분된 등기번호
	 */
	public static String rgNoFormat(String rgNo) {
		if (rgNo != null && rgNo.length() == 13) {
			return rgNo.substring(0,5)+"-"+rgNo.substring(5);
		}
		return rgNo;
	}

	/**
     * 날짜형 문자열 입력받아 요일을 한글 문자 하나로 반환한다.
     * @param dateStr 날짜
     * @return 요일
     */
	public static String getDayOfWeek(String dateStr) {
		int y = Integer.parseInt(dateStr.substring(0,4));
		int m = Integer.parseInt(dateStr.substring(4,6));
		int d = Integer.parseInt(dateStr.substring(6,8));
		LocalDate date = LocalDate.of(y, m, d);
		DayOfWeek dayOfWeek = date.getDayOfWeek();
		String dow = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.KOREAN);
		return dow;
	}

	/** 숫자를 한글로 표시한다.
	 * @param number
	 * @return
	 */
	public static String convertNumberToHangul(String number) {
		String[] han1 = { "", "일", "이", "삼", "사", "오", "육", "칠", "팔", "구" };
		String[] han2 = { "", "십", "백", "천" };
		String[] han3 = { "", "만", "억", "조", "경" };
		String result = "";
		int len = number.length();
		int nowInt = 0;
		boolean hasHan3 = false;
		for (int i = len; i > 0; i--) {
			nowInt = Integer.parseInt(number.substring(len - i, len - i + 1));
			int han2Pick = (i - 1) % 4;
			if (nowInt > 0) {
				result += (han1[nowInt]) + (han2[han2Pick]);
				if (han2Pick > 0) {
					hasHan3 = false;
				}
			}
			if (!hasHan3 && han2Pick == 0) {
				result += (han3[(i - 1) / 4]);
				hasHan3 = true;
			}
		}
		return result.toString();
	}

	public static String convertNumberToHangul(int number) {
		String numStr = Integer.toString(number);
		return convertNumberToHangul(numStr);
	}

	/** 카멜케이스 문자열을 스네이크케이스 문자열로 변경한다.
	 * @param String
	 * @return String
	 */
	public static String convertCamelCaseToSnakeCase(String camelCase) {
		Pattern pattern = Pattern.compile("([a-z])([A-Z])");
		Matcher matcher = pattern.matcher(camelCase);

		String convert = matcher.replaceAll(matchResult -> {
			return String.format("%s_%s", matchResult.group(1), matchResult.group(2));
		});

		return convert.toUpperCase();
	}

	/** 스네이크케이스 문자열을 카멜케이스 문자열로 변경한다.
	 * @param String
	 * @return String
	 */
	public static String convertSnakeCaseToCamelCase(String snakeCase) {
		Pattern compile = Pattern.compile("([a-z])_([a-z])");
		Matcher matcher = compile.matcher(snakeCase.toLowerCase());

		String convert = matcher.replaceAll(matchResult -> {
			return String.format("%s%s", matchResult.group(1).toLowerCase(), matchResult.group(2).toUpperCase());
		});

		return convert;
    }

	/**OCR 밴드의 검을 반환한다.
	 * @param vrfcCode 검증에 사용할 코드
	 *        ocrData OCR 데이터
	 *        bgngPstn 시작 위치
	 *        totalLength OCR 데이터의 총길이
	 * @return 검증 코드
	 * <ul><li>저장됐으면 true</li>
	 *     <li>그렇지 않으면 false</li>
	 * </ul>
	 */
	public static String getOcrVrfcCode(String vrfcCode, String strData, int totalLength) {
		int temp = 0;
		int cdTemp = 0;
		String retCode = "";

		for (int iLoop = 0; iLoop < totalLength; iLoop++) {
			temp = (iLoop + vrfcCode.length()) % vrfcCode.length();
			cdTemp = cdTemp + (Integer.valueOf(strData.substring(iLoop, iLoop + 1)) * Integer.valueOf(vrfcCode.substring(temp, temp + 1)));
		}

		retCode = String.valueOf(cdTemp % 10);

		return retCode;
	}

	public static String getOcrband1(String deptCode, String spclBizCd, String fyr, String acntgSeCd, String txitmCd, String levyNo, String ispySn) {
		if (deptCode.equals("")) {
			deptCode = "0000000";
		}
		if (spclBizCd.equals("")) {
			spclBizCd = "0000";
		}
		if (fyr.equals("")) {
			fyr = "0000";
		}
		if (acntgSeCd.equals("")) {
			acntgSeCd = "00";
		}
		if (txitmCd.equals("")) {
			txitmCd = "000000";
		}
		if (levyNo.equals("")) {
			levyNo = "000000";
		}
		if (ispySn.equals("")) {
		    ispySn = "00";
		}
		String band1 = deptCode + spclBizCd + fyr + acntgSeCd + txitmCd + levyNo + ispySn;

		String ocrVrfcCode = getOcrVrfcCode("12357", band1, 31);
		band1 = band1 + ocrVrfcCode;

		return band1;
	}

	public static String getOcrband2(String band1, String levySeCd, int lastAmt, String dudtYmd) {
		if (dudtYmd.equals("")) {
			dudtYmd = "00000000";
		}

		String levySeCd1 = "";
		if (levySeCd.equals("01")) {
			levySeCd1 = "1" ;
		} else if (levySeCd.equals("02")) {
			levySeCd1 = "2" ;
		} else {
			levySeCd1 = "2" ;
		}

		String band2 = levySeCd1
				+ String.format("%011d", lastAmt)
				+ dudtYmd
				+ "0";
		String ocrVrfcCode = getOcrVrfcCode("23571", band1 + band2, 53);

		band2 = band2 + ocrVrfcCode;
		band2 = band1 + band2;

		return band2;
	}

	public static String getOcrband3(String ocr1, int lastAmt, String dudtYmd, int afterSumAmt) {
		if (dudtYmd.equals("")) {
			dudtYmd = "00000000";
		}

		// OCR 2번째줄 54
		// band3 = 국세 + 국세요율(납기후에 추가되는 가산금의 비율) + 시도세 + 시도세요율 + 시군구세 + 시군구세요율
		String band3 = "0000000000"
				+ "00"
				+ "0000000000"
				+ "00"
				+ String.format("%011d", lastAmt)
				+ "00";
		String ocrVrfcCode = getOcrVrfcCode("35712", band3, 36);

		// band4 = 부과월 + 납기후처리 + 납기후금액(12자리)
		// 0=납기후 수납가능, 1=납기후 수납불가, 2=납기 무관, 7~9=납기후 수납가능
		String band4 = dudtYmd.substring(4, 6)
				+ "1"
				+ String.format("%012d", afterSumAmt);
		ocrVrfcCode = getOcrVrfcCode("57123", band3 + band4, 52);
		band4 = band4 + ocrVrfcCode;

		// band5 = 전체 합산해서 검코드 구하기용, 사용하지는 않음
		String band5 = ocr1 + band3 + band4;
		ocrVrfcCode = getOcrVrfcCode("71235", band5, 107);

		// band6
		String band6 = band3 + band4 + ocrVrfcCode;

		return band6;
	}

	public static void setOcrInfo(DataObject dataObject) {

		String deptCode = dataObject.string("DEPT_CD");
		String spclBizCd = dataObject.string("SPCL_BIZ_CD");
		String fyr = dataObject.string("FYR");
		String acntgSeCd = dataObject.string("ACNTG_SE_CD");
		String txitmCd = dataObject.string("TXITM_CD");
		String levyNo = dataObject.string("LEVY_NO");
		String ispySn = dataObject.string("ISPY_SN");
		String ocr0 = CmmnUtil.getOcrband1(deptCode, spclBizCd, fyr, acntgSeCd, txitmCd, levyNo, ispySn);

		dataObject.set("OCR0", ocr0);
		dataObject.set("OCR0_1", ocr0.substring(0, 17));
		dataObject.set("OCR0_2", ocr0.substring(17));

        String levySeCd = dataObject.string("LEVY_SE_CD");
        int lastAmt = toInt(dataObject.get("LAST_AMT"));                 // 최종 금액
        String dudtYmd = dataObject.string("DUDT_YMD");
        String ocr1 = CmmnUtil.getOcrband2(ocr0, levySeCd, lastAmt, dudtYmd);
        dataObject.set("OCR1", ocr1);

        int afterSumAmt = toInt(dataObject.get("DUDT_AFTR_AMT"));		// 납기후 금액
        String ocr2 = CmmnUtil.getOcrband3(ocr1, lastAmt, dudtYmd, afterSumAmt);
        		dataObject.set("OCR2", ocr2);
	}

	public static String getBarcode(String rgSe, String sggCd, String sndngDtlId) {
		String rgSe1 = "";
		if (rgSe.length() > 1) {
			rgSe1 = rgSe.substring(1);
		} else {
			rgSe1 = rgSe;
		}

		return "*"+rgSe1+sggCd+sndngDtlId+"*";
	}

	public static Long sumByKey(List<DataObject> dataObjectList, String key) {
		return dataObjectList.stream().mapToLong(i -> i.number(key).longValue()).sum();
	}

	public static void setBarcode(DataObject dataObject) {
		String recevDivCd = dataObject.string("RECEV_DIV_CD");
		String sggCd = dataObject.string("SGG_CD");
		String sndngDtlId = dataObject.string("SNDNG_DTL_ID");
		String barcode = CmmnUtil.getBarcode(recevDivCd, sggCd, sndngDtlId);
		dataObject.put("BARCODE", barcode);
		dataObject.put("BARCODE1", barcode);
	}

	public static float mmToPt(float mm) {
		return mm * 2.83465f;
	}


}