import { Color } from "../../../../core/color/Color.js";
import Vector1 from "../../../../core/geom/Vector1.js";
import ObservedBoolean from "../../../../core/model/ObservedBoolean.js";
import ObservedEnum from "../../../../core/model/ObservedEnum.js";
import { LightType } from "./LightType.js";


const DEFAULT_DISTANCE = 1;
const DEFAULT_MAX_SHADOW_DISTANCE = Infinity;

export class Light {
    /**
     *
     * @constructor
     */
    constructor() {
        /**
         *
         * @type {ObservedEnum}
         */
        this.type = new ObservedEnum(LightType.DIRECTION, LightType);

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

        /**
         *
         * @type {Vector1}
         */
        this.intensity = new Vector1(1);

        /**
         * Only applicable for SPOT light, controls conic angle of the light
         * @type {Vector1}
         */
        this.angle = new Vector1(Math.PI / 4);

        /**
         * Only applicable for SPOT light, controls penumbra share of the illuminated area. 1 - only penumbra and no umbra, 0 - no penumbra and only umbra
         * @type {Vector1}
         */
        this.penumbra = new Vector1(0.4);

        /**
         * Applicable for SPOT and POINT lights, controls maximum extents of influence of the light
         * @type {Vector1}
         */
        this.distance = new Vector1(DEFAULT_DISTANCE);

        /**
         * Applicable for DIRECTION light. Caps the world-space depth of the view
         * frustum the shadow map is fit against — smaller values trade visible
         * shadow distance for sharpness. `Infinity` fits the entire camera frustum.
         * @type {Vector1}
         */
        this.maxShadowDistance = new Vector1(DEFAULT_MAX_SHADOW_DISTANCE);

        /**
         *
         * @type {ObservedBoolean}
         */
        this.castShadow = new ObservedBoolean(false);

        /**
         *
         * @type {Light|Object3D|null}
         * @private
         */
        this.__threeObject = null;

        /**
         * Forward+ renderer light
         * @type {null}
         * @private
         */
        this.__fp_light = null;
    }

    /**
     *
     * @param j
     * @returns {Light}
     */
    static fromJSON(j) {
        const r = new Light();

        r.fromJSON(j);

        return r;
    }

    fromJSON(json) {
        if (json.type !== undefined) {
            this.type.fromJSON(json.type);
        }

        const j_color = json.color;

        if (j_color !== undefined) {
            const type = typeof j_color;
            if (type === "string") {
                this.color.parse(j_color);
            } else if (type === "object") {
                this.color.fromJSON(j_color);
            } else {
                this.color.fromUint(j_color);
            }
        } else {
            // default
            this.color.setRGB(1, 1, 1);
        }

        if (json.intensity !== undefined) {
            this.intensity.fromJSON(json.intensity);
        } else {
            this.intensity.set(1);
        }

        if (json.castShadow !== undefined) {
            this.castShadow.fromJSON(json.castShadow);
        } else {
            this.castShadow.set(false);
        }

        const actual_type = this.type.getValue();

        if (actual_type === LightType.SPOT) {
            if (json.angle !== undefined) {
                this.angle.fromJSON(json.angle);
            }
            if (json.penumbra !== undefined) {
                this.penumbra.fromJSON(json.penumbra);
            }
        }

        if (actual_type === LightType.SPOT || actual_type === LightType.POINT) {

            if (json.distance !== undefined) {
                this.distance.fromJSON(json.distance);
            } else {
                this.distance.set(DEFAULT_DISTANCE);
            }
        }

        if (actual_type === LightType.DIRECTION) {
            if (json.maxShadowDistance !== undefined && json.maxShadowDistance !== null) {
                this.maxShadowDistance.fromJSON(json.maxShadowDistance);
            } else {
                this.maxShadowDistance.set(DEFAULT_MAX_SHADOW_DISTANCE);
            }
        }
        this.__threeObject = null;
    }

    toJSON() {
        const result = {
            type: this.type.toJSON(),
            color: this.color.toUint(),
            intensity: this.intensity.toJSON(),
            castShadow: this.castShadow.toJSON()
        };

        const actual_type = this.type.getValue();

        if (actual_type === LightType.SPOT) {
            result.angle = this.angle.toJSON();
            result.penumbra = this.penumbra.toJSON();
        }

        if (actual_type === LightType.SPOT || actual_type === LightType.POINT) {
            result.distance = this.distance.toJSON();
        }

        if (actual_type === LightType.DIRECTION) {
            // JSON.stringify turns Infinity into null and the rehydrated value
            // would fail Vector1.set's number check; default is Infinity so
            // missing-on-disk is the same as default.
            const v = this.maxShadowDistance.getValue();
            if (Number.isFinite(v)) {
                result.maxShadowDistance = this.maxShadowDistance.toJSON();
            }
        }


        return result;
    }
}

Light.typeName = "Light";

Light.Type = LightType;

