import assert from "assert";
import WebTorrent from "webtorrent-hybrid";
import TorrentClient from "./TorrentClient";
import { IpcServerService } from "@autoplex/ipc";
import { SOCKET_PATH } from "@autoplex-api/torrent";

type IAddTorrent = string | {
    type: "Buffer",
    data: number[]
}

export default class IpcInterface extends IpcServerService
{
    /**
     * The torrent client instance
     */
    protected torrentClient!: TorrentClient;

    /**
     * The service name
     */
    public readonly NAME = "IPC";

    /**
     * The path to the socket file
     */
    protected readonly SOCKET_PATH = SOCKET_PATH;

    /**
     * Boot the IPC interface
     */
    public async boot() {
        this.torrentClient = this.app.service<TorrentClient>("Torrent Client");
        await super.boot();
    }

    /**
     * Install the the event handlers
     */
    protected installMessageHandlers() {
        this.addMessageHandler("add", this.add);
        this.addMessageHandler("remove", this.remove);
        this.addMessageHandler("list", this.list);
        this.addMessageHandler("details", this.details);
        this.addMessageHandler("has", this.has);
        this.torrentClient.on("torrent_finished", this.torrentFinished.bind(this));
    }

    // Interface Methods ---------------------------------------------------------------------------

    /**
     * Add a torrent to the client
     */
    protected async add(payload: { torrent: IAddTorrent, downloadPath?: string }) {
        let torrent: WebTorrent.Torrent;
        if (typeof payload.torrent == "string") {
            torrent = await this.torrentClient.add(payload.torrent, { downloadPath: payload.downloadPath });
        } else {
            torrent = await this.torrentClient.add(Buffer.from(payload.torrent.data), { downloadPath: payload.downloadPath });
        }
        return torrent.infoHash;
    }

    /**
     * Remove a torrent from the client
     */
    protected async remove(payload: { torrentId: string, withData: boolean }) {
        if (!this.torrentClient.has(payload.torrentId)) {
            return false;
        }
        await this.torrentClient.remove(payload.torrentId, payload.withData);
        return true;
    }

    /**
     * List the torrents in the client
     */
    protected async list() {
        return this.torrentClient.torrents.map(torrent => Object({
            name    : torrent.name,
            infoHash: torrent.infoHash,
            progress: torrent.progress,
            state   : this.torrentClient.torrentState(torrent)
        }));
    }

    /**
     * Get the details of the provided torrents
     */
    protected async details(torrentIds: string[]) {
        let torrents: WebTorrent.Torrent[];
        if (torrentIds.length == 0) {
            torrents = this.torrentClient.torrents;
        } else {
            torrents = torrentIds.map(torrentId => {
                let torrent = this.torrentClient.get(torrentId);
                assert(torrent !== null, `Unknown torrent ID provided: ${torrentId}`);
                return torrent;
            });
        }
        return torrents.map(torrent => this.torrentClient.serializeTorrent(torrent));
    }

    /**
     * Determine if the torrent client has the given torrent
     */
    protected async has(torrentId: string) {
        return this.torrentClient.get(torrentId) !== null;
    }

    // Subscription Interface Methods --------------------------------------------------------------

    /**
     * Broadcast a message to the connected clients
     */
    // protected broadcast(method: string, ...message: any[]) {
    //     for (let socket of <Socket[]>(<any>ipc.server).sockets) {
    //         this.server.emit(socket, method, message);
    //     }
    // }

    /**
     * Notify connected clients that a torrent has finished
     */
    public torrentFinished(torrent: WebTorrent.Torrent) {
        this.broadcast("torrent_finished", torrent.infoHash);
    }
}
