import { ClampToEdgeWrapping, DataTexture, NearestFilter } from "three";
import { assert } from "../../../../core/assert.js";
import { float_to_uint8 } from "../../../../core/binary/float_to_uint8.js";
import { uint8_to_float } from "../../../../core/binary/uint8_to_float.js";
import { array_copy } from "../../../../core/collection/array/array_copy.js";
import { isTypedArray } from "../../../../core/collection/array/typed/isTypedArray.js";
import Vector1 from "../../../../core/geom/Vector1.js";
import Vector2 from "../../../../core/geom/Vector2.js";
import Vector4 from '../../../../core/geom/Vector4.js';
import ObservedString from "../../../../core/model/ObservedString.js";
import html_canvas_to_sampler2d from "../../../graphics/texture/html_canvas_to_sampler2d.js";
import { sampler2d_scale } from "../../../graphics/texture/sampler/resize/sampler2d_scale.js";
import { Sampler2D } from "../../../graphics/texture/sampler/Sampler2D.js";
import { sampler2d_paint } from "../../../graphics/texture/sampler/sampler2d_paint.js";
import { WHITE_PIXEL_DATA_URL } from "../../../graphics/WHITE_PIXEL_DATA_URL.js";

class Context {
    /**
     *
     * @param {Sampler2D} sampler
     * @param {number} borderWidth
     * @param {string} tileImage
     * @constructor
     */
    constructor(sampler, borderWidth, tileImage) {
        /**
         *
         * @type {Sampler2D}
         */
        this.sampler = sampler;

        /**
         *
         * @type {number}
         */
        this.borderWidth = borderWidth;

        /**
         * URL of the tile image
         * @type {string}
         */
        this.tileImage = tileImage;
    }
}

const COLOR_UINT_8_TRANSPARENT = [0, 0, 0, 0];

export class TerrainOverlay {
    /**
     *
     * @param {Vector2} size
     * @constructor
     */
    constructor(size) {
        /**
         * width is in fraction between 0 and 1, for example: 0.1 represents 10% border between tiles
         * @type {Vector1}
         * @readonly
         */
        this.borderWidth = new Vector1(0.1);
        /**
         *
         * @type {Vector2}
         * @readonly
         */
        this.size = new Vector2(size.x, size.y);

        /**
         *
         * @type {ObservedString}
         */
        this.tileImage = new ObservedString(WHITE_PIXEL_DATA_URL);

        /**
         *
         * @type {Sampler2D}
         */
        this.sampler = Sampler2D.uint8(4, size.x, size.y);

        const texture = this.texture = new DataTexture(
            this.sampler.data,
            size.x,
            size.y,
        );

        texture.wrapS = ClampToEdgeWrapping;
        texture.wrapT = ClampToEdgeWrapping;

        texture.magFilter = NearestFilter;
        texture.minFilter = NearestFilter;

        texture.anisotropy = 0;

        texture.flipY = false;

        texture.repeat.set(1, 1);
        //texture.anisotropy = 8;
        texture.needsUpdate = true;
        texture.generateMipmaps = false;

        /**
         *
         * @type {Context[]}
         */
        this.stack = [];

        this.size.onChanged.add((x, y) => {
            this.sampler.resize(x, y);

            this.texture.dispose();

            this.texture.image.data = this.sampler.data;

            this.texture.image.width = x;
            this.texture.image.height = y;
        });
    }

    /**
     *
     * @returns {string}
     */
    get baseTileImage() {
        return this.tileImage.getValue();
    }

    /**
     *
     * @param {string} v
     */
    set baseTileImage(v) {

        this.tileImage.set(v);

    }

    /**
     * @returns {HTMLCanvasElement}
     */
    get canvas() {
        throw new Error('Deprecated, used .sampler instead');
    }

    /**
     * Pushes new context onto the stack, allowing you to preserve current state for later
     */
    push() {
        const sampler = this.sampler.clone();

        const context = new Context(
            sampler,
            this.borderWidth.getValue(),
            this.tileImage.getValue()
        );

        this.stack.push(context);

        //clear
        this.clear();
    }

    /**
     * Pops top context from the stack, restoring previous state
     */
    pop() {
        if (this.stack.length === 0) {
            console.error("Can't pop overlay context, nothing on the stack");
            return;
        }

        /**
         * @type {Context}
         */
        const context = this.stack.pop();

        const sampler = context.sampler;

        this.size.set(sampler.width, sampler.height);

        // write context onto canvas
        this.sampler.copy(sampler, 0, 0, 0, 0, sampler.width, sampler.height);

        this.borderWidth.set(context.borderWidth);

        this.tileImage.set(context.tileImage);

        this.update();
    }

    clear() {
        const size = this.size;

        this.sampler.zeroFill(0, 0, size.x, size.y);

        this.update();
    }

    update() {
        this.texture.needsUpdate = true;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {Vector4} result
     */
    readPoint(x, y, result) {
        for (let i = 0; i < 4; i++) {
            const v = this.sampler.readChannel(x, y, i);

            result[i] = uint8_to_float(v);
        }
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {Vector4} vec4
     */
    paintPointAlphaBlend(x, y, vec4) {
        const r = new Vector4();

        this.readPoint(x, y, r);

        const b = 1 - vec4.w;

        r.set(
            vec4.x * vec4.w + r.x * b,
            vec4.y * vec4.w + r.y * b,
            vec4.z * vec4.w + r.z * b,
            vec4.w + r.w * b
        );

        this.clearPoint(x, y);

        this.paintPoint(x, y, r);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     */
    clearPoint(x, y) {
        this.sampler.write(x, y, COLOR_UINT_8_TRANSPARENT);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {Vector4} vec4
     */
    paintPoint(x, y, vec4) {
        for (let i = 0; i < 4; i++) {

            this.sampler.writeChannel(x, y, i, float_to_uint8(vec4[i]));

        }

        this.texture.needsUpdate = true;
    }

    /**
     *
     * @param {Uint8Array|number[]} data
     */
    writeData(data) {
        assert.isArrayLike(data, 'data');

        const size = this.size;

        if (size.x === 0 || size.y === 0) {
            // nothing to write
            return;
        }

        assert.equal(data.length, this.sampler.data.length);

        if (isTypedArray(this.sampler.data)) {
            this.sampler.data.set(data);
        } else {
            array_copy(data, 0, this.sampler.data, 0, data.length);
        }

        this.sampler.version++;

        this.texture.needsUpdate = true;
    }

    /**
     *
     * @param {HTMLCanvasElement} image
     * @param {number} dx
     * @param {number} dy
     * @param {number} [dWidth]
     * @param {number} [dHeight]
     */
    paintImage(image, dx, dy, dWidth, dHeight) {
        const source = html_canvas_to_sampler2d(image);

        this.paintSampler(source, dx, dy, dWidth, dHeight);
    }

    /**
     *
     * @param {Sampler2D} source
     * @param {number} dx
     * @param {number} dy
     * @param {number} dWidth
     * @param {number} dHeight
     */
    paintSampler(source, dx, dy, dWidth, dHeight) {

        // rescale source if needed
        const scaled_source = Sampler2D.uint8(4, dWidth, dHeight);
        sampler2d_scale(source, scaled_source);

        sampler2d_paint(this, scaled_source, 0, 0, dx, dy, dWidth, dHeight);

        this.texture.needsUpdate = true;
    }
}

