/**
 * Copyright (c) 2020 Gitpod GmbH. All rights reserved.
 * Licensed under the GNU Affero General Public License (AGPL).
 * See License-AGPL.txt in the project root for license information.
 */

import { Message } from 'vscode-jsonrpc/lib/messages';
import { AbstractMessageWriter, MessageWriter } from 'vscode-jsonrpc/lib/messageWriter';
import { AbstractMessageReader, MessageReader, DataCallback } from 'vscode-jsonrpc/lib/messageReader';
import { MessageConnection, createMessageConnection } from 'vscode-jsonrpc/lib/main';
import { ConsoleLogger } from 'vscode-ws-jsonrpc';

interface WindowMessage extends Message {
    serviceId: string
}
function isWindowMessage(value: any): value is WindowMessage {
    return !!value && typeof value === 'object' &&
        ('jsonrpc' in value && typeof value['jsonrpc'] === 'string') &&
        ('serviceId' in value && typeof value['serviceId'] === 'string');
}

export class WindowMessageWriter extends AbstractMessageWriter implements MessageWriter {

    constructor(readonly serviceId: string, readonly window: Window, readonly targetOrigin: string) {
        super();
    }

    write(msg: Message): void {
        const { serviceId } = this;
        this.window.postMessage(Object.assign(msg, { serviceId }), this.targetOrigin);
    }

}

export class WindowMessageReader extends AbstractMessageReader implements MessageReader {

    protected callback?: DataCallback;
    protected readonly buffer: Message[] = [];

    constructor(readonly serviceId: string, readonly sourceOrigin: string) {
        super();
        window.addEventListener('message', event => {
            if (this.sourceOrigin !== '*' && event.origin !== this.sourceOrigin) {
                return;
            }
            if (!isWindowMessage(event.data) || event.data.serviceId !== this.serviceId) {
                return;
            }
            if (this.callback) {
                this.callback(event.data);
            } else {
                this.buffer.push(event.data);
            }
        }, false);
    }

    listen(callback: DataCallback): void {
        let message;
        while (message = this.buffer.pop()) {
            callback(message);
        }
        Object.freeze(this.buffer);
        this.callback = callback;
    }

}

export function createWindowMessageConnection(serviceId: string, window: Window, sourceOrigin: string, targetOrigin = sourceOrigin): MessageConnection {
    const reader = new WindowMessageReader(serviceId, sourceOrigin);
    const writer = new WindowMessageWriter(serviceId, window, targetOrigin);
    return createMessageConnection(reader, writer, new ConsoleLogger());
}