/* global IITC -- eslint */

/**
 * @file This file contains the code for displaying and handling the regional scoreboard.
 * @module region_scoreboard
 */

/**
 * Sets up and manages the main dialog for the regional scoreboard.
 *
 * @function RegionScoreboardSetup
 * @returns {Function} A setup function to initialize the scoreboard.
 */
window.RegionScoreboardSetup = (function () {
  var mainDialog;
  var regionScore;
  var timer;

  /**
   * Constructs a RegionScore object from server results. Contains methods to process and retrieve score data.
   *
   * @class
   * @name RegionScore
   * @param {Object} serverResult - The data returned from the server for regional scores.
   */
  function RegionScore(serverResult) {
    this.ori_data = serverResult;
    this.topAgents = serverResult.topAgents;
    this.regionName = serverResult.regionName;
    this.gameScore = serverResult.gameScore;

    this.median = [-1, -1, -1];
    this.CP_COUNT = 35;
    this.CP_DURATION = 5 * 60 * 60 * 1000;
    this.CYCLE_DURATION = this.CP_DURATION * this.CP_COUNT;

    this.checkpoints = [];

    this.hasNoTopAgents = function () {
      return this.topAgents.length === 0;
    };

    this.getAvgScore = function (faction) {
      return parseInt(this.gameScore[faction === window.TEAM_ENL ? 0 : 1]);
    };

    this.getAvgScoreMax = function () {
      return Math.max(this.getAvgScore(window.TEAM_ENL), this.getAvgScore(window.TEAM_RES), 1);
    };

    this.getCPScore = function (cp) {
      return this.checkpoints[cp];
    };

    this.getScoreMax = function (min_value) {
      var max = min_value || 0;
      for (var i = 1; i < this.checkpoints.length; i++) {
        var cp = this.checkpoints[i];
        max = Math.max(max, cp[0], cp[1]);
      }
      return max;
    };

    this.getCPSum = function () {
      var sums = [0, 0];
      for (var i = 1; i < this.checkpoints.length; i++) {
        sums[0] += this.checkpoints[i][0];
        sums[1] += this.checkpoints[i][1];
      }

      return sums;
    };

    this.getAvgScoreAtCP = function (faction, cp_idx) {
      var idx = faction === window.TEAM_RES ? 1 : 0;

      var score = 0;
      var count = 0;
      var cp_len = Math.min(cp_idx, this.checkpoints.length);

      for (var i = 1; i <= cp_len; i++) {
        if (this.checkpoints[i] !== undefined) {
          score += this.checkpoints[i][idx];
          count++;
        }
      }

      if (count < cp_idx) {
        score += this.getScoreMedian(faction) * (cp_idx - count);
      }

      return Math.floor(score / cp_idx);
    };

    this.getScoreMedian = function (faction) {
      if (this.median[faction] < 0) {
        var idx = faction === window.TEAM_RES ? 1 : 0;
        var values = this.checkpoints.map(function (val) {
          return val[idx];
        });
        values = values.filter(function (n) {
          return n !== undefined;
        });
        this.median[faction] = this.findMedian(values);
      }

      return this.median[faction];
    };

    this.findMedian = function (values) {
      var len = values.length;
      var rank = Math.floor((len - 1) / 2);

      if (len === 0) return 0;

      var l = 0,
        m = len - 1;
      var b, i, j, x;
      while (l < m) {
        x = values[rank];
        i = l;
        j = m;
        do {
          while (values[i] < x) i++;
          while (x < values[j]) j--;
          if (i <= j) {
            b = values[i];
            values[i] = values[j];
            values[j] = b;
            i++;
            j--;
          }
        } while (i <= j);
        if (j < rank) l = i;
        if (rank < i) m = j;
      }
      return values[rank];
    };

    this.getLastCP = function () {
      if (this.checkpoints.length === 0) return 0;
      return this.checkpoints.length - 1;
    };

    this.getCycleEnd = function () {
      return this.getCheckpointEnd(this.CP_COUNT);
    };

    this.getCheckpointEnd = function (cp) {
      return new Date(this.cycleStartTime.getTime() + this.CP_DURATION * cp);
    };

    for (var i = 0; i < serverResult.scoreHistory.length; i++) {
      var h = serverResult.scoreHistory[i];
      this.checkpoints[parseInt(h[0])] = [parseInt(h[1]), parseInt(h[2])];
    }

    this.cycleStartTime = new Date(Math.floor(Date.now() / this.CYCLE_DURATION) * this.CYCLE_DURATION);
  }

  function showDialog() {
    var latLng = window.map.getCenter();

    var latE6 = Math.round(latLng.lat * 1e6);
    var lngE6 = Math.round(latLng.lng * 1e6);

    showRegion(latE6, lngE6);
  }

  /*
    function showScoreOf (region) {
      const latlng = regionToLatLong(region);
      const latE6 = Math.round(latLng.lat*1E6);
      const lngE6 = Math.round(latLng.lng*1E6);
      showRegion(latE6,lngE6);
    }
    */

  function showRegion(latE6, lngE6) {
    var text = 'Loading regional scores...';
    if (window.useAppPanes()) {
      var style = 'position: absolute; top: 0; width: 100%; max-width: 412px';
      mainDialog = $('<div>', { style: style }).html(text).appendTo(document.body);
    } else {
      mainDialog = window.dialog({
        title: 'Region scores',
        html: text,
        width: 450,
        height: 340,
        closeCallback: onDialogClose,
      });
    }

    window.postAjax('getRegionScoreDetails', { latE6: latE6, lngE6: lngE6 }, onRequestSuccess, onRequestFailure);
  }

  function onRequestFailure() {
    mainDialog.html('Failed to load region scores - try again');
  }

  function onRequestSuccess(data) {
    if (data.result === undefined) {
      return onRequestFailure();
    }

    regionScore = new RegionScore(data.result);
    updateDialog();
    startTimer();
  }

  function updateDialog(logscale) {
    mainDialog.html(
      `<div class="cellscore">` +
        `<b>Region scores for ${regionScore.regionName}</b>` +
        `<div class="historychart">${createResults()}${HistoryChart(regionScore, logscale)}</div>` +
        `<b>Checkpoint overview</b><div>${createHistoryTable()}</div>` +
        `<b>Top agents</b><div>${createAgentTable()}</div>` +
        `</div>` +
        createTimers()
    );

    setupToolTips();

    var tooltip = createResultTooltip();
    $('#overview', mainDialog).tooltip({
      content: window.convertTextToTableMagic(tooltip),
    });

    $('.cellscore', mainDialog).accordion({
      header: 'b',
      heightStyle: 'fill',
    });

    $('input.logscale', mainDialog).change(function () {
      var input = $(this);
      updateDialog(input.prop('checked'));
    });
  }

  function setupToolTips() {
    $('g.checkpoint', mainDialog).each(function (i, elem) {
      elem = $(elem);

      function formatScore(idx, score_now, score_last) {
        if (!score_now[idx]) return '';
        var res = window.digits(score_now[idx]);
        if (score_last && score_last[idx]) {
          var delta = score_now[idx] - score_last[idx];
          res += '\t(';
          if (delta > 0) res += '+';
          res += window.digits(delta) + ')';
        }
        return res;
      }

      var tooltip;
      var cp = parseInt(elem.attr('data-cp'));
      if (cp) {
        var score_now = regionScore.getCPScore(cp);
        var score_last = regionScore.getCPScore(cp - 1);
        var enl_str = score_now ? '\nEnl:\t' + formatScore(0, score_now, score_last) : '';
        var res_str = score_now ? '\nRes:\t' + formatScore(1, score_now, score_last) : '';

        tooltip = 'CP:\t' + cp + '\t-\t' + formatDayHours(regionScore.getCheckpointEnd(cp)) + '\n<hr>' + enl_str + res_str;
      }

      elem.tooltip({
        content: window.convertTextToTableMagic(tooltip),
        position: { my: 'center bottom', at: 'center top-10' },
        tooltipClass: 'checkpointtooltip',
        show: 100,
      });
    });
  }

  function onDialogClose() {
    stopTimer();
  }

  function createHistoryTable() {
    var _invert = window.PLAYER.team === 'RESISTANCE';
    function order(_1, _2) {
      return (_invert ? [_2, _1] : [_1, _2]).join('');
    }
    var enl = { class: window.TEAM_TO_CSS[window.TEAM_ENL], name: window.TEAM_NAMES[window.TEAM_ENL] };
    var res = { class: window.TEAM_TO_CSS[window.TEAM_RES], name: window.TEAM_NAMES[window.TEAM_RES] };

    var table = `<table class="checkpoint_table"><thead><tr><th>CP</th><th>Time</th>${order('<th>' + enl.name + '</th>', '<th>' + res.name + '</th>')}</tr>`;

    var total = regionScore.getCPSum();
    table +=
      '<tr class="cp_total"><th></th><th></th>' +
      order('<th class="' + enl.class + '">' + window.digits(total[0]) + '</th>', '<th class="' + res.class + '">' + window.digits(total[1]) + '</th>') +
      '</tr></thead>';

    for (var cp = regionScore.getLastCP(); cp > 0; cp--) {
      var score = regionScore.getCPScore(cp);
      var class_e = score[0] > score[1] ? ' class="' + enl.class + '"' : '';
      var class_r = score[1] > score[0] ? ' class="' + res.class + '"' : '';

      table +=
        `<tr>` +
        `<td>${cp}</td>` +
        `<td>${formatDayHours(regionScore.getCheckpointEnd(cp))}</td>` +
        order(`<td${class_e}>${window.digits(score[0])}</td>`, `<td${class_r}>${window.digits(score[1])}</td>`) +
        `</tr>`;
    }

    table += '</table>';
    return table;
  }

  function createAgentTable() {
    var agentTable = '<table><tr><th>#</th><th>Agent</th></tr>';

    for (var i = 0; i < regionScore.topAgents.length; i++) {
      var agent = regionScore.topAgents[i];
      agentTable +=
        '<tr>' + '<td>' + (i + 1) + '</td>' + '<td class="nickname ' + (agent.team === 'RESISTANCE' ? 'res' : 'enl') + '">' + agent.nick + '</td></tr>';
    }

    if (regionScore.hasNoTopAgents()) {
      agentTable += '<tr><td colspan="2"><i>no top agents</i></td></tr>';
    }
    agentTable += '</table>';

    return agentTable;
  }

  function createResults() {
    var maxAverage = regionScore.getAvgScoreMax();
    var order = window.PLAYER.team === 'RESISTANCE' ? [window.TEAM_RES, window.TEAM_ENL] : [window.TEAM_ENL, window.TEAM_RES];

    var result = '<table id="overview" title="">';
    for (var t = 0; t < 2; t++) {
      var faction = order[t];
      var team = window.TEAM_NAMES[faction];
      var teamClass = window.TEAM_TO_CSS[faction];
      var teamCol = window.COLORS[faction];
      var barSize = Math.round((regionScore.getAvgScore(faction) / maxAverage) * 100);
      result +=
        `<tr><th class="${teamClass}">${team}</th>` +
        `<td class="${teamClass}">${window.digits(regionScore.getAvgScore(faction))}</td>` +
        `<td style="width:100%"><div style="background:${teamCol}; width: ${barSize}%; height: 1.3ex; border: 2px outset ${teamCol}; margin-top: 2px"> </td>` +
        `<td class="${teamClass}"><small>( ${window.digits(regionScore.getAvgScoreAtCP(faction, 35))} )</small></td>` +
        `</tr>`;
    }

    return result + '</table>';
  }

  function createResultTooltip() {
    var e_res = regionScore.getAvgScoreAtCP(window.TEAM_RES, regionScore.CP_COUNT);
    var e_enl = regionScore.getAvgScoreAtCP(window.TEAM_ENL, regionScore.CP_COUNT);
    var loosing_faction = e_res < e_enl ? window.TEAM_RES : window.TEAM_ENL;

    var order = loosing_faction === window.TEAM_ENL ? [window.TEAM_RES, window.TEAM_ENL] : [window.TEAM_ENL, window.TEAM_RES];

    function percentToString(score, total) {
      if (total === 0) return '50%';
      return Math.round((score / total) * 10000) / 100 + '%';
    }

    function currentScore() {
      var res = 'Current:\n';
      var total = regionScore.getAvgScore(window.TEAM_RES) + regionScore.getAvgScore(window.TEAM_ENL);
      for (var t = 0; t < 2; t++) {
        var faction = order[t];
        var score = regionScore.getAvgScore(faction);
        res += window.TEAM_NAMES[faction] + '\t' + window.digits(score) + '\t' + percentToString(score, total) + '\n';
      }

      return res;
    }

    function estimatedScore() {
      var res = '<hr>Estimated:\n';
      var total = e_res + e_enl;
      for (var t = 0; t < 2; t++) {
        var faction = order[t];
        var score = regionScore.getAvgScoreAtCP(faction, regionScore.CP_COUNT);
        res += window.TEAM_NAMES[faction] + '\t' + window.digits(score) + '\t' + percentToString(score, total) + '\n';
      }

      return res;
    }

    function requiredScore() {
      var res = '';
      var required_mu = Math.abs(e_res - e_enl) * regionScore.CP_COUNT + 1;
      res += '<hr>\n';
      res += window.TEAM_NAMES[loosing_faction] + ' requires:\t' + window.digits(Math.ceil(required_mu)) + ' \n';
      res += 'Checkpoint(s) left:\t' + (regionScore.CP_COUNT - regionScore.getLastCP()) + ' \n';

      return res;
    }

    return currentScore() + estimatedScore() + requiredScore();
  }

  function createTimers() {
    var nextcp = regionScore.getCheckpointEnd(regionScore.getLastCP() + 1);
    var endcp = regionScore.getCycleEnd();

    return (
      `<div class="checkpoint_timers"><table><tr>` +
      `<td>Next CP at: ${formatHours(nextcp)} (in <span id="cycletimer"></span>)</td>` +
      `<td>Cycle ends: ${formatDayHours(endcp)}</td>` +
      `</tr></table></div>`
    );
  }

  function startTimer() {
    stopTimer();

    timer = window.setInterval(onTimer, 1000);
    onTimer();
  }

  function stopTimer() {
    if (timer) {
      window.clearInterval(timer);
      timer = undefined;
    }
  }

  function onTimer() {
    var d = regionScore.getCheckpointEnd(regionScore.getLastCP() + 1) - new Date();
    $('#cycletimer', mainDialog).html(formatMinutes(Math.max(0, Math.floor(d / 1000))));
  }

  function formatMinutes(sec) {
    var hours = Math.floor(sec / 3600);
    var minutes = Math.floor((sec % 3600) / 60);
    sec = sec % 60;

    var time = '';
    time += hours + ':';
    if (minutes < 10) time += '0';
    time += minutes;
    time += ':';
    if (sec < 10) time += '0';
    time += sec;
    return time;
  }

  function formatHours(time) {
    return ('0' + time.getHours()).slice(-2) + ':00';
  }
  function formatDayHours(time) {
    return ('0' + time.getDate()).slice(-2) + '.' + ('0' + (time.getMonth() + 1)).slice(-2) + ' ' + ('0' + time.getHours()).slice(-2) + ':00';
  }

  return function setup() {
    if (window.useAppPanes()) {
      window.app.addPane('regionScoreboard', 'Region scores', 'ic_action_view_as_list');
      window.addHook('paneChanged', function (pane) {
        if (pane === 'regionScoreboard') {
          showDialog();
        } else if (mainDialog) {
          mainDialog.remove();
        }
      });
    } else {
      IITC.toolbox.addButton({
        id: 'scoreboard',
        label: 'Region scores',
        title: 'View regional scoreboard',
        action: showDialog,
      });
    }
  };
})();

