import { Component } from "@uino/thing";
import { Utils, getTransformOffset } from "../common/Utils";

const __ = {
    private: Symbol('private'),
}

let _defaultQuat = [0, 0, 0, 1];
const emptyParam = {};

/**
 * 房间平面组件
 * @class PlaneComponent
 * @extends THING.Component
 * @public
 * @example
 * // 1. 单独使用(不作为房间的子)
 * // 模拟数据
 * const points = [[0, 0], [2, 0], [2, 2], [0, 2]]
 * const holes = [[[0.25, 0.25], [0.75, 0.25], [0.75, 0.75], [0.25, 0.75],]]
 * const materialResource = { sideType: 'Double', map: './images/camera.png' }
 * // 创建对象作为平面
 * const plane = new THING.Object3D();
 * // 实例化平面组件
 * const planeComponent = new THING.PlaneComponent();
 * plane.addComponent(planeComponent, 'planeComponent');
 * planeComponent.load({ points, holes, materialResource }).then(() => {
 *     console.log('平面加载完成');
 * })
 * // 2. 配合房间一起使用
 * const roomFloorMaterialResource = { sideType: 'Double', map: './images/camera.png' }
 * const roomCeilingMaterialResource = { sideType: 'Back', map: './images/camera.png' }
 * const roomRoofMaterialResource = { sideType: 'Front', map: './images/camera.png' }
 * // 创建房间
 * const room = new THING.Room({
 *     position: [10, 0, 10],
 *     points,
 *     holes
 * });
 * // 创建对象作为房间地面
 * const roomFloor = new THING.Object3D({
 *     parent: room,
 *     tags: ['BuildingElement', 'RoomFloor']
 * });
 * // 实例化平面组件
 * const roomFloorComponent = new THING.PlaneComponent();
 * roomFloor.addComponent(roomFloorComponent, 'planeComponent');
 * roomFloorComponent.load({ materialResource: roomFloorMaterialResource }).then(() => {
 *     console.log('房间地面加载完成');
 * })
 * // 创建对象作为房间天花板
 * const roomCeiling = new THING.Object3D({
 *     parent: room,
 *     localPosition: [0, 2.9, 0],
 *     tags: ['BuildingElement', 'RoomCeiling']
 * });
 * // 实例化平面组件
 * const roomCeilingComponent = new THING.PlaneComponent();
 * roomCeiling.addComponent(roomCeilingComponent, 'planeComponent');
 * roomCeilingComponent.load({ materialResource: roomCeilingMaterialResource }).then(() => {
 *     console.log('房间天花板加载完成');
 * })
 * // 创建对象作为房间天花板
 * const roomRoof = new THING.Object3D({
 *     parent: room,
 *     localPosition: [0, 3, 0],
 *     tags: ['BuildingElement', 'RoomRoof']
 * });
 * // 实例化平面组件
 * const roomRoofComponent = new THING.PlaneComponent();
 * roomRoof.addComponent(roomRoofComponent, 'planeComponent');
 * roomRoofComponent.load({ materialResource: roomRoofMaterialResource }).then(() => {
 *     console.log('房间房顶加载完成');
 * })
 */
class PlaneComponent extends Component {

