/* 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 asyncStorage = require("resource://devtools/shared/async-storage.js");

const {
  ADD_DEVICE,
  ADD_DEVICE_TYPE,
  EDIT_DEVICE,
  LOAD_DEVICE_LIST_START,
  LOAD_DEVICE_LIST_ERROR,
  LOAD_DEVICE_LIST_END,
  REMOVE_DEVICE,
  UPDATE_DEVICE_DISPLAYED,
  UPDATE_DEVICE_MODAL,
} = require("resource://devtools/client/responsive/actions/index.js");
const {
  post,
} = require("resource://devtools/client/responsive/utils/message.js");

const {
  addDevice,
  editDevice,
  getDevices,
  removeDevice,
} = require("resource://devtools/client/shared/devices.js");
const {
  changeUserAgent,
  toggleTouchSimulation,
} = require("resource://devtools/client/responsive/actions/ui.js");
const {
  changeDevice,
  changePixelRatio,
  changeViewportAngle,
} = require("resource://devtools/client/responsive/actions/viewports.js");

const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";

/**
 * Returns an object containing the user preference of displayed devices.
 *
 * @return {object} containing two Sets:
 * - added: Names of the devices that were explicitly enabled by the user
 * - removed: Names of the devices that were explicitly removed by the user
 */
function loadPreferredDevices() {
  const preferredDevices = {
    added: new Set(),
    removed: new Set(),
  };

  if (Services.prefs.prefHasUserValue(DISPLAYED_DEVICES_PREF)) {
    try {
      let savedData = Services.prefs.getStringPref(DISPLAYED_DEVICES_PREF);
      savedData = JSON.parse(savedData);
      if (savedData.added && savedData.removed) {
        preferredDevices.added = new Set(savedData.added);
        preferredDevices.removed = new Set(savedData.removed);
      }
    } catch (e) {
      console.error(e);
    }
  }

  return preferredDevices;
}

/**
 * Update the displayed device list preference with the given device list.
 *
 * @param {object} containing two Sets:
 * - added: Names of the devices that were explicitly enabled by the user
 * - removed: Names of the devices that were explicitly removed by the user
 */
function updatePreferredDevices(devices) {
  let devicesToSave = {
    added: Array.from(devices.added),
    removed: Array.from(devices.removed),
  };
  devicesToSave = JSON.stringify(devicesToSave);
  Services.prefs.setStringPref(DISPLAYED_DEVICES_PREF, devicesToSave);
}

module.exports = {
  // This function is only exported for testing purposes
  _loadPreferredDevices: loadPreferredDevices,

  updatePreferredDevices,

  addCustomDevice(device) {
    return async function ({ dispatch }) {
      // Add custom device to device storage
      await addDevice(device, "custom");
      dispatch({
        type: ADD_DEVICE,
        device,
        deviceType: "custom",
      });
    };
  },

  addDevice(device, deviceType) {
    return {
      type: ADD_DEVICE,
      device,
      deviceType,
    };
  },

  addDeviceType(deviceType) {
    return {
      type: ADD_DEVICE_TYPE,
      deviceType,
    };
  },

  editCustomDevice(viewport, oldDevice, newDevice) {
    return async function ({ dispatch }) {
      // Edit custom device in storage
      await editDevice(oldDevice, newDevice, "custom");
      // Notify the window that the device should be updated in the device selector.
      post(window, {
        type: "change-device",
        device: newDevice,
        viewport,
      });

      // Update UI if the device is selected.
      if (viewport) {
        dispatch(changeUserAgent(newDevice.userAgent));
        dispatch(toggleTouchSimulation(newDevice.touch));
      }

      dispatch({
        type: EDIT_DEVICE,
        deviceType: "custom",
        viewport,
        oldDevice,
        newDevice,
      });
    };
  },

  removeCustomDevice(device) {
    return async function ({ dispatch }) {
      // Remove custom device from device storage
      await removeDevice(device, "custom");
      dispatch({
        type: REMOVE_DEVICE,
        device,
        deviceType: "custom",
      });
    };
  },

  updateDeviceDisplayed(device, deviceType, displayed) {
    return {
      type: UPDATE_DEVICE_DISPLAYED,
      device,
      deviceType,
      displayed,
    };
  },

  loadDevices() {
    return async function ({ dispatch }) {
      dispatch({ type: LOAD_DEVICE_LIST_START });
      const preferredDevices = loadPreferredDevices();
      let deviceByTypes;

      try {
        deviceByTypes = await getDevices();
      } catch (e) {
        console.error("Could not load device list: " + e);
        dispatch({ type: LOAD_DEVICE_LIST_ERROR });
        return;
      }

      for (const [type, devices] of deviceByTypes.entries()) {
        dispatch(module.exports.addDeviceType(type));
        for (const device of devices) {
          if (device.os == "fxos") {
            continue;
          }

          const newDevice = Object.assign({}, device, {
            displayed:
              preferredDevices.added.has(device.name) ||
              (device.featured && !preferredDevices.removed.has(device.name)),
          });

          dispatch(module.exports.addDevice(newDevice, type));
        }
      }

      // Add an empty "custom" type if it doesn't exist in device storage
      if (!deviceByTypes.has("custom")) {
        dispatch(module.exports.addDeviceType("custom"));
      }

      dispatch({ type: LOAD_DEVICE_LIST_END });
    };
  },

  restoreDeviceState() {
    return async function ({ dispatch, getState }) {
      const deviceState = await asyncStorage.getItem(
        "devtools.responsive.deviceState"
      );
      if (!deviceState) {
        return;
      }

      const { id, device: deviceName, deviceType } = deviceState;
      const devices = getState().devices;

      if (!devices.types.includes(deviceType)) {
        // Can't find matching device type.
        return;
      }

      const device = devices[deviceType].find(d => d.name === deviceName);
      if (!device) {
        // Can't find device with the same device name.
        return;
      }

      const viewport = getState().viewports[0];

      post(window, {
        type: "change-device",
        device,
        viewport,
      });

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

  updateDeviceModal(isOpen, modalOpenedFromViewport = null) {
    return {
      type: UPDATE_DEVICE_MODAL,
      isOpen,
      modalOpenedFromViewport,
    };
  },
};
