package com.njcn.product.carrycapacity.util;

import com.njcn.common.pojo.exception.BusinessException;
import org.apache.commons.math3.linear.DecompositionSolver;
import org.apache.commons.math3.linear.LUDecomposition;
import org.apache.commons.math3.linear.MatrixUtils;
import org.apache.commons.math3.linear.RealMatrix;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

public class CarryCapacityUtil {

    private static final String DATA_CSV = "C:\\njcn\\pqs\\pqs-advance\\advance-boot\\src\\main\\resources\\test.csv";
    private static final int MAX_PRO_DATA_NUM = 5000;
    private static final int MAX_DATA_COL_NUM = 9;
    private static double[][] arr = new double[MAX_PRO_DATA_NUM][MAX_DATA_COL_NUM];

    public static void main(String[] args) {
        double[] data_u = new double[MAX_PRO_DATA_NUM];
        double[] data_p = new double[MAX_PRO_DATA_NUM];
        double[] data_q = new double[MAX_PRO_DATA_NUM];

        int data_num = parseCSV(DATA_CSV, data_u, data_p, data_q);
        System.out.println("data_num: " + data_num);

        double[] res = new double[3];
//        cznlpgDataTrain(data_u, data_p, data_q, data_num, res);
        System.out.println("C = " + res[0] + " a = " + res[1] + " b = " + res[2]);
    }

