/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {
  createFactory,
  PureComponent,
} = require("resource://devtools/client/shared/vendor/react.mjs");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
const {
  connect,
} = require("resource://devtools/client/shared/vendor/react-redux.js");

const Toolbar = createFactory(
  require("resource://devtools/client/responsive/components/Toolbar.js")
);

loader.lazyGetter(this, "DeviceModal", () =>
  createFactory(
    require("resource://devtools/client/responsive/components/DeviceModal.js")
  )
);

const {
  changeNetworkThrottling,
} = require("resource://devtools/client/shared/components/throttling/actions.js");
const {
  addCustomDevice,
  editCustomDevice,
  removeCustomDevice,
  updateDeviceDisplayed,
  updateDeviceModal,
  updatePreferredDevices,
} = require("resource://devtools/client/responsive/actions/devices.js");
const {
  takeScreenshot,
} = require("resource://devtools/client/responsive/actions/screenshot.js");
const {
  changeUserAgent,
  toggleLeftAlignment,
  toggleReloadOnTouchSimulation,
  toggleReloadOnUserAgent,
  toggleTouchSimulation,
  toggleUserAgentInput,
} = require("resource://devtools/client/responsive/actions/ui.js");
const {
  changeDevice,
  changePixelRatio,
  changeViewportAngle,
  removeDeviceAssociation,
  resizeViewport,
  rotateViewport,
} = require("resource://devtools/client/responsive/actions/viewports.js");
const {
  getOrientation,
} = require("resource://devtools/client/responsive/utils/orientation.js");

const Types = require("resource://devtools/client/responsive/types.js");

class App extends PureComponent {
  static get propTypes() {
    return {
      devices: PropTypes.shape(Types.devices).isRequired,
      dispatch: PropTypes.func.isRequired,
      leftAlignmentEnabled: PropTypes.bool.isRequired,
      networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
      screenshot: PropTypes.shape(Types.screenshot).isRequired,
      viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
    };
  }

