Source: GenerateProblemMatrix.js

import { Shape3D } from "./Shape3D.js";
import {A, B, C, D, E, F, G, H, I, J, K, L} from "./Shapes3D.js";
/**
 * Object representing the shape columns.
 * @type {Object.<string, number>}
 */
let shape_cols = {
    "A": 0,
    "B": 1,
    "C": 2,
    "D": 3,
    "E": 4,
    "F": 5,
    "G": 6,
    "H": 7,
    "I": 8,
    "J": 9,
    "K": 10,
    "L": 11
};

/**
 * Converts a 3D coordinate to a column index in the problem matrix.
 * @param {number[]} coord - The 3D coordinate [x, y, z].
 * @returns {number} - The column index in the problem matrix.
 */
function coord_to_col(coord) {
    let out = 12;
    let z_diff = 0;
    let row_length = 0;

    if (coord[2] === 0) {
        z_diff = 0;
        row_length = 5;
    } else if (coord[2] === 1) {
        z_diff = 25;
        row_length = 4;
    } else if (coord[2] === 2) {
        z_diff = 25 + 16;
        row_length = 3;
    } else if (coord[2] === 3) {
        z_diff = 25 + 16 + 9;
        row_length = 2;
    } else {
        z_diff = 25 + 16 + 9 + 4;
        row_length = 1;
    }

    out += z_diff;
    out += row_length * coord[0] + coord[1];
    return out;
}

/**
 * Converts a shape object to a row in the problem matrix.
 * @param {Object} shape - The shape object to convert.
 * @returns {Array} - The row representing the shape in the problem matrix.
 */
function shape_to_row(shape) {
    let row = new Array(67);
    for (let i = 0; i < 67; i++) {
        row[i] = 0;
    }
    row[shape_cols[shape.name]] = 1;

    for (let i = 0; i < shape.layout.length; i++) {
        row[coord_to_col(shape.layout[i])] = 1;
    }
    return row;
}

/**
 * Generates headers for a matrix based on shape names and coordinates.
 * @returns {string[]} An array of headers.
 */
function generate_headers() {
    let headers = [];
    let shape_names = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"];

    for (let i of shape_names) {
        headers.push(i);
    }

    for (let z = 0; z < 5; z++) {
        for (let i = 0; i < 5 - z; i++) {
            for (let j = 0; j < 5 - z; j++) {
                headers.push(i.toString() + "," + j.toString() + "," + z.toString());
            }
        }
    }
    return headers;
}

let shapes = [A, B, C, D, E, F, G, H, I, J, K, L];

/**
 * Generates the horizontal slices for a polyhedron.
 * @returns {Array<Array<number>>} The horizontal slices of the polyhedron.
 */
function get_horizontal_slices() {
    let horizontal_slices = [];
    for (let i = 4; i >= 0; i--) {
        horizontal_slices.push([]);
        for (let x = 0; x < i + 1; x++) {
            for (let y = 0; y < i + 1; y++) {
                horizontal_slices[4 - i].push([x, y, 4 - i]);
            }
        }
    }
    return horizontal_slices;
}

/**
 * Retrieves the diagonal slices from the horizontal slices.
 * @returns {Array<Array<Array<Array<number>>>>} The diagonal slices.
 */
