"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.AndroidWebView = exports.AndroidInput = exports.AndroidSocket = exports.AndroidDevice = exports.Android = void 0;

var _fs = _interopRequireDefault(require("fs"));

var _utils = require("../utils/utils");

var _events = require("./events");

var _browserContext = require("./browserContext");

var _channelOwner = require("./channelOwner");

var _timeoutSettings = require("../utils/timeoutSettings");

var _waiter = require("./waiter");

var _events2 = require("events");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Copyright (c) Microsoft Corporation.
 *
 * 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.
 */
class Android extends _channelOwner.ChannelOwner {
  static from(android) {
    return android._object;
  }

  constructor(parent, type, guid, initializer) {
    super(parent, type, guid, initializer);
    this._timeoutSettings = void 0;
    this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
  }

  setDefaultTimeout(timeout) {
    this._timeoutSettings.setDefaultTimeout(timeout);

    this._channel.setDefaultTimeoutNoReply({
      timeout
    });
  }

  async devices() {
    return this._wrapApiCall('android.devices', async channel => {
      const {
        devices
      } = await channel.devices();
      return devices.map(d => AndroidDevice.from(d));
    });
  }

}

exports.Android = Android;

class AndroidDevice extends _channelOwner.ChannelOwner {
  static from(androidDevice) {
    return androidDevice._object;
  }

  constructor(parent, type, guid, initializer) {
    super(parent, type, guid, initializer);
    this._timeoutSettings = void 0;
    this._webViews = new Map();
    this.input = void 0;
    this.input = new AndroidInput(this);
    this._timeoutSettings = new _timeoutSettings.TimeoutSettings(parent._timeoutSettings);

    this._channel.on('webViewAdded', ({
      webView
    }) => this._onWebViewAdded(webView));

    this._channel.on('webViewRemoved', ({
      pid
    }) => this._onWebViewRemoved(pid));
  }

  _onWebViewAdded(webView) {
    const view = new AndroidWebView(this, webView);

    this._webViews.set(webView.pid, view);

    this.emit(_events.Events.AndroidDevice.WebView, view);
  }

  _onWebViewRemoved(pid) {
    const view = this._webViews.get(pid);

    this._webViews.delete(pid);

    if (view) view.emit(_events.Events.AndroidWebView.Close);
  }

  setDefaultTimeout(timeout) {
    this._timeoutSettings.setDefaultTimeout(timeout);

    this._channel.setDefaultTimeoutNoReply({
      timeout
    });
  }

  serial() {
    return this._initializer.serial;
  }

  model() {
    return this._initializer.model;
  }

  webViews() {
    return [...this._webViews.values()];
  }

  async webView(selector, options) {
    const webView = [...this._webViews.values()].find(v => v.pkg() === selector.pkg);
    if (webView) return webView;
    return this.waitForEvent('webview', { ...options,
      predicate: view => view.pkg() === selector.pkg
    });
  }

  async wait(selector, options) {
    await this._wrapApiCall('androidDevice.wait', async channel => {
      await channel.wait({
        selector: toSelectorChannel(selector),
        ...options
      });
    });
  }

  async fill(selector, text, options) {
    await this._wrapApiCall('androidDevice.fill', async channel => {
      await channel.fill({
        selector: toSelectorChannel(selector),
        text,
        ...options
      });
    });
  }

  async press(selector, key, options) {
    await this.tap(selector, options);
    await this.input.press(key);
  }

  async tap(selector, options) {
    await this._wrapApiCall('androidDevice.tap', async channel => {
      await channel.tap({
        selector: toSelectorChannel(selector),
        ...options
      });
    });
  }

  async drag(selector, dest, options) {
    await this._wrapApiCall('androidDevice.drag', async channel => {
      await channel.drag({
        selector: toSelectorChannel(selector),
        dest,
        ...options
      });
    });
  }

  async fling(selector, direction, options) {
    await this._wrapApiCall('androidDevice.fling', async channel => {
      await channel.fling({
        selector: toSelectorChannel(selector),
        direction,
        ...options
      });
    });
  }

  async longTap(selector, options) {
    await this._wrapApiCall('androidDevice.longTap', async channel => {
      await channel.longTap({
        selector: toSelectorChannel(selector),
        ...options
      });
    });
  }

  async pinchClose(selector, percent, options) {
    await this._wrapApiCall('androidDevice.pinchClose', async channel => {
      await channel.pinchClose({
        selector: toSelectorChannel(selector),
        percent,
        ...options
      });
    });
  }

  async pinchOpen(selector, percent, options) {
    await this._wrapApiCall('androidDevice.pinchOpen', async channel => {
      await channel.pinchOpen({
        selector: toSelectorChannel(selector),
        percent,
        ...options
      });
    });
  }

  async scroll(selector, direction, percent, options) {
    await this._wrapApiCall('androidDevice.scroll', async channel => {
      await channel.scroll({
        selector: toSelectorChannel(selector),
        direction,
        percent,
        ...options
      });
    });
  }

  async swipe(selector, direction, percent, options) {
    await this._wrapApiCall('androidDevice.swipe', async channel => {
      await channel.swipe({
        selector: toSelectorChannel(selector),
        direction,
        percent,
        ...options
      });
    });
  }

  async info(selector) {
    return await this._wrapApiCall('androidDevice.info', async channel => {
      return (await channel.info({
        selector: toSelectorChannel(selector)
      })).info;
    });
  }

  async screenshot(options = {}) {
    return await this._wrapApiCall('androidDevice.screenshot', async channel => {
      const {
        binary
      } = await channel.screenshot();
      const buffer = Buffer.from(binary, 'base64');
      if (options.path) await _fs.default.promises.writeFile(options.path, buffer);
      return buffer;
    });
  }

