/* 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 {
  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 {
  isKeyIn,
} = require("resource://devtools/client/responsive/utils/key.js");
const {
  MIN_VIEWPORT_DIMENSION,
} = require("resource://devtools/client/responsive/constants.js");
const Types = require("resource://devtools/client/responsive/types.js");

loader.lazyRequireGetter(
  this,
  "KeyCodes",
  "resource://devtools/client/shared/keycodes.js",
  true
);

class ViewportDimension extends PureComponent {
  static get propTypes() {
    return {
      doResizeViewport: PropTypes.func.isRequired,
      onRemoveDeviceAssociation: PropTypes.func,
      viewport: PropTypes.shape(Types.viewport).isRequired,
    };
  }

  static getDerivedStateFromProps(props, state) {
    const { width, height } = props.viewport;
    if (state.prevWidth !== width || state.prevHeight !== height) {
      return {
        width,
        height,
        prevWidth: width,
        prevHeight: height,
      };
    }
    return null;
  }

  constructor(props) {
    super(props);

    const { width, height } = props.viewport;

    this.state = {
      width,
      height,
      prevWidth: width,
      prevHeight: height,
      isEditing: false,
      isWidthValid: true,
      isHeightValid: true,
    };

    this.isInputValid = this.isInputValid.bind(this);
    this.onInputBlur = this.onInputBlur.bind(this);
    this.onInputChange = this.onInputChange.bind(this);
    this.onInputFocus = this.onInputFocus.bind(this);
    this.onInputKeyDown = this.onInputKeyDown.bind(this);
    this.onInputKeyUp = this.onInputKeyUp.bind(this);
    this.onInputSubmit = this.onInputSubmit.bind(this);
  }

  /**
   * Return true if the given value is a number and greater than MIN_VIEWPORT_DIMENSION
   * and false otherwise.
   */
  isInputValid(value) {
    return (
      /^\d{2,4}$/.test(value) && parseInt(value, 10) >= MIN_VIEWPORT_DIMENSION
    );
  }

  onInputBlur() {
    const { width, height } = this.props.viewport;

    if (this.state.width != width || this.state.height != height) {
      this.onInputSubmit();
    }

    this.setState({ isEditing: false });
  }

  onInputChange({ target }, callback) {
    if (target.value.length > 4) {
      return;
    }

    if (this.widthInput == target) {
      this.setState(
        {
          width: target.value,
          isWidthValid: this.isInputValid(target.value),
        },
        callback
      );
    }

    if (this.heightInput == target) {
      this.setState(
        {
          height: target.value,
          isHeightValid: this.isInputValid(target.value),
        },
        callback
      );
    }
  }

  onInputFocus(e) {
    this.setState({ isEditing: true });
    e.target.select();
  }

  onInputKeyDown(event) {
    const increment = getIncrement(event);
    if (!increment) {
      return;
    }

    const { target } = event;
    target.value = parseInt(target.value, 10) + increment;
    this.onInputChange(event, this.onInputSubmit);

    // Keep this event from having default processing. Since the field is a
    // number field, default processing would trigger additional manipulations
    // of the value, and we've already applied the desired amount.
    event.preventDefault();
  }

  onInputKeyUp({ target, keyCode }) {
    // On Enter, submit the input
    if (keyCode == KeyCodes.DOM_VK_RETURN) {
      this.onInputSubmit();
    }

    // On Esc, revert the value and blur the target
    if (keyCode == KeyCodes.DOM_VK_ESCAPE) {
      if (this.widthInput == target) {
        const width = this.props.viewport.width;
        this.setState(
          {
            width,
            isWidthValid: this.isInputValid(width),
          },
          () => target.blur()
        );
      }

      if (this.heightInput == target) {
        const height = this.props.viewport.height;
        this.setState(
          {
            height,
            isHeightValid: this.isInputValid(height),
          },
          () => target.blur()
        );
      }
    }
  }

  onInputSubmit() {
    const { viewport, onRemoveDeviceAssociation, doResizeViewport } =
      this.props;

    if (!this.state.isWidthValid || !this.state.isHeightValid) {
      const { width, height } = viewport;

      this.setState({
        width,
        height,
        isWidthValid: true,
        isHeightValid: true,
      });

      return;
    }

    // Change the device selector back to an unselected device
    // TODO: Bug 1332754: Logic like this probably belongs in the action creator.
    if (viewport.device && typeof onRemoveDeviceAssociation === "function") {
      onRemoveDeviceAssociation(viewport.id, { resetProfile: false });
    }

    doResizeViewport(
      viewport.id,
      parseInt(this.state.width, 10),
      parseInt(this.state.height, 10)
    );
  }

  render() {
    return dom.div(
      {
        className:
          "viewport-dimension" +
          (this.state.isEditing ? " editing" : "") +
          (!this.state.isWidthValid || !this.state.isHeightValid
            ? " invalid"
            : ""),
      },
      dom.input({
        ref: input => {
          this.widthInput = input;
        },
        className:
          "text-input viewport-dimension-input" +
          (this.state.isWidthValid ? "" : " invalid"),
        size: 4,
        type: "number",
        value: this.state.width,
        onBlur: this.onInputBlur,
        onChange: this.onInputChange,
        onFocus: this.onInputFocus,
        onKeyDown: this.onInputKeyDown,
        onKeyUp: this.onInputKeyUp,
      }),
      dom.span(
        {
          className: "viewport-dimension-separator",
        },
        "×"
      ),
      dom.input({
        ref: input => {
          this.heightInput = input;
        },
        className:
          "text-input viewport-dimension-input" +
          (this.state.isHeightValid ? "" : " invalid"),
        size: 4,
        type: "number",
        value: this.state.height,
        onBlur: this.onInputBlur,
        onChange: this.onInputChange,
        onFocus: this.onInputFocus,
        onKeyDown: this.onInputKeyDown,
        onKeyUp: this.onInputKeyUp,
      })
    );
  }
}

/**
 * Get the increment/decrement step to use for the provided key event.
 */
function getIncrement(event) {
  const defaultIncrement = 1;
  const largeIncrement = 100;
  const mediumIncrement = 10;

  let increment = 0;
  const key = event.keyCode;

  if (isKeyIn(key, "UP", "PAGE_UP")) {
    increment = 1 * defaultIncrement;
  } else if (isKeyIn(key, "DOWN", "PAGE_DOWN")) {
    increment = -1 * defaultIncrement;
  }

  if (event.shiftKey) {
    if (isKeyIn(key, "PAGE_UP", "PAGE_DOWN")) {
      increment *= largeIncrement;
    } else {
      increment *= mediumIncrement;
    }
  }

  return increment;
}

module.exports = ViewportDimension;
