/*
 Copyright 2007-2009 WebDriver committers
 Copyright 2007-2009 Google Inc.
 Portions copyright 2011 Software Freedom Conservancy

 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.
 */


goog.provide('WebElement');

goog.require('Utils');
goog.require('WebLoadingListener');
goog.require('bot.ErrorCode');
goog.require('bot.action');
goog.require('bot.dom');
goog.require('fxdriver.io');
goog.require('fxdriver.logging');
goog.require('fxdriver.moz');
goog.require('fxdriver.preconditions');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.selection');
goog.require('goog.math.Coordinate');
goog.require('webdriver.atoms.element');


WebElement.elementEquals = function(respond, parameters) {
  try {
    var elementA = Utils.getElementAt(parameters.id,
                                      respond.session.getDocument());
    var elementB = Utils.getElementAt(parameters.other,
                                      respond.session.getDocument());
    respond.value = elementA == elementB;
  } catch (e) {
    if (e.code && e.code == bot.ErrorCode.STALE_ELEMENT_REFERENCE) {
      // Assume any style elements are not equal to any others.
      // Users shouldn't care about equality of stale elements.
      respond.value = false;
    } else {
      throw e;
    }
  }
  respond.send();
};

WebElement.clickElement = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());

  var unwrapped = fxdriver.moz.unwrapFor4(element);
  var nativeMouse = Utils.getNativeMouse();
  var node = Utils.getNodeForNativeEvents(unwrapped);
  var appInfo = Components.classes['@mozilla.org/xre/app-info;1'].
      getService(Components.interfaces.nsIXULAppInfo);
  var versionChecker = Components.
      classes['@mozilla.org/xpcom/version-comparator;1'].
      getService(Components.interfaces.nsIVersionComparator);

  // I'm having trouble getting clicks to work on Firefox 2 on Windows. Always
  // fall back for that
  var useNativeClick =
      versionChecker.compare(appInfo.platformVersion, '1.9') >= 0;
  var thmgr_cls = Components.classes['@mozilla.org/thread-manager;1'];

  // For now, we need to bypass native events for option elements
  var isOption = 'option' == unwrapped.tagName.toLowerCase();

  var location = Utils.getLocation(unwrapped, unwrapped.tagName == 'A');
  var elementHalfWidth = (location.width ? Math.floor(location.width / 2) : 0);
  var elementHalfHeight = (location.height ? Math.floor(location.height / 2) : 0);

  if (!isOption && this.enableNativeEvents && nativeMouse && node && useNativeClick && thmgr_cls) {
    fxdriver.logging.info('Using native events for click');

    var inViewAfterScroll = bot.action.scrollIntoView(
        unwrapped,
        new goog.math.Coordinate(elementHalfWidth, elementHalfHeight));

    var isSVG = Utils.isSVG(element.ownerDocument);

    if (!isSVG && !inViewAfterScroll) {
        respond.sendError(
            new WebDriverError(bot.ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS,
                'Element cannot be scrolled into view:' + element));
        return;
    }

    location = Utils.getLocationRelativeToWindowHandle(unwrapped, unwrapped.tagName == 'A');
    var x = location.x + elementHalfWidth;
    var y = location.y + elementHalfHeight;

    try {
      var currentPosition = respond.session.getMousePosition();
      var clickPosition = new goog.math.Coordinate(x, y);
      var browserOffset = Utils.getBrowserSpecificOffset(respond.session.getBrowser());

      nativeMouse.mouseMove(node,
          currentPosition.x + browserOffset.x, currentPosition.y + browserOffset.y,
          clickPosition.x + browserOffset.x, clickPosition.y + browserOffset.y);

      var pageUnloadedIndicator = Utils.getPageUnloadedIndicator(unwrapped);

      nativeMouse.click(node,
          clickPosition.x + browserOffset.x, clickPosition.y + browserOffset.y, 1);

      respond.session.setMousePosition(clickPosition.x, clickPosition.y);

      Utils.waitForNativeEventsProcessing(unwrapped, Utils.getNativeEvents(),
          pageUnloadedIndicator, this.jsTimer);

      respond.send();

      return;
    } catch (e) {
      // Make sure that we only fall through only if
      // the error returned from the native call indicates it's not
      // implemented.

      fxdriver.logging.info('Detected error when clicking: ' + e.name);

      if (e.name != 'NS_ERROR_NOT_IMPLEMENTED') {
        throw new WebDriverError(bot.ErrorCode.INVALID_ELEMENT_STATE, e);
      }

      // Fall through to the synthesized click code.
    }
  }

  fxdriver.logging.info('Falling back to synthesized click');

  // TODO(simon): Delete the above and sink most of it into a "nativeMouse"
  Utils.installWindowCloseListener(respond);
  Utils.installClickListener(respond, WebLoadingListener);

  var res = this.mouse.move(element, elementHalfWidth, elementHalfHeight);
  if (res.status != bot.ErrorCode.SUCCESS) {
    respond.status = res.status;
    respond.value = res.message;
    respond.send();
    return;
  }

  res = this.mouse.click(element);
  respond.status = res.status;
  respond.value = res.message;
};
WebElement.clickElement.preconditions =
    [fxdriver.preconditions.visible];


WebElement.getElementText = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());
  respond.value = webdriver.atoms.element.getText(element);
  respond.send();
};


