text: /*!
   *
   * Copyright (c) 2013 Sebastian Golasch
   *
   * Permission is hereby granted, free of charge, to any person obtaining a
   * copy of this software and associated documentation files (the "Software"),
   * to deal in the Software without restriction, including without limitation
   * the rights to use, copy, modify, merge, publish, distribute, sublicense,
   * and/or sell copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included
   * in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   * DEALINGS IN THE SOFTWARE.
   *
   */
  
  'use strict';
  
  // ext. libs
  var Q = require('q');
  
  /**
   * Element related methods
   *
   * @module Driver
   * @class Element
   * @namespace Dalek.DriverNative.Commands
   */
  
  var Element = {
  
    /**
     * Checks if an element exists
     *
     * @method exists
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    exists: function (selector, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this._existsCb.bind(this, selector, hash));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `exists` call
     *
     * @method _existsCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} result Serialized JSON with the reuslts of the exists call
     * @return {object} promise Exists promise
     * @private
     */
  
    _existsCb: function (selector, hash, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'exists', selector: selector, hash: hash, value: (JSON.parse(result).value === -1 || JSON.parse(result).status === 7 ? 'false' : 'true')});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Checks if an element is visible
     *
     * @method visible
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    visible: function (selector, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.displayed.bind(this.webdriverClient, selector));
      this.actionQueue.push(this._visibleCb.bind(this, selector, hash));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `visible` call
     *
     * @method _visibleCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} result Serialized JSON with the reuslts of the visible call
     * @return {object} promise Visible promise
     * @private
     */
  
    _visibleCb: function (selector, hash, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'visible', selector: selector, hash: hash, value: JSON.parse(result).value});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Checks if an element has the expected text
     *
     * @method text
     * @param {string} selector Selector expression to find the element
     * @param {string} expected The expected text content
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    text: function (selector, expected, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.text.bind(this.webdriverClient, selector));
      this.actionQueue.push(this._textCb.bind(this, selector, hash, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `text` call
     *
     * @method _textCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} expected The expected text content
     * @param {string} result Serialized JSON with the reuslts of the text call
     * @return {object} Promise
     * @private
     */
  
    _textCb: function (selector, hash, expected, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'text', hash: hash, expected: expected, selector: selector, value: JSON.parse(result).value});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Checks if an element has the expected css property/value pairs
     *
     * @method css
     * @param {string} selector Selector expression to find the element
     * @param {string} property The css property to check
     * @param {string} expected The expected css value
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    css: function (selector, property, expected, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.cssProperty.bind(this.webdriverClient, property));
      this.actionQueue.push(this._cssCb.bind(this, selector, property, hash, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `css` call
     *
     * @method _cssCb
     * @param {string} selector Selector expression to find the element
     * @param {string} property The css property to check
     * @param {string} expected The expected css value
     * @param {string} hash Unique hash of that fn call
     * @param {string} result Serialized JSON with the reuslts of the text call
     * @return {object} Promise
     * @private
     */
    
    _cssCb: function (selector, property, hash, expected, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'css', hash: hash, property: property, expected: expected, selector: selector, value: JSON.parse(result).value});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Checks th width of an element
     *
     * @method width
     * @param {string} selector Selector expression to find the element
     * @param {string} expected The expected width value
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    width: function (selector, expected, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.size.bind(this.webdriverClient));
      this.actionQueue.push(this._widthCb.bind(this, selector, hash, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `width` call
     *
     * @method _widthCb
     * @param {string} selector Selector expression to find the element
     * @param {string} expected The expected width value
     * @param {string} hash Unique hash of that fn call
     * @param {string} result Serialized JSON with the reuslts of the text call
     * @return {object} Promise
     * @private
     */
  
    _widthCb: function (selector, hash, expected, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'width', hash: hash, expected: expected, selector: selector, value: JSON.parse(result).value.width});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Checks th height of an element
     *
     * @method height
     * @param {string} selector Selector expression to find the element
     * @param {string} expected The expected height value
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    height: function (selector, expected, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.size.bind(this.webdriverClient));
      this.actionQueue.push(this._heightCb.bind(this, selector, hash, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `height` call
     *
     * @method _heightCb
     * @param {string} selector Selector expression to find the element
     * @param {string} expected The expected height value
     * @param {string} hash Unique hash of that fn call
     * @param {string} result Serialized JSON with the reuslts of the text call
     * @return {object} Promise
     * @private
     */
  
    _heightCb: function (selector, hash, expected, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'height', hash: hash, expected: expected, selector: selector, value: JSON.parse(result).value.height});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Checks if an element has an attribute with the expected value
     *
     * @method attribute
     * @param {string} selector Selector expression to find the element
     * @param {string} attribute The attribute that should be checked
     * @param {string} expected The expected text content
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    attribute: function (selector, attribute, expected, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.getAttribute.bind(this.webdriverClient, attribute));
      this.actionQueue.push(this._attributeCb.bind(this, selector, hash, attribute, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `attribute` call
     *
     * @method _attributeCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} attribute The attribute that should be checked
     * @param {string} expected The expected attribute content
     * @param {string} result Serialized JSON with the results of the attribute call
     * @return {object} promise Attribute promise
     * @private
     */
  
    _attributeCb: function (selector, hash, attribute, expected, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'attribute', selector: selector, hash: hash, expected: expected, value: JSON.parse(result).value });
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Checks if an element has the expected value
     *
     * @method val
     * @param {string} selector Selector expression to find the element
     * @param {string} expected The expected content
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    val: function (selector, expected, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.getAttribute.bind(this.webdriverClient, 'value'));
      this.actionQueue.push(this._valCb.bind(this, selector, hash, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `val` call
     *
     * @method _valCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} expected The expected content
     * @param {string} result Serialized JSON with the results of the val call
     * @return {object} Promise
     * @private
     */
  
    _valCb: function (selector, hash, expected, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'val', selector: selector, hash: hash, expected: expected, value: JSON.parse(result).value });
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Checks if an element is selected
     *
     * @method selected
     * @param {string} selector Selector expression to find the element
     * @param {string} expected The expected content
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    selected: function (selector, expected, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.selected.bind(this.webdriverClient));
      this.actionQueue.push(this._selectedCb.bind(this, selector, hash, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `selected` call
     *
     * @method _selectedCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} expected The expected content
     * @param {string} result Serialized JSON with the results of the selected call
     * @return {object} Promise
     * @private
     */
  
    _selectedCb: function (selector, hash, expected, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'selected', selector: selector, hash: hash, expected: expected, value: JSON.parse(result).value });
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Checks if an element is enabled
     *
     * @method enabled
     * @param {string} selector Selector expression to find the element
     * @param {string} expected The expected content
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    enabled: function (selector, expected, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.enabled.bind(this.webdriverClient));
      this.actionQueue.push(this._enabledCb.bind(this, selector, hash, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `enabled` call
     *
     * @method _enabledCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} expected The expected content
     * @param {string} result Serialized JSON with the results of the selected call
     * @return {object} Promise
     * @private
     */
  
    _enabledCb: function (selector, hash, expected, result) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'enabled', selector: selector, hash: hash, expected: expected, value: JSON.parse(result).value });
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Submits a form
     *
     * @method submit
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} uuid Unique hash of that fn call
     * @chainable
     */
  
    submit: function (selector, hash, uuid) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.submit.bind(this.webdriverClient));
      this.actionQueue.push(this._submitCb.bind(this, selector, hash, uuid));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `submit` call
     *
     * @method _submitCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} uuid Unique hash of that fn call
     * @return {object} promise Click promise
     * @private
     */
  
    _submitCb: function (selector, hash, uuid) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'submit', value: selector, uuid: uuid, hash: hash});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Clicks an element
     *
     * @method click
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} uuid Unique hash of that fn call
     * @chainable
     */
  
    click: function (selector, hash, uuid) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.click.bind(this.webdriverClient));
      this.actionQueue.push(this._clickCb.bind(this, selector, hash, uuid));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `click` call
     *
     * @method _clickCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} uuid Unique hash of that fn call
     * @return {object} promise Click promise
     * @private
     */
  
    _clickCb: function (selector, hash, uuid) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'click', value: selector, uuid: uuid, hash: hash});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Scrolls from an element to a location defined in pixels
     *
     * @method scroll
     * @param {string} selector Selector expression to find the element
     * @param {object} options X offset, Y offset, Speed
     * @param {string} hash Unique hash of that fn call
     * @param {string} uuid Unique hash of that fn call
     * @chainable
     */
  
    scroll: function (selector, options, hash, uuid) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector, options));
      this.actionQueue.push(this.webdriverClient.scroll.bind(this.webdriverClient));
      this.actionQueue.push(this._clickCb.bind(this, selector, options, hash, uuid));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `scroll` call
     *
     * @method _scrollCb
     * @param {string} selector Selector expression to find the element
     * @param {object} options X offset, Y offset, Speed
     * @param {string} hash Unique hash of that fn call
     * @param {string} uuid Unique hash of that fn call
     * @return {object} promise Scroll promise
     * @private
     */
  
    _scrollCb: function (selector, options, hash, uuid) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'scroll', value: selector, uuid: uuid, hash: hash});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Clicks an element
     *
     * @method click
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    type: function (selector, keystrokes, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.val.bind(this.webdriverClient, keystrokes));
      this.actionQueue.push(this._typeCb.bind(this, selector, keystrokes, hash));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `type` call
     *
     * @method _typeCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @return {object} promise Type promise
     * @private
     */
  
    _typeCb: function (selector, keystrokes, hash) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'type', value: selector, keystrokes: keystrokes, uuid: hash, hash: hash});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Sends keys to an element (whether or not it is an input)
     *
     * @method sendKeys
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @chainable
     */
  
    sendKeys: function (selector, keystrokes, hash) {
      this.actionQueue.push(this.webdriverClient.element.bind(this.webdriverClient, selector));
      this.actionQueue.push(this.webdriverClient.sendKeys.bind(this.webdriverClient, keystrokes));
      this.actionQueue.push(this._sendKeysCb.bind(this, selector, keystrokes, hash));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `sendKeys` call
     *
     * @method _sendKeysCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @return {object} promise Type promise
     * @private
     */
  
    _sendKeysCb: function (selector, keystrokes, hash) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'sendKeys', value: selector, keystrokes: keystrokes, uuid: hash, hash: hash});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Wait for an element for a specific amount of time
     *
     * @method waitForElement
     * @param {string} selector Selector expression to find the element
     * @param {integer} timeout Time to wait in ms
     * @param {string} hash Unique hash of that fn call
     * @param {string} uuid Unique hash of that fn call
     * @chainable
     */
  
    waitForElement: function (selector, timeout, hash, uuid) {
      this.actionQueue.push(this.webdriverClient.implicitWait.bind(this.webdriverClient, timeout));
      this.actionQueue.push(this._waitForElementCb.bind(this, selector, hash, uuid));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `waitForElement` call
     *
     * @method _waitForElementCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {string} uuid Unique hash of that fn call
     * @return {object} promise WaitForElement promise
     * @private
     */
  
    _waitForElementCb: function (selector, hash, uuid) {
      var deferred = Q.defer();
      this.events.emit('driver:message', {key: 'waitForElement', selector: selector, uuid: uuid, hash: hash});
      deferred.resolve();
      return deferred.promise;
    },
  
    /**
     * Returns the number of elements matched by the selector
     *
     * @method getNumberOfElements
     * @param {string} selector Selector expression to find the elements
     * @param {integer} expected Expected number of matched elements
     * @param {string} uuid Unique hash of that fn call
     * @chainable
     */
  
    getNumberOfElements: function (selector, expected, hash) {
      this.actionQueue.push(this.webdriverClient.elements.bind(this.webdriverClient, selector));
      this.actionQueue.push(this._getNumberOfElementsCb.bind(this, selector, hash, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `getNumberOfElements` call
     *
     * @method _getNumberOfElementsCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {integer} expected Expected number of matched elements
     * @param {string} res Serialized JSON with the results of the getNumberOfElements call
     * @return {object} promise GetNumberOfElements promise
     * @private
     */
  
    _getNumberOfElementsCb: function (selector, hash, expected, res) {
      var deferred = Q.defer();
      var result = JSON.parse(res);
      // check if the expression matched any element
      if (result.value === -1) {
        this.events.emit('driver:message', {key: 'numberOfElements', hash: hash, selector: selector, expected: expected, value: 0});
      } else {
        this.events.emit('driver:message', {key: 'numberOfElements', selector: selector, expected: expected, hash: hash, value: result.value.length});
      }
  
      deferred.resolve();
      return deferred.promise;
    },
  
  
    /**
     * Returns the number of visible elements matched by the selector
     *
     * @method getNumberOfVisibleElements
     * @param {string} selector Selector expression to find the elements
     * @param {integer} expected Expected number of matched elements
     * @param {string} uuid Unique hash of that fn call
     * @chainable
     */
  
    getNumberOfVisibleElements: function (selector, expected, hash) {
      this.actionQueue.push(this.webdriverClient.elements.bind(this.webdriverClient, selector));
      this.actionQueue.push(function (result) {
        var deferred = Q.defer();
        var res = JSON.parse(result);
        var resLength = res.value.length;
        var curLength = 0;
        var visibleElement = [];
  
        res.value.forEach(function (element) {
          var fakeResponse = JSON.stringify({sessionId: res.sessionId, status: 0, value: element.ELEMENT});
          this.webdriverClient.options.id = element.ELEMENT;
          this.webdriverClient.displayed.bind(this.webdriverClient, selector)(fakeResponse).then(function (visRes) {
            curLength++;
            if (JSON.parse(visRes).value === true) {
              visibleElement.push(element);
            }
            if (curLength === resLength) {
              deferred.resolve(JSON.stringify({sessionId: res.sessionId, status: 0, value: visibleElement}));
            }
          });
        }.bind(this));
  
        return deferred.promise;
      }.bind(this));
      this.actionQueue.push(this._getNumberOfVisibleElementsCb.bind(this, selector, hash, expected));
      return this;
    },
  
    /**
     * Sends out an event with the results of the `getNumberOfVisibleElements` call
     *
     * @method _getNumberOfElementsCb
     * @param {string} selector Selector expression to find the element
     * @param {string} hash Unique hash of that fn call
     * @param {integer} expected Expected number of matched elements
     * @param {string} res Serialized JSON with the results of the getNumberOfVisibleElements call
     * @return {object} promise GetNumberOfElements promise
     * @private
     */
  
    _getNumberOfVisibleElementsCb: function (selector, hash, expected, res) {
      var deferred = Q.defer();
      var result = JSON.parse(res);
  
      // check if the expression matched any element
      if (result.value === -1) {
        this.events.emit('driver:message', {key: 'numberOfVisibleElements', hash: hash, selector: selector, expected: expected, value: 0});
      } else {
        this.events.emit('driver:message', {key: 'numberOfVisibleElements', selector: selector, expected: expected, hash: hash, value: result.value.length});
      }
  
      deferred.resolve();
      return deferred.promise;
    }
  
  };
  
  /**
   * Mixes in element methods
   *
   * @param {Dalek.DriverNative} DalekNative Native driver base class
   * @return {Dalek.DriverNative} DalekNative Native driver base class
   */
  
  module.exports = function (DalekNative) {
    // mixin methods
    Object.keys(Element).forEach(function (fn) {
      DalekNative.prototype[fn] = Element[fn];
    });
  
    return DalekNative;
  };
  