/* import-globals-from common.js */
/* import-globals-from events.js */

// //////////////////////////////////////////////////////////////////////////////
// Event constants

const MOUSEDOWN_EVENT = 1;
const MOUSEUP_EVENT = 2;
const CLICK_EVENT = 4;
const COMMAND_EVENT = 8;
const FOCUS_EVENT = 16;

const CLICK_EVENTS = MOUSEDOWN_EVENT | MOUSEUP_EVENT | CLICK_EVENT;
const XUL_EVENTS = CLICK_EVENTS | COMMAND_EVENT;

// //////////////////////////////////////////////////////////////////////////////
// Public functions

/**
 * Test default accessible actions.
 *
 * Action tester interface is:
 *
 *  var actionObj = {
 *    // identifier of accessible to perform an action on
 *    get ID() {},
 *
 *    // index of the action
 *    get actionIndex() {},
 *
 *    // name of the action
 *    get actionName() {},
 *
 *    // DOM events (see constants defined above)
 *    get events() {},
 *
 *    // [optional] identifier of target DOM events listeners are registered on,
 *    // used with 'events', if missing then 'ID' is used instead.
 *    get targetID() {},
 *
 *    // [optional] true to match DOM events bubbled up to the target,
 *    // false (default) to only match events fired directly on the target.
 *    get allowBubbling() {},
 *
 *    // [optional] perform checks when 'click' event is handled if 'events'
 *    // is used.
 *    checkOnClickEvent: function() {},
 *
 *    // [optional] an array of invoker's checker objects (see eventQueue
 *    // constructor events.js)
 *    get eventSeq() {}
 *  };
 *
 *
 * @param  aArray [in] an array of action cheker objects
 */
function testActions(aArray) {
  gActionsQueue = new eventQueue();

  for (var idx = 0; idx < aArray.length; idx++) {
    var actionObj = aArray[idx];
    var accOrElmOrID = actionObj.ID;
    var actionIndex = actionObj.actionIndex;
    var actionName = actionObj.actionName;
    var events = actionObj.events;
    var accOrElmOrIDOfTarget = actionObj.targetID
      ? actionObj.targetID
      : accOrElmOrID;

    var eventSeq = [];
    if (events) {
      var elm = getNode(accOrElmOrIDOfTarget);
      if (events & MOUSEDOWN_EVENT) {
        eventSeq.push(new checkerOfActionInvoker("mousedown", elm, actionObj));
      }

      if (events & MOUSEUP_EVENT) {
        eventSeq.push(new checkerOfActionInvoker("mouseup", elm, actionObj));
      }

      if (events & CLICK_EVENT) {
        eventSeq.push(new checkerOfActionInvoker("click", elm, actionObj));
      }

      if (events & COMMAND_EVENT) {
        eventSeq.push(new checkerOfActionInvoker("command", elm, actionObj));
      }

      if (events & FOCUS_EVENT) {
        eventSeq.push(new focusChecker(elm));
      }
    }

    if (actionObj.eventSeq) {
      eventSeq = eventSeq.concat(actionObj.eventSeq);
    }

    var invoker = new actionInvoker(
      accOrElmOrID,
      actionIndex,
      actionName,
      eventSeq
    );
    gActionsQueue.push(invoker);
  }

  gActionsQueue.invoke();
}

/**
 * Test action names and descriptions.
 */
function testActionNames(aID, aActions) {
  var actions = typeof aActions == "string" ? [aActions] : aActions || [];

  var acc = getAccessible(aID);
  is(acc.actionCount, actions.length, "Wong number of actions.");
  for (var i = 0; i < actions.length; i++) {
    is(
      acc.getActionName(i),
      actions[i],
      "Wrong action name at " + i + " index."
    );
    is(
      acc.getActionDescription(0),
      gActionDescrMap[actions[i]],
      "Wrong action description at " + i + "index."
    );
  }
}

// //////////////////////////////////////////////////////////////////////////////
// Private

var gActionsQueue = null;

function actionInvoker(aAccOrElmOrId, aActionIndex, aActionName, aEventSeq) {
  this.invoke = function actionInvoker_invoke() {
    var acc = getAccessible(aAccOrElmOrId);
    if (!acc) {
      return INVOKER_ACTION_FAILED;
    }

    var isThereActions = acc.actionCount > 0;
    ok(
      isThereActions,
      "No actions on the accessible for " + prettyName(aAccOrElmOrId)
    );

    if (!isThereActions) {
      return INVOKER_ACTION_FAILED;
    }

    is(
      acc.getActionName(aActionIndex),
      aActionName,
      "Wrong action name of the accessible for " + prettyName(aAccOrElmOrId)
    );

    try {
      acc.doAction(aActionIndex);
    } catch (e) {
      ok(false, "doAction(" + aActionIndex + ") failed with: " + e.name);
      return INVOKER_ACTION_FAILED;
    }
    return null;
  };

  this.eventSeq = aEventSeq;

  this.getID = function actionInvoker_getID() {
    return (
      "invoke an action " +
      aActionName +
      " at index " +
      aActionIndex +
      " on " +
      prettyName(aAccOrElmOrId)
    );
  };
}

function checkerOfActionInvoker(aType, aTarget, aActionObj) {
  this.type = aType;

  this.target = aTarget;

  if (aActionObj && "eventTarget" in aActionObj) {
    this.eventTarget = aActionObj.eventTarget;
  }

  if (aActionObj && aActionObj.allowBubbling) {
    // Normally, we add event listeners on the document. To catch bubbled
    // events, we need to add the listener on the target itself.
    this.eventTarget = "element";
    // Normally, we only match an event fired directly on the target. Override
    // this to match a bubbled event.
    this.match = function (aEvent) {
      return aEvent.currentTarget == aTarget;
    };
  }

  this.phase = false;

  this.getID = function getID() {
    return aType + " event handling";
  };

  this.check = function check(aEvent) {
    if (aType == "click" && aActionObj && "checkOnClickEvent" in aActionObj) {
      aActionObj.checkOnClickEvent(aEvent);
    }
  };
}

var gActionDescrMap = {
  jump: "Jump",
  press: "Press",
  check: "Check",
  uncheck: "Uncheck",
  select: "Select",
  open: "Open",
  close: "Close",
  switch: "Switch",
  click: "Click",
  collapse: "Collapse",
  expand: "Expand",
  activate: "Activate",
  cycle: "Cycle",
  "click ancestor": "Click ancestor",
};