  constructor(props) {
    super(props);

    this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
    this.onChangeDevice = this.onChangeDevice.bind(this);
    this.onChangeNetworkThrottling = this.onChangeNetworkThrottling.bind(this);
    this.onChangePixelRatio = this.onChangePixelRatio.bind(this);
    this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this);
    this.onChangeUserAgent = this.onChangeUserAgent.bind(this);
    this.onChangeViewportOrientation =
      this.onChangeViewportOrientation.bind(this);
    this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this);
    this.onEditCustomDevice = this.onEditCustomDevice.bind(this);
    this.onExit = this.onExit.bind(this);
    this.onRemoveCustomDevice = this.onRemoveCustomDevice.bind(this);
    this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this);
    this.doResizeViewport = this.doResizeViewport.bind(this);
    this.onRotateViewport = this.onRotateViewport.bind(this);
    this.onScreenshot = this.onScreenshot.bind(this);
    this.onToggleLeftAlignment = this.onToggleLeftAlignment.bind(this);
    this.onToggleReloadOnTouchSimulation =
      this.onToggleReloadOnTouchSimulation.bind(this);
    this.onToggleReloadOnUserAgent = this.onToggleReloadOnUserAgent.bind(this);
    this.onToggleUserAgentInput = this.onToggleUserAgentInput.bind(this);
    this.onUpdateDeviceDisplayed = this.onUpdateDeviceDisplayed.bind(this);
    this.onUpdateDeviceModal = this.onUpdateDeviceModal.bind(this);
  }

  onAddCustomDevice(device) {
    this.props.dispatch(addCustomDevice(device));
  }

  onChangeDevice(id, device, deviceType) {
    // Resize the viewport first.
    this.doResizeViewport(id, device.width, device.height);

    // TODO: Bug 1332754: Move messaging and logic into the action creator so that the
    // message is sent from the action creator and device property changes are sent from
    // there instead of this function.
    window.postMessage(
      {
        type: "change-device",
        device,
        viewport: device,
      },
      "*"
    );

    const orientation = getOrientation(device, device);

    this.props.dispatch(changeViewportAngle(0, orientation.angle));
    this.props.dispatch(changeDevice(id, device.name, deviceType));
    this.props.dispatch(changePixelRatio(id, device.pixelRatio));
    this.props.dispatch(changeUserAgent(device.userAgent));
    this.props.dispatch(toggleTouchSimulation(device.touch));
  }

  onChangeNetworkThrottling(enabled, profile) {
    window.postMessage(
      {
        type: "change-network-throttling",
        enabled,
        profile,
      },
      "*"
    );
    this.props.dispatch(changeNetworkThrottling(enabled, profile));
  }

  onChangePixelRatio(pixelRatio) {
    window.postMessage(
      {
        type: "change-pixel-ratio",
        pixelRatio,
      },
      "*"
    );
    this.props.dispatch(changePixelRatio(0, pixelRatio));
  }

  onChangeTouchSimulation(enabled) {
    window.postMessage(
      {
        type: "change-touch-simulation",
        enabled,
      },
      "*"
    );
    this.props.dispatch(toggleTouchSimulation(enabled));
  }

  onChangeUserAgent(userAgent) {
    window.postMessage(
      {
        type: "change-user-agent",
        userAgent,
      },
      "*"
    );
    this.props.dispatch(changeUserAgent(userAgent));
  }

  onChangeViewportOrientation(id, type, angle) {
    window.postMessage(
      {
        type: "viewport-orientation-change",
        orientationType: type,
        angle,
      },
      "*"
    );

    this.props.dispatch(changeViewportAngle(id, angle));
  }

  onDeviceListUpdate(devices) {
    updatePreferredDevices(devices);
  }

  onEditCustomDevice(oldDevice, newDevice) {
    // If the edited device is currently selected, then update its original association
    // and reset UI state.
    let viewport = this.props.viewports.find(
      ({ device }) => device === oldDevice.name
    );

    if (viewport) {
      viewport = {
        ...viewport,
        device: newDevice.name,
        deviceType: "custom",
        height: newDevice.height,
        width: newDevice.width,
        pixelRatio: newDevice.pixelRatio,
        touch: newDevice.touch,
        userAgent: newDevice.userAgent,
      };
    }

    this.props.dispatch(editCustomDevice(viewport, oldDevice, newDevice));
  }

  onExit() {
    window.postMessage({ type: "exit" }, "*");
  }

  onRemoveCustomDevice(device) {
    // If the custom device is currently selected on any of the viewports,
    // remove the device association and reset all the ui state.
    for (const viewport of this.props.viewports) {
      if (viewport.device === device.name) {
        this.onRemoveDeviceAssociation(viewport.id, { resetProfile: true });
      }
    }

    this.props.dispatch(removeCustomDevice(device));
  }

  onRemoveDeviceAssociation(id, { resetProfile }) {
    // TODO: Bug 1332754: Move messaging and logic into the action creator so that device
    // property changes are sent from there instead of this function.
    this.props.dispatch(removeDeviceAssociation(id, { resetProfile }));
    if (resetProfile) {
      this.props.dispatch(toggleTouchSimulation(false));
      this.props.dispatch(changePixelRatio(id, 0));
      this.props.dispatch(changeUserAgent(""));
    }
  }

  doResizeViewport(id, width, height) {
    // This is the setter function that we pass to Toolbar and Viewports
    // so they can modify the viewport.
    window.postMessage(
      {
        type: "viewport-resize",
        width,
        height,
      },
      "*"
    );
    this.props.dispatch(resizeViewport(id, width, height));
  }

  /**
   * Dispatches the rotateViewport action creator. This utilized by the RDM toolbar as
   * a prop.
   *
   * @param {number} id
   *        The viewport ID.
   */
  onRotateViewport(id) {
    let currentDevice;
    const viewport = this.props.viewports[id];

    for (const type of this.props.devices.types) {
      for (const device of this.props.devices[type]) {
        if (viewport.device === device.name) {
          currentDevice = device;
        }
      }
    }

    // If no device is selected, then assume the selected device's primary orientation is
    // opposite of the viewport orientation.
    if (!currentDevice) {
      currentDevice = {
        height: viewport.width,
        width: viewport.height,
      };
    }

    const currentAngle = Services.prefs.getIntPref(
      "devtools.responsive.viewport.angle"
    );
    const angleToRotateTo = currentAngle === 90 ? 0 : 90;
    const { type, angle } = getOrientation(
      currentDevice,
      viewport,
      angleToRotateTo
    );

    this.onChangeViewportOrientation(id, type, angle, true);
    this.props.dispatch(rotateViewport(id));

    window.postMessage(
      {
        type: "viewport-resize",
        height: viewport.width,
        width: viewport.height,
      },
      "*"
    );
  }

  onScreenshot() {
    this.props.dispatch(takeScreenshot());
  }

  onToggleLeftAlignment() {
    this.props.dispatch(toggleLeftAlignment());

    window.postMessage(
      {
        type: "toggle-left-alignment",
        leftAlignmentEnabled: this.props.leftAlignmentEnabled,
      },
      "*"
    );
  }

  onToggleReloadOnTouchSimulation() {
    this.props.dispatch(toggleReloadOnTouchSimulation());
  }

  onToggleReloadOnUserAgent() {
    this.props.dispatch(toggleReloadOnUserAgent());
  }

  onToggleUserAgentInput() {
    this.props.dispatch(toggleUserAgentInput());
  }

  onUpdateDeviceDisplayed(device, deviceType, displayed) {
    this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
  }

  onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
    this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
    window.postMessage({ type: "update-device-modal", isOpen }, "*");
  }

  render() {
    const { devices, networkThrottling, screenshot, viewports } = this.props;

    const {
      onAddCustomDevice,
      onChangeDevice,
      onChangeNetworkThrottling,
      onChangePixelRatio,
      onChangeTouchSimulation,
      onChangeUserAgent,
      onDeviceListUpdate,
      onEditCustomDevice,
      onExit,
      onRemoveCustomDevice,
      onRemoveDeviceAssociation,
      doResizeViewport,
      onRotateViewport,
      onScreenshot,
      onToggleLeftAlignment,
      onToggleReloadOnTouchSimulation,
      onToggleReloadOnUserAgent,
      onToggleUserAgentInput,
      onUpdateDeviceDisplayed,
      onUpdateDeviceModal,
    } = this;

    if (!viewports.length) {
      return null;
    }

    const selectedDevice = viewports[0].device;
    const selectedPixelRatio = viewports[0].pixelRatio;

    let deviceAdderViewportTemplate = {};
    if (devices.modalOpenedFromViewport !== null) {
      deviceAdderViewportTemplate = viewports[devices.modalOpenedFromViewport];
    }

    return dom.div(
      { id: "app" },
      Toolbar({
        devices,
        networkThrottling,
        screenshot,
        selectedDevice,
        selectedPixelRatio,
        viewport: viewports[0],
        onChangeDevice,
        onChangeNetworkThrottling,
        onChangePixelRatio,
        onChangeTouchSimulation,
        onChangeUserAgent,
        onExit,
        onRemoveDeviceAssociation,
        doResizeViewport,
        onRotateViewport,
        onScreenshot,
        onToggleLeftAlignment,
        onToggleReloadOnTouchSimulation,
        onToggleReloadOnUserAgent,
        onToggleUserAgentInput,
        onUpdateDeviceModal,
      }),
      devices.isModalOpen
        ? DeviceModal({
            deviceAdderViewportTemplate,
            devices,
            onAddCustomDevice,
            onDeviceListUpdate,
            onEditCustomDevice,
            onRemoveCustomDevice,
            onUpdateDeviceDisplayed,
            onUpdateDeviceModal,
          })
        : null
    );
  }
}

const mapStateToProps = state => {
  return {
    ...state,
    leftAlignmentEnabled: state.ui.leftAlignmentEnabled,
  };
};

module.exports = connect(mapStateToProps)(App);
