const jsonPath = require('jsonpath');
const XLSX = require('xlsx');
const psd = require('psd');
const fs = require('fs');
const path = require('path');
const xpath = require('xpath');
const { DOMParser } = require('xmldom');

// 복사된 답안 파일 폴더
const answerFilesDir = './output/B/DIC/';

// 답안 폴더 내부에 디렉토리가 아닌 일반 파일이 있을 경우 디렉토리만 필터링 해서 불러옴
const studentDirs = fs.readdirSync(answerFilesDir).filter(file => {
  const filePath = path.join(answerFilesDir, file);
  return fs.statSync(filePath).isDirectory();
});

//const students = fs.readdirSync(answerFilesDir);

// 기준표 파일 읽기
const scoring = require('./제2501회 정기 DIC B형_old.json');

const psdData = [];
// 채점 결과 리스트
const gradingResults = [];

studentDirs.forEach(student => {
  // 맥에서 한글 디렉토리 이름을 읽어서 엑셀에 저장 할 시 자소 분리가 되어 저장되는 문제 노말라이즈해서 해결
  const name = student.normalize('NFC');
  const studentDir = path.join(answerFilesDir, student);
  const psdFiles = fs.readdirSync(studentDir).filter(file => file.endsWith('.psd'));
  const gmepFile = fs.readdirSync(studentDir).filter(file => file.endsWith('.gmep'));

  // 학생 이름을 key로 하는 객체 생성
  const gradingResult = {
    0: name
  };

  psdFiles.forEach((psdFile, index) => {
    const psdPath = path.join('./', studentDir, psdFile);
    console.log(`Reading ${psdPath}...`);
    psdData[index] = psd.fromFile(psdPath);
    psdData[index].parse();
    gradingResult[index + 1] = getScore(psdData, scoring, index);
  });
  gmepFile.forEach((gmep, index) => {
    const gmepPath = path.join('./', studentDir, gmep);
    console.log(`Reading ${gmepPath}...`);
    const xmlContent = fs.readFileSync(gmepPath, 'utf8');
    // XML 파싱
    const doc = new DOMParser().parseFromString(xmlContent, 'application/xml');
    // console.log('doc:', doc);

    gradingResult[3] = getGmepScore(doc, scoring, 2);
  });
  gradingResults.push(gradingResult);
});

// 엑셀 파일 생성

// Flatten the resultData for better representation in Excel
const flattenedData = gradingResults.map(student => {
  const name = student["0"]
  const flattened = { "학생": student["0"] };
  Object.keys(student).forEach(key => {
    if (key !== "0") {
      Object.keys(student[key]).forEach(subKey => {
        flattened[`${key}_${subKey}`] = student[key][subKey];
      });
    }
  });
  return flattened;
});
// console.log(flattenedData);

const worksheet = XLSX.utils.json_to_sheet(flattenedData);
const workbook = XLSX.utils.book_new();

// Add the worksheet to the workbook
XLSX.utils.book_append_sheet(workbook, worksheet, '채점 결과');

// 엑셀 파일 저장
XLSX.writeFile(workbook, 'output.xlsx');
console.log('채점 결과가 output.xlsx 파일에 저장되었습니다.');


