/*************************************************************
 *
 *  Copyright (c) 2018 The MathJax Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * @fileoverview  Implements the CommonMaction wrapper mixin for the MmlMaction object
 *
 * @author dpvc@mathjax.org (Davide Cervone)
 */

import {AnyWrapper, WrapperConstructor, Constructor, AnyWrapperClass} from '../Wrapper.js';
import {MmlMaction} from '../../../core/MmlTree/MmlNodes/maction.js';
import {BBox} from '../../../util/BBox.js';
import {split} from '../../../util/string.js';

/*****************************************************************/
/**
 * The types needed to define the actiontypes
 *
 * @template W  The maction wrapper type
 */
export type ActionData = {[name: string]: any};
export type ActionHandler<W extends AnyWrapper> = (node: W, data?: ActionData) => void;
export type ActionPair<W extends AnyWrapper> = [ActionHandler<W>, ActionData];
export type ActionMap<W extends AnyWrapper> = Map<string, ActionPair<W>>;
export type ActionDef<W extends AnyWrapper> = [string, [ActionHandler<W>, ActionData]];

export type EventHandler = (event: Event) => void;

/**
 * Data used for tooltip actions
 */
export const TooltipData = {
  dx: '.2em',          // x-offset of tooltip from right side of maction bbox
  dy: '.1em',          // y-offset of tooltip from bottom of maction bbox

  postDelay: 600,      // milliseconds before tooltip posts
  clearDelay: 100,     // milliseconds before tooltip is removed

  hoverTimer: new Map<any, number>(),    // timers for posting tooltips
  clearTimer: new Map<any, number>(),    // timers for removing tooltips

  /*
   * clear the timers if any are active
   */
  stopTimers: (node: any, data: ActionData) => {
    if (data.clearTimer.has(node)) {
      clearTimeout(data.clearTimer.get(node));
      data.clearTimer.delete(node);
    }
    if (data.hoverTimer.has(node)) {
      clearTimeout(data.hoverTimer.get(node));
      data.hoverTimer.delete(node);
    }
  }

};

/*****************************************************************/
/**
 * The CommonMaction interface
 *
 * @template W  The maction wrapper type
 */
export interface CommonMaction<W extends AnyWrapper> extends AnyWrapper {
  /**
   * The handler for the specified actiontype
   */
  action: ActionHandler<W>;
  data: ActionData;

  /**
   * Tooltip offsets
   */
  dx: number;
  dy: number;

  /**
   * The selected child wrapper
   */
  readonly selected: W;

}

/**
 * The CommonMaction class interface
 *
 * @template W  The maction wrapper type
 */
export interface CommonMactionClass<W extends AnyWrapper> extends AnyWrapperClass {
  /**
   * The valid action types and their handlers
   */
  actions: ActionMap<W>;
}

/**
 * Shorthand for the CommonMaction constructor
 *
 * @template W  The maction wrapper type
 */
export type MactionConstructor<W extends AnyWrapper> = Constructor<CommonMaction<W>>;

/*****************************************************************/
/**
 * The CommonMaction wrapper mixin for the MmlMaction object
 *
 * @template W  The maction wrapper type
 * @template T  The Wrapper class constructor type
 */
export function CommonMactionMixin<
  W extends AnyWrapper,
  T extends WrapperConstructor
>(Base: T): MactionConstructor<W> & T {

  return class extends Base {

    /**
     * The handler for the specified actiontype
     */
    public action: ActionHandler<W>;
    /**
     * The data for the specified actiontype
     */
    public data: ActionData;

    /**
     * The x-offset for tooltips
     */
    public dx: number;
    /**
     * The y-offset for tooltips
     */
    public dy: number;

    /**
     * @return {W}  The selected child wrapper
     */
    public get selected(): W {
      const selection = this.node.attributes.get('selection') as number;
      const i = Math.max(1, Math.min(this.childNodes.length, selection)) - 1;
      return this.childNodes[i] || this.wrap((this.node as MmlMaction).selected);
    }

    /*************************************************************/

    /**
     * @override
     */
    constructor(...args: any[]) {
      super(...args);
      const actions = (this.constructor as CommonMactionClass<W>).actions;
      const action = this.node.attributes.get('actiontype') as string;
      const [handler, data] = actions.get(action) || [((_node, _data) => {}) as ActionHandler<W>, {}];
      this.action = handler;
      this.data = data;
      this.getParameters();
    }

    /**
     * Look up attribute parameters
     */
    public getParameters() {
      const offsets = this.node.attributes.get('data-offsets') as string;
      let [dx, dy] = split(offsets || '');
      this.dx = this.length2em(dx || TooltipData.dx);
      this.dy = this.length2em(dy || TooltipData.dy);
    }

    /**
     * @override
     */
    public computeBBox(bbox: BBox, recompute: boolean = false) {
      bbox.updateFrom(this.selected.getBBox());
      this.selected.setChildPWidths(recompute);
    }

  };

}