WebElement.sendKeysToElement = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());

  var browser = respond.session.getBrowser();
  var dispatcher = browser.ownerDocument.commandDispatcher;
  var currentDocument =
      dispatcher.focusedElement && goog.dom.getOwnerDocument(dispatcher.focusedElement);
  currentDocument = currentDocument ? new XPCNativeWrapper(currentDocument) : null;

  var alreadyFocused = true;
  var currentlyActive = Utils.getActiveElement(respond.session.getDocument());
  var newDocument = goog.dom.getOwnerDocument(currentlyActive);

  if (currentlyActive != element || currentDocument != new XPCNativeWrapper(newDocument)) {
    fxdriver.logging.info('Need to switch focus');
    alreadyFocused = false;
    currentlyActive.blur();
    element.focus();
    element.ownerDocument.defaultView.focus();
  } else {
    fxdriver.logging.info('No need to switch focus');
  }

  var use = element;
  var tagName = element.tagName.toLowerCase();
  if (tagName == 'body' && element.ownerDocument.defaultView.frameElement) {
    element.ownerDocument.defaultView.focus();

    // Turns out, this is what we should be using as the target
    // to send events to
    use = element.ownerDocument.getElementsByTagName('html')[0];
  }

  // Handle the special case of the file input element here

  if (bot.dom.isElement(element, goog.dom.TagName.INPUT)) {
    var inputtype = element.getAttribute('type');
    if (inputtype && inputtype.toLowerCase() == 'file') {
      element.value = parameters.value.join('');
      Utils.fireHtmlEvent(element, 'change');
      respond.send();
      return;
    }
  }

  var originalDriver = this;

  // We may need a beat for firefox to hand over focus.
  this.jsTimer.setTimeout(function() {
    // Unless the element already had focus, set the cursor location to the end of the line
    // TODO(simon): This seems a little arbitrary.
    if (!alreadyFocused && bot.dom.isEditable(element)) {
        var length = element.value ? element.value.length : goog.dom.getTextContent(element).length;
        goog.dom.selection.setCursorPosition(element, length);
    }

    Utils.type(respond.session.getDocument(), use, parameters.value.join(''),
        originalDriver.enableNativeEvents, originalDriver.jsTimer, true /*release modifiers*/);

    respond.send();
  }, 0);
};
WebElement.sendKeysToElement.preconditions =
    [fxdriver.preconditions.visible, fxdriver.preconditions.enabled];


WebElement.clearElement = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());
  bot.setWindow(respond.session.getWindow());
  bot.action.clear(element);
  respond.send();
};
WebElement.clearElement.preconditions =
    [fxdriver.preconditions.visible, fxdriver.preconditions.enabled, fxdriver.preconditions.writable];


WebElement.getElementTagName = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());

  respond.value = element.tagName.toLowerCase();
  respond.send();
};


WebElement.getElementAttribute = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                  respond.session.getDocument());
  var attributeName = parameters.name;

  respond.value = webdriver.atoms.element.getAttribute(element, attributeName);
  respond.send();
};


WebElement.isElementEnabled = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());
  respond.value = bot.dom.isEnabled(element);
  respond.send();
};


WebElement.submitElement = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());

  if (element) {
    while (element.parentNode != null && element.tagName.toLowerCase() != 'form') {
      element = element.parentNode;
    }
    if (element.tagName && element.tagName.toLowerCase() == 'form') {
      var current = respond.session.getWindow().location;
      if (Utils.fireHtmlEvent(element, 'submit') &&
          fxdriver.io.isLoadExpected(current, element.action)) {
        new WebLoadingListener(respond.session.getBrowser(), function(timedOut) {
          if (timedOut) {
            respond.sendError(new WebDriverError(bot.ErrorCode.TIMEOUT,
                'Timed out waiting for page load.'));
          } else {
            respond.send();
          }
        }, respond.session.getPageLoadTimeout(), respond.session.getWindow());
        element.submit();
        return;
      } else {
        //Event was blocked, so don't submit
        respond.send();
        return;
      }
    } else {
      throw new WebDriverError(bot.ErrorCode.NO_SUCH_ELEMENT,
          "Element was not in a form so couldn't submit");
    }
  }
  respond.send();
};

WebElement.isElementSelected = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());

  var selected = false;

  try {
    var option =
        element.QueryInterface(Components.interfaces.nsIDOMHTMLOptionElement);
    selected = option.selected;
  } catch (e) {
  }

  try {
    var inputElement =
        element.QueryInterface(Components.interfaces.nsIDOMHTMLInputElement);
    if (inputElement.type == 'checkbox' || inputElement.type == 'radio') {
      selected = inputElement.checked;
    }
  } catch (e) {
  }

 respond.value = selected;
  respond.send();
};


WebElement.isElementDisplayed = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());
  respond.value = bot.dom.isShown(element);
  respond.send();
};


WebElement.getElementLocation = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());

  var location = Utils.getElementLocation(element);

  respond.value = {
    x: Math.round(location.x),
    y: Math.round(location.y)
  };

  respond.send();
};


WebElement.getElementSize = function(respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());

  var box = Utils.getLocation(element);

  respond.value = {
    width: Math.round(box.width),
    height: Math.round(box.height)
  };
  respond.send();
};


WebElement.getElementValueOfCssProperty = function(respond,
                                                                parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());
  respond.value = bot.dom.getEffectiveStyle(element, parameters.propertyName);
  respond.send();
};


WebElement.getElementLocationOnceScrolledIntoView = function(
    respond, parameters) {
  var element = Utils.getElementAt(parameters.id,
                                   respond.session.getDocument());

  var theDoc = element.ownerDocument;
  Utils.getMainDocumentElement(theDoc).focus();
  var elementLocation = Utils.getLocationOnceScrolledIntoView(element);

  respond.value = {
    x: Math.round(elementLocation.x),
    y: Math.round(elementLocation.y)
  };

  respond.send();
};


