import { Color, Material, Mesh } from "three";

import { WaitForSeconds } from "../engine/engine_coroutine.js";
import { RoomEvents } from "../engine/engine_networking.js";
import { PlayerState } from "../engine-components-experimental/networking/PlayerSync.js";
import { Behaviour, GameObject } from "./Component.js";
import { AvatarMarker } from "./webxr/WebXRAvatar.js";

/**
 * [PlayerColor](https://engine.needle.tools/docs/api/PlayerColor) Assigns a unique color for each user in the room to the object it is attached to.  
 * The color is generated based on the user's ID.
 * 
 * @summary Assigns a unique color to the player object
 * @category Networking
 * @group Components
 */
export class PlayerColor extends Behaviour {

    private _didAssignPlayerColor: boolean = false;

    onEnable(): void {
        this.context.connection.beginListen(RoomEvents.JoinedRoom, this.tryAssignColor);
        if (!this._didAssignPlayerColor)
            this.startCoroutine(this.waitForConnection());
    }
    onDisable(): void {
        this.context.connection.stopListen(RoomEvents.JoinedRoom, this.tryAssignColor);
    }

    private *waitForConnection() {
        while (!this.destroyed && this.activeAndEnabled) {
            yield WaitForSeconds(.2);
            if (this.tryAssignColor()) break;
        }
    }

    private tryAssignColor = () => {
        const marker = GameObject.getComponentInParent(this.gameObject, PlayerState);
        if (marker && marker.owner) {
            this._didAssignPlayerColor = true;
            this.assignUserColor(marker.owner);
            return true;
        }
        const avatar = GameObject.getComponentInParent(this.gameObject, AvatarMarker);
        if (avatar?.connectionId) {
            this._didAssignPlayerColor = true;
            this.assignUserColor(avatar.connectionId);
            return true;
        }
        return false;
    }

    assignUserColor(id: string) {

        // console.log(this.name, id, this);

        const hash = PlayerColor.hashCode(id);
        const color = PlayerColor.colorFromHashCode(hash);
        if (this.gameObject.type === "Mesh") {
            const mesh: Mesh = this.gameObject as any;
            this.assignColor(color, id, mesh);
        }
        else if (this.gameObject.children) {
            for (const ch of this.gameObject.children) {
                const obj = ch as any;
                if (obj.material && obj.material.color) {
                    this.assignColor(color, id, obj);
                }
            }
        }
    }

    private assignColor(col: Color, id: string, mesh: Mesh) {
        let mat = mesh.material as Material;
        if (!mat) return;
        if (mat["_playerMaterial"] !== id) {
            // console.log("ORIG", mat);
            mat = mat.clone();
            mat["_playerMaterial"] = id;
            mesh.material = mat;
            // console.log("CLONE", mat);
        }
        // else console.log("DONT CLONE", mat);
        mat["color"] = col;
    }

    public static hashCode(str: string) {
        var hash = 0, i, chr;
        if (str.length === 0) return hash;
        for (i = 0; i < str.length; i++) {
            chr = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + chr;
            hash |= 0; // Convert to 32bit integer
        }
        return hash;
    };

    public static colorFromHashCode(hash: number) {
        const r = (hash & 0xFF0000) >> 16;
        const g = (hash & 0x00FF00) >> 8;
        const b = hash & 0x0000FF;
        return new Color(r / 255, g / 255, b / 255);
    }
}