/**
 * Creates an SVG-based history chart for regional scores.
 *
 * @function HistoryChart
 * @param {RegionScore} _regionScore - The RegionScore object containing score data.
 * @param {boolean} logscale - Whether to use logarithmic scale for the chart.
 * @returns {string} An SVG string representing the history chart.
 */
var HistoryChart = (function () {
  var regionScore;
  var scaleFct;
  var logscale;
  var svgTickText;

  function create(_regionScore, logscale) {
    regionScore = _regionScore;

    var max = regionScore.getScoreMax(10); // NOTE: ensure a min of 10 for the graph
    max *= 1.09; // scale up maximum a little, so graph isn't squashed right against upper edge
    setScaleType(max, logscale);

    svgTickText = [];

    // svg area 400x130. graph area 350x100, offset to 40,10
    var svg =
      '<div><svg width="400" height="133" style="margin-left: 10px;">' +
      svgBackground() +
      svgAxis(max) +
      svgAveragePath() +
      svgFactionPath() +
      svgCheckPointMarkers() +
      svgTickText.join('') +
      '<foreignObject height="18" width="60" y="113" x="0" class="node"><label title="Logarithmic scale">' +
      '<input type="checkbox" class="logscale"' +
      (logscale ? ' checked' : '') +
      '/>' +
      'log</label></foreignObject>' +
      '</svg></div>';

    return svg;
  }

  function svgFactionPath() {
    var svgPath = '';

    for (var t = 0; t < 2; t++) {
      var col = getFactionColor(t);
      var teamPaths = [];

      for (var cp = 1; cp <= regionScore.getLastCP(); cp++) {
        var score = regionScore.getCPScore(cp);
        if (score !== undefined) {
          var x = cp * 10 + 40;
          teamPaths.push(x + ',' + scaleFct(score[t]));
        }
      }

      if (teamPaths.length > 0) {
        svgPath += '<polyline points="' + teamPaths.join(' ') + '" stroke="' + col + '" fill="none" />';
      }
    }

    return svgPath;
  }

  function svgCheckPointMarkers() {
    var markers = '';

    var col1 = getFactionColor(0);
    var col2 = getFactionColor(1);

    for (var cp = 1; cp <= regionScore.CP_COUNT; cp++) {
      var scores = regionScore.getCPScore(cp);

      markers +=
        `<g title="dummy" class="checkpoint" data-cp="${cp}">` + `<rect x="${cp * 10 + 35}" y="10" width="10" height="100" fill="black" fill-opacity="0" />`;

      if (scores) {
        markers +=
          `<circle cx="${cp * 10 + 40}" cy="${scaleFct(scores[0])}" r="3" stroke-width="1" stroke="${col1}" fill="${col1}" fill-opacity="0.5" />` +
          `<circle cx="${cp * 10 + 40}" cy="${scaleFct(scores[1])}" r="3" stroke-width="1" stroke="${col2}" fill="${col2}" fill-opacity="0.5" />`;
      }

      markers += '</g>';
    }

    return markers;
  }

  function svgBackground() {
    return '<rect x="0" y="1" width="400" height="132" stroke="#FFCE00" fill="#08304E" />';
  }

  function svgAxis(max) {
    return '<path d="M40,110 L40,10 M40,110 L390,110" stroke="#fff" />' + createTicks(max);
  }

  function createTicks(max) {
    var ticks = createTicksHorz();

    function addVTick(i) {
      var y = scaleFct(i);

      ticks.push('M40,' + y + ' L390,' + y);
      svgTickText.push(
        '<text x="35" y="' + y + '" font-size="12" font-family="Roboto, Helvetica, sans-serif" text-anchor="end" fill="#fff">' + formatNumber(i) + '</text>'
      );
    }

    // vertical
    // first we calculate the power of 10 that is smaller than the max limit
    var vtickStep = Math.pow(10, Math.floor(Math.log10(max)));
    if (logscale) {
      for (var i = 0; i < 4; i++) {
        addVTick(vtickStep);
        vtickStep /= 10;
      }
    } else {
      // this could be between 1 and 10 grid lines - so we adjust to give nicer spacings
      if (vtickStep < max / 5) {
        vtickStep *= 2;
      } else if (vtickStep > max / 2) {
        vtickStep /= 2;
      }

      for (var ti = vtickStep; ti <= max; ti += vtickStep) {
        addVTick(ti);
      }
    }

    return '<path d="' + ticks.join(' ') + '" stroke="#fff" opacity="0.3" />';
  }

  function createTicksHorz() {
    var ticks = [];
    for (var i = 5; i <= 35; i += 5) {
      var x = i * 10 + 40;
      ticks.push('M' + x + ',10 L' + x + ',110');
      svgTickText.push(
        '<text x="' + x + '" y="125" font-size="12" font-family="Roboto, Helvetica, sans-serif" text-anchor="middle" fill="#fff">' + i + '</text>'
      );
    }

    return ticks;
  }

  function svgAveragePath() {
    var path = '';
    for (var faction = 1; faction < 3; faction++) {
      var col = window.COLORS[faction];

      var points = [];
      for (var cp = 1; cp <= regionScore.CP_COUNT; cp++) {
        var score = regionScore.getAvgScoreAtCP(faction, cp);

        var x = cp * 10 + 40;
        var y = scaleFct(score);
        points.push(x + ',' + y);
      }

      path += '<polyline points="' + points.join(' ') + '" stroke="' + col + '" stroke-dasharray="3,2" opacity="0.8" fill="none"/>';
    }

    return path;
  }

  function setScaleType(max, useLogScale) {
    logscale = useLogScale;
    if (useLogScale) {
      if (!Math.log10)
        Math.log10 = function (x) {
          return Math.log(x) / Math.LN10;
        };

      // 0 cannot be displayed on a log scale, so we set the minimum to 0.001 and divide by lg(0.001)=-3
      scaleFct = function (y) {
        return Math.round(10 - (Math.log10(Math.max(0.001, y / max)) / 3) * 100);
      };
    } else {
      scaleFct = function (y) {
        return Math.round(110 - (y / max) * 100);
      };
    }
  }

  function getFactionColor(t) {
    return t === 0 ? window.COLORS[window.TEAM_ENL] : window.COLORS[window.TEAM_RES];
  }

  function formatNumber(num) {
    if (num >= 1_000_000_000) {
      return num / 1_000_000_000 + 'B';
    } else if (num >= 1_000_000) {
      return num / 1_000_000 + 'M';
    } else if (num >= 1_000) {
      return num / 1_000 + 'k';
    } else {
      return num.toString();
    }
  }

  return create;
})();