// xml 형식의 gmep 파일을 읽어서 점수를 계산
// scoring.json 파일 내에 있는 ele 요소는 xmlpath 형식으로 접근하여 요소를 탐색하고 나오는 값을 value와 비교하여 점수를 계산
// scoring.json 파일 내에 있는 type은 비교할 값의 타입을 의미하며, boolean, array 등이 있음
// scoring.json 파일 내에 있는 type에 따라 비교하는 방식이 달라짐
// 채점 결과를 gradingResults 배열에 저장
function getGmepScore(gmepData, scoring, index) {
  const doc = gmepData;
  const gradingResult = {};

  const scoringData = scoring[index];
  // console.log(scoringData);

  let totalScore = 0;
  for (const key in scoringData) {
    const ele = scoringData[key].ele;
    const value = scoringData[key].value;
    const point = scoringData[key].point;
    const type = scoringData[key].type;
    console.log(`example number: ${key}`);
    if (ele === 'none') {
      gradingResult[key] = "확인필요";
      continue;
    }

    if (type == "boolean") {
      gradingResult[key] = result.length > 0 ? point : 0;
    }
    else if (type == "array") {
      // result:  Path="동영상.mp4", Path="음악.mp3", Path="이미지2.jpg", Path="이미지1.jpg"
      // value: 동영상.mp4,이미지1.jpg,이미지3.jpg,이미지2.jpg
      // result 와 value를 순서대로 비교하여 모두 같으면 점수 point 부여

      // XPath를 사용하여 CRTrackList Name="비디오1" 요소 찾기
      const trackListNode = xpath.select1('//CRVideoTrackArr/CRTrackList[@Name="비디오1"]', doc);
      const values = [];
      let isSame = true;

      if (trackListNode) {
        // CRTrackClip 요소의 ClipIndex를 참조하여 CRClip 요소의 Path와 Type 출력
        const clipIndexes = xpath.select('CRTrackClip/@ClipIndex', trackListNode);
        clipIndexes.forEach(indexNode => {
          const clipIndex = parseInt(indexNode.value, 10) + 1; // XPath는 1-based index를 사용
          console.log(`clipIndex: ${clipIndex}`);
          if (clipIndex === 0) {
            return;
          }
          const clipPathNode = xpath.select1(`//CRClipArr/CRClip[${clipIndex}]/@Path`, doc);

          if (clipPathNode === undefined) {
            console.log(`clipPathNode: undefined`);
            return;
          }
          console.log(`clipPathNode: ${clipPathNode.value}`);
          values.push(clipPathNode.value);
        });
        // values에 값이 있는지 확인
        if (values.length == 0) {
          console.log('values length 0');
          gradingResult[key] = 0;
          continue;
        }
        values.forEach((v, i) => {
          console.log(`values: ${v}  value: ${value[i]}`);
          if (value[i] !== v) {
            isSame = false;
          }
        });
        totalScore += isSame ? point : 0;
        gradingResult[key] = isSame ? point : 0;
      } else {
        console.log('CRTrackList with Name="비디오1" not found.');
        gradingResult[key] = 0;
      }
    }
    else if (type == "startend") {
      console.log('type:', type);
      const start = scoringData[key].start;
      const end = scoringData[key].end;
      // XPath를 사용하여 CRClip 요소 중 Path가 '동영상.mp4'인 요소의 위치 찾기
      const clipIndexNode = xpath.select1(ele, doc);
      console.log(`clipIndexNode: ${clipIndexNode}`);

      // XPath를 사용하여 해당 ClipIndex를 사용하는 CRTrackClip 요소 찾기
      const trackClipNodes = xpath.select1(`//CRTrackClip[@ClipIndex='${clipIndexNode}']`, doc);

      if (!trackClipNodes) {
        gradingResult[key] = 0;
        continue;
      }
      const posNode = xpath.select1('@Pos', trackClipNodes);
      const lengthNode = xpath.select1('@Length', trackClipNodes);
      console.log(`Pos: ${posNode.value}, Length: ${lengthNode.value}`);

      gradingResult[key] = posNode.value === start && lengthNode.value === end ? point : 0;
      totalScore += posNode.value === start && lengthNode.value === end ? point : 0;

    }
    else if (type == "subtitle") {
      const result = xpath.select(ele, doc);
      const length = scoringData[key].length;
      // 결과는 배열로 나오는데 2개 일 경우가 있음

      if (result.length !== length) {
        gradingResult[key] = 0;
        continue;
      }

      gradingResult[key] = point;
      totalScore += point;
    }
    else if (type == "color") {
      const result = xpath.select(ele, doc);

      if (result.length == 0) {
        gradingResult[key] = 0;
        continue;
      }

      console.log(`value: ${value}  result: ${result[0].value}`);
      // value와 result[0].value를 비교하여 같으면 점수 point 부여
      totalScore += result.length > 0 && value === result[0].value ? point : 0;
      gradingResult[key] = result.length > 0 && value === result[0].value ? point : 0;

    }
    else if (type == "multi") {
      try {
        const result = xpath.select(ele, doc);
        let isSame = true;
        // console.log(`ele: ${ele}, value: ${value}   result: ${result}`);

        if (result.length == 0) {
          console.log('result length 0');
          gradingResult[key] = 0;
          continue;
        }

        result.forEach((v, i) => {
          // value[i] 값이 정수형인 경우에는 float로 변환하여 비교
          // 정수형 v값을 float 형으로 변환하고 소수점 3자리까지 버림
          let temp = v.value;
          let answer = value[i];

          if (Number.isFinite(value[i]) && !Number.isInteger(value[i])) {
            temp = parseFloat(v.value);
            answer = parseFloat(value[i]);
            // 소수점 3자리까지 버림
            temp = Math.floor(temp * 1000) / 1000;
          }
          // answer 문자열 중 : 가 포함되어 있다면 각각 분리하고 그 값의 차이를 구함
          if (typeof answer == "string" && answer.indexOf(':') > -1) {
            const [answerStart, answerEnd] = answer.split(':').map(Number);
            const [tempStart, tempEnd] = temp.split(':').map(Number);
            answer = answerEnd - answerStart;
            temp = tempEnd - tempStart;
          }

          console.log(`temp: ${temp}  answer: ${answer}`);
          if (answer !== temp) {
            console.log(`answer !== temp`);
            isSame = false;
          }
        });
        totalScore += isSame ? point : 0;
        gradingResult[key] = isSame ? point : 0;
      } catch (e) {
        console.log('err :', e);
        gradingResult[key] = 0;
      }
    }
    else if (type == "searchIndex") {
      let existEle = scoringData[key].existEle;
      // XPath를 사용하여 ELE 요소가 존재하는지 확인
      const crcUnitArrNode = xpath.select1(existEle, doc);

      if (crcUnitArrNode) {
        // ELE 요소가 몇번째 요소인지 찾고 필요한 요소 확인        
        const unitOrderNode = xpath.select1(ele, doc);
        console.log(`unitOrderNode: ${unitOrderNode}`);
        if (unitOrderNode === undefined) {
          gradingResult[key] = 0;
          continue;
        }
        if (unitOrderNode.value === value) {
          console.log(`unit: ${unitOrderNode.value} === ${value}`);
          gradingResult[key] = point;
          totalScore += point;
        }
        else if (unitOrderNode === value) {
          console.log(`unitValue: ${unitOrderNode} === ${value}`);
          gradingResult[key] = point;
          totalScore += point;
        }
        else {
          gradingResult[key] = 0;
        }

      } else {
        console.log(`not found. ${existEle} `);
        gradingResult[key] = 0;
      }
    }
    else {
      const ele2 = scoringData[key].ele2;

      console.log('Unknown type:', ele);
      let result = xpath.select(ele, doc);
      let result2 = null;
      let isCheck = false;
      
      if (result.length == 0) {
        isCheck = true;
      }
      if (isCheck && ele2) {
        result2 = xpath.select(ele2, doc);

        if (result2.length == 0) {          
          gradingResult[key] = 0;
          continue;
        }

        result = result2;
        console.log(`1st isChecked: ${isCheck}, result: ${result}`)
      }

      // console.log(`result: ${result[0].value}`);
      // value와 result[0].value를 비교하여 같으면 점수 point 부여
      // console.log(`${(value === result[0].value)}, ${result.length > 0 && value === result[0].value} `)
      console.log(`2nd isChecked: ${isCheck}, result: ${result}`)
      totalScore += result.length > 0 ? point : 0;
      gradingResult[key] = result.length > 0 ? point : 0;
    }
  }


  gradingResult['총점'] = totalScore;
  return gradingResult;
}


