/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";

const { debug, warn } = GeckoViewUtils.initLogging("Module");

export class GeckoViewModule {
  static initLogging(aModuleName) {
    const tag = aModuleName.replace("GeckoView", "");
    return GeckoViewUtils.initLogging(tag);
  }

  constructor(aModuleInfo) {
    this._info = aModuleInfo;

    this._isContentLoaded = false;
    this._eventProxy = new EventProxy(this, this.eventDispatcher);

    this.onInitBrowser();
  }

  get name() {
    return this._info.name;
  }

  get enabled() {
    return this._info.enabled;
  }

  get window() {
    return this.moduleManager.window;
  }

  getActor(aActorName) {
    return this.moduleManager.getActor(aActorName);
  }

  get browser() {
    return this.moduleManager.browser;
  }

  get messageManager() {
    return this.moduleManager.messageManager;
  }

  get eventDispatcher() {
    return this.moduleManager.eventDispatcher;
  }

  get settings() {
    return this.moduleManager.settings;
  }

  get moduleManager() {
    return this._info.manager;
  }

  // Override to initialize the browser before it is bound to the window.
  onInitBrowser() {}

  // Override to cleanup when the browser is destroyed.
  onDestroyBrowser() {}

  // Override to initialize module.
  onInit() {}

  // Override to cleanup when the window is closed
  onDestroy() {}

  // Override to detect settings change. Access settings via this.settings.
  onSettingsUpdate() {}

  // Override to enable module after setting a Java delegate.
  onEnable() {}

  // Override to disable module after clearing the Java delegate.
  onDisable() {}

  // Override to perform actions when content module has started loading;
  // by default, pause events so events that depend on content modules can work.
  onLoadContentModule() {
    this._eventProxy.enableQueuing(true);
  }

  // Override to perform actions when content module has finished loading;
  // by default, un-pause events and flush queued events.
  onContentModuleLoaded() {
    this._eventProxy.enableQueuing(false);
    this._eventProxy.dispatchQueuedEvents();
  }

  registerListener(aEventList) {
    this._eventProxy.registerListener(aEventList);
  }

  unregisterListener(aEventList) {
    this._eventProxy.unregisterListener(aEventList);
  }
}

class EventProxy {
  constructor(aListener, aEventDispatcher) {
    this.listener = aListener;
    this.eventDispatcher = aEventDispatcher;
    this._eventQueue = [];
    this._registeredEvents = [];
    this._enableQueuing = false;
  }

  registerListener(aEventList) {
    debug`registerListener ${aEventList}`;
    this.eventDispatcher.registerListener(this, aEventList);
    this._registeredEvents = this._registeredEvents.concat(aEventList);
  }

  unregisterListener(aEventList) {
    debug`unregisterListener`;
    if (this._registeredEvents.length === 0) {
      return;
    }

    if (!aEventList) {
      this.eventDispatcher.unregisterListener(this, this._registeredEvents);
      this._registeredEvents = [];
    } else {
      this.eventDispatcher.unregisterListener(this, aEventList);
      this._registeredEvents = this._registeredEvents.filter(
        e => !aEventList.includes(e)
      );
    }
  }

  onEvent(aEvent, aData, aCallback) {
    if (this._enableQueuing) {
      debug`queue ${aEvent}, data=${aData}`;
      this._eventQueue.unshift(arguments);
    } else {
      this._dispatch(...arguments);
    }
  }

  enableQueuing(aEnable) {
    debug`enableQueuing ${aEnable}`;
    this._enableQueuing = aEnable;
  }

  _dispatch(aEvent, aData, aCallback) {
    debug`dispatch ${aEvent}, data=${aData}`;
    if (this.listener.onEvent) {
      this.listener.onEvent(...arguments);
    } else {
      this.listener(...arguments);
    }
  }

  dispatchQueuedEvents() {
    debug`dispatchQueued`;
    while (this._eventQueue.length) {
      const args = this._eventQueue.pop();
      this._dispatch(...args);
    }
  }
}
