/**
* @flow
* This file is generated automatically, run npm run build to re-generate.
**/

import { inspect } from 'util';
import type Driver from './driver';
import type { ElementHandle } from './flow-types/element-handle';
import type { SelectorType } from './enums/selector-types';
import addDebugging from './add-debugging';
import BaseClass from './base-class';
import Mouse from './mouse';
import SelectorTypes from './enums/selector-types';
import Touch from './touch';
import waitFor from './utils/wait-for';

/*
 * A representation of a remote element.  You can use it to click on, type text into and check the attributes of.
 *
 * ```js
 * const element = await driver.activeWindow.getElement('[data-test-id="my-test-element"]');
 *
 * const elements = await driver.activeWindow.getElements('.some-class-name');
 * ```
 */
class Element extends BaseClass {
  _parent: Element | Driver;
  _selector: string;

  /*
   * The selenium id of the element
   */
  elementID: string;
  /*
   * Utility methods for clicking on the element
   */
  mouse: Mouse;
  /*
   * Methods for interacting with the element via touch
   */
  touch: Touch;
  constructor(driver: Driver, parent: Element | Driver, selector: string, elementHandle: ElementHandle) {
    const prefix = '/element/' + elementHandle.ELEMENT;
    super(driver, prefix);
    this._parent = parent;
    this._selector = selector;

    this.elementID = elementHandle.ELEMENT;
    this.mouse = new Mouse(this.driver, this);
    this.touch = new Touch(this.driver, this);
  }

  /*
   * Get the value of an attribute.
   */
  getAttribute(attribute: string): string {
    return this.requestJSON('GET', '/attribute/' + attribute);
  }

  /*
   * Test if the element have a specific class
   */
  hasClass(className: string): boolean {
    const classNames = this.getAttribute('class');
    return !!classNames.match(new RegExp('\\b' + className + '\\b'));
  }

  /*
   * Get the text body of an element.
   */
  getText(): string {
    return this.requestJSON('GET', '/text');
  }

  /*
   * Get the tag-name of an element.
   */
  getTagName(): string {
    return this.requestJSON('GET', '/name');
  }

  /*
   * Query the value of an element's computed CSS property. The CSS property to query
   * should be specified using the CSS property name, not the JavaScript property name
   * (e.g. background-color instead of backgroundColor).
   */
  getCssValue(property: string): string {
    return this.requestJSON('GET', '/css/' + property);
  }

  /*
   * Return true if the element is currently displayed on the page
   */
  isDisplayed(): boolean {
    return this.requestJSON('GET', '/displayed');
  }

  /*
   * Return true if the form element is selected
   */
  isSelected(): boolean {
    return this.requestJSON('GET', '/selected');
  }

  /*
   * Return true if the current element is equal to the supplied element
   */
  isEqual(element: Element): boolean {
    return this.requestJSON('GET', '/equals/' + element.elementID);
  }

  /*
   * Return true if the form element is enabled
   */
  isEnabled(): boolean {
    return this.requestJSON('GET', '/enabled');
  }

  /*
   * Return true if the form element is disabled
   */
  isDisabled(): boolean {
    const isEnabled = this.isEnabled();
    return !isEnabled;
  }

  // todo: uploading files ala wd.Element.sendKeys
  /*
   * Type a string of characters into an input
   */
  sendKeys(str: string | Array<string>): void {
    this.requestJSON('POST', '/value', { value: Array.isArray(str) ? str : [str] });
  }

  /*
   * Clear the value of an input
   */
  clear(): void {
    this.requestJSON('POST', '/clear');
  }

  /*
   * Submit a form element
   */
  submit(): void {
    this.requestJSON('POST', '/submit');
  }

  /*
   * Get the size of an element
   */
  getSize(): { width: number; height: number; } {
    const size = this.requestJSON('GET', '/size');
    return { width: size.width, height: size.height };
  }

  /*
   * Get the position of an element
   */
  getPosition(): { x: number; y: number; } {
    const position = this.requestJSON('GET', '/location');
    return { x: position.x, y: position.y };
  }

  /*
   * Get the frame of an element
   */
  getFrame(): { x: number; y: number; width: number; height: number; } {
    const position = this.getPosition();
    const size = this.getSize();
    return { x: position.x, y: position.y, width: size.width, height: size.height };
  }