function get_diag_slices() {
    let diag_slices = [[new Array(), new Array(), new Array(), new Array()],
                       [new Array(), new Array(), new Array(), new Array()],
                       [new Array(), new Array(), new Array(), new Array()],
                       [new Array(), new Array()]];
    let horiz_slice = get_horizontal_slices();
    let count = 0;

    for (let layer of horiz_slice) {
        let max = Math.sqrt(layer.length) - 1;
        for (let coord of layer) {
            if (max - coord[1] - coord[0] === 3) {
                diag_slices[0][0].push(coord);
            } else if (max - coord[1] - coord[0] === -3) {
                diag_slices[0][2].push(coord);
            } else if (max - coord[1] - coord[0] === 2) {
                diag_slices[1][0].push(coord);
            } else if (max - coord[1] - coord[0] === -2) {
                diag_slices[1][2].push(coord);
            } else if (max - coord[1] - coord[0] === 1) {
                diag_slices[2][0].push(coord);
            } else if (max - coord[1] - coord[0] === -1) {
                diag_slices[2][2].push(coord);
            } else if (max - coord[1] - coord[0] === 0) {
                diag_slices[3][0].push(coord);
            }

            if (coord[1] - coord[0] === 3) {
                diag_slices[0][1].push(coord);
            } else if (coord[1] - coord[0] === -3) {
                diag_slices[0][3].push(coord);
            } else if (coord[1] - coord[0] === 2) {
                diag_slices[1][1].push(coord);
            } else if (coord[1] - coord[0] === -2) {
                diag_slices[1][3].push(coord);
            } else if (coord[1] - coord[0] === 1) {
                diag_slices[2][1].push(coord);
            } else if (coord[1] - coord[0] === -1) {
                diag_slices[2][3].push(coord);
            } else if (coord[1] - coord[0] === 0) {
                diag_slices[3][1].push(coord);
            }
        }
        count += 1;
    }
    return diag_slices;
}

let diag_slices = get_diag_slices();

/**
 * Converts rectangular coordinates to diagonal coordinates based on the shape layout and size.
 * @param {Array<Array<number>>} shape_layout - The layout of the shape in rectangular coordinates.
 * @param {number} size - The size of the shape.
 * @returns {Array<Array<number>>} - The converted diagonal coordinates.
 */
function convert_rect_coords_to_diags(shape_layout, size) {
    let out;

    if (size !== 5) {
        out = new Array(4);
    } else {
        out = new Array(2);
    }

    let diags = diag_slices[size - 2];
    let count = 0;

    for (let diag of diags) {
        let layout = structuredClone(shape_layout);

        for (let i = 0; i < shape_layout.length; i++) {
            if (shape_layout[i][1] - shape_layout[i][0] === 0) {
                layout[i] = diag[shape_layout[i][0]];
            } else if (shape_layout[i][1] - shape_layout[i][0] === 1) {
                layout[i] = diag[size + shape_layout[i][0]];
            } else if (shape_layout[i][1] - shape_layout[i][0] === 2) {
                layout[i] = diag[2 * size - 1 + shape_layout[i][0]];
            } else if (shape_layout[i][1] - shape_layout[i][0] === 3) {
                layout[i] = diag[3 * size - 3 + shape_layout[i][0]];
            } else if (shape_layout[i][1] - shape_layout[i][0] === 4) {
                layout[i] = diag[4 * size - 6 + shape_layout[i][0]];
            }
        }
        out[count] = layout;
        count += 1;
    }
    return out;
}

/**
 * Adds rows to the problem matrix if the shape's layout is valid.
 * @param {Array<Array<number>>} problem_mat - The problem matrix.
 * @param {Object} shape - The shape object.
 * @param {number} size - The size of the shape.
 * @returns {boolean} - True if rows were added, false otherwise.
 */
function add_row_for_diags_if_valid(problem_mat, shape, size) {
    for (let coord of shape.layout) {
        if (coord[0] > coord[1]) {
            return false;
        }
    }

    let placement_layouts = convert_rect_coords_to_diags(shape.layout, size);

    for (let layout of placement_layouts) {
        problem_mat.push(shape_to_row(new Shape3D(shape.name, layout)));
    }
    return true;
}

let diags = get_diag_slices();

/**
 * Adds rows for a given shape in horizontal and vertical slices to the problem matrix.
 * @param {Array<Array<number>>} prob_mat - The problem matrix to add rows to.
 * @param {Shape} shape - The shape to add rows for.
 * @returns {Array<Array<number>>} - The updated problem matrix.
 */
