import { uint8_to_float } from "../../../src/core/binary/uint8_to_float.js";
import { QuadTreeNode } from "../../../src/core/geom/2d/quad-tree/QuadTreeNode.js";
import { clamp } from "../../../src/core/math/clamp.js";
import { inverseLerp } from "../../../src/core/math/inverseLerp.js";
import { lerp } from "../../../src/core/math/lerp.js";
import { Transform } from "../../../src/engine/ecs/transform/Transform.js";
import { PatchTerrainHeightAction } from "../../actions/concrete/PatchTerrainHeightAction.js";
import TransformModifyAction from "../../actions/concrete/TransformModifyAction.js";
import EditorEntity from "../../ecs/EditorEntity.js";
import { TerrainPaintTool } from "./TerrainPaintTool.js";

const LIMIT_VALUE = 1000;

/**
 *
 * @param {Terrain} terrain
 * @param {number} uv_x0
 * @param {number} uv_y0
 * @param {number} uv_x1
 * @param {number} uv_y1
 * @param {Sampler2D} marker
 * @param {number} value_delta
 * @param {number} limitMin
 * @param {number} limitMax
 * @returns {PatchTerrainHeightAction}
 */
function make_mod_action(terrain, uv_x0, uv_y0, uv_x1, uv_y1, marker, value_delta, limitMin, limitMax) {

    const heightMap = terrain.samplerHeight;

    const h_x0 = uv_x0 * heightMap.width;
    const h_x1 = uv_x1 * heightMap.width;

    const h_y0 = uv_y0 * heightMap.height;
    const h_y1 = uv_y1 * heightMap.height;

    const x0 = Math.ceil(h_x0);
    const x1 = Math.floor(h_x1);

    const y0 = Math.ceil(h_y0);
    const y1 = Math.floor(h_y1);

    const action = new PatchTerrainHeightAction(terrain, x0, y0, (x1 - x0), (y1 - y0));

    const patch_destination_data = action.patch.data;

    for (let y = y0; y < y1; y++) {

        const v = inverseLerp(h_y0, h_y1 - 1, y);


        for (let x = x0; x < x1; x++) {

            const u = inverseLerp(h_x0, h_x1 - 1, x);

            //Get alpha
            const markerValue = marker.sampleChannelBilinearUV(u, v, 3);

            const marker_value_normalized = uint8_to_float(markerValue);

            const source_address = y * heightMap.width + x;

            const base = heightMap.data[source_address];

            const patchAddress = (y - y0) * action.patch.width + (x - x0);

            const targetValue = clamp(base + value_delta, limitMin, limitMax);


            const value = lerp(base, targetValue, marker_value_normalized);

            if (Number.isNaN(value)) {
                // console.warn('.');

                patch_destination_data[patchAddress] = base;

            } else {

                patch_destination_data[patchAddress] = value;

            }
        }

    }
    return action;
}

export class TerrainHeightPaintTool extends TerrainPaintTool {

    constructor() {
        super();

        this.settings.limitMin = -LIMIT_VALUE;
        this.settings.limitMax = LIMIT_VALUE;

        /**
         *
         * @type {QuadTreeNode<number>}
         */
        this.transform_index = new QuadTreeNode();
    }

    buildTransformIndex() {
        /**
         *
         * @type {Engine}
         */
        const engine = this.engine;

        /**
         *
         * @type {EntityManager}
         */
        const entityManager = engine.entityManager;

        /**
         *
         * @type {EntityComponentDataset}
         */
        const dataset = entityManager.dataset;

        //purge existing data
        this.transform_index.clear();

        /**
         *
         * @type {number}
         */
        const gridScale = this.terrain.gridScale;

        const uS = 1 / (this.terrain.size.x * gridScale);
        const vS = 1 / (this.terrain.size.y * gridScale);

        dataset.traverseComponents(Transform, (transform, entity) => {
            if (dataset.getComponent(entity, EditorEntity) !== undefined) {
                //it's an editor entity
                return;
            }

            const position = transform.position;

            const x = position.x;
            const z = position.z;

            const u = x * uS;
            const v = z * vS;

            const datum = this.transform_index.add(transform, u, v, u, v);

            datum.entity = entity;
        });
    }

    initialize() {
        super.initialize();

        this.buildTransformIndex();

    }

    finalize() {
        super.finalize();
    }

    /**
     *
     * @param {number} timeDelta
     */
    async paint(timeDelta) {
        const power = this.settings.brushStrength * timeDelta;

        /**
         *
         * @type {Terrain}
         */
        const terrain = this.terrain;

        const brushPosition = this.__brushPosition;
        const brushSize = this.settings.brushSize;

        const terrainSize = terrain.size;

        const uv_x = brushPosition.x / terrainSize.x;
        const uv_y = brushPosition.y / terrainSize.y;

        const uv_w = brushSize / terrainSize.x;
        const uv_h = brushSize / terrainSize.y;

        const uv_x0 = uv_x - uv_w / 2;
        const uv_x1 = uv_x + uv_w / 2;

        const uv_y0 = uv_y - uv_h / 2;
        const uv_y1 = uv_y + uv_h / 2;


        const marker = this.settings.marker;

        const direction = this.modifiers.shift ? -1 : 1;

        const value_delta = power * direction;

        const limitMin = this.settings.limitMin;
        const limitMax = this.settings.limitMax;

        const action = make_mod_action(terrain, uv_x0, uv_y0, uv_x1, uv_y1, marker, value_delta, limitMin, limitMax);

        this.editor.actions.do(action);

        const objectMoveActions = this.createObjectMoveActions(action);

        this.editor.actions.doMany(objectMoveActions);
    }

    /**
     *
     * @param {PatchTerrainHeightAction} action
     * @returns {Action[]}
     */
    createObjectMoveActions(action) {

        const heightSampler = this.terrain.samplerHeight;

        const x0 = action.x;
        const y0 = action.y;

        const patch = action.patch;

        const patch_width = patch.width;
        const patch_height = patch.height;

        const x1 = x0 + patch_width;
        const y1 = y0 + patch_height;


        const u0 = x0 / heightSampler.width;
        const v0 = y0 / heightSampler.height;

        const u1 = x1 / heightSampler.width;
        const v1 = y1 / heightSampler.height;

        /**
         *
         * @type {QuadTreeDatum<Transform>[]}
         */
        const leaves = [];

        /**
         *
         * @type {Action[]}
         */
        const result = [];

        const overlaps = this.transform_index.requestDatumIntersectionsRectangle(leaves, u0, v0, u1, v1);

        for (let i = 0; i < overlaps; i++) {
            const leaf = leaves[i];

            /**
             *
             * @type {Transform}
             */
            const transform = leaf.data;

            const tX = leaf.x0 * heightSampler.width;
            const tY = leaf.y0 * heightSampler.height;

            const action_value = action.patch.sampleChannelBicubic(tX - action.x, tY - action.y, 0);
            const existing_value = heightSampler.sampleChannelBicubic(tY, tY, 0);

            const delta = action_value - existing_value;

            if (delta === 0) {
                continue;
            }

            if (Number.isNaN(delta)) {
                console.error('Delta is NaN, skipping.', action, tX, tY);
                continue;
            }

            const tM = new Transform();
            tM.copy(transform);
            tM.position._add(0, delta, 0);
            const a = new TransformModifyAction(leaf.entity, tM);
            result.push(a);
        }

        return result;
    }

    start() {
        super.start();

        this.editor.actions.mark('terrain height paint');
    }

}