    /**
     * Represents a PlaneComponent.
     * @param {Object} param - The parameter object for the PlaneComponent.
     */
    constructor(param = emptyParam) {
        super(param);

        let _private = this[__.private] = {
            dynamic: param.dynamic,
            points: null,
            holes: null,
            alwaysHide: false
        };

        _defaultQuat[1] = getTransformOffset();

        //#region private

        /**
         * Sets the data of the private object.
         * @param {Object} data - The data object to set.
         */
        _private.setData = (data) => {
            _private.materialResourceData = data['materialResource'];

            _private.alwaysHide = Utils.parseBoolean(data.alwaysHide, false);

            _private.uv = data.uv;

            _private.selfPoints = data.points;
            _private.selfHoles = data.holes;
            if (!_private.selfPoints) {
                _private.selfPoints = this.object.parent._selfPoints;
            }
            if (!_private.selfHoles) {
                _private.selfHoles = this.object.parent._selfHoles;
            }

            _private.instanceGroupName = data.instanceGroupName;
        }

        /**
         * Gets the data of the private object.
         * @returns {Object} - The data object.
         */
        _private.getData = () => {
            const data = {}

            if (_private.materialResourceData) {
                data.materialResource = _private.materialResourceData;
            }

            if (_private.alwaysHide === true) {
                data.alwaysHide = _private.alwaysHide;
            }

            // Use the parent data
            let parentExternal = this.object.parent.external;
            if (parentExternal && parentExternal.points) {
                return data;
            }

            // Use self data
            if (_private.selfPoints) {
                data.points = _private.selfPoints;
            }

            if (_private.selfHoles) {
                data.holes = _private.selfHoles;
            }
            return data;
        }

        /**
         * Creates the plane component.
         * @param {Object} object - The object to create the component on.
         * @param {MaterialResource} materialResource - The material resource for the component.
         */
        _private.create = function (object, materialResource) {

            const node = Utils.createObject('ExtrudeShape', { materialResource });
            node.setQuaternion(_defaultQuat);

            object.body.setNode(node);

            node.begin();
            {
                node.setPoints(Utils.cloneObject(_private.selfPoints, false));
                if (_private.selfHoles) {
                    node.setHoles(Utils.cloneObject(_private.selfHoles, false));
                }
            }
            node.end();

            if (_private.uv) {
                _private.updateVertexUV(object);
            }

            if (_private.instanceGroupName) {
                object.instanceGroupName = _private.instanceGroupName;
                object.makeInstancedDrawing(true);
            }
        }

        _private.updateVertexUV = function (object) {

            const geo = object.node._node.geometry;

            const uvBuffer = geo.attributes.a_Uv.buffer;
            const uvArray = uvBuffer.array;

            const uvMatrix = getUVMatrix(_private.uv);

            for (let i = 0; i < uvArray.length; i += 2) {
                const x = uvArray[i];
                const y = uvArray[i + 1];

                const newUV = vec2ApplyMatrix3(x, y, uvMatrix);

                // update uv
                uvArray[i] = newUV[0];
                uvArray[i + 1] = newUV[1];
            }

            uvBuffer.version++;


            function getUVMatrix(uv) {
                let offset = uv.offset || [0, 0];
                let repeat = uv.repeat || [1, 1];
                let center = uv.center || [0, 0];
                let rotation = (uv.rotation || 0) * Math.PI / 180;

                const c = Math.cos(rotation);
                const s = Math.sin(rotation);

                return [
                    repeat[0] * c, -repeat[1] * s, 0,
                    repeat[0] * s, repeat[1] * c, 0,
                    -repeat[0] * (c * center[0] + s * center[1]) + center[0] + offset[0],
                    -repeat[1] * (-s * center[0] + c * center[1]) + center[1] + offset[1], 1
                ];
            }

            function vec2ApplyMatrix3(x, y, m) {
                const z = 1;

                return [
                    m[0] * x + m[3] * y + m[6] * z,
                    m[1] * x + m[4] * y + m[7] * z
                ];
            }
        }

        //#endregion
    }


    /**
     * 加载平面
     * @param {Object} data - 平面数据 (数据格式参考:https://wiki.uino.com/book/6538d1e88ea0ba892398fdf2/6526121eae83a4dfb2c2d08d.html)
     * @returns
     * @public
     */
    load(data) {
        const _private = this[__.private];
        _private.setData(data);

        return this.onLoadResource();
    }

    /**
     * Handles the import of external data.
     * @param {Object} external - The external data to import.
     * @param {Object} options - The import options.
     */
    onImport(external, options) {
        super.onImport(external, options);
        let _private = this[__.private];
        _private.setData(external);
    }

    /**
     * Handles the export of data.
     * @param {Object} options - The export options.
     * @returns {Object} - The exported data.
     */
    onExport(options) {
        super.onExport(options);
        const data = this[__.private].getData();
        return data;
    }

    /**
     * Handles the loading of the resource.
     */
    onLoadResource() {
        let _private = this[__.private];
        return new Promise((resolve, reject) => {
            if (_private.alwaysHide) {
                resolve();
                return;
            }

            this.app._materialResourceLoader.createMaterialResource(_private.materialResourceData, (materialResource) => {
                _private.create(this.object, materialResource);
                resolve();
            }, reject);
        });
    }

    get alwaysHide() {
        let _private = this[__.private];
        return _private.alwaysHide;
    }

}

Utils.registerClass("PlaneComponent", PlaneComponent);

export { PlaneComponent }