    private static int parseCSV(String path, double[] data_u, double[] data_p, double[] data_q) {
        int line = 0;
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String lines;
            while ((lines = br.readLine()) != null) {
                String[] tokens = lines.split(",");
                for (int i = 0; i < tokens.length; i++) {
                    arr[line][i] = Double.parseDouble(tokens[i]);
                }

                System.out.println("line " + line + ": ");
                for (int i = 0; i < tokens.length; i++) {
                    System.out.println("arr[" + line + "][" + i + "]=" + arr[line][i]);
                }

                data_u[line] = arr[line][0];
                data_p[line] = arr[line][1];
                data_q[line] = arr[line][2];

                line++;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return line;
    }
    /*
    * 模型训练
    * */
    public static void cznlpgDataTrain(List<Double> u, List<Double> p, List<Double> q, int num, Double[] outRes) {
        if (num > MAX_PRO_DATA_NUM) {
            return;
        }

        RealMatrix matPQ = MatrixUtils.createRealMatrix(num, 3);
        RealMatrix matU = MatrixUtils.createRealMatrix(num, 1);
        RealMatrix matW = MatrixUtils.createRealMatrix(3, 1);

        // Matrix assignment
        for (int i = 0; i < num; i++) {
            matPQ.setEntry(i, 0, 1);
            matPQ.setEntry(i, 1, p.get(i));
            matPQ.setEntry(i, 2, q.get(i));

            matU.setEntry(i, 0, u.get(i));
        }

//        System.out.println("matPQ=");
        printMatrix(matPQ);
//        System.out.println("matPQ transpose=");
        printMatrix(matPQ.transpose());

        // w = inv(PQ1'*PQ1)*PQ1'*U
        // U = 224.5133 - 2.3041e-5 * P - 1.1900e-4 * Q
        RealMatrix matPQT = matPQ.transpose();
        RealMatrix matInverse = inverseMatrix(matPQT.multiply(matPQ));
        matW = matInverse.multiply(matPQT).multiply(matU);

        outRes[0] = matW.getEntry(0, 0);
        outRes[1] = matW.getEntry(1, 0);
        outRes[2] = matW.getEntry(2, 0);
    }

    private static void printMatrix(RealMatrix matrix) {
        System.out.println(matrix);
    }


    //矩阵求逆

    public static RealMatrix inverseMatrix(RealMatrix matrix) {
        try {
            LUDecomposition LUDe = new LUDecomposition(matrix);
            DecompositionSolver solver = LUDe.getSolver();
            RealMatrix result = solver.getInverse();
            return result;
        }catch (Exception e){
            System.out.println("数据存在问题无法进行矩阵求逆");
            throw new BusinessException("数据存在问题无法进行矩阵求逆");
        }


    }

    /**
     * @Description: 负载率约束指标计算P_βmin和Q_βmin分别为近一周的配变每日9时~15时段的负载率数据中概率95%小值所对应时刻的有功功率和无功功率值；
     * S_T为配变额定容量；S_pv为拟接入光伏容量；k为修正系数 ，取值可参照如下。
     * 台区日照条件	k
     * 光照强度大于1250kWh/m^2	0.8~0.9
     * 光照强度小于1250kWh/m^2	0.75~0.8
     * 海南 0.8
     * @Param:
     * @return: double Loadrate
     * @Author: clam
     * @Date: 2024/1/26
     */
    public static double calculateB(double P_βmin, double Q_βmin, double k, double S_T, double S_pv, double P_pv) {
        double term1 = Math.pow(P_βmin - k * S_T, 2);
        double term2 = Math.pow(Q_βmin, 2);
        double numerator = Math.sqrt(term1 + term2);
        if (P_βmin > P_pv) {
            return numerator / S_pv;
        } else {
            return -numerator / S_pv;
        }

    }
    /** 
    * @Description: calculatePF_T  功率因数指标计算
    * @Param:  
    * @return: double 
    * @Author: clam
    * @Date: 2024/2/20 
    */ 
    public static double calculatePF_T(double P_βmin, double Q_βmin, double k,  double S_pv) {
        double term1 = Math.pow(P_βmin - k * S_pv, 2);
        double term2 = Math.pow(Q_βmin, 2);
        double v = P_βmin - k * S_pv;
        double numerator = Math.sqrt(term1 + term2);

        return v/numerator;

    }

    /** 
    * @Description: 总结：
     * 	 p_min和 q_min能够根据测点数据获取得到；
     * 	 S_pv为拟接入光伏容量，此部分需要现场选取好台区后获取。
     * 	 k为修正系数，徐工提供海南k系数，是否需要考虑不同季节台区日照系数。
     * 	C、a、b需要用模型计算，是此算法中难点。
     * 结论：【拟接入光伏容量】为入参；【A/B/C相有功功率】和【A/B/C相无功功率值】95%小值从A/B/C相历史数据中计算得出； 为枚举参数；能够计算三相配变首端电压 、 、 ，从而得出U 。
     * 380v -U=C-a(p_min -k*S_pv/3)-b*q_min
     * 220v -U=C-a(p_min -k*S_pv)-b*q_min
     *（后续咨询只分单项三项，目前数据都是三项）
    * @Param:  
    * @return: double
    * @Author: clam
    * @Date: 2024/2/2 
    */ 
    public static double calculateU(double C, double a, double b, double p_min, double K, double q_min,double S_pv, double voltage) {

//        if (voltage == 220) {
//            return C-a*(p_min-K*S_pv)-b*q_min;
//        } else if (voltage == 380) {
            return C-a*(p_min-K*S_pv/3)-b*q_min;
//        } else {
//            return 0;
//        }

    }

    /**
     * I_(stock,h)为台区一周内的h次谐波电流95%概率大值，I_"inv" ^h%为光伏逆变器第h次的典型谐波电流含有率；
     * S_pv为拟接入光伏容量，此部分需要现场选取好台区后获取。
     * k为修正系数，徐工提供海南k系数，是否需要考虑不同季节台区日照系数。
     * 结论：【电压等级】为入参；I_(stock,h)为台区一周内的h次谐波电流95%概率大值，I_"inv" ^h%为光伏逆变器第h次的典型谐波电流含有率，
     * 为枚举参数；k为枚举参数；能够计算各次的谐波电流幅值 、 、 ，从而得出 。
     * （后续咨询只分单项三项，目前数据都是三项）
     */
    public static double calculateITm(double I_cp95, double k, double voltage, double S_pv, double K, double I_inv) {
        double term1 = Math.pow(I_cp95, 2);
        double term2 = 0, term3 = 0;
//        if (voltage == 220) {
//            term2 = Math.pow(k * S_pv * I_inv / 220, 2);
//            term3 = K * I_cp95 * (k * S_pv * I_inv / 220);
//        } else if (voltage == 380) {
            term2 = Math.pow(k * S_pv * I_inv /(3 * 220) , 2);
            term3 = K * I_cp95 * (k * S_pv * I_inv / (3 * 220));
//        } else {
//            return 0;
//        }

        double sumOfTerms = term1 + term2 + term3;

        return Math.sqrt(sumOfTerms);
    }

    /**
     * @Description: evaluateVoltageLevel 根据规则评估配变首端电压等级
     * @Param:
     * @return: int
     * @Author: clam
     * @Date: 2024/1/30
     */
    public static int evaluateVoltageLevel(double voltage) {
        if (voltage <= 235.4) {
            return 1; // 安全
        } else if (voltage > 235.4 && voltage <= 253.0) {
            return 2; // Ⅲ级预警
        } else if (voltage > 253.0 && voltage < 260.0) {
            return 3; // Ⅱ级预警
        } else {
            return 4; // Ⅰ级预警
        }
    }

    /**
     * @Description: evaluatePowerFactorLevel // 根据规则评估功率因数等级
     * @Param:
     * @return: int
     * @Author: clam
     * @Date: 2024/1/30
     */
    public static int evaluatePowerFactorLevel(double powerFactor) {
        if (powerFactor >= 0.9) {
            return 1; // 安全
        } else if (powerFactor >= 0.85 && powerFactor < 0.9) {
            return 2; // Ⅲ级预警
        } else if (powerFactor >= 0.8 && powerFactor < 0.85) {
            return 3; // Ⅱ级预警
        } else {
            return 4; // Ⅰ级预警
        }
    }

    /**
     * @Description: / 根据规则评估等效负载率等级
     * @Param:
     * @return: int
     * @Author: clam
     * @Date: 2024/1/30
     */
    public static int evaluateEquivalentLoadRateLevel(double equivalentLoadRate) {
        if (equivalentLoadRate >= 0.0) {
            return 1; // 安全
        } else if (equivalentLoadRate >= -40.0 && equivalentLoadRate < 0.0) {
            return 2; // Ⅲ级预警
        } else if (equivalentLoadRate >= -80.0 && equivalentLoadRate < -40.0) {
            return 3; // Ⅱ级预警
        } else {
            return 4; // Ⅰ级预警
        }
    }

    /**
     * @Description: 判断O:各项指标是否均为“安全” 安全接入
     * 判断2: 至多2项指标达到“III级预警”，其余指标均为“安全” 3接入预警
     * @: 超过2项指标达到“III级预警”且无“II级预警”及以上的指标:或至多1项指标达到“I 级预警且其余指标均为“安全” 2接入预警
     * 判断@: 至多2项指标达到“II 级预警”且其余指标均为“安全”: 或至多1项指标达到“II级预警”且其余指标存在“III级预警” 1级接入预警
     * 否则 限制接入
     * @Param:
     * @return:
     * @Author: clam
     * @Date: 2024/1/30
     */
    public static int evaluateG(List<Integer> indicators) {
        long count1 = indicators.stream().filter(i -> i == 1).count();
        long count2 = indicators.stream().filter(i -> i == 2).count();
        long count3 = indicators.stream().filter(i -> i == 3).count();
        if (count1 == 4) {
            return 1;
        } else if (count2 <= 2 && count2 + count1 == 4) {
            return 2;
        } else if ((count2 >= 2 && count2 + count1 == 4) || (count3 == 1 && count1 == 3)) {
            return 3;
        } else if ((count3 <= 2 && count1 + count3 == 4) || (count3 == 1 && count2 >= 1 && count1 + count2 == 3)) {
            return 4;
        } else {
            return 5;
        }
    }

    /**
     * 计算一组数据的最大95概率值，最小95概率值 入参一组double集合，一个flag表示计算类型  返回double
     *
     * @param data
     * @param type 0 最大95概率值 1 最小95概率值
     * @return
     */
    public static double calculatePercentile(List<Double> data, Integer type) {
        // 对数组进行排序
        // 正序排序
        Collections.sort(data);
        int index =0;
        if (type == 0) {
            // 计算最大95%概率值的索引
             index =(int) Math.ceil(0.95 * data.size()) ;
        } else if (type == 1) {
            // 计算最小95%概率值的索引
            index = (int) Math.ceil(0.05 * data.size()) - 1;
        }

        // 根据计算类型返回相应的值
        return data.get(index);
    }


}

