package jnpf.attendance.schedule;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

/**
 * 第七步：按岗位用候选固定班次贪心覆盖半小时需求矩阵（无权重打分，纯规则序）。
 */
public final class FixedShiftGreedyCoverPlanner {

    private FixedShiftGreedyCoverPlanner() {}

    public static FixedShiftCoverOutcome planForPost(
            HalfHourPostDemandMatrix matrix,
            String postId,
            List<PostFixedShiftCandidate> candidates,
            int maxIterations) {
        Objects.requireNonNull(matrix, "matrix");
        if (postId == null) {
            postId = "";
        }
        List<String> slots = matrix.getSlotRangeLabelsOrdered();
        int n = slots.size();
        int[] target = new int[n];
        for (int i = 0; i < n; i++) {
            target[i] = matrix.getTarget(slots.get(i), postId);
        }
        int[] covered = new int[n];
        List<PostFixedShiftCandidate> candList = candidates == null ? Collections.emptyList() : candidates;

        List<CandidateSlotIndices> wraps = new ArrayList<>();
        for (PostFixedShiftCandidate c : candList) {
            if (c == null) {
                continue;
            }
            List<Integer> ix =
                    slotIndicesOverlapping(matrix, c.getStartMinuteInclusive(), c.getEndMinuteExclusive());
            if (!ix.isEmpty()) {
                wraps.add(new CandidateSlotIndices(c, ix));
            }
        }

        List<PostFixedShiftCandidate> picks = new ArrayList<>();
        int guard = 0;
        while (guard++ < maxIterations) {
            Comparator<CandidateSlotIndices> cmp = comparator(covered, target);
            CandidateSlotIndices best = null;
            for (CandidateSlotIndices w : wraps) {
                if (gapHalfHours(covered, target, w.indices) <= 0) {
                    continue;
                }
                if (best == null || cmp.compare(w, best) < 0) {
                    best = w;
                }
            }
            if (best == null) {
                break;
            }
            for (int i : best.indices) {
                covered[i]++;
            }
            picks.add(best.candidate);
        }

        return new FixedShiftCoverOutcome(postId, picks, covered, target, slots);
    }

    static int gapHalfHours(int[] covered, int[] target, List<Integer> indices) {
        int g = 0;
        for (int i : indices) {
            if (covered[i] < target[i]) {
                g++;
            }
        }
        return g;
    }

    static int redundantHalfHours(int[] covered, int[] target, List<Integer> indices) {
        int r = 0;
        for (int i : indices) {
            if (covered[i] >= target[i]) {
                r++;
            }
        }
        return r;
    }

    static List<Integer> slotIndicesOverlapping(
            HalfHourPostDemandMatrix matrix, int windowStartMinuteInclusive, int windowEndMinuteExclusive) {
        List<String> slots = matrix.getSlotRangeLabelsOrdered();
        List<Integer> ix = new ArrayList<>();
        for (int i = 0; i < slots.size(); i++) {
            int[] se = HalfHourSlotLabel.startEndExclusiveMinutes(slots.get(i));
            if (HalfHourSlotLabel.overlaps(se[0], se[1], windowStartMinuteInclusive, windowEndMinuteExclusive)) {
                ix.add(i);
            }
        }
        return ix;
    }

    private static final class CandidateSlotIndices {
        final PostFixedShiftCandidate candidate;
        final List<Integer> indices;

        CandidateSlotIndices(PostFixedShiftCandidate candidate, List<Integer> indices) {
            this.candidate = candidate;
            this.indices = indices;
        }
    }

    static Comparator<CandidateSlotIndices> comparator(int[] covered, int[] target) {
        return (a, b) -> {
            int ga = gapHalfHours(covered, target, a.indices);
            int gb = gapHalfHours(covered, target, b.indices);
            if (ga != gb) {
                return Integer.compare(gb, ga);
            }
            int ra = redundantHalfHours(covered, target, a.indices);
            int rb = redundantHalfHours(covered, target, b.indices);
            if (ra != rb) {
                return Integer.compare(ra, rb);
            }
            int da = a.candidate.getAppearanceDayCount();
            int db = b.candidate.getAppearanceDayCount();
            if (da != db) {
                return Integer.compare(db, da);
            }
            int la = a.candidate.getEndMinuteExclusive() - a.candidate.getStartMinuteInclusive();
            int lb = b.candidate.getEndMinuteExclusive() - b.candidate.getStartMinuteInclusive();
            if (la != lb) {
                return Integer.compare(la, lb);
            }
            return Integer.compare(
                    a.candidate.getStartMinuteInclusive(), b.candidate.getStartMinuteInclusive());
        };
    }
}