  /*
   * Get the absolute center of an element
   */
  getAbsoluteCenter(): { x: number; y: number; } {
    const frame = this.getFrame();
    return { x: Math.floor(frame.width / 2) + frame.x, y: Math.floor(frame.height / 2) + frame.y };
  }

  /*
   * Get the relative center of an element
   */
  getRelativeCenter(): { x: number; y: number; } {
    const size = this.getSize();
    return { x: Math.floor(size.width / 2), y: Math.floor(size.height / 2) };
  }

  /*
   * Get an element via a selector.
   * Will throw an error if the element does not exist.
   */
  getElement(selector: string, selectorType: SelectorType = SelectorTypes.CSS): Element {
    const elementHandle = waitFor(() => this.requestJSON('POST', '/element', { using: selectorType, value: selector }));
    return new Element(this.driver, this, [this._selector, selector].join(' '), elementHandle);
  }

  /*
   * Get an element via a selector.
   * Will return null if the element does not exist
   */
  tryGetElement(selector: string, selectorType: SelectorType = SelectorTypes.CSS): Element | null {
    try {
      const elementHandle = this.requestJSON('POST', '/element', { using: selectorType, value: selector });
      return new Element(this.driver, this, [this._selector, selector].join(' '), elementHandle);
    } catch (ex) {
      if (ex.code === 'NoSuchElement' || ex.code === 'ElementNotVisible' || ex.code === 'ElementIsNotSelectable') {
        return null;
      }
      throw ex;
    }
  }

  /*
   * Get elements via a selector.
   */
  getElements(selector: string, selectorType: SelectorType = SelectorTypes.CSS): Array<Element> {
    const elementHandles = this.requestJSON('POST', '/elements', { using: selectorType, value: selector });
    return elementHandles.map(elementHandle => {
      return new Element(this.driver, this, [this._selector, selector].join(' '), elementHandle);
    });
  }

  /*
   * Get elements by its text content, optionally narrowed down using a selector.
   *
   * N.B. this is **much** slower than getting elements by ID or css selector.
   */
  getElementsByTextContent(textContent: string, selector: string = '*', selectorType: SelectorType = SelectorTypes.CSS): Array<Element> {
    if (selector === 'a' && (selectorType === SelectorTypes.CSS || selectorType === SelectorTypes.TAG)) {
      selector = textContent;
      selectorType = SelectorTypes.PARTIAL_LINK_TEXT;
    }
    textContent = textContent.trim();
    const elements = this.getElements(selector, selectorType);
    const elementsToReturn = [];
    for (let i = 0; i < elements.length; i++) {
      if (textContent === elements[i].getText().trim()) {
        elementsToReturn.push(elements[i]);
      }
    }
    return elementsToReturn;
  }

  /*
   * Get elements by its text content, optionally narrowed down using a selector.
   *
   * N.B. this is **much** slower than getting elements by ID or css selector.
   */
  getElementByTextContent(textContent: string, selector: string = '*', selectorType: SelectorType = SelectorTypes.CSS): Element {
    const element = waitFor(() => this.tryGetElementByTextContent(textContent, selector, selectorType));
    if (element) {
      return element;
    }
    const err = new Error('Could not find an element with the text content: ' + textContent);
    err.name = 'NoSuchElement';
    (err: any).code = 'NoSuchElement';
    throw err;
  }

  /*
   * Get elements by its text content, optionally narrowed down using a selector.
   *
   * N.B. this is **much** slower than getting elements by ID or css selector.
   */
  tryGetElementByTextContent(textContent: string, selector: string = '*', selectorType: SelectorType = SelectorTypes.CSS): Element | null {
    if (selector === 'a' && (selectorType === SelectorTypes.CSS || selectorType === SelectorTypes.TAG)) {
      selector = textContent;
      selectorType = SelectorTypes.PARTIAL_LINK_TEXT;
    }
    textContent = textContent.trim().toLowerCase();
    const elements = this.getElements(selector, selectorType);
    for (let i = 0; i < elements.length; i++) {
      if (textContent === elements[i].getText().trim().toLowerCase()) {
        return elements[i];
      }
    }
    return null;
  }

  /*
   * Does a specific element exist?
   */
  hasElement(selector: string, selectorType: SelectorType = SelectorTypes.CSS): boolean {
    const elements = this.getElements(selector, selectorType);
    return elements.length > 0;
  }
}
addDebugging(Element, {
  inspect(obj, depth, options) {
    return 'Element(' + inspect(obj._selector, options) + ')';
  }
});
export default Element;