import * as Comlink from 'comlink';
import { MessageInterface } from '../communication-interface/MessageInterface';
class LocalEndpoint {

    protected listeners: Map<string, EventListenerOrEventListenerObject[]> = new Map();

    protected _otherEndpoint?: LocalEndpoint;

    set otherEndpoint(otherEndpoint: LocalEndpoint) {
        this._otherEndpoint = otherEndpoint;
    }

    async postMessage(message: any, transfer?: Transferable[]): Promise<void> {
        console.log(`postmessage ${JSON.stringify(message)}`);
        const messageEvent = { data: message, type: 'message' } as unknown;
        this._otherEndpoint?.dispatchEvent(messageEvent as Event);
    }

    addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: Record<string, unknown>): void {
        if (!this.listeners.has(type)) {
            this.listeners.set(type, []);
        }
        this.listeners.get(type)!.push(listener)
        console.info(`event listener ${type} added`);
    }
    removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: Record<string, unknown>): void {
        const lIndex = this.listeners.get(type)?.indexOf(listener) ?? -1;
        if (lIndex == -1) {
            throw new Error('could not remove listener');
        }
        this.listeners.get(type)?.splice(lIndex, 1);
        console.log(`event listener ${type} removed`);
    }

    dispatchEvent(event: Event) {
        this.listeners.get(event.type)?.forEach(listener => {
            if (typeof listener === 'function') {
                listener(event);
            } else {
                listener.handleEvent(event);
            }
        });
    }
}

class RemoteEndpoint extends LocalEndpoint {
    private _messageInstance: MessageInterface;
    private _targetAddress: string;
    private _objectName: string;

    constructor(messageInstance: MessageInterface, targetAddress: string, objectName: string) {
        super();
        this._messageInstance = messageInstance;
        this._targetAddress = targetAddress;
        this._objectName = objectName;

        this._messageReceived = this._messageReceived.bind(this);

        this._messageInstance.addMessageListener(this._messageReceived);
    }

    private async _messageReceived(fromId: string, payload: any) {
        if (typeof payload === 'string') {
            try {
                payload = JSON.parse(payload);
            } catch (err) {
                console.info('failed to JSON parse payload');
                console.info(payload);
                console.info(err);
            }
        }
        console.log(payload);

        if (this._targetAddress === fromId && payload.objName === this._objectName) {
            const messageEvent = { data: payload, type: 'message' } as unknown;
            const event = messageEvent as Event;
            this.listeners.get(event.type)?.forEach(listener => {
                if (typeof listener === 'function') {
                    listener(event);
                } else {
                    listener.handleEvent(event);
                }
            });
        } else {
            console.error(`got command from unknown userId ${fromId}`);
        }
    }

    async postMessage(message: any, transfer?: Transferable[]): Promise<void> {
        const messageEvent = { data: message, type: 'message', objName: this._objectName } as unknown;
        this.dispatchEvent(messageEvent as Event);
    }

    async dispatchEvent(event: any) {
        const message = JSON.stringify(event.data);
        this._messageInstance.send(this._targetAddress, message);
    }
}


export function RemoteObject(obj: object, mesasgeInstance: MessageInterface, targetAddress: string, objectName: string = obj.constructor.name): any {
    const localProxy = new RemoteEndpoint(mesasgeInstance, targetAddress, objectName);
    return Comlink.wrap(localProxy, obj);
}

export function ExposeRemoteObject(obj: object, mesasgeInstance: MessageInterface, targetAddress: string, objectName: string = obj.constructor.name): any {
    const localProxy = new RemoteEndpoint(mesasgeInstance, targetAddress, objectName);
    Comlink.expose(obj, localProxy);
}