/* 
 * This file is part of the warpgate module (https://github.com/trioderegion/warpgate)
 * Copyright (c) 2021 Matthew Haentschke.
 * 
 * This program is free software: you can redistribute it and/or modify  
 * it under the terms of the GNU General Public License as published by  
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import { logger } from './logger.js'
import { MODULE } from './module.js'
import { Gateway } from './gateway.js' 
import { Events } from './events.js'
import { RemoteMutator } from './remote-mutator.js'
import {queueUpdate} from './update-queue.js'



const ops = {
  DISMISS_SPAWN : "dismiss", //tokenId, sceneId, userId
  EVENT : "event", //name, ...payload
  REQUEST_MUTATE: "req-mutate", // ...payload
  REQUEST_REVERT: "req-revert", // ...payload
  NOTICE: "req-notice",
}

export class Comms {

  static register() {
    Comms.hooks();
  }

  static hooks() {
    Hooks.on("ready", Comms._ready); 
  }

  static _ready() {
    logger.info("Registering sockets");

    game.socket.on(`module.${MODULE.data.name}`, Comms._receiveSocket);
  }

  static _receiveSocket(socketData) {
    logger.debug("Received socket data => ", socketData);

    /* all users should immediately respond to notices */
    if (socketData.op == ops.NOTICE) {
      MODULE.handleNotice(socketData.payload.location, socketData.payload.sceneId, socketData.payload.options);
      return socketData;
    }

    queueUpdate( async () => {
      logger.debug("Routing operation: ",socketData.op);
      switch (socketData.op){
        case ops.DISMISS_SPAWN:
          /* let the first GM handle all dismissals */
          if (MODULE.isFirstGM()) await Gateway.dismissSpawn(socketData.payload.tokenId, socketData.payload.sceneId, socketData.payload.userId);
          break;
        case ops.EVENT:
          /* all users should respond to events */
          await Events.run(socketData.eventName, socketData.payload);
          break;
        case ops.REQUEST_MUTATE:
          /* First owner of this target token/actor should respond */
          await RemoteMutator.handleMutationRequest(socketData.payload);
          break;
        case ops.REQUEST_REVERT:
          /* First owner of this target token/actor should respond */
          await RemoteMutator.handleRevertRequest(socketData.payload);
          break;
        default:
          logger.error("Unrecognized socket request", socketData);
          break;
      }
    });

    return socketData;
  }

  static _emit(socketData) {
    game.socket.emit(`module.${MODULE.data.name}`, socketData);

    /* always send events to self as well */
    return Comms._receiveSocket(socketData);
  }

  static requestDismissSpawn(tokenId, sceneId) {
    /** craft the socket data */
    const data = {
      op : ops.DISMISS_SPAWN,
      payload : { tokenId, sceneId, userId: game.user.id }
    }
    
    return Comms._emit(data);
  }

  /*
   * payload = {userId, tokenId, sceneId, updates, options}
   */
  static requestMutate(tokenId, sceneId, { updates = {}, options = {} } = {}, onBehalf = game.user.id ) {
    
    /* insert common fields */
    const payload = {
      userId: onBehalf,
      tokenId,
      sceneId,
      updates,
      options,
    }

    /* craft the socket data */
    const data = {
      op: ops.REQUEST_MUTATE,
      payload
    }

    return Comms._emit(data);
  }

  static requestRevert(tokenId, sceneId, {mutationId = undefined, onBehalf = game.user.id, options = {}}) {

    /* insert common fields */
    const payload = {
      userId: onBehalf,
      tokenId,
      sceneId,
      mutationId,
      options,
    }

    /* craft the socket data */
    const data = {
      op: ops.REQUEST_REVERT,
      payload
    }

    return Comms._emit(data);
  }

  static requestNotice(location, sceneId = canvas.scene?.id, options = {}) {
    const data = {
      op: ops.NOTICE,
      payload: {
        sceneId,
        location,
        options,
      }
    }

    return Comms._emit(data);
  }

  static packToken(tokenDoc) {
    const tokenData = tokenDoc.toObject();
    delete tokenData.actorData;

    let actorData = tokenDoc.actor?.toObject() ?? {};
    actorData.token = tokenData;
    return actorData;
  }

  /**
   * Allow custom events to be fired using the Warp Gate event system. Is broadcast to all users, including the initiator. 
   * Like Hooks, these functions cannot be awaited for a response, but all event functions executing on a given client
   * will be evaluated in order of initial registration and the processing of the event functions will respect 
   * (and await) returned Promises.
   * 
   * @param {string} name Name of this event. Watches and triggers use this name to register themselves. 
   *  Like Hooks, any string can be used and it is dependent upon the watch or trigger registration to monitor the correct event name.
   * @param {object} [payload={sceneId: canvas.scene.id, userId: game.user.id}] eventData {Object} The data that will be 
   *  provided to watches and triggers and their condition functions.
   * @param {string} [onBehalf=game.user.id] User ID that will be used in place of the current user in the
   *  cases of a relayed request to the GM (e.g. dismissal).
   * 
   * @returns {Object} Data object containing the event's payload (execution details), and identifying metadata about
   *  this event, sent to all watching and triggering clients.
   */
  static notifyEvent(name, payload = {}, onBehalf = game.user.id) {
    /** insert common fields */
    payload.sceneId = canvas.scene.id;
    payload.userId = onBehalf;

    /* craft the socket data */
    const data = {
      op : ops.EVENT,
      eventName: name,
      payload
    }

    return Comms._emit(data);
  }

}
