import { FLOAT32_MAX } from "../../../../core/binary/FLOAT32_MAX.js";
import { BvhClient } from "../../../../core/bvh2/bvh3/BvhClient.js";
import { Color } from "../../../../core/color/Color.js";
import Vector3 from "../../../../core/geom/Vector3.js";
import { clamp01 } from "../../../../core/math/clamp01.js";
import ThreeFactory from "../../three/ThreeFactory.js";
import { RIBBON_ATTRIBUTE_ADDRESS_AGE } from "../../trail/x/ribbon_attributes_spec.js";
import { RibbonX } from "../../trail/x/RibbonX.js";
import { RibbonXMaterialSpec } from "../../trail/x/RibbonXMaterialSpec.js";
import { makeGradientTrail } from "./makeGradientTrail.js";
import { Trail2DFlags } from "./Trail2DFlags.js";

const DEFAULT_FLAGS = Trail2DFlags.Spawning | Trail2DFlags.Aging | Trail2DFlags.BoundsNeedUpdate;

const DEFAULT_MAX_AGE = 5;

const DEFAULT_WIDTH = 1;

const v3_array = new Float32Array(3);

class Trail2D {

    /**
     * Age at which trail segment disappears, in seconds
     * @type {number}
     */
    maxAge = DEFAULT_MAX_AGE;

    /**
     * Trail width
     * @type {number}
     */
    width = DEFAULT_WIDTH;

    /**
     * Current simulated time since trail birth
     * @type {number}
     */
    time = 0;

    /**
     *
     * @type {number}
     */
    trailingIndex = 0;

    /**
     * Time elapsed since last update
     * @type {number}
     */
    timeSinceLastUpdate = 0;

    /**
     * @readonly
     * @type {Color}
     */
    color = new Color(1, 1, 1, 1);

    /**
     * World offset
     * @readonly
     * @type {Vector3}
     */
    offset = new Vector3();

    /**
     * transient
     * @type {RibbonX|null}
     */
    ribbon = null;

    /**
     * transient
     * @type {RibbonXMaterialSpec}
     */
    material = new RibbonXMaterialSpec();

    /**
     * @readonly
     * @type {BvhClient}
     */
    bvh = new BvhClient();

    /**
     * @private
     * @type {Trail2DFlags|number}
     */
    flags = DEFAULT_FLAGS;

    dispose() {
        if (this.ribbon === null) {
            // nothing to dispose
            return;
        }

        this.ribbon.dispose();
        this.ribbon = null;
    }

    get textureURL() {
        return this.material.diffuse;
    }

    set textureURL(v) {
        this.material.diffuse = v;
    }

    /**
     *
     * @param {number|Trail2DFlags} flag
     * @returns {void}
     */
    setFlag(flag) {
        this.flags |= flag;
    }

    /**
     *
     * @param {number|Trail2DFlags} flag
     * @returns {void}
     */
    clearFlag(flag) {
        this.flags &= ~flag;
    }

    /**
     *
     * @param {number|Trail2DFlags} flag
     * @param {boolean} value
     */
    writeFlag(flag, value) {
        if (value) {
            this.setFlag(flag);
        } else {
            this.clearFlag(flag);
        }
    }

    /**
     *
     * @param {number|Trail2DFlags} flag
     * @returns {boolean}
     */
    getFlag(flag) {
        return (this.flags & flag) === flag;
    }

    /**
     *
     * @param {number} segment_count
     */
    build(segment_count) {

        const ribbon = new RibbonX();

        ribbon.buildGeometry();
        ribbon.setCount(segment_count);

        const geometry = ribbon.getGeometry();

        const mesh = ThreeFactory.createMesh(geometry, null);

        this.ribbon = ribbon;
        this.mesh = mesh;
    }

    /**
     * Clear the trail's geometry and reset its state.
     */
    clear() {
        const ribbon = this.ribbon;

        if (ribbon === null) {
            // nothing to clear
            return;
        }

        const n = ribbon.getCount();

        for (let i = 0; i < n; i++) {
            ribbon.setPointAttribute_Scalar(i, RIBBON_ATTRIBUTE_ADDRESS_AGE, FLOAT32_MAX);
            ribbon.setPointAlpha(i, 0);
        }

    }

    static fromJSON(json) {
        const r = new Trail2D();

        r.fromJSON(json);

        return r;
    }

    fromJSON({
                 maxAge = DEFAULT_MAX_AGE,
                 width = DEFAULT_WIDTH,
                 textureURL,
                 offset = Vector3.zero,
                 color = { x: 1, y: 1, z: 1, w: 1 }
             }) {

        this.maxAge = maxAge;
        this.width = width;
        this.textureURL = textureURL;

        this.offset.fromJSON(offset);
        this.color.fromJSON(color);
    }


    toJSON() {
        return {
            maxAge: this.maxAge,
            width: this.width,
            color: this.color.toJSON(),
            textureURL: this.textureURL,
            offset: this.offset.toJSON()
        };
    }

    /**
     *
     * @param {Trail2D} other
     */
    equals(other) {
        return this.textureURL === other.textureURL
            && this.width === other.width
            && this.maxAge === other.maxAge
            && this.color.equals(other.color)
            && this.offset.equals(other.offset)
            ;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @param {number} head_age
     * @returns {boolean}
     */
    updateHead(x, y, z, head_age) {
        const ribbon = this.ribbon;

        const maxAge = this.maxAge;

        const refitTimeDelta = maxAge / ribbon.getCount();

        let head_index = ribbon.getHeadIndex();
        ribbon.getPointPosition(v3_array, head_index);

        const head_position_changed = x !== v3_array[0]
            || y !== v3_array[1]
            || z !== v3_array[2];

        if (this.timeSinceLastUpdate >= refitTimeDelta) {

            if (
                head_position_changed
            ) {
                // make sure that this is a new position before rotating new segment
                this.timeSinceLastUpdate = 0;

                // rotating segment
                ribbon.rotate();

                head_index = ribbon.getHeadIndex();
            }
        }

        if (head_position_changed) {
            ribbon.setPointPosition(head_index, x, y, z);

            this.setFlag(Trail2DFlags.BoundsNeedUpdate);
        }

        // set head to fresh value
        ribbon.setPointAttribute_Scalar(head_index, RIBBON_ATTRIBUTE_ADDRESS_AGE, head_age);
        ribbon.setPointAlpha(head_index, clamp01(head_age / maxAge));
        ribbon.setPointThickness(head_index, this.width);

        return head_position_changed;
    }

}

Trail2D.typeName = "Trail2D";

Trail2D.serializable = false;

Trail2D.makeGradientTrail = makeGradientTrail;

export default Trail2D;
