/* 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 {
  Component,
  createFactory,
} = require("resource://devtools/client/shared/vendor/react.js");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const { div } = dom;
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
const {
  connect,
} = require("resource://devtools/client/shared/redux/visibility-handler-connect.js");
const {
  findDOMNode,
} = require("resource://devtools/client/shared/vendor/react-dom.js");
const Actions = require("resource://devtools/client/netmonitor/src/actions/index.js");
const {
  updateFormDataSections,
} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
const {
  getSelectedRequest,
  isSelectedRequestVisible,
} = require("resource://devtools/client/netmonitor/src/selectors/index.js");

// Components
const SplitBox = createFactory(
  require("resource://devtools/client/shared/components/splitter/SplitBox.js")
);
const RequestList = createFactory(
  require("resource://devtools/client/netmonitor/src/components/request-list/RequestList.js")
);
const Toolbar = createFactory(
  require("resource://devtools/client/netmonitor/src/components/Toolbar.js")
);

loader.lazyGetter(this, "NetworkDetailsBar", function () {
  return createFactory(
    require("resource://devtools/client/netmonitor/src/components/request-details/NetworkDetailsBar.js")
  );
});

loader.lazyGetter(this, "NetworkActionBar", function () {
  return createFactory(
    require("resource://devtools/client/netmonitor/src/components/NetworkActionBar.js")
  );
});

// MediaQueryList object responsible for switching sidebar splitter
// between landscape and portrait mode (depending on browser window size).
const MediaQueryVert = window.matchMedia("(min-width: 700px)");

// MediaQueryList object responsible for switching the toolbar
// between single and 2-rows layout (depending on browser window size).
const MediaQuerySingleRow = window.matchMedia("(min-width: 1020px)");

/**
 * Monitor panel component
 * The main panel for displaying various network request information
 */
class MonitorPanel extends Component {
  static get propTypes() {
    return {
      actions: PropTypes.object.isRequired,
      connector: PropTypes.object.isRequired,
      isEmpty: PropTypes.bool.isRequired,
      networkDetailsOpen: PropTypes.bool.isRequired,
      openNetworkDetails: PropTypes.func.isRequired,
      toolboxDoc: PropTypes.object.isRequired,
      // Callback for opening split console.
      openSplitConsole: PropTypes.func,
      onNetworkDetailsResized: PropTypes.func.isRequired,
      request: PropTypes.object,
      selectedRequestVisible: PropTypes.bool.isRequired,
      sourceMapURLService: PropTypes.object,
      openLink: PropTypes.func,
      updateRequest: PropTypes.func.isRequired,
      networkActionOpen: PropTypes.bool.isRequired,
    };
  }

  constructor(props) {
    super(props);

    this.state = {
      isSingleRow: MediaQuerySingleRow.matches,
      isVerticalSpliter: MediaQueryVert.matches,
    };

    this.onLayoutChange = this.onLayoutChange.bind(this);
    this.onNetworkDetailsResized = this.onNetworkDetailsResized.bind(this);
  }

  componentDidMount() {
    MediaQuerySingleRow.addListener(this.onLayoutChange);
    MediaQueryVert.addListener(this.onLayoutChange);
    this.persistDetailsPanelSize();
    this.persistActionBarSize();
  }

  // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507
  UNSAFE_componentWillReceiveProps(nextProps) {
    updateFormDataSections(nextProps);
  }

  componentDidUpdate() {
    const { selectedRequestVisible, openNetworkDetails } = this.props;
    if (!selectedRequestVisible) {
      openNetworkDetails(false);
    }
  }

  componentWillUnmount() {
    MediaQuerySingleRow.removeListener(this.onLayoutChange);
    MediaQueryVert.removeListener(this.onLayoutChange);
    this.persistDetailsPanelSize();
    this.persistActionBarSize();
  }

