//

import { array_copy } from "../../../collection/array/array_copy.js";
import { UintArrayForCount } from "../../../collection/array/typed/uint_array_for_count.js";
import { compute_polygon_area_2d } from "../compute_polygon_area_2d.js";
import { intersect_ray_2d } from "../intersect_ray_2d.js";

/**
 * Compute a bounding polygon with a fixed given number of vertices such that the area would be minimal
 * Uses combinatorial sets of edges to find the subset, which when extending those edges produces poly with minimum area
 * NOTE: very very slow as it's O(n^k) where n is number of edges and k is number of points on the desired bounding polygon
 *
 * @param {number[]|Float32Array} output result will be written here
 * @param {number} output_offset offset into output array where to write the result
 * @param {number} output_point_count number of points to comprise bounding polygon
 * @param {number[]} input_points Points to be bounded, must be an ordered loop. Must form a convex hull
 * @param {number} input_point_count number of points in the input to consider
 * @returns {number} number of points added to the output
 *
 * @see 2014 "Implementation of linear minimum area enclosing triangle algorithm" Ovidiu Pârvu & David Gilbert
 * @see https://github.com/MagnusTiberius/humus3/blob/9d43412d6e6be869e224817f0a966fe48a1af40f/Util/ConvexHull.cpp#L281
 */
export function fixed_convex_hull_humus(
    output, output_offset, output_point_count,
    input_points, input_point_count
) {

    if (output_point_count >= input_point_count) {
        // special case, input polygon is smaller or equal to in cardinality to what's desired
        array_copy(input_points, 0, output, output_offset, input_point_count * 2);

        return input_point_count;
    }

    /*
    The following code iterated k-size sets of elements in the set of edges
    @see https://stackoverflow.com/questions/29910312/algorithm-to-get-all-the-combinations-of-size-n-from-an-array-java
    @see https://en.wikipedia.org/wiki/Combination
     */

    const edge_count = input_point_count;
    const k = output_point_count;

    const ArrayConstructor = UintArrayForCount(k);

    /**
     *
     * @type {number[]}
     */
    const s = new ArrayConstructor(k);

    let combos = 0;
    let combos_valid = 0;

    const intersections = new Float64Array(k * 2);
    let best_area = Infinity;

    /**
     *
     * @param {number[]|Uint16Array} subset
     */
    function process_combination(subset) {
        combos++;

        for (let i = 0; i < k; i++) {
            // pick point indices to represent a line
            const i0 = subset[i];
            const i1 = subset[(i + 1) % k];

            const i0_2 = i0 << 1;

            const a0x = input_points[i0_2];
            const a0y = input_points[i0_2 + 1];

            const a1 = (i0 + 1) % input_point_count;

            const a1_2 = a1 << 1;

            const a1x = input_points[a1_2];
            const a1y = input_points[a1_2 + 1];

            const i1_2 = i1 << 1;

            const b0x = input_points[i1_2];
            const b0y = input_points[i1_2 + 1];

            const b1 = (i1 + 1) % input_point_count;

            const b1_2 = b1 << 1;

            const b1x = input_points[b1_2];
            const b1y = input_points[b1_2 + 1];

            const a_dir_x = a1x - a0x;
            const a_dir_y = a1y - a0y;

            const b_dir_x = b1x - b0x;
            const b_dir_y = b1y - b0y;

            if (!intersect_ray_2d(
                intersections, i << 1,
                a0x, a0y, a_dir_x, a_dir_y,
                b0x, b0y, b_dir_x, b_dir_y
            )) {
                // invalid combination, edges do not meet
                return;
            }
        }

        // we got intersection points, lets compute area
        const area = compute_polygon_area_2d(intersections, k);

        if (area < best_area) {
            array_copy(intersections, 0, output, output_offset, k << 1);
            best_area = area;
        }

        combos_valid++;
    }


    for (let i = 0; (s[i] = i) < k - 1; i++) {
        // populate initial set
    }

    process_combination(s);

    for (; ;) {
        let i;

        for (i = k - 1; i >= 0 && s[i] === edge_count - k + i; i--) {
            // find position of item that can be incremented
        }

        if (i < 0) {
            break;
        }

        s[i]++; // increment this item

        for (++i; i < k; i++) {
            // fill up remaining items
            s[i] = s[i - 1] + 1;
        }

        process_combination(s);
    }

    // console.log(`combinations:  ${combos}, valid: ${combos_valid} (${(combos_valid * 100 / combos).toFixed(2)}%)`);
    if(combos_valid === 0){
        // no valid combinations
        return 0;
    }

    return output_point_count;
}