  async close() {
    return this._wrapApiCall('androidDevice.close', async channel => {
      await channel.close();
      this.emit(_events.Events.AndroidDevice.Close);
    });
  }

  async shell(command) {
    return this._wrapApiCall('androidDevice.shell', async channel => {
      const {
        result
      } = await channel.shell({
        command
      });
      return Buffer.from(result, 'base64');
    });
  }

  async open(command) {
    return this._wrapApiCall('androidDevice.open', async channel => {
      return AndroidSocket.from((await channel.open({
        command
      })).socket);
    });
  }

  async installApk(file, options) {
    return this._wrapApiCall('androidDevice.installApk', async channel => {
      await channel.installApk({
        file: await loadFile(file),
        args: options && options.args
      });
    });
  }

  async push(file, path, options) {
    return this._wrapApiCall('androidDevice.push', async channel => {
      await channel.push({
        file: await loadFile(file),
        path,
        mode: options ? options.mode : undefined
      });
    });
  }

  async launchBrowser(options = {}) {
    return this._wrapApiCall('androidDevice.launchBrowser', async channel => {
      const contextOptions = await (0, _browserContext.prepareBrowserContextParams)(options);
      const {
        context
      } = await channel.launchBrowser(contextOptions);
      return _browserContext.BrowserContext.from(context);
    });
  }

  async waitForEvent(event, optionsOrPredicate = {}) {
    const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);

    const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;

    const waiter = _waiter.Waiter.createForEvent(this, 'androidDevice', event);

    waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
    if (event !== _events.Events.AndroidDevice.Close) waiter.rejectOnEvent(this, _events.Events.AndroidDevice.Close, new Error('Device closed'));
    const result = await waiter.waitForEvent(this, event, predicate);
    waiter.dispose();
    return result;
  }

}

exports.AndroidDevice = AndroidDevice;

class AndroidSocket extends _channelOwner.ChannelOwner {
  static from(androidDevice) {
    return androidDevice._object;
  }

  constructor(parent, type, guid, initializer) {
    super(parent, type, guid, initializer);

    this._channel.on('data', ({
      data
    }) => this.emit(_events.Events.AndroidSocket.Data, Buffer.from(data, 'base64')));

    this._channel.on('close', () => this.emit(_events.Events.AndroidSocket.Close));
  }

  async write(data) {
    return this._wrapApiCall('androidDevice.write', async channel => {
      await channel.write({
        data: data.toString('base64')
      });
    });
  }

  async close() {
    return this._wrapApiCall('androidDevice.close', async channel => {
      await channel.close();
    });
  }

}

exports.AndroidSocket = AndroidSocket;

async function loadFile(file) {
  if ((0, _utils.isString)(file)) return _fs.default.promises.readFile(file, {
    encoding: 'base64'
  }).toString();
  return file.toString('base64');
}

class AndroidInput {
  constructor(device) {
    this._device = void 0;
    this._device = device;
  }

  async type(text) {
    return this._device._wrapApiCall('androidDevice.inputType', async channel => {
      await channel.inputType({
        text
      });
    });
  }

  async press(key) {
    return this._device._wrapApiCall('androidDevice.inputPress', async channel => {
      await channel.inputPress({
        key
      });
    });
  }

  async tap(point) {
    return this._device._wrapApiCall('androidDevice.inputTap', async channel => {
      await channel.inputTap({
        point
      });
    });
  }

  async swipe(from, segments, steps) {
    return this._device._wrapApiCall('androidDevice.inputSwipe', async channel => {
      await channel.inputSwipe({
        segments,
        steps
      });
    });
  }

  async drag(from, to, steps) {
    return this._device._wrapApiCall('androidDevice.inputDragAndDrop', async channel => {
      await channel.inputDrag({
        from,
        to,
        steps
      });
    });
  }

}

exports.AndroidInput = AndroidInput;

function toSelectorChannel(selector) {
  const {
    checkable,
    checked,
    clazz,
    clickable,
    depth,
    desc,
    enabled,
    focusable,
    focused,
    hasChild,
    hasDescendant,
    longClickable,
    pkg,
    res,
    scrollable,
    selected,
    text
  } = selector;

  const toRegex = value => {
    if (value === undefined) return undefined;
    if (value instanceof RegExp) return value.source;
    return '^' + value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d') + '$';
  };

  return {
    checkable,
    checked,
    clazz: toRegex(clazz),
    pkg: toRegex(pkg),
    desc: toRegex(desc),
    res: toRegex(res),
    text: toRegex(text),
    clickable,
    depth,
    enabled,
    focusable,
    focused,
    hasChild: hasChild ? {
      selector: toSelectorChannel(hasChild.selector)
    } : undefined,
    hasDescendant: hasDescendant ? {
      selector: toSelectorChannel(hasDescendant.selector),
      maxDepth: hasDescendant.maxDepth
    } : undefined,
    longClickable,
    scrollable,
    selected
  };
}

class AndroidWebView extends _events2.EventEmitter {
  constructor(device, data) {
    super();
    this._device = void 0;
    this._data = void 0;
    this._pagePromise = void 0;
    this._device = device;
    this._data = data;
  }

  pid() {
    return this._data.pid;
  }

  pkg() {
    return this._data.pkg;
  }

  async page() {
    if (!this._pagePromise) this._pagePromise = this._fetchPage();
    return this._pagePromise;
  }

  async _fetchPage() {
    return this._device._wrapApiCall('androidWebView.page', async channel => {
      const {
        context
      } = await channel.connectToWebView({
        pid: this._data.pid,
        sdkLanguage: 'javascript'
      });
      return _browserContext.BrowserContext.from(context).pages()[0];
    });
  }

}

exports.AndroidWebView = AndroidWebView;
//# sourceMappingURL=android.js.map