// psdData를 scoring.json 파일 내에 있는 ele 요소의 jsonpath로 접근하여 요소를 탐색하고 나오는 값을 value와 비교하여 점수를 계산
// 학생 별로 psdData는 2개씩 있으므로 PSD 파일 1번과 2번에 대해서 채점
// scoring.json 파일에 있는 항목 수만큼 점수를 계산
// scoring.json 파일의 1번째 배열은 PSD 파일 1번에 대한 점수 계산
// scoring.json 파일의 2번째 배열은 PSD 파일 2번에 대한 점수 계산
// 채점 결과를 gradingResults 배열에 저장
function getScore(psdData, scoring, index) {
  const psdTree = psdData[index].tree().export();
  const jsonData = JSON.stringify(psdTree, null, 2);
  // console.log(jsonData);
  const gradingResult = {};

  const scoringData = scoring[index];
  let totalScore = 0;
  for (const key in scoringData) {
    const ele = scoringData[key].ele;
    const value = scoringData[key].value;
    const point = scoringData[key].point;
    const type = scoringData[key].type;

    if (ele === 'none') {
      gradingResult[key] = "확인필요";
      continue;
    }

    try {
      const result = jsonPath.query(psdTree, ele);
      console.log(`ele: ${ele}, value: ${value}   result: ${result}`);
      if (result.length == 0) {
        gradingResult[key] = 0;
        continue;
      }
      if (type == "boolean") {
      // console.log(`result ${result.length}`);

        gradingResult[key] = result.length > 0 ? point : 0;
      }
      // value가 color code인 경우 R,G,B를 16진수로 변환하여 비교하고 같다면 점수 부여
      // value: "ffa200"
      // result: [255,162,0,255]
      // 255,162,0,255 -> ffa200
      else if (type == "color") {
      // console.log(`result ${result}`); // result 255,162,0,255
        const temp = result[0].slice(0, 3).join(','); // 255,162,0
        color = temp.split(',').map(v => parseInt(v).toString(16)).join(''); // ffa20
        //  ffa20 -> ffa200
        if (color.length == 5) {
          color = color + '0';
        }

      // console.log(`color: ${color}`);
        gradingResult[key] = result.length > 0 && value === color ? point : 0;
      }


      // type이 font인 경우 font의 이름만 추출하여 비교
      // value: "Arial"
      // result: ["Arial-BoldItalicMT"]
      else if (type == "font") {
      // console.log(`result ${result}`);
        const font = result[0].split('-')[0];
      // console.log(`font: ${font}`);
        gradingResult[key] = result.length > 0 && value === font ? point : 0;
      }

      else if (result[0] === value) {
        gradingResult[key] = point;
        totalScore += point;
      } else {
        gradingResult[key] = 0;
      }
    } catch (error) {
      console.error(`Error processing JSONPath query for ele: ${ele}`, error);
      gradingResult[key] = 0;
    }
  }

  gradingResult['총점'] = totalScore;
  return gradingResult;
}