package com.aps.service.mp;

import com.google.ortools.Loader;
import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPObjective;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;
import java.util.ArrayList;
import java.util.List;

/**
 * 多工序多设备 日级生产计划 MIP 模型
 * 核心思路：用 WIP 在制品库存衔接工序先后，每台设备有日产能约束
 *
 * P1: 工序1(M1) → WIP1 → 工序2(M2) → WIP2 → 工序3(M3) → 成品
 * P2: 工序1(M2) → WIP1 → 工序2(M3) → 成品
 *
 * 目标：最小化 生产成本 + 换型成本 + 库存持有成本
 */
public class MultiOperationDailyPlanning {

    // 工序定义
    static class Operation {
        String product;     // 产品名
        int opIndex;        // 工序序号（从0开始）
        int machine;        // 设备编号
        double rate;        // 加工效率（件/小时）
        double unitCost;    // 单位加工成本（元/件）
        double setupCost;   // 换型成本（元/次）
        double wipCost;     // 该工序后WIP的持有成本（元/件/天）

        Operation(String product, int opIndex, int machine,
                  double rate, double unitCost, double setupCost, double wipCost) {
            this.product = product;
            this.opIndex = opIndex;
            this.machine = machine;
            this.rate = rate;
            this.unitCost = unitCost;
            this.setupCost = setupCost;
            this.wipCost = wipCost;
        }
    }

    public static void main(String[] args) {
        Loader.loadNativeLibraries();

        // ========== 1. 基础参数 ==========
        String[] machineNames = {"M1", "M2", "M3"};
        int numMachines = machineNames.length;
        double[] dailyCapacity = {10, 12, 8}; // 各设备日产能（小时）

        String[] products = {"P1", "P2"};
        int numProducts = products.length;
        int numDays = 4; // 计划周期：4天

        // 成品需求
        double[][] demand = {
                {50, 60, 40, 70},  // P1 日需求
                {30, 40, 50, 35}   // P2 日需求
        };

        // 成品初始库存、持有成本
        double[] initFgInv = {20, 10};
        double[] fgHoldCost = {1.0, 1.5};

        // ========== 2. 工艺路线定义 ==========
        // 每个产品的工序列表（按先后顺序）
        List<List<Operation>> routes = new ArrayList<>();

        // P1: 3道工序，M1 → M2 → M3
        List<Operation> p1Route = new ArrayList<>();
        p1Route.add(new Operation("P1", 0, 0, 25, 2.0, 100, 0.3)); // 工序1: M1, 25件/h
        p1Route.add(new Operation("P1", 1, 1, 20, 3.0, 150, 0.4)); // 工序2: M2, 20件/h
        p1Route.add(new Operation("P1", 2, 2, 30, 2.5, 80,  0.0)); // 工序3: M3, 30件/h (末道无WIP)
        routes.add(p1Route);

        // P2: 2道工序，M2 → M3
        List<Operation> p2Route = new ArrayList<>();
        p2Route.add(new Operation("P2", 0, 1, 15, 4.0, 120, 0.5)); // 工序1: M2, 15件/h
        p2Route.add(new Operation("P2", 1, 2, 20, 3.5, 100, 0.0)); // 工序2: M3, 20件/h (末道无WIP)
        routes.add(p2Route);

        // WIP初始库存（每道工序后）
        double[][] initWipInv = {
                {30, 20, 0},  // P1: 工序1后30件, 工序2后20件
                {25, 0}       // P2: 工序1后25件
        };

        double bigM = 10000;

        // ========== 3. 创建求解器 ==========
        MPSolver solver = MPSolver.createSolver("CBC");

        // ========== 4. 决策变量 ==========

        // x[i][k][t]: 第t天产品i的第k道工序产量
        MPVariable[][][] x = new MPVariable[numProducts][][];
        // y[i][k][t]: 第t天产品i的第k道工序是否生产（0-1，换型用）
        MPVariable[][][] y = new MPVariable[numProducts][][];
        // wip[i][k][t]: 第t天末产品i第k道工序后的在制品库存
        MPVariable[][][] wip = new MPVariable[numProducts][][];

        for (int i = 0; i < numProducts; i++) {
            int numOps = routes.get(i).size();
            x[i] = new MPVariable[numOps][numDays];
            y[i] = new MPVariable[numOps][numDays];
            wip[i] = new MPVariable[numOps][numDays];

            for (int k = 0; k < numOps; k++) {
                Operation op = routes.get(i).get(k);
                for (int t = 0; t < numDays; t++) {
                    x[i][k][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                            "x_" + op.product + "_op" + (k + 1) + "_d" + (t + 1));
                    y[i][k][t] = solver.makeBoolVar(
                            "y_" + op.product + "_op" + (k + 1) + "_d" + (t + 1));
                    // 末道工序没有WIP（直接进成品库），但变量还是创建，值为0即可
                    wip[i][k][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                            "wip_" + op.product + "_op" + (k + 1) + "_d" + (t + 1));
                }
            }
        }

        // 成品库存 fg[i][t]
        MPVariable[][] fg = new MPVariable[numProducts][numDays];
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                fg[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                        "fg_" + products[i] + "_d" + (t + 1));
            }
        }

        // ========== 5. 目标函数 ==========
        MPObjective obj = solver.objective();

        // 生产成本 + 换型成本
        for (int i = 0; i < numProducts; i++) {
            for (int k = 0; k < routes.get(i).size(); k++) {
                Operation op = routes.get(i).get(k);
                for (int t = 0; t < numDays; t++) {
                    obj.setCoefficient(x[i][k][t], op.unitCost);
                    obj.setCoefficient(y[i][k][t], op.setupCost);
                }
            }
        }

        // WIP持有成本（非末道工序）
        for (int i = 0; i < numProducts; i++) {
            int numOps = routes.get(i).size();
            for (int k = 0; k < numOps - 1; k++) { // 末道没有WIP
                Operation op = routes.get(i).get(k);
                for (int t = 0; t < numDays; t++) {
                    obj.setCoefficient(wip[i][k][t], op.wipCost);
                }
            }
        }

        // 成品持有成本
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                obj.setCoefficient(fg[i][t], fgHoldCost[i]);
            }
        }

        obj.setMinimization();

        // ========== 6. 约束条件 ==========

        // ===== 约束1：成品库存平衡 =====
        // 期初成品 + 末道工序产量 = 需求 + 期末成品
        for (int i = 0; i < numProducts; i++) {
            int lastOp = routes.get(i).size() - 1;
            for (int t = 0; t < numDays; t++) {
                double prevInv = (t == 0) ? initFgInv[i] : 0;
                MPConstraint c = solver.makeConstraint(
                        demand[i][t] - prevInv, demand[i][t] - prevInv,
                        "fg_inv_" + products[i] + "_d" + (t + 1));
                if (t > 0) c.setCoefficient(fg[i][t - 1], 1);
                c.setCoefficient(x[i][lastOp][t], 1); // 末道工序产出 = 成品入库
                c.setCoefficient(fg[i][t], -1);
            }
        }

        // ===== 约束2：WIP在制品库存平衡（核心！工序先后约束）=====
        // 期初WIP + 本工序产量 = 下道工序投入 + 期末WIP
        // （同一天内本工序产出可直接供下工序使用，即"天内流水"）
        for (int i = 0; i < numProducts; i++) {
            int numOps = routes.get(i).size();
            for (int k = 0; k < numOps - 1; k++) { // 每道非末道工序都有WIP
                for (int t = 0; t < numDays; t++) {
                    double prevWip = (t == 0) ? initWipInv[i][k] : 0;
                    MPConstraint c = solver.makeConstraint(-prevWip, -prevWip,
                            "wip_inv_" + products[i] + "_op" + (k + 1) + "_d" + (t + 1));
                    if (t > 0) c.setCoefficient(wip[i][k][t - 1], 1); // 期初WIP
                    c.setCoefficient(x[i][k][t], 1);                  // 本工序产出（+）
                    c.setCoefficient(x[i][k + 1][t], -1);             // 下工序投入（-）
                    c.setCoefficient(wip[i][k][t], -1);               // 期末WIP（-）
                }
            }
        }

        // ===== 约束3：设备产能约束 =====
        // 每台设备每天所有工序的总工时 ≤ 日产能
        for (int m = 0; m < numMachines; m++) {
            for (int t = 0; t < numDays; t++) {
                MPConstraint cap = solver.makeConstraint(0, dailyCapacity[m],
                        "cap_" + machineNames[m] + "_d" + (t + 1));
                for (int i = 0; i < numProducts; i++) {
                    for (int k = 0; k < routes.get(i).size(); k++) {
                        Operation op = routes.get(i).get(k);
                        if (op.machine == m) {
                            cap.setCoefficient(x[i][k][t], 1.0 / op.rate);
                        }
                    }
                }
            }
        }

        // ===== 约束4：生产开关约束（换型用）=====
        // x[i][k][t] <= M * y[i][k][t]
        for (int i = 0; i < numProducts; i++) {
            for (int k = 0; k < routes.get(i).size(); k++) {
                Operation op = routes.get(i).get(k);
                for (int t = 0; t < numDays; t++) {
                    MPConstraint c = solver.makeConstraint(-Double.POSITIVE_INFINITY, 0,
                            "switch_" + op.product + "_op" + (k + 1) + "_d" + (t + 1));
                    c.setCoefficient(x[i][k][t], 1);
                    c.setCoefficient(y[i][k][t], -bigM);
                }
            }
        }

        // ===== 约束5：末道工序WIP强制为0（直接入成品库）=====
        for (int i = 0; i < numProducts; i++) {
            int lastOp = routes.get(i).size() - 1;
            for (int t = 0; t < numDays; t++) {
                MPConstraint c = solver.makeConstraint(0, 0,
                        "wip_last_" + products[i] + "_d" + (t + 1));
                c.setCoefficient(wip[i][lastOp][t], 1);
            }
        }

        // ========== 7. 求解 ==========
        System.out.println("========== 多工序多设备 日级排产 MIP ==========");
        System.out.printf("产品：%d种，设备：%d台，周期：%d天%n",
                numProducts, numMachines, numDays);
        System.out.print("工艺路线：");
        for (int i = 0; i < numProducts; i++) {
            System.out.print(products[i] + "(");
            for (int k = 0; k < routes.get(i).size(); k++) {
                if (k > 0) System.out.print("→");
                System.out.print(machineNames[routes.get(i).get(k).machine]);
            }
            System.out.print(")  ");
        }
        System.out.println("\n");

        MPSolver.ResultStatus status = solver.solve();

        // ========== 8. 结果输出 ==========
        if (status == MPSolver.ResultStatus.OPTIMAL) {
            System.out.println("✅ 求解成功！全局最优解");
            System.out.printf("最小总成本：%.2f 元%n%n", obj.value());

            // ---- 按天输出 ----
            for (int t = 0; t < numDays; t++) {
                System.out.println("══════════════════ 第 " + (t + 1) + " 天 ══════════════════");

                // 每台设备的排产
                for (int m = 0; m < numMachines; m++) {
                    System.out.println("  【" + machineNames[m] + "】 日产能 " + dailyCapacity[m] + "h");
                    double totalHours = 0;
                    boolean hasProd = false;

                    for (int i = 0; i < numProducts; i++) {
                        for (int k = 0; k < routes.get(i).size(); k++) {
                            Operation op = routes.get(i).get(k);
                            if (op.machine != m) continue;

                            double qty = x[i][k][t].solutionValue();
                            double hours = qty / op.rate;
                            totalHours += hours;

                            if (qty > 0.001) {
                                hasProd = true;
                                double setup = y[i][k][t].solutionValue() * op.setupCost;
                                System.out.printf("    %s-工序%d：生产 %6.1f 件，耗时 %5.1fh  " +
                                                "（加工费 %.0f元，换型 %.0f元）%n",
                                        op.product, k + 1, qty, hours,
                                        qty * op.unitCost, setup);
                            }
                        }
                    }

                    if (!hasProd) {
                        System.out.println("    （闲置）");
                    }
                    System.out.printf("    工时合计：%.1f / %.1f h （利用率 %.0f%%）%n",
                            totalHours, dailyCapacity[m], totalHours / dailyCapacity[m] * 100);
                    System.out.println();
                }

                // 库存状态
                System.out.println("  【库存状态】");
                for (int i = 0; i < numProducts; i++) {
                    System.out.print("    " + products[i] + "：");
                    // WIP
                    for (int k = 0; k < routes.get(i).size() - 1; k++) {
                        System.out.printf("WIP%d=%.0f  ", k + 1, wip[i][k][t].solutionValue());
                    }
                    System.out.printf("成品=%.0f", fg[i][t].solutionValue());
                    System.out.println();
                }
                System.out.println();
            }

            // ---- 成本明细 ----
            double totalProdCost = 0, totalSetupCost = 0, totalWipCost = 0, totalFgCost = 0;
            for (int i = 0; i < numProducts; i++) {
                for (int k = 0; k < routes.get(i).size(); k++) {
                    Operation op = routes.get(i).get(k);
                    for (int t = 0; t < numDays; t++) {
                        totalProdCost += x[i][k][t].solutionValue() * op.unitCost;
                        totalSetupCost += y[i][k][t].solutionValue() * op.setupCost;
                        if (k < routes.get(i).size() - 1) {
                            totalWipCost += wip[i][k][t].solutionValue() * op.wipCost;
                        }
                    }
                }
            }
            for (int i = 0; i < numProducts; i++) {
                for (int t = 0; t < numDays; t++) {
                    totalFgCost += fg[i][t].solutionValue() * fgHoldCost[i];
                }
            }

            System.out.println("══════════════════ 成本明细 ══════════════════");
            System.out.printf("加工成本：  %8.2f 元%n", totalProdCost);
            System.out.printf("换型成本：  %8.2f 元%n", totalSetupCost);
            System.out.printf("WIP 成本：  %8.2f 元%n", totalWipCost);
            System.out.printf("成品成本：  %8.2f 元%n", totalFgCost);
            System.out.printf("────────────────────────────%n");
            System.out.printf("总成本：    %8.2f 元%n", obj.value());

            // 设备利用率总览
            System.out.println();
            System.out.println("══════════════════ 设备利用率总览 ══════════════════");
            for (int m = 0; m < numMachines; m++) {
                double totalHours = 0;
                for (int i = 0; i < numProducts; i++) {
                    for (int k = 0; k < routes.get(i).size(); k++) {
                        Operation op = routes.get(i).get(k);
                        if (op.machine == m) {
                            for (int t = 0; t < numDays; t++) {
                                totalHours += x[i][k][t].solutionValue() / op.rate;
                            }
                        }
                    }
                }
                double totalCap = dailyCapacity[m] * numDays;
                System.out.printf("  %s：总工时 %6.1f / %6.1f h （利用率 %.1f%%）%n",
                        machineNames[m], totalHours, totalCap, totalHours / totalCap * 100);
            }

            System.out.println();
            System.out.println("══════════════════ 求解统计 ══════════════════");
            System.out.println("变量数：" + solver.numVariables());
            System.out.println("约束数：" + solver.numConstraints());
            System.out.printf("求解时间：%.3f 秒%n", solver.wallTime() / 1000.0);

        } else if (status == MPSolver.ResultStatus.INFEASIBLE) {
            System.out.println("❌ 无解 —— 产能不足，无法满足所有需求");
            // 可以输出哪个设备是瓶颈
        } else {
            System.out.println("求解状态：" + status);
        }
    }
}
