/* 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 { createSelector } = require("devtools/client/shared/vendor/reselect");
const {
  Filters,
  isFreetextMatch,
} = require("devtools/client/netmonitor/src/utils/filter-predicates");
const {
  Sorters,
} = require("devtools/client/netmonitor/src/utils/sort-predicates");

/**
 * Take clones into account when sorting.
 * If a request is a clone, use the original request for comparison.
 * If one of the compared request is a clone of the other, sort them next to each other.
 */
function sortWithClones(requests, sorter, a, b) {
  const aId = a.id,
    bId = b.id;

  if (aId.endsWith("-clone")) {
    const aOrigId = aId.replace(/-clone$/, "");
    if (aOrigId === bId) {
      return +1;
    }
    a = requests.find(item => item.id === aOrigId);
  }

  if (bId.endsWith("-clone")) {
    const bOrigId = bId.replace(/-clone$/, "");
    if (bOrigId === aId) {
      return -1;
    }
    b = requests.find(item => item.id === bOrigId);
  }

  const defaultSorter = () => false;
  return sorter ? sorter(a, b) : defaultSorter;
}

/**
 * Take clones into account when filtering. If a request is
 * a clone, it's not filtered out.
 */
const getFilterWithCloneFn = createSelector(
  state => state.filters,
  filters => r => {
    const matchesType = Object.keys(filters.requestFilterTypes).some(filter => {
      if (r.id.endsWith("-clone")) {
        return true;
      }
      return (
        filters.requestFilterTypes[filter] &&
        Filters[filter] &&
        Filters[filter](r)
      );
    });
    return matchesType && isFreetextMatch(r, filters.requestFilterText);
  }
);

const getTypeFilterFn = createSelector(
  state => state.filters,
  filters => r => {
    return Object.keys(filters.requestFilterTypes).some(filter => {
      return (
        filters.requestFilterTypes[filter] &&
        Filters[filter] &&
        Filters[filter](r)
      );
    });
  }
);

const getSortFn = createSelector(
  state => state.requests,
  state => state.sort,
  ({ requests }, sort) => {
    const sorter = Sorters[sort.type || "waterfall"];
    const ascending = sort.ascending ? +1 : -1;
    return (a, b) => ascending * sortWithClones(requests, sorter, a, b);
  }
);

const getSortedRequests = createSelector(
  state => state.requests,
  getSortFn,
  ({ requests }, sortFn) => [...requests].sort(sortFn)
);

const getDisplayedRequests = createSelector(
  state => state.requests,
  getFilterWithCloneFn,
  getSortFn,
  ({ requests }, filterFn, sortFn) => requests.filter(filterFn).sort(sortFn)
);

const getTypeFilteredRequests = createSelector(
  state => state.requests,
  getTypeFilterFn,
  ({ requests }, filterFn) => requests.filter(filterFn)
);

const getDisplayedRequestsSummary = createSelector(
  getDisplayedRequests,
  state => state.requests.lastEndedMs - state.requests.firstStartedMs,
  (requests, totalMs) => {
    if (requests.length === 0) {
      return { count: 0, bytes: 0, ms: 0 };
    }

    const totalBytes = requests.reduce(
      (totals, item) => {
        if (typeof item.contentSize == "number") {
          totals.contentSize += item.contentSize;
        }

        if (
          typeof item.transferredSize == "number" &&
          !(item.fromCache || item.status === "304")
        ) {
          totals.transferredSize += item.transferredSize;
        }

        return totals;
      },
      { contentSize: 0, transferredSize: 0 }
    );

    return {
      count: requests.length,
      contentSize: totalBytes.contentSize,
      ms: totalMs,
      transferredSize: totalBytes.transferredSize,
    };
  }
);

const getSelectedRequest = createSelector(
  state => state.requests,
  ({ selectedId, requests }) =>
    selectedId ? requests.find(item => item.id === selectedId) : undefined
);

const isSelectedRequestVisible = createSelector(
  state => state.requests,
  getDisplayedRequests,
  ({ selectedId }, displayedRequests) =>
    displayedRequests.some(r => r.id === selectedId)
);

function getRequestById(state, id) {
  return state.requests.requests.find(item => item.id === id);
}

function getRequestByChannelId(state, channelId) {
  return [...state.requests.requests.values()].find(
    r => r.resourceId == channelId
  );
}

function getDisplayedRequestById(state, id) {
  return getDisplayedRequests(state).find(r => r.id === id);
}

/**
 * Returns the current recording boolean state (HTTP traffic is
 * monitored or not monitored)
 */
function getRecordingState(state) {
  return state.requests.recording;
}

const getClickedRequest = createSelector(
  state => state.requests,
  ({ requests, clickedRequestId }) =>
    requests.find(request => request.id == clickedRequestId)
);

module.exports = {
  getClickedRequest,
  getDisplayedRequestById,
  getDisplayedRequests,
  getDisplayedRequestsSummary,
  getRecordingState,
  getRequestById,
  getRequestByChannelId,
  getSelectedRequest,
  getSortedRequests,
  getTypeFilteredRequests,
  isSelectedRequestVisible,
};