function add_rows_for_shape_in_horizontal_and_vertical_slices(prob_mat, shape) {
    let max_size = 0;

    for (let i of shape.layout) {
        if (i[0] > max_size) {
            max_size = i[0];
        }
        if (i[1] > max_size) {
            max_size = i[1];
        }
    }

    for (let size = 5; size >= max_size; size--) {
        for (let coord of shape.layout) {
            coord[2] = 5 - size;
        }

        let starting_pos_store = [];
        let rotation_count = 0;

        while (true) {
            let skip = false;

            for (let starting_layout of starting_pos_store) {
                if (shape.equal_layouts(starting_layout)) {
                    skip = true;
                }
            }

            if (!skip) {
                starting_pos_store.push(shape.layout);

                for (let row = 0; row < size; row++) {
                    let place_count = 0;

                    if (shape.translate(row, 0, size)) {
                        for (let col = 0; col < size; col++) {
                            if (col === 0) {
                                place_count += 1;
                                prob_mat.push(shape_to_row(shape));
                                add_row_for_diags_if_valid(prob_mat, shape, size);
                            } else if (shape.translate(0, 1, size)) {
                                place_count += 1;
                                prob_mat.push(shape_to_row(shape));
                                add_row_for_diags_if_valid(prob_mat, shape, size);
                                if (col === size - 1) {
                                    shape.reset_coord();
                                }
                            } else {
                                shape.reset_coord();
                                break;
                            }
                        }
                    }
                }
            }

            if (rotation_count < 4) {
                shape.rotate();
                rotation_count += 1;
            } else if (rotation_count === 4) {
                shape.flip();
                rotation_count += 1;
            } else if (rotation_count > 4 && rotation_count < 8) {
                shape.rotate();
                rotation_count += 1;
            } else {
                break;
            }
        }
    }
    return prob_mat;
}

/**
 * Populates a problem matrix for 3D shapes.
 * 
 * @returns {Array} The problem matrix.
 */
function populate_problem_matrix3D() {
    let problem_matrix = [];

    for (let shape of shapes) {
        add_rows_for_shape_in_horizontal_and_vertical_slices(problem_matrix, shape);
    }
    return problem_matrix;
}

/**
 * Reduces the problem matrix by removing rows and columns based on the shapes and squares used.
 * @param {Array<Array<number>>} problem_matrix - The problem matrix to be reduced.
 * @param {Array<string>} problem_headers - The headers of the problem matrix.
 * @param {Array<string>} shapes_used - The shapes used in the problem.
 * @param {Array<Array<string>>} squares_used - The squares used in the problem.
 * @returns {Array<Array<number>>} - The reduced problem matrix.
 */
function reduce_problem_matrix(problem_matrix, problem_headers, shapes_used, squares_used) {
    console.log(problem_headers);
    let used_cols = [];

    for (let shape of shapes_used) {
        used_cols.push(shape_cols[shape]);
    }

    for (let squares of squares_used) {
        for (let square of squares) {
            console.log(square);
            used_cols.push(problem_headers.indexOf(square.toString()));
        }
    }

    let used_cols_sorted = new Uint8Array(used_cols);
    used_cols_sorted = used_cols_sorted.sort();
    used_cols_sorted = used_cols_sorted.reverse();
    console.log(used_cols_sorted);

    for (let i = problem_matrix.length - 1; i >= 0; i--) {
        for (let j of used_cols_sorted) {
            if (problem_matrix[i][j] && used_cols_sorted.includes(j)) {
                problem_matrix.splice(i, 1);
                break;
            } else if (used_cols_sorted.includes(j)) {
                problem_matrix[i].splice(j, 1);
            }
        }
    }

    for (let i of used_cols_sorted) {
        problem_headers.splice(i, 1);
    }

    return [problem_matrix, problem_headers];
}
export { generate_headers, populate_problem_matrix3D, reduce_problem_matrix };