  persistDetailsPanelSize() {
    const { clientWidth, clientHeight } = findDOMNode(this.refs.endPanel) || {};

    if (this.state.isVerticalSpliter && clientWidth) {
      Services.prefs.setIntPref(
        "devtools.netmonitor.panes-network-details-width",
        clientWidth
      );
    }
    if (!this.state.isVerticalSpliter && clientHeight) {
      Services.prefs.setIntPref(
        "devtools.netmonitor.panes-network-details-height",
        clientHeight
      );
    }
  }

  persistActionBarSize() {
    const { clientWidth, clientHeight } =
      findDOMNode(this.refs.actionBar) || {};
    if (clientWidth) {
      Services.prefs.setIntPref(
        "devtools.netmonitor.panes-search-width",
        clientWidth
      );
    }
    if (clientHeight) {
      Services.prefs.setIntPref(
        "devtools.netmonitor.panes-search-height",
        clientHeight
      );
    }
  }

  onLayoutChange() {
    this.setState({
      isSingleRow: MediaQuerySingleRow.matches,
      isVerticalSpliter: MediaQueryVert.matches,
    });
  }

  onNetworkDetailsResized(width, height) {
    // Cleaning width and height parameters, because SplitBox passes ALWAYS two values,
    // while depending on orientation ONLY ONE dimension is managed by it at a time.
    const { isVerticalSpliter } = this.state;
    return this.props.onNetworkDetailsResized(
      isVerticalSpliter ? width : null,
      isVerticalSpliter ? null : height
    );
  }

  renderActionBar() {
    const { connector, isEmpty, networkActionOpen } = this.props;

    const initialWidth = Services.prefs.getIntPref(
      "devtools.netmonitor.panes-search-width"
    );
    const initialHeight = Services.prefs.getIntPref(
      "devtools.netmonitor.panes-search-height"
    );

    return SplitBox({
      className: "devtools-responsive-container",
      initialWidth,
      initialHeight,
      minSize: "250px",
      maxSize: "80%",
      splitterSize: networkActionOpen ? 1 : 0,
      startPanel:
        networkActionOpen &&
        NetworkActionBar({
          ref: "actionBar",
          connector,
        }),
      endPanel: RequestList({ isEmpty, connector }),
      endPanelControl: false,
      vert: true,
    });
  }

  render() {
    const {
      actions,
      connector,
      networkDetailsOpen,
      openLink,
      openSplitConsole,
      sourceMapURLService,
      toolboxDoc,
    } = this.props;

    const initialWidth = Services.prefs.getIntPref(
      "devtools.netmonitor.panes-network-details-width"
    );

    const initialHeight = Services.prefs.getIntPref(
      "devtools.netmonitor.panes-network-details-height"
    );

    return div(
      { className: "monitor-panel" },
      Toolbar({
        actions,
        connector,
        openSplitConsole,
        singleRow: this.state.isSingleRow,
        toolboxDoc,
      }),
      SplitBox({
        className: "devtools-responsive-container",
        initialWidth,
        initialHeight,
        minSize: "50px",
        maxSize: "80%",
        splitterSize: networkDetailsOpen ? 1 : 0,
        startPanel: this.renderActionBar(),
        endPanel:
          networkDetailsOpen &&
          NetworkDetailsBar({
            ref: "endPanel",
            connector,
            openLink,
            sourceMapURLService,
          }),
        endPanelCollapsed: !networkDetailsOpen,
        endPanelControl: true,
        vert: this.state.isVerticalSpliter,
        onControlledPanelResized: this.onNetworkDetailsResized,
      })
    );
  }
}

module.exports = connect(
  state => ({
    isEmpty: !state.requests.requests.length,
    networkDetailsOpen: state.ui.networkDetailsOpen,
    networkActionOpen: state.ui.networkActionOpen,
    request: getSelectedRequest(state),
    selectedRequestVisible: isSelectedRequestVisible(state),
  }),
  dispatch => ({
    openNetworkDetails: open => dispatch(Actions.openNetworkDetails(open)),
    onNetworkDetailsResized: (width, height) =>
      dispatch(Actions.resizeNetworkDetails(width, height)),
    updateRequest: (id, data, batch) =>
      dispatch(Actions.updateRequest(id, data, batch)),
  })
)(MonitorPanel);
