package com.interplug.qcast.biz.pwrGnrSimulation;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.interplug.qcast.biz.pwrGnrSimulation.dto.*;
import com.interplug.qcast.config.Exception.ErrorCode;
import com.interplug.qcast.config.Exception.QcastException;
import com.interplug.qcast.util.InterfaceQsp;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class PwrGnrSimService {
  private final InterfaceQsp interfaceQsp;

  @Value("${qsp.url}")
  private String qspUrl;

  @Value("${qsp.simulation-guide-info-url}")
  private String qspSimulationGuideInfUrl;

  private final PwrGnrSimMapper pwrGnrSimMapper;

  double[] snow = readValuesFromFile("template/pwrGnrSimulation/snow.txt");

  // cosθz
  double[][] cosThetaZ = readValuesFromFile2("template/pwrGnrSimulation/cosThetaZ.txt");

  // hinode_nichibotsu
  double[][] hinode_nichibotsu =
      readValuesFromFile2("template/pwrGnrSimulation/hinode_nichibotsu.txt");

  double[] ion = readValuesFromFile("template/pwrGnrSimulation/ion.txt");

  // 시간 각도
  double[][] jikakudo = readValuesFromFile2("template/pwrGnrSimulation/jikakudo.txt");

  double[] jimenhansharitsu = readValuesFromFile("template/pwrGnrSimulation/jimenhansharitsu.txt");

  // 위도 경도[rad]
  double[][] latlng = readValuesFromFile2("template/pwrGnrSimulation/latlng.txt");

  double[][] rd = readValuesFromFile2("template/pwrGnrSimulation/rd.txt");

  double[][] rdx = readValuesFromFile2("template/pwrGnrSimulation/rdx.txt");

  double[][] rtx = readValuesFromFile2("template/pwrGnrSimulation/rtx.txt");

  // 산란 일사
  double[] sanrannissha = readValuesFromFile("template/pwrGnrSimulation/sanrannissha.txt");

  // 수평면 일사
  double[] suiheimennissha = readValuesFromFile("template/pwrGnrSimulation/suiheimennissha.txt");

  // 태양 적위
  double[] taiyousekii = readValuesFromFile("template/pwrGnrSimulation/taiyousekii.txt");

  static double[][] peakcut_by_pcs_data =
      readValuesFromFile2("template/pwrGnrSimulation/peakcut_by_pcs_data.txt");

  static double[][] tashounen_data_deg0 =
      readValuesFromFile2("template/pwrGnrSimulation/tashounen_data_deg0.txt");

  static double[][] tashounen_data_deg90 =
      readValuesFromFile2("template/pwrGnrSimulation/tashounen_data_deg90.txt");

  static double[][] amp_peakcut_data_deg0 =
      readValuesFromFile2("template/pwrGnrSimulation/amp_peakcut_data_deg0.txt");

  static double[][] amp_peakcut_data_deg90 =
      readValuesFromFile2("template/pwrGnrSimulation/amp_peakcut_data_deg90.txt");

  /**
   * 발전시뮬레이션 계산 결과와 견적서 정보 리턴
   *
   * @param pwrGnrSimRequest
   * @return
   * @throws Exception
   */
  public PwrGnrSimResponse selectPwrGnrSimulation(PwrGnrSimRequest pwrGnrSimRequest) {

    // 데이터가 없어서 오류가 발생한 경우, 빈값으로 리턴
    PwrGnrSimResponse exceptionRes = new PwrGnrSimResponse();
    double[] dExceptionData = new double[13];
    String[] strExceptionData = formatAndPrintArray(dExceptionData);
    exceptionRes.setHatsudenryouAll(strExceptionData);
    exceptionRes.setHatsudenryouAllSnow(strExceptionData);
    exceptionRes.setHatsudenryouPeakcutAll(strExceptionData);
    exceptionRes.setHatsudenryouPeakcutAllSnow(strExceptionData);

    // 견적서 정보를 조회한다.
    PwrGnrSimPlanResponse planInfo = pwrGnrSimMapper.selectPlanInfo(pwrGnrSimRequest);

    // 지역이 없으면 계산 불가능
    if (planInfo == null || planInfo.getAreaId() == 0) {
      log.error("지역값이 없음.");
      return exceptionRes;
    }

    // 견적서의 지붕재 목록 조회
    List<PwrGnrSimRoofResponse> roofList = pwrGnrSimMapper.selectRoofList(pwrGnrSimRequest);
    int roofLength = roofList.size(); // Set의 크기 = 고유 roofNo 개수

    // 지붕재 정보가 없음.
    if (roofList == null || roofList.isEmpty()) {
      log.error("지붕재 정보가 없음.");
      return exceptionRes;
    }

    // 지붕재의 모든 모듈 Item 조회
    pwrGnrSimRequest.setItemGroup("MODULE_");
    List<PwrGnrSimRoofResponse> roofModuleList =
        pwrGnrSimMapper.selectRoofItemList(pwrGnrSimRequest);

    // 전체 모듈에서 모듈 그룹형태로 변경
    List<PwrGnrSimRoofResponse> moduleGroupList =
        roofModuleList.stream()
            .collect(
                Collectors.groupingBy(
                    PwrGnrSimRoofResponse::getItemId, // 첫 번째 그룹화 기준
                    Collectors.collectingAndThen(
                        Collectors.toList(),
                        list -> {
                          // 그룹화된 첫 번째 요소를 기반으로 새 객체 생성
                          ObjectMapper mapper = new ObjectMapper();
                          PwrGnrSimRoofResponse setDto =
                              mapper.convertValue(list.get(0), PwrGnrSimRoofResponse.class);

                          // specification 합산
                          double totSpecification =
                              list.stream()
                                  .mapToDouble(response -> response.getTotSpecification())
                                  .sum();

                          // 합산 결과 설정
                          setDto.setTotSpecification(totSpecification);

                          return setDto;
                        })))
            .values()
            .stream()
            .sorted(
                Comparator.comparingDouble(PwrGnrSimRoofResponse::getTotSpecification)
                    .reversed()) // 내림차순 정렬
            .collect(Collectors.toList());

    // 모듈 아이템이 없음.
    if (moduleGroupList.isEmpty()) {
      log.error("모듈 아이템이 없음.");
      return exceptionRes;
    }

    // 지붕재의 모든 PCS Item 조회
    //    pwrGnrSimRequest.setItemGroup("PC_");
    //    List<PwrGnrSimRoofResponse> roofPcsList =
    //            pwrGnrSimMapper.selectRoofItemList(pwrGnrSimRequest);

    // GROUP PCS Group Item 조회
    List<PwrGnrSimRoofResponse> pcsGroupList =
        pwrGnrSimMapper.selectRoofPcsGroupList(pwrGnrSimRequest);

    // pcs 아이템이 없음.
    if (pcsGroupList.isEmpty()) {
      log.error("PCS 아이템이 없음.");
      return exceptionRes;
    }

    // 견적서 정보 기준으로 발전시뮬레이션 계산을 진행한다.
    PwrGnrSimRequest pwrGnrSimReq = new PwrGnrSimRequest();
    pwrGnrSimReq.setSimulationPointNumber(planInfo.getAreaId() - 1);
    double[] dKoubai = new double[roofLength];
    double[] dHoui = new double[roofLength];

    int[] dModuleInput1 = new int[roofLength];
    int[] dModuleInput2 = new int[roofLength];
    int[] dModuleInput3 = new int[roofLength];

    // pcs 정보
    int k = 0;
    for (PwrGnrSimRoofResponse p : pcsGroupList) {
      if (k == 0) {
        pwrGnrSimReq.setPcs1(p.getItemId());
        pwrGnrSimReq.setPcsInput1(Integer.parseInt(p.getAmount()));
        pwrGnrSimReq.setPcsCnvEff1(p.getCnvEff() == 0.0 ? 1 : p.getCnvEff()); // 디폴트 값 1
        pwrGnrSimReq.setPcsAmp1(p.getAmp());
        pwrGnrSimReq.setPcsSpecification1(p.getSpecification() / 1000);
      } else if (k == 1) {
        pwrGnrSimReq.setPcs2(p.getItemId());
        pwrGnrSimReq.setPcsInput2(Integer.parseInt(p.getAmount()));
        pwrGnrSimReq.setPcsCnvEff2(p.getCnvEff() == 0.0 ? 1 : p.getCnvEff());
        pwrGnrSimReq.setPcsAmp2(p.getAmp());
        pwrGnrSimReq.setPcsSpecification2(p.getSpecification() / 1000);
      } else {
        pwrGnrSimReq.setPcs3(p.getItemId());
        pwrGnrSimReq.setPcsInput3(Integer.parseInt(p.getAmount()));
        pwrGnrSimReq.setPcsCnvEff3(p.getCnvEff() == 0.0 ? 1 : p.getCnvEff());
        pwrGnrSimReq.setPcsAmp3(p.getAmp());
        pwrGnrSimReq.setPcsSpecification3(p.getSpecification() / 1000);
      }
      k++;
    }
    // module 정보
    k = 0;
    for (PwrGnrSimRoofResponse m : moduleGroupList) {
      if (k == 0) {
        pwrGnrSimReq.setModule1(m.getItemId());
        pwrGnrSimReq.setModuleAmp1(m.getAmp());
        pwrGnrSimReq.setModuleSpecification1(m.getSpecification());
        pwrGnrSimReq.setModuleTempCoeff1(m.getTempCoeff());
        pwrGnrSimReq.setModuleTempLoss1(m.getTempLoss());
      } else if (k == 1) {
        pwrGnrSimReq.setModule2(m.getItemId());
        pwrGnrSimReq.setModuleAmp2(m.getAmp());
        pwrGnrSimReq.setModuleSpecification2(m.getSpecification());
        pwrGnrSimReq.setModuleTempCoeff2(m.getTempCoeff());
        pwrGnrSimReq.setModuleTempLoss2(m.getTempLoss());
      } else {
        pwrGnrSimReq.setModule3(m.getItemId());
        pwrGnrSimReq.setModuleAmp3(m.getAmp());
        pwrGnrSimReq.setModuleSpecification3(m.getSpecification());
        pwrGnrSimReq.setModuleTempCoeff3(m.getTempCoeff());
        pwrGnrSimReq.setModuleTempLoss3(m.getTempLoss());
      }
      k++;
    }

    // 전체 시스템 용량
    double dSpecification = 0.0;

    int i = 0;
    for (PwrGnrSimRoofResponse data : roofList) {

      if (data.getClassType() == 0) { // 경사
        dKoubai[i] =
            (2 * Math.PI)
                * ((Math.atan(Double.parseDouble(data.getSlopeAngle()) / 10) * 180 / Math.PI)
                    / 360);
      } else { // 각도
        dKoubai[i] = (2 * Math.PI) * (Double.parseDouble(data.getSlopeAngle()) / 360);
      }

      dHoui[i] = Math.abs((2 * Math.PI) * (Double.parseDouble(data.getAzimuth()) / 360));

      // 지붕별 모듈정보 셋팅
      int j = 0;
      for (PwrGnrSimRoofResponse m : roofModuleList) {
        if (data.getRoofNo().equals(m.getRoofNo())) {
          dSpecification += m.getSpecification();
          if (j == 0) {
            dModuleInput1[i] = Integer.parseInt(m.getAmount());
          } else if (j == 1) {
            dModuleInput2[i] = Integer.parseInt(m.getAmount());
          } else {
            dModuleInput3[i] = Integer.parseInt(m.getAmount());
          }

          j++;
        }
      }

      i++;
    }

    pwrGnrSimReq.setKoubai(dKoubai);
    pwrGnrSimReq.setHoui(dHoui);
    pwrGnrSimReq.setModuleInput1(dModuleInput1);
    pwrGnrSimReq.setModuleInput2(dModuleInput2);
    pwrGnrSimReq.setModuleInput3(dModuleInput3);

    // 발전시뮬레이션 결과와 견적서 정보를 return 한다.
    PwrGnrSimResponse pwrGnrSimRes = new PwrGnrSimResponse();
    try {
      pwrGnrSimRes = this.calcResults(pwrGnrSimReq, roofLength);
    } catch (Exception e) {
      e.printStackTrace();
    }

    pwrGnrSimRes.setObjectNo(planInfo.getObjectNo());
    pwrGnrSimRes.setPlanNo(planInfo.getPlanNo());
    pwrGnrSimRes.setDrawingEstimateCreateDate(planInfo.getDrawingEstimateCreateDate());
    pwrGnrSimRes.setCapacity(String.valueOf(dSpecification));
    pwrGnrSimRes.setPrefName(planInfo.getPrefName());
    pwrGnrSimRes.setAreaName(planInfo.getAreaName());
    pwrGnrSimRes.setSnowfall(planInfo.getSnowfall());
    pwrGnrSimRes.setStandardWindSpeedId(planInfo.getStandardWindSpeedId());
    pwrGnrSimRes.setRoofModuleList(roofModuleList);
    pwrGnrSimRes.setPcsList(pcsGroupList);

    return pwrGnrSimRes;
  }

  /**
   * 실제 계산로직
   *
   * @param pwrGnrSimReq
   * @param roofLength
   * @return
   * @throws Exception
   */
  public PwrGnrSimResponse calcResults(PwrGnrSimRequest pwrGnrSimReq, int roofLength) {

    int[] days = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 각 월별 일수 선언

    // 모듈 1에 대한 데이터
    double module_system_loss = pwrGnrSimReq.getModuleTempLoss1(); // 모듈 온도 손실
    double ondo_keisu = pwrGnrSimReq.getModuleTempCoeff1(); // 모듈 온도 계수

    // 각 월별 온도 손실 계산
    double[] ondo_sonshitsu = new double[12];
    ondo_sonshitsu[0] = 1 - 0.1 * ondo_keisu / 0.5;
    ondo_sonshitsu[1] = 1 - 0.1 * ondo_keisu / 0.5;
    ondo_sonshitsu[2] = 1 - 0.1 * ondo_keisu / 0.5;
    ondo_sonshitsu[3] = 1 - 0.15 * ondo_keisu / 0.5;
    ondo_sonshitsu[4] = 1 - 0.15 * ondo_keisu / 0.5;
    ondo_sonshitsu[5] = 1 - 0.2 * ondo_keisu / 0.5;
    ondo_sonshitsu[6] = 1 - 0.2 * ondo_keisu / 0.5;
    ondo_sonshitsu[7] = 1 - 0.2 * ondo_keisu / 0.5;
    ondo_sonshitsu[8] = 1 - 0.2 * ondo_keisu / 0.5;
    ondo_sonshitsu[9] = 1 - 0.15 * ondo_keisu / 0.5;
    ondo_sonshitsu[10] = 1 - 0.15 * ondo_keisu / 0.5;
    ondo_sonshitsu[11] = 1 - 0.1 * ondo_keisu / 0.5;

    // PCS1 선택 정보
    double pcs1_henkankouritsu = pwrGnrSimReq.getPcsCnvEff1() / 100; // 변환 효율
    double pcs1_youryou = pwrGnrSimReq.getPcsSpecification1(); // 용량 (예시 4.4)
    double pcs2_henkankouritsu = pwrGnrSimReq.getPcsCnvEff2() / 100;
    double pcs2_youryou = pwrGnrSimReq.getPcsSpecification2();
    double pcs3_henkankouritsu = pwrGnrSimReq.getPcsCnvEff3() / 100;
    double pcs3_youryou = pwrGnrSimReq.getPcsSpecification3();

    // pcs1과 pcs2, pcs3의 실효 변환 효율 중 낮은 값을 사용
    double[] values = {pcs1_henkankouritsu, pcs2_henkankouritsu, pcs3_henkankouritsu};
    double pcs_henkankouritsu =
        Arrays.stream(values)
            .filter(value -> value > 0.0) // 0.0 제외
            .min()
            .orElse(0.0); // 모두 0.0인 경우 기본값 반환

    // pcs 총 용량 계산
    double pcs_youryou_total =
        (pcs1_youryou * pwrGnrSimReq.getPcsInput1())
            + (pcs2_youryou * pwrGnrSimReq.getPcsInput2())
            + (pcs3_youryou * pwrGnrSimReq.getPcsInput3());

    double module1_watt = pwrGnrSimReq.getModuleSpecification1(),
        module1_watt_flash = module1_watt + 2;
    double module2_watt = pwrGnrSimReq.getModuleSpecification2(),
        module2_watt_flash = module2_watt + 2; // 모듈 1 출력 + 2w (플래시)
    double module3_watt = pwrGnrSimReq.getModuleSpecification3(),
        module3_watt_flash = module3_watt + 2;

    int module1_number_total = 0;
    int module2_number_total = 0;
    int module3_number_total = 0;

    for (int i = 0; i < roofLength; i++) {
      if (!Double.isNaN(pwrGnrSimReq.getKoubai()[i]) && !Double.isNaN(pwrGnrSimReq.getHoui()[i])) {
        // 설치면 기울기와 방위가 입력되어 있으면 모듈의 매수를 취득
        module1_number_total += pwrGnrSimReq.getModuleInput1()[i];
        module2_number_total += pwrGnrSimReq.getModuleInput2()[i];
        module3_number_total += pwrGnrSimReq.getModuleInput3()[i];
      }
    }

    // 모듈 1, 2, 3 총 용량 계산
    double module1_youryou_total = module1_watt * module1_number_total;
    double module2_youryou_total = module2_watt * module2_number_total;
    double module3_youryou_total = module3_watt * module3_number_total;

    // 모듈 총 용량 계산
    double module_youryou_total =
        module1_youryou_total + module2_youryou_total + module3_youryou_total;

    // 적재율 [실수] 계산
    double sekisairitsu = module_youryou_total / (pcs_youryou_total * 1000);

    // 피크 컷 및 발전량 배열 선언
    double[] hatsudenryou_all = new double[13]; // 피크 컷 없음 발전량(적설 고려 없음)
    double[] hatsudenryou_all_snow = new double[13]; // 피크 컷 없음 발전량(적설 고려 있음)
    double[] hatsudenryou_peakcut_all = new double[13]; // 피크 컷 있음 발전량(적설 고려 없음)
    double[] hatsudenryou_peakcut_all_snow = new double[13]; // 피크 컷 있음 발전량(적설 고려 있음)

    if (sekisairitsu <= 3) {

      // 설치면 피크 컷 및 발전량 배열 선언
      double[][] hatsudenryou = new double[roofLength][12]; // 전체 설치면의 피크 컷 없음 발전량
      double[][] hatsudenryou_peakcut = new double[roofLength][12]; // 전체 설치면의 피크 컷 있음 발전량

      int simulationPointNumber = pwrGnrSimReq.getSimulationPointNumber();
      // 우기일 사량 관측 지점의 경우, 계산을 하지 않는다 , 이리 오모테 섬, 이시가키 섬, 오하라, 하 테루 간]
      if (simulationPointNumber != 361
          && simulationPointNumber != 362
          && simulationPointNumber != 363
          && simulationPointNumber != 364
          && simulationPointNumber != 365
          && simulationPointNumber != 827
          && simulationPointNumber != 828
          && simulationPointNumber != 829
          && simulationPointNumber != 830
          && simulationPointNumber != 831
          && simulationPointNumber != 832
          && simulationPointNumber != 833
          && simulationPointNumber != 834
          && simulationPointNumber != 835
          && simulationPointNumber != 836
          && simulationPointNumber != 837) {

        for (int i = 0; i < roofLength; i++) {
          // 설치면 발전량 계산
          if (!Double.isNaN(pwrGnrSimReq.getKoubai()[i])
              && !Double.isNaN(pwrGnrSimReq.getHoui()[i])) {
            // 설치면의 사면 일사량 계산
            double[] shamen_nissha =
                this.shamenNissharyou(
                    pwrGnrSimReq.getKoubai()[i], pwrGnrSimReq.getHoui()[i], simulationPointNumber);
            double[] peakcut =
                this.peakcut(
                    simulationPointNumber,
                    pwrGnrSimReq.getKoubai()[i],
                    pwrGnrSimReq.getHoui()[i],
                    sekisairitsu,
                    pwrGnrSimReq.getModuleAmp1(),
                    pwrGnrSimReq.getPcsAmp1(),
                    pcs_henkankouritsu);

            for (int j = 0; j < 12; j++) {

              // 피크 컷 없음 발전량 계산
              // shamen_nissha 조금 다름
              hatsudenryou[i][j] =
                  shamen_nissha[j]
                      * days[j]
                      * ondo_sonshitsu[j] // 0.931
                      * pcs_henkankouritsu // 0.97
                      * ((module1_watt_flash * pwrGnrSimReq.getModuleInput1()[i])
                          + (module2_watt_flash * pwrGnrSimReq.getModuleInput2()[i])
                          + (module3_watt_flash * pwrGnrSimReq.getModuleInput3()[i]))
                      / 1000
                      * module_system_loss // 0.99
                      * peakcut[0];

              // 피크 컷 있음 발전량 계산
              hatsudenryou_peakcut[i][j] =
                  shamen_nissha[j]
                      * days[j]
                      * ondo_sonshitsu[j]
                      * pcs_henkankouritsu
                      * ((module1_watt_flash * pwrGnrSimReq.getModuleInput1()[i])
                          + (module2_watt_flash * pwrGnrSimReq.getModuleInput2()[i])
                          + (module3_watt_flash * pwrGnrSimReq.getModuleInput3()[i]))
                      / 1000
                      * module_system_loss
                      * peakcut[1];
            }
          }
        }

        // 설치면 1~4의 합계 발전량
        for (int j = 0; j < 12; j++) {

          for (int i = 0; i < roofLength; i++) {
            hatsudenryou_all[j] += hatsudenryou[i][j]; // hatsudenryou[i][j]);
            hatsudenryou_peakcut_all[j] += hatsudenryou_peakcut[i][j];
          }

          hatsudenryou_all_snow[j] =
              (int)
                  Math.round(hatsudenryou_all[j] * (1 - (snow[(simulationPointNumber * 12) + j])));
          hatsudenryou_peakcut_all_snow[j] =
              (int)
                  Math.round(
                      hatsudenryou_peakcut_all[j] * (1 - (snow[(simulationPointNumber * 12) + j])));

          // 계산 후 반올림 처리
          hatsudenryou_all[j] = (int) Math.round(hatsudenryou_all[j]);
          hatsudenryou_peakcut_all[j] = (int) Math.round(hatsudenryou_peakcut_all[j]);
        }
      }

      // 마지막에 총합계 추가
      hatsudenryou_all[12] = Arrays.stream(hatsudenryou_all).sum();
      hatsudenryou_all_snow[12] = Arrays.stream(hatsudenryou_all_snow).sum();
      hatsudenryou_peakcut_all[12] = Arrays.stream(hatsudenryou_peakcut_all).sum();
      hatsudenryou_peakcut_all_snow[12] = Arrays.stream(hatsudenryou_peakcut_all_snow).sum();
    }

    // 결과 확인용
    System.err.println("hatsudenryou_all 결과 ::: " + Arrays.toString(hatsudenryou_all));
    //    System.err.println("hatsudenryou_all_snow 결과 ::: " +
    // Arrays.toString(hatsudenryou_all_snow));
    //    System.err.println(
    //        "hatsudenryou_peakcut_all 결과 ::: " + Arrays.toString(hatsudenryou_peakcut_all));
    //    System.err.println(
    //        "hatsudenryou_peakcut_all_snow 결과 ::: " +
    // Arrays.toString(hatsudenryou_peakcut_all_snow));

    PwrGnrSimResponse pwrGnrSimRes = new PwrGnrSimResponse();
    pwrGnrSimRes.setHatsudenryouAll(formatAndPrintArray(hatsudenryou_all));
    pwrGnrSimRes.setHatsudenryouAllSnow(formatAndPrintArray(hatsudenryou_all_snow));
    pwrGnrSimRes.setHatsudenryouPeakcutAll(formatAndPrintArray(hatsudenryou_peakcut_all));
    pwrGnrSimRes.setHatsudenryouPeakcutAllSnow(formatAndPrintArray(hatsudenryou_peakcut_all_snow));
    return pwrGnrSimRes;
  }

  // 배열 변환 및 포맷 처리 메서드
  private static String[] formatAndPrintArray(double[] doubleArray) {
    return Arrays.stream(doubleArray)
        .mapToInt(d -> (int) d) // double -> int 변환
        .mapToObj(i -> String.format("%,d", i))
        .toArray(String[]::new); // String[]로 변환
  }

  // 설치면의 사면 일사량 계산
  public static double[] peakcut(
      int simulationPointNumber,
      double koubai,
      double houi,
      double sekisairitsu,
      double moduleAmp1,
      double pcsAmp1,
      double pcsHenkankouritsu) {

    // koubai[rad]을 [도]로 변환 _아래 계산식도 변경된 값으로 계산됨.
    koubai = (koubai / (2 * Math.PI)) * 360;

    // 방위각 0도 설치각에서의 전력 피크 컷
    double wattPeakcutAboutDeg0 =
        computePowerPeakCutDeg0(simulationPointNumber, sekisairitsu, koubai);

    // 방위각 90도 설치각에서의 전력 피크 컷
    double watt_peakcut_about_deg90 =
        computePowerPeakCutDeg90(simulationPointNumber, sekisairitsu, koubai);

    double matrix_multiply1 = (1 * watt_peakcut_about_deg90) + (-1 * watt_peakcut_about_deg90);
    double matrix_multiply2 = (-1 * watt_peakcut_about_deg90) + (2 * watt_peakcut_about_deg90);

    // houi[rad]을 [도]로 변환
    houi = (houi / (2 * Math.PI)) * 360;

    // 電力ピークカット
    double watt_peakcut_result =
        ((matrix_multiply1 * (Math.cos(houi * Math.PI / 180) + 1) + matrix_multiply2) < 0.1)
            ? 0.1
            : (matrix_multiply1 * (Math.cos(houi * Math.PI / 180) + 1) + matrix_multiply2);

    ////////////
    // pcs1과 모듈 1의 전류차 구하기 용도
    boolean pcsAmpFound = true; // pcs_ampr 값은 항상있을거라 true

    // pcs1과 모듈 1의 전류차(pcs의 입력 전류 데이터가 없는 경우는 0)
    double amp_peakcut_result = 1; // 디폴트 : pcs1이 없거나 pcs1과모듈1dml wjsfbckrk 0 보다 작은경우
    if (pcsAmpFound) {

      // pcs1과 모듈 1의 전류차(pcs의 입력 전류 데이터가 없는 경우는 0)
      double amp_delta = moduleAmp1 - pcsAmp1;
      if (amp_delta >= 0) {

        double amp_peakcut_about_deg0 =
            computeAmpPeakCutDeg0(simulationPointNumber, koubai, amp_delta);

        double amp_peakcut_about_deg90 =
            computeAmpPeakCutDeg90(simulationPointNumber, koubai, amp_delta);

        double matrix_multiply3 = (1 * amp_peakcut_about_deg0) + (-1 * amp_peakcut_about_deg90);
        double matrix_multiply4 = (-1 * amp_peakcut_about_deg0) + (2 * amp_peakcut_about_deg90);

        // 전류 피크 컷
        amp_peakcut_result =
            1
                - (((matrix_multiply3 * (Math.cos(houi * Math.PI / 180) + 1) + matrix_multiply4)
                        < 0.001)
                    ? 0
                    : (matrix_multiply3 * (Math.cos(houi * Math.PI / 180) + 1) + matrix_multiply4));
      }
    }

    //////////////

    // 전력 피크 컷과 전류 피크 컷의 최소값 계산
    if (sekisairitsu >= 1.25) {
      sekisairitsu *= 100;
      int sekisairitsuCeil = (int) Math.ceil(sekisairitsu / 5.0) * 5;
      double pcsEfficiency =
          (pcsHenkankouritsu * 1000 <= 985) ? Math.ceil(pcsHenkankouritsu * 1000 / 5) * 5 : 985;

      int i = 1, j = 1;
      while (i < peakcut_by_pcs_data.length && peakcut_by_pcs_data[i][0] != sekisairitsuCeil) i++;
      while (j < peakcut_by_pcs_data[0].length && peakcut_by_pcs_data[0][j] != pcsEfficiency) j++;

      double peakcutByPcs = peakcut_by_pcs_data[i][j];
      double peakcutByPcsKijun = peakcut_by_pcs_data[i][10];
      double peakcutByPcsResult = peakcutByPcsKijun - peakcutByPcs;

      watt_peakcut_result =
          ((100 - wattPeakcutAboutDeg0 + peakcutByPcsResult) / 100 < 1)
              ? ((100 - wattPeakcutAboutDeg0 + peakcutByPcsResult) / 100)
              : 1.0;
    } else {
      watt_peakcut_result = 1.0;
    }

    // 피크컷이 없는 경우, 발전량에 곱하는 피크컷 계수
    double[] peakcutResult = new double[2];
    peakcutResult[0] = amp_peakcut_result;
    peakcutResult[1] = Math.min(watt_peakcut_result, amp_peakcut_result);

    return peakcutResult;
  }

  /** 0도 방위 전력 피크 컷을 계산하는 방법 */
  private static double computePowerPeakCutDeg0(
      int simulationPointNumber, double sekisairitsu, double koubai) {
    // 0度近似曲線係数
    double[] x = {1, 5, 15, 20, 30, 40};
    double[] y = new double[6];
    double x1, x2, y1, y2;
    int m1;
    double[] z1 = new double[4];

    // 적재율과 다조년 데이터(방위 0도)에서 y를 대입
    for (int i = 0; i < 6; i++) {
      int idx = i * 6;
      y[i] =
          tashounen_data_deg0[simulationPointNumber][idx] * Math.pow(sekisairitsu, 5)
              + tashounen_data_deg0[simulationPointNumber][idx + 1] * Math.pow(sekisairitsu, 4)
              + tashounen_data_deg0[simulationPointNumber][idx + 2] * Math.pow(sekisairitsu, 3)
              + tashounen_data_deg0[simulationPointNumber][idx + 3] * Math.pow(sekisairitsu, 2)
              + tashounen_data_deg0[simulationPointNumber][idx + 4] * sekisairitsu
              + tashounen_data_deg0[simulationPointNumber][idx + 5];
    }

    // 3차 최소 제곱법
    double[][] w = new double[4][5];
    double[][] A = new double[6][4];

    for (int i = 0; i < 6; i++) {
      A[i][2] = x[i];
      A[i][3] = 1.0;
      x1 = A[i][2];
      x2 = x1;
      for (int j = 1; j >= 0; j--) {
        x2 *= x1;
        A[i][j] = x2;
      }
    }

    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        w[i][j] = 0.0;
        for (int k = 0; k < 6; k++) {
          w[i][j] += A[k][i] * A[k][j];
        }
      }
    }

    for (int i = 0; i < 4; i++) {
      w[i][4] = 0.0;
      for (int j = 0; j < 6; j++) {
        w[i][4] += A[j][i] * y[j];
      }
    }

    for (int i = 0; i < 4; i++) {
      y1 = 0.0;
      m1 = i + 1;
      int m2Index = 0;
      for (int j = i; j < 4; j++) {
        y2 = Math.abs(w[j][i]);
        if (y1 < y2) {
          y1 = y2;
          m2Index = j;
        }
      }

      for (int j = i; j < 5; j++) {
        double temp = w[i][j];
        w[i][j] = w[m2Index][j];
        w[m2Index][j] = temp;
      }

      y1 = 1.0 / w[i][i];

      for (int j = m1; j < 5; j++) {
        w[i][j] *= y1;
      }

      for (int j = 0; j < 4; j++) {
        if (j != i) {
          for (int k = m1; k < 5; k++) {
            w[j][k] -= w[j][i] * w[i][k];
          }
        }
      }
    }

    // z1에 3차, 2차, 1차 계수와 절편을 대입
    for (int i = 0; i < 4; i++) {
      z1[i] = w[i][4];
    }

    // 방위각 90도 설치각에서의 전력 피크 컷
    return (z1[0] * Math.pow(koubai, 3)) + (z1[1] * Math.pow(koubai, 2)) + (z1[2] * koubai) + z1[3];
  }

  /** 전류 피크 컷(5차) 90도 근사 곡선 계수 */
  private static double computeAmpPeakCutDeg90(
      int simulationPointNumber, double koubai, double amp_delta) {

    // 초기 설정
    double[] x = {5, 15, 20, 30, 40, 50};
    double[] z2 = new double[6];
    double[] y = new double[6];

    // 데이터 초기화 - y 값 계산 (amp_delta의 다항식 계산)
    for (int i = 0; i < 6; i++) {
      int idx = i * 5;
      y[i] =
          amp_peakcut_data_deg90[simulationPointNumber][idx] * Math.pow(amp_delta, 4)
              + amp_peakcut_data_deg90[simulationPointNumber][idx + 1] * Math.pow(amp_delta, 3)
              + amp_peakcut_data_deg90[simulationPointNumber][idx + 2] * Math.pow(amp_delta, 2)
              + amp_peakcut_data_deg90[simulationPointNumber][idx + 3] * amp_delta
              + amp_peakcut_data_deg90[simulationPointNumber][idx + 4];
    }

    // 최소 제곱법을 위한 행렬 A와 w 초기화
    double[][] w = new double[6][7];
    double[][] A = new double[6][6];

    // A 행렬 설정 (x 값을 사용하여 다항식 계수 생성)
    for (int i1 = 0; i1 < 6; i1++) {
      A[i1][4] = x[i1];
      A[i1][5] = 1.0;
      double x1 = A[i1][4];
      double x2 = x1;
      for (int i2 = 3; i2 >= 0; i2--) {
        x2 *= x1;
        A[i1][i2] = x2;
      }
    }

    // w 행렬 초기화
    for (int i1 = 0; i1 < 6; i1++) {
      for (int i2 = 0; i2 < 6; i2++) {
        w[i1][i2] = 0.0;
        for (int i3 = 0; i3 < 6; i3++) {
          w[i1][i2] += A[i3][i1] * A[i3][i2];
        }
      }
    }

    // w의 마지막 열 값 설정
    for (int i1 = 0; i1 < 6; i1++) {
      w[i1][6] = 0.0;
      for (int i2 = 0; i2 < 6; i2++) {
        w[i1][6] += A[i2][i1] * y[i2];
      }
    }

    // 가우스 소거법을 통해 연립방정식 해 구하기
    for (int i1 = 0; i1 < 6; i1++) {
      double y1 = 0.0;
      int m1 = i1 + 1;
      int m2 = 0;

      for (int i2 = i1; i2 < 6; i2++) {
        double y2 = Math.abs(w[i2][i1]);
        if (y1 < y2) {
          y1 = y2;
          m2 = i2;
        }
      }

      for (int i2 = i1; i2 < 7; i2++) {
        y1 = w[i1][i2];
        w[i1][i2] = w[m2][i2];
        w[m2][i2] = y1;
      }

      y1 = 1.0 / w[i1][i1];
      for (int i2 = m1; i2 < 7; i2++) {
        w[i1][i2] *= y1;
      }

      for (int i2 = 0; i2 < 6; i2++) {
        if (i2 != i1) {
          for (int i3 = m1; i3 < 7; i3++) {
            w[i2][i3] -= w[i2][i1] * w[i1][i3];
          }
        }
      }
    }

    // z2에 계수 저장
    for (int i1 = 0; i1 < 6; i1++) {
      z2[i1] = w[i1][6];
    }

    return z2[0] * Math.pow(koubai, 5)
        + z2[1] * Math.pow(koubai, 4)
        + z2[2] * Math.pow(koubai, 3)
        + z2[3] * Math.pow(koubai, 2)
        + z2[4] * koubai
        + z2[5];
  }

  /** 전류 피크 컷(5차) 0도 근사 곡선 계수 */
  private static double computeAmpPeakCutDeg0(
      int simulationPointNumber, double koubai, double amp_delta) {
    double[] x = {5, 15, 20, 30, 40, 50};
    double[] z2 = new double[6];

    double[] y = new double[6];
    for (int i = 0; i < 6; i++) {
      int idx = i * 5;
      y[i] =
          amp_peakcut_data_deg0[simulationPointNumber][idx] * Math.pow(amp_delta, 4)
              + amp_peakcut_data_deg0[simulationPointNumber][idx + 1] * Math.pow(amp_delta, 3)
              + amp_peakcut_data_deg0[simulationPointNumber][idx + 2] * Math.pow(amp_delta, 2)
              + amp_peakcut_data_deg0[simulationPointNumber][idx + 3] * amp_delta
              + amp_peakcut_data_deg0[simulationPointNumber][idx + 4];
    }

    double[][] w = new double[6][7];
    double[][] A = new double[6][6];

    for (int i1 = 0; i1 < 6; i1++) {
      A[i1][4] = x[i1];
      A[i1][5] = 1.0;
      double x1 = A[i1][4];
      double x2 = x1;
      for (int i2 = 3; i2 >= 0; i2--) {
        x2 *= x1;
        A[i1][i2] = x2;
      }
    }

    for (int i1 = 0; i1 < 6; i1++) {
      for (int i2 = 0; i2 < 6; i2++) {
        w[i1][i2] = 0.0;
        for (int i3 = 0; i3 < 6; i3++) {
          w[i1][i2] += A[i3][i1] * A[i3][i2];
        }
      }
    }

    for (int i1 = 0; i1 < 6; i1++) {
      w[i1][6] = 0.0;
      for (int i2 = 0; i2 < 6; i2++) {
        w[i1][6] += A[i2][i1] * y[i2];
      }
    }

    for (int i1 = 0; i1 < 6; i1++) {
      double y1 = 0.0;
      int m1 = i1 + 1;
      int m2 = 0;
      for (int i2 = i1; i2 < 6; i2++) {
        double y2 = Math.abs(w[i2][i1]);
        if (y1 < y2) {
          y1 = y2;
          m2 = i2;
        }
      }

      for (int i2 = i1; i2 < 7; i2++) {
        y1 = w[i1][i2];
        w[i1][i2] = w[m2][i2];
        w[m2][i2] = y1;
      }

      y1 = 1.0 / w[i1][i1];
      for (int i2 = m1; i2 < 7; i2++) {
        w[i1][i2] *= y1;
      }

      for (int i2 = 0; i2 < 6; i2++) {
        if (i2 != i1) {
          for (int i3 = m1; i3 < 7; i3++) {
            w[i2][i3] -= w[i2][i1] * w[i1][i3];
          }
        }
      }
    }

    for (int i1 = 0; i1 < 6; i1++) {
      z2[i1] = w[i1][6];
    }

    return z2[0] * Math.pow(koubai, 5)
        + z2[1] * Math.pow(koubai, 4)
        + z2[2] * Math.pow(koubai, 3)
        + z2[3] * Math.pow(koubai, 2)
        + z2[4] * koubai
        + z2[5];
  }

  /** 0도 방위 전력 피크 컷을 계산하는 방법 */
  private static double computePowerPeakCutDeg90(
      int simulationPointNumber, double sekisairitsu, double koubai) {
    // 데이터 초기화
    double[] x = {1, 5, 15, 20, 30, 40};

    // y 배열 초기화 및 값 할당
    double[] y = new double[6];
    for (int i = 0; i < 6; i++) {
      int idx = i * 6;
      y[i] =
          tashounen_data_deg90[simulationPointNumber][idx] * Math.pow(sekisairitsu, 5)
              + tashounen_data_deg90[simulationPointNumber][idx + 1] * Math.pow(sekisairitsu, 4)
              + tashounen_data_deg90[simulationPointNumber][idx + 2] * Math.pow(sekisairitsu, 3)
              + tashounen_data_deg90[simulationPointNumber][idx + 3] * Math.pow(sekisairitsu, 2)
              + tashounen_data_deg90[simulationPointNumber][idx + 4] * sekisairitsu
              + tashounen_data_deg90[simulationPointNumber][idx + 5];
    }

    // w 행렬 초기화
    double[][] w = new double[4][5];

    // A 행렬 초기화
    double[][] A = new double[6][4];
    for (int i = 0; i < 6; i++) {
      A[i][2] = x[i];
      A[i][3] = 1.0;
      double x1 = A[i][2];
      double x2 = x1;
      for (int j = 1; j >= 0; j--) {
        x2 *= x1;
        A[i][j] = x2;
      }
    }

    // w[i1][i2] 계산
    for (int i1 = 0; i1 < 4; i1++) {
      for (int i2 = 0; i2 < 4; i2++) {
        w[i1][i2] = 0.0;
        for (int i3 = 0; i3 < 6; i3++) {
          w[i1][i2] += A[i3][i1] * A[i3][i2];
        }
      }
    }

    // w[i1][4] 계산
    for (int i1 = 0; i1 < 4; i1++) {
      w[i1][4] = 0.0;
      for (int i2 = 0; i2 < 6; i2++) {
        w[i1][4] += A[i2][i1] * y[i2];
      }
    }

    // 가우스 소거법 적용
    for (int i1 = 0; i1 < 4; i1++) {
      double y1 = 0.0;
      int m1 = i1 + 1;
      int m2 = 0;
      for (int i2 = i1; i2 < 4; i2++) {
        double y2 = Math.abs(w[i2][i1]);
        if (y1 < y2) {
          y1 = y2;
          m2 = i2;
        }
      }

      for (int i2 = i1; i2 < 5; i2++) {
        y1 = w[i1][i2];
        w[i1][i2] = w[m2][i2];
        w[m2][i2] = y1;
      }

      y1 = 1.0 / w[i1][i1];
      for (int i2 = m1; i2 < 5; i2++) {
        w[i1][i2] *= y1;
      }

      for (int i2 = 0; i2 < 4; i2++) {
        if (i2 != i1) {
          for (int i3 = m1; i3 < 5; i3++) {
            w[i2][i3] -= w[i2][i1] * w[i1][i3];
          }
        }
      }
    }

    // 결과 z1 배열에 저장
    double[] z1 = new double[4];
    for (int i1 = 0; i1 < 4; i1++) {
      z1[i1] = w[i1][4];
    }

    // 출력 결과 확인
    //		System.out.println("Fitted coefficients:");
    //		for (double coef : z1) {
    //			System.out.println(coef);
    //		}

    return (z1[0] * Math.pow(koubai, 3)) + (z1[1] * Math.pow(koubai, 2)) + (z1[2] * koubai) + z1[3];
  }

  // 설치면의 사면 일사량 계산
  public double[] shamenNissharyou(double koubai, double houi, int simulationPointNumber) {

    double[] shamenNissha = new double[12]; // 경사면 일사량 대입을 위한 빈 배열 변수 생성

    int i = simulationPointNumber * 12; // 일사량 관측 지점의 행 번호로부터 데이터베이스의 일치하는 행을 산출
    // (例:波照間は836行目→データベースでは836×12=10032行目から10044行目までが1月～12月)

    if (koubai == 0) { // 경사가 0이면 경사 일사량 = 수평면 일사
      for (int m = 0; m < 12; m++) {
        shamenNissha[m] = suiheimennissha[i];
        i++;
      }
    } else { // 기울기가 0이 아니면 경사 일사량 계산

      // 경사면 일사량 (직접, 산란, 지면 반사)의 도출을위한 빈 배열 변수 생성
      double[][] cosTheta = new double[12][26];
      double[][] rb = new double[12][26];
      double[][] io = new double[12][26];
      double[][] I = new double[12][26];
      double[][] id = new double[12][26];
      double[][] Isby = new double[12][26];
      double[][] Ibby = new double[12][26];
      double[][] Irby = new double[12][26];

      // 시간
      double[] timeParam = {
        100, 100, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5,
        15.5, 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5
      };

      double IsbyMath = 0;
      double[] IsbyTotal = new double[12];

      double IbbyMath = 0;
      double[] IbbyTotal = new double[12];

      double IrbyMath = 0;
      double[] IrbyTotal = new double[12];

      i = simulationPointNumber * 12; // Reset i
      //			System.err.println(  " i >>>>>>>>> " + i);

      // 매월 각 시간의 각 파라미터 계산
      for (int m = 0; m < 12; m++) {
        //				System.err.println(  "  " );
        //				System.err.println(  " m >>>>>>>>> " + m);
        for (int j = 0; j < 26; j++) {
          //					System.err.println(  " i >>>>>>>>> " + i);
          //					System.err.println(  " j >>>>>>>>> " + j);
          //					System.err.println(  " latlng[i] >>>>>>>>> " + latlng[i]);
          //					System.err.println(  " latlng[i][0] >>>>>>>>> " + latlng[i][0]);
          //					System.err.println(  " taiyousekii[i] >>>>>>>>> " + taiyousekii[i]);
          //					System.err.println(  " jikakudo[i][j] >>>>>>>>> " + jikakudo[i][j]);
          //					System.err.println(  " koubai>>>>>>>>> " + koubai);
          //					System.err.println(  " houi>>>>>>>>> " + houi);
          //					System.err.println(  " 결과>>>>>>>>> " + Math.max(0, (Math.sin(latlng[i][0]) *
          // Math.cos(koubai) - Math.cos(latlng[i][0]) * Math.sin(koubai) * Math.cos(houi)) *
          // Math.sin(taiyousekii[i])
          //							+ (Math.cos(latlng[i][0]) * Math.cos(koubai) + Math.sin(latlng[i][0]) *
          // Math.sin(koubai) * Math.cos(houi)) * Math.cos(taiyousekii[i]) *
          // Math.cos(jikakudo[i][j])
          //							+ Math.cos(taiyousekii[i]) * Math.sin(koubai) * Math.sin(houi) *
          // Math.sin(jikakudo[i][j])));
          //					System.err.println(  "  " );

          cosTheta[m][j] =
              Math.max(
                  0,
                  (Math.sin(latlng[i][0]) * Math.cos(koubai)
                              - Math.cos(latlng[i][0]) * Math.sin(koubai) * Math.cos(houi))
                          * Math.sin(taiyousekii[i])
                      + (Math.cos(latlng[i][0]) * Math.cos(koubai)
                              + Math.sin(latlng[i][0]) * Math.sin(koubai) * Math.cos(houi))
                          * Math.cos(taiyousekii[i])
                          * Math.cos(jikakudo[i][j])
                      + Math.cos(taiyousekii[i])
                          * Math.sin(koubai)
                          * Math.sin(houi)
                          * Math.sin(jikakudo[i][j]));

          rb[m][j] = (cosThetaZ[i][j] == 0) ? 0 : cosTheta[m][j] / cosThetaZ[i][j];
          io[m][j] = ion[i] * cosThetaZ[i][j];
          I[m][j] = suiheimennissha[i] * rtx[i][j];
          id[m][j] = Math.min(sanrannissha[i] * rdx[i][j], I[m][j]);

          Isby[m][j] =
              Math.max(
                  0,
                  (io[m][j] == 0)
                      ? 0
                      : id[m][j]
                          * (((I[m][j] - id[m][j]) / io[m][j]) * rb[m][j]
                              + (1 - (I[m][j] - id[m][j]) / io[m][j])
                                  * (1 + Math.cos(koubai))
                                  / 2));
          Ibby[m][j] = Math.max(0, (I[m][j] - id[m][j]) * rb[m][j]);
          Irby[m][j] = I[m][j] * jimenhansharitsu[i] * ((1 - Math.cos(koubai)) / 2);
        }
        i++;
      }

      // Reset i
      i = simulationPointNumber * 12;

      // 직접, 산란 계산
      for (int m = 0; m < 12; m++) {
        for (int j = 0; j < 26; j++) {
          if (timeParam[j] > (Math.ceil(hinode_nichibotsu[i][0] * 2.0) / 2)
              && timeParam[j] < (Math.floor(hinode_nichibotsu[i][1] * 2.0) / 2)) {
            IsbyMath += Isby[m][j];
            IbbyMath += Ibby[m][j];
          }
        }

        IsbyMath +=
            ((((Math.ceil(hinode_nichibotsu[i][0]) - 0.5) < hinode_nichibotsu[i][0])
                    ? 0
                    : Isby[m][0])
                + (((Math.floor(hinode_nichibotsu[i][1]) + 0.5) > hinode_nichibotsu[i][1])
                    ? 0
                    : Isby[m][1]));
        IsbyTotal[m] = IsbyMath;

        IbbyMath +=
            (((Math.ceil(hinode_nichibotsu[i][0]) - 0.5) < hinode_nichibotsu[i][0])
                    ? 0
                    : Ibby[m][0])
                + (((Math.floor(hinode_nichibotsu[i][1]) + 0.5) > hinode_nichibotsu[i][1])
                    ? 0
                    : Ibby[m][1]);
        IbbyTotal[m] = IbbyMath;

        IsbyMath = 0;
        IbbyMath = 0;
        i++;
      }

      // Reset i
      i = simulationPointNumber * 12;

      // 지면 반사 계산
      for (int m = 0; m < 12; m++) {
        for (int j = 0; j < 26; j++) {
          // 배열 범위에 데이터가 없어서 추가했음.
          if ((j + 2 < rd[i].length)
              && (rd[i][j + 2]
                  > ((jikakudo[i][0] >= 0)
                      ? Math.ceil(jikakudo[i][0] * 2.0) / 2
                      : Math.floor(jikakudo[i][0] * 2.0) / 2))
              && (rd[i][j + 2]
                  < ((jikakudo[i][1] >= 0)
                      ? Math.floor(jikakudo[i][1] * 2.0) / 2
                      : Math.ceil(jikakudo[i][1] * 2.0) / 2))) {
            IrbyMath += Irby[m][j + 2];
          }
        }

        IrbyMath +=
            (((jikakudo[i][0] >= 0)
                            ? Math.ceil(jikakudo[i][0]) - 0.5
                            : Math.floor(jikakudo[i][0]) - 0.5)
                        < jikakudo[i][0]
                    ? 0
                    : Irby[m][0])
                + (((jikakudo[i][1] >= 0)
                            ? Math.floor(jikakudo[i][1]) + 0.5
                            : Math.ceil(jikakudo[i][1]) + 0.5)
                        > jikakudo[i][1]
                    ? 0
                    : Irby[m][1]);
        IrbyTotal[m] = IrbyMath;

        IrbyMath = 0;
        i++;
      }

      log.error(Arrays.toString(IsbyTotal));
      // 직달, 산란, 지면 반사를 매월 합계
      for (int m = 0; m < 12; m++) {
        shamenNissha[m] = IsbyTotal[m] + IbbyTotal[m] + IrbyTotal[m];
      }
    }

    return shamenNissha;
  }

  public static double[] readValuesFromFile(String fileName) {
    List<Double> values = new ArrayList<>();

    try (InputStream inputStream =
            PwrGnrSimService.class.getClassLoader().getResourceAsStream(fileName);
        BufferedReader br =
            new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
      String line;
      while ((line = br.readLine()) != null) {
        // 줄을 , 기준으로 분리하여 배열에 추가
        String[] parts = line.split(",");
        for (String part : parts) {
          values.add(Double.parseDouble(part.trim()));
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

    // List를 배열로 변환하여 반환
    return values.stream().mapToDouble(Double::doubleValue).toArray();
  }

  public static String[][] readValuesFromFileString2(String fileName) {

    List<String[]> values = new ArrayList<>();
    StringBuilder sb = new StringBuilder();

    try (InputStream inputStream =
            PwrGnrSimService.class.getClassLoader().getResourceAsStream(fileName);
        BufferedReader br =
            new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {

      String line;
      while ((line = br.readLine()) != null) {
        sb.append(line);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

    String content = sb.toString();
    content = content.substring(1, content.length() - 1); // 시작과 끝의 중괄호 제거
    String[] arrays = content.split("\\}\\s*,\\s*\\{");

    for (String innerArray : arrays) {
      innerArray = innerArray.replaceAll("[{}]", "").trim();
      String[] elements = innerArray.split(",");
      String[] row = new String[elements.length];

      for (int i = 0; i < elements.length; i++) {
        String trimmedValue = elements[i].trim();
        trimmedValue = trimmedValue.replace("\"", "");
        row[i] = trimmedValue.isEmpty() ? "" : trimmedValue;
      }
      values.add(row);
    }

    return values.toArray(new String[0][]);
  }

  public static double[][] readValuesFromFile2(String fileName) {
    List<double[]> values = new ArrayList<>();

    StringBuilder sb = new StringBuilder();

    try (InputStream inputStream =
            PwrGnrSimService.class.getClassLoader().getResourceAsStream(fileName);
        BufferedReader br =
            new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
      String line;
      while ((line = br.readLine()) != null) {
        sb.append(line);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

    String content = sb.toString();
    content = content.substring(1, content.length() - 1); // 시작과 끝의 중괄호 제거
    String[] arrays = content.split("\\},\\{");

    for (String innerArray : arrays) {

      innerArray = innerArray.replaceAll("[{}]", "").trim();

      String[] elements = innerArray.split(","); // 각 요소를 쉼표로 분리
      double[] row = new double[elements.length];

      for (int i = 0; i < elements.length; i++) {
        String trimmedValue = elements[i].trim();
        if (trimmedValue.isEmpty()) {
          row[i] = 0.0; // 빈 문자열인 경우 기본값 설정
        } else {
          row[i] = Double.parseDouble(trimmedValue);
        }
      }
      values.add(row);
    }

    // List<double[]>을 double[][]로 변환하여 반환
    return values.toArray(new double[0][]);
  }

  public PwrGnrSimGuideResponse selectPwrGnrSimulationGuideInfo() throws Exception {
    PwrGnrSimGuideResponse guideResponse = new PwrGnrSimGuideResponse();

    String strResponse =
        interfaceQsp.callApi(HttpMethod.GET, qspUrl + qspSimulationGuideInfUrl, null);

    if (!"".equals(strResponse)) {
      ObjectMapper om =
          new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      guideResponse = om.readValue(strResponse, PwrGnrSimGuideResponse.class);
    }

    return guideResponse;
  }

  public static Document pwrGnrSimPdfHtml(Document doc, PwrGnrSimResponse data)
      throws IOException, QcastException {

    String[] pwrGnrSimList = new String[13];
    if ("A".equals(data.getPwrGnrSimType())) {
      pwrGnrSimList = data.getHatsudenryouAll();
    } else if ("B".equals(data.getPwrGnrSimType())) {
      pwrGnrSimList = data.getHatsudenryouAllSnow();
    } else if ("C".equals(data.getPwrGnrSimType())) {
      pwrGnrSimList = data.getHatsudenryouPeakcutAll();
    } else if ("D".equals(data.getPwrGnrSimType())) {
      pwrGnrSimList = data.getHatsudenryouPeakcutAllSnow();
    }

    Element elm;

    // 상단 요약정보
    elm = doc.getElementById("objectNo");
    elm.text(
        StringUtils.defaultString(data.getObjectNo() + " (Plan No : " + data.getPlanNo() + ")"));

    elm = doc.getElementById("drawingEstimateCreateDate");
    elm.text(StringUtils.defaultString(data.getDrawingEstimateCreateDate()));

    elm = doc.getElementById("prefName");

    elm.text(StringUtils.defaultString(data.getPrefName()));

    elm = doc.getElementById("areaName");
    elm.text(StringUtils.defaultString(data.getAreaName()));

    elm = doc.getElementById("capacity");
    elm.text(StringUtils.defaultString(data.getCapacity()));

    elm = doc.getElementById("anlFrcsGnrt");
    elm.text(StringUtils.defaultString(String.valueOf(pwrGnrSimList[12])));

    elm = doc.getElementById("snowfall");
    elm.text(StringUtils.defaultString(data.getSnowfall()));

    elm = doc.getElementById("standardWindSpeedId");
    elm.text(StringUtils.defaultString(data.getStandardWindSpeedId()));

    boolean isUnchanged = Arrays.stream(pwrGnrSimList).allMatch(value -> value == "00");
    if (!isUnchanged) { // 변경된값 확인

      int[] onlyData =
          Arrays.stream(Arrays.copyOfRange(pwrGnrSimList, 0, pwrGnrSimList.length - 1))
              .mapToInt(Integer::parseInt)
              .toArray();

      int referenceValue = 300; // table 높이
      int orgMaxValue =
          Arrays.stream(onlyData)
              .max()
              .orElseThrow(() -> new QcastException(ErrorCode.INTERNAL_SERVER_ERROR, "배열이 없음"));
      int maxValue = roundUpAuto(orgMaxValue / 8);

      // chart y축
      StringBuilder sb = new StringBuilder();
      for (int i = 8; i >= 1; i--) {
        sb.append("<tr>");
        sb.append("<td ")
            .append(i == 8 ? "class='top'" : "")
            .append(">")
            .append(maxValue * i)
            .append("</td>");
        sb.append("</tr>");
      }
      sb.append("<tr>");
      sb.append("<td class='zero'>0</td>");
      elm = doc.getElementById("htmlY");
      elm.append(sb.toString());

      // chart 데이터
      String[] color = {
        "#B5D4F5", "#FFE899", "#FBC3AB", "#D1D1D1", "#FFE899", "#B5D0F0", "#C1E4B8", "#A3C9EE",
        "#F7BBA2", "#BEBEBE", "#A8BBE0", "#B5D7A3"
      };
      StringBuilder sb2 = new StringBuilder();
      for (int i = 0; i < onlyData.length; i++) {
        double scaledValues = ((double) onlyData[i] / (maxValue * 8)) * referenceValue;

        sb2.append("<td class='bar-cell'>");

        sb2.append("<table class='chart-line' style='border-collapse: collapse;'>")
            .append("<tbody>")
            .append("<tr>")
            .append("<td class='top' style='border-bottom: 1px solid #ddd;'></td>")
            .append("</tr>")
            .append("<tr>")
            .append("<td style='border-bottom: 1px solid #ddd;'></td>")
            .append("</tr>")
            .append("<tr>")
            .append("<td style='border-bottom: 1px solid #ddd;'></td>")
            .append("</tr>")
            .append("<tr>")
            .append("<td style='border-bottom: 1px solid #ddd;'></td>")
            .append("</tr>")
            .append("<tr>")
            .append("<td style='border-bottom: 1px solid #ddd;'></td>")
            .append("</tr>")
            .append("<tr>")
            .append("<td style='border-bottom: 1px solid #ddd;'></td>")
            .append("</tr>")
            .append("<tr>")
            .append("<td style='border-bottom: 1px solid #ddd;'></td>")
            .append("</tr>")
            .append("<tr>")
            .append("<td style='border-bottom: 1px solid #ddd;'></td>")
            .append("</tr>")
            .append("<tr>")
            .append("<td style='border-bottom: 1px solid #ddd;'></td>")
            .append("</tr>")
            .append("</tbody>")
            .append("</table>");

        sb2.append(
            "<div class='bar' style='height: "
                + scaledValues
                + "px; background-color: "
                + color[i]
                + "'></div>");
        sb2.append("</td>");
      }
      elm = doc.getElementById("htmlX");
      elm.append(sb2.toString());
    }

    // 예측발전량
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 13; i++) {
      sb.append("<td>" + StringUtils.defaultString(pwrGnrSimList[i]) + "</td>");
    }

    elm = doc.getElementById("frcPwrGnrList_detail");
    elm.append(sb.toString());

    // 모듈 list
    if (data.getRoofModuleList() != null && data.getRoofModuleList().size() > 0) {
      sb = new StringBuilder();

      for (int i = 0; i < data.getRoofModuleList().size(); i++) {
        PwrGnrSimRoofResponse listItem = data.getRoofModuleList().get(i);
        sb.append("<tr>");
        sb.append("<td>" + StringUtils.defaultString(listItem.getRoofSurface()) + "</td>");
        sb.append(
            "<td>"
                + StringUtils.defaultString(listItem.getSlopeAngle())
                + (listItem.getClassType() == 0 ? "寸" : "º")
                + "</td>");
        sb.append("<td>" + StringUtils.defaultString(listItem.getAzimuth()) + "</td>");
        sb.append("<td>" + StringUtils.defaultString(listItem.getItemNo()) + "</td>");
        sb.append("<td>" + StringUtils.defaultString(listItem.getAmount()) + "</td>");
        sb.append("</tr>");
      }

      elm = doc.getElementById("roofModuleList_detail");
      elm.append(sb.toString());
    }

    // pcs list
    if (data.getPcsList() != null && data.getPcsList().size() > 0) {
      sb = new StringBuilder();

      for (int i = 0; i < data.getPcsList().size(); i++) {
        PwrGnrSimRoofResponse listItem = data.getPcsList().get(i);

        sb.append("<tr>");
        sb.append("<td>" + StringUtils.defaultString(listItem.getItemNo()) + "</td>");
        sb.append("<td>" + StringUtils.defaultString(listItem.getAmount()) + "</td>");
        sb.append("</tr>");
      }

      elm = doc.getElementById("pcsList_detail");
      elm.append(sb.toString());
    }

    return doc;
  }

  public static int roundUpAuto(double value) {
    int length = (int) Math.log10(value) + 1; // 숫자의 자리수를 계산
    int place = (int) Math.pow(10, length - 1); // 자리수에 따른 올림 기준(10, 100, 1000 등)

    return (int) Math.ceil(value / place) * place;
  }
}
