// GridTracker Copyright © 2024 GridTracker.org
// All rights reserved.
// See LICENSE for more information.
// gtCommon.js is common functions used by gt.js , adifWorker.js, trackerWorker.js and others
// var GT must be initiliazed before loading this script.

GT.callsignDatabaseDXCC = {
  291: true,
  1: true,
  6: true,
  110: true,
  202: true
};

GT.callsignDatabaseUS = {
  291: true,
  6: true,
  110: true
};

GT.callsignDatabaseUSplus = {
  291: true,
  6: true,
  110: true,
  202: true
};

function validateGridFromString(inputText)
{
  var validGrid = false;
  if (inputText.length == 4 || inputText.length == 6)
  {
    var LETTERS = inputText.substr(0, 2);
    var NUMBERS = inputText.substr(2, 2);
    if (/^[A-R]+$/.test(LETTERS) && /^[0-9]+$/.test(NUMBERS))
    {
      validGrid = true;
    }
    if (validGrid && inputText.length == 6)
    {
      var LETTERS_SUB = inputText.substr(4, 2);
      if (!(/^[A-Xa-x]+$/.test(LETTERS_SUB)))
      {
        validGrid = false;
      }
    }
  }

  return validGrid;
}

function isKnownCallsignDXCC(dxcc)
{
  return (dxcc in GT.callsignDatabaseDXCC);
}

function isKnownCallsignUS(dxcc)
{
  return (dxcc in GT.callsignDatabaseUS);
}

function isKnownCallsignUSplus(dxcc)
{
  return (dxcc in GT.callsignDatabaseUSplus);
}

function cqZoneFromCallsign(insign, dxcc)
{
  var callsign = insign;

  if (!/\d/.test(callsign) || !/[a-zA-Z]/.test(callsign))
  {
    return null;
  }

  if (callsign in GT.directCallToCQzone) { return GT.directCallToCQzone[callsign]; }

  for (var x = callsign.length; x > 0; x--)
  {
    if (callsign.substr(0, x) in GT.prefixToCQzone)
    {
      return GT.prefixToCQzone[callsign.substr(0, x)];
    }
  }

  if (dxcc > 0)
  {
    return GT.dxccInfo[dxcc].cqzone;
  }

  return null;
}

function ituZoneFromCallsign(insign, dxcc)
{
  var callsign = insign;

  if (!/\d/.test(callsign) || !/[a-zA-Z]/.test(callsign))
  {
    return null;
  }

  if (callsign in GT.directCallToITUzone) { return GT.directCallToITUzone[callsign]; }

  for (var x = callsign.length; x > 0; x--)
  {
    if (callsign.substr(0, x) in GT.prefixToITUzone)
    {
      return GT.prefixToITUzone[callsign.substr(0, x)];
    }
  }

  if (dxcc > 0)
  {
    return GT.dxccInfo[dxcc].ituzone;
  }

  return null;
}

function getWpx(callsign)
{
  var prefix = null;

  if (callsign.includes("/"))
  // Handle in the future?
  { return null; }
  if (!/\d/.test(callsign))
  // Insert 0, never seen this
  { return null; }

  var end = callsign.length;
  var foundPrefix = false;
  var prefixEnd = 1;
  while (prefixEnd != end)
  {
    if (/\d/.test(callsign.charAt(prefixEnd)))
    {
      while (prefixEnd + 1 != end && /\d/.test(callsign.charAt(prefixEnd + 1))) { prefixEnd++; }
      foundPrefix = true;
      break;
    }
    prefixEnd++;
  }

  if (foundPrefix) prefix = callsign.substr(0, prefixEnd + 1);

  return String(prefix);
}

GT.ancPrefixes = ["P", "M", "MM", "AM", "A", "NWS"];

function callsignToDxcc(insign)
{
  var callsign = insign;

  if (!/\d/.test(callsign) || !/[a-zA-Z]/.test(callsign))
  {
    return -1;
  }

  if (callsign in GT.directCallToDXCC) { return Number(GT.directCallToDXCC[callsign]); }

  if (callsign.includes("/"))
  {
    var parts = callsign.split("/");
    var end = parts.length - 1;
    if (GT.ancPrefixes.includes(parts[end]))
    {
      if (parts[end].toUpperCase() == "MM")
      {
        return 0;
      }
      parts.pop();
      end = parts.length - 1;
    }
    if (end)
    {
      if (isNaN(parts[end]))
      {
        if (parts[1].length > parts[0].length)
        {
          callsign = parts[0];
        }
        else
        {
          if (callsignToDxcc(parts[1]) != -1) callsign = parts[1];
          else callsign = parts[0];
        }
      }
      else callsign = parts[0];
    }
    else callsign = parts[0];

    if (callsign in GT.directCallToDXCC) { return Number(GT.directCallToDXCC[callsign]); }
  }

  for (var x = callsign.length; x > 0; x--)
  {
    if (callsign.substr(0, x) in GT.prefixToMap)
    {
      return Number(GT.dxccInfo[GT.prefixToMap[callsign.substr(0, x)]].dxcc);
    }
  }
  return -1;
}

function initQSOdata()
{
  GT.tracker = {};
  GT.tracker.worked = {};
  GT.tracker.confirmed = {};

  GT.tracker.worked.call = {};
  GT.tracker.worked.grid = {};
  GT.tracker.worked.field = {};
  GT.tracker.worked.dxcc = {};
  GT.tracker.worked.cqz = {};
  GT.tracker.worked.ituz = {};
  GT.tracker.worked.state = {};
  GT.tracker.worked.px = {};
  GT.tracker.worked.cnty = {};
  GT.tracker.worked.cont = {};
  GT.tracker.worked.pota = {};

  GT.tracker.confirmed.call = {};
  GT.tracker.confirmed.grid = {};
  GT.tracker.confirmed.field = {};
  GT.tracker.confirmed.dxcc = {};
  GT.tracker.confirmed.cqz = {};
  GT.tracker.confirmed.ituz = {};
  GT.tracker.confirmed.state = {};
  GT.tracker.confirmed.px = {};
  GT.tracker.confirmed.cnty = {};
  GT.tracker.confirmed.cont = {};
  GT.tracker.confirmed.pota = {};
}

function trackQSO(details, currentYear)
{
  let qsoDate = new Date(1970, 0, 1); qsoDate.setSeconds(details.time);
  let isCurrentYear = (qsoDate.getFullYear() == currentYear);
  let dayAsString = String(parseInt(details.time / 86400));
  let fourGrid = details.grid.substring(0, 4);
  let isDigi = details.digital;
  let isPhone = details.phone;

  GT.tracker.worked.call[details.DEcall + details.band + details.mode] = true;
  GT.tracker.worked.call[details.DEcall] = true;
  GT.tracker.worked.call[details.DEcall + details.mode] = true;
  GT.tracker.worked.call[details.DEcall + details.band] = true;

  if (isDigi == true)
  {
    GT.tracker.worked.call[details.DEcall + "dg"] = true;
    GT.tracker.worked.call[details.DEcall + details.band + "dg"] = true;
  }

  if (fourGrid != "")
  {
    GT.tracker.worked.grid[fourGrid] = true;
    GT.tracker.worked.grid[fourGrid + details.mode] = true;
    GT.tracker.worked.grid[fourGrid + details.band] = true;
    GT.tracker.worked.grid[fourGrid + details.band + details.mode] = true;

    let field = fourGrid.substring(0, 2);

    GT.tracker.worked.field[field] = true;
    GT.tracker.worked.field[field + details.mode] = true;
    GT.tracker.worked.field[field + details.band] = true;
    GT.tracker.worked.field[field + details.band + details.mode] = true;

    if (isDigi == true)
    {
      GT.tracker.worked.grid[fourGrid + "dg"] = true;
      GT.tracker.worked.grid[fourGrid + details.band + "dg"] = true;

      GT.tracker.worked.field[field + "dg"] = true;
      GT.tracker.worked.field[field + details.band + "dg"] = true;
    }
  }

  if (details.ituz)
  {
    GT.tracker.worked.ituz[details.ituz + "|" + details.band + details.mode] = true;
    GT.tracker.worked.ituz[details.ituz + "|"] = true;
    GT.tracker.worked.ituz[details.ituz + "|" + details.mode] = true;
    GT.tracker.worked.ituz[details.ituz + "|" + details.band] = true;
    if (isDigi == true)
    {
      GT.tracker.worked.ituz[details.ituz + "|dg"] = true;
      GT.tracker.worked.ituz[details.ituz + "|" + details.band + "dg"] = true;
    }
  }

  if (details.cqz)
  {
    GT.tracker.worked.cqz[details.cqz + "|" + details.band + details.mode] = true;
    GT.tracker.worked.cqz[details.cqz + "|"] = true;
    GT.tracker.worked.cqz[details.cqz + "|" + details.mode] = true;
    GT.tracker.worked.cqz[details.cqz + "|" + details.band] = true;
    if (isDigi == true)
    {
      GT.tracker.worked.cqz[details.cqz + "|dg"] = true;
      GT.tracker.worked.cqz[details.cqz + "|" + details.band + "dg"] = true;
    }
    if (isCurrentYear)
    {
      GT.tracker.worked.cqz[`${details.cqz}-${currentYear}`] = true;
    }
  }

  if (details.dxcc > 0)
  {
    var sDXCC = String(details.dxcc);
    GT.tracker.worked.dxcc[sDXCC + "|" + details.band + details.mode] = true;
    GT.tracker.worked.dxcc[sDXCC + "|"] = true;
    GT.tracker.worked.dxcc[sDXCC + "|" + details.mode] = true;
    GT.tracker.worked.dxcc[sDXCC + "|" + details.band] = true;
    if (isDigi == true)
    {
      GT.tracker.worked.dxcc[sDXCC + "|dg"] = true;
      GT.tracker.worked.dxcc[sDXCC + "|" + details.band + "dg"] = true;
    }
    if (isPhone == true)
    {
      GT.tracker.worked.dxcc[sDXCC + "|ph"] = true;
    }
    if (isCurrentYear)
    {
      GT.tracker.worked.dxcc[`${sDXCC}-${currentYear}`] = true;
    }
  }

  if (details.px)
  {
    GT.tracker.worked.px[details.px + details.band + details.mode] = true;
    // store the last one
    GT.tracker.worked.px[details.px] = details.hash;
    GT.tracker.worked.px[details.px + details.mode] = true;
    GT.tracker.worked.px[details.px + details.band] = true;
    if (isDigi == true)
    {
      GT.tracker.worked.px[details.px + "dg"] = true;
      GT.tracker.worked.px[details.px + details.band + "dg"] = true;
    }
    if (isPhone == true)
    {
      GT.tracker.worked.px[details.px + "ph"] = true;
      GT.tracker.worked.px[details.px + details.band + "ph"] = true;
    }
  }

  if (details.cont)
  {
    GT.tracker.worked.cont[details.cont + details.band + details.mode] = true;
    // store the last one
    GT.tracker.worked.cont[details.cont] = details.hash;
    GT.tracker.worked.cont[details.cont + details.mode] = true;
    GT.tracker.worked.cont[details.cont + details.band] = true;
    if (isDigi == true)
    {
      GT.tracker.worked.cont[details.cont + "dg"] = true;
      GT.tracker.worked.cont[details.cont + details.band + "dg"] = true;
    }
  }

  if (details.state)
  {
    GT.tracker.worked.state[details.state] = true;
    GT.tracker.worked.state[details.state + details.mode] = true;
    GT.tracker.worked.state[details.state + details.band] = true;
    GT.tracker.worked.state[details.state + details.band + details.mode] = true;

    if (isDigi)
    {
      GT.tracker.worked.state[details.state + "dg"] =
      GT.tracker.worked.state[details.state + details.band + "dg"] = true;
    }
  }

  if (details.cnty)
  {
    GT.tracker.worked.cnty[details.cnty] = true;
    GT.tracker.worked.cnty[details.cnty + details.mode] = true;
    GT.tracker.worked.cnty[details.cnty + details.band] = true;
    GT.tracker.worked.cnty[details.cnty + details.band + details.mode] = true;

    if (isDigi)
    {
      GT.tracker.worked.cnty[details.cnty + "dg"] = true;
      GT.tracker.worked.cnty[details.cnty + details.band + "dg"] = true;
    }
  }

  if (details.pota)
  {
    var potas = details.pota.split(",");
    for (let x in potas)
    {
      let pota = potas[x].trim();
      GT.tracker.worked.pota[dayAsString + details.DEcall + pota] = true;
      GT.tracker.worked.pota[dayAsString + details.DEcall + pota + details.mode] = true;
      GT.tracker.worked.pota[dayAsString + details.DEcall + pota + details.band] = true;
      GT.tracker.worked.pota[dayAsString + details.DEcall + pota + details.band + details.mode] = true;

      GT.tracker.worked.pota[dayAsString + pota + details.band + details.mode] = true;

      GT.tracker.worked.pota[pota] = true;
      GT.tracker.worked.pota[pota + details.mode] = true;
      GT.tracker.worked.pota[pota + details.band] = true;
      GT.tracker.worked.pota[pota + details.band + details.mode] = true;

      if (isDigi == true)
      {
        GT.tracker.worked.pota[dayAsString + details.DEcall + pota + "dg"] = true;
        GT.tracker.worked.pota[dayAsString + details.DEcall + pota + details.band + "dg"] = true;

        GT.tracker.worked.pota[pota + "dg"] = true;
        GT.tracker.worked.pota[pota + details.band + "dg"] = true;
      }
    }
  }

  if (details.confirmed == true)
  {
    GT.tracker.confirmed.call[details.DEcall + details.band + details.mode] = true;
    GT.tracker.confirmed.call[details.DEcall] = true;
    GT.tracker.confirmed.call[details.DEcall + details.mode] = true;
    GT.tracker.confirmed.call[details.DEcall + details.band] = true;
    if (isDigi == true)
    {
      GT.tracker.confirmed.call[details.DEcall + "dg"] = true;
      GT.tracker.confirmed.call[details.DEcall + details.band + "dg"] = true;
    }

    if (fourGrid != "")
    {
      GT.tracker.confirmed.grid[fourGrid + details.band + details.mode] = true;
      GT.tracker.confirmed.grid[fourGrid] = true;
      GT.tracker.confirmed.grid[fourGrid + details.mode] = true;
      GT.tracker.confirmed.grid[fourGrid + details.band] = true;
      if (isDigi == true)
      {
        GT.tracker.confirmed.grid[fourGrid + "dg"] = true;
        GT.tracker.confirmed.grid[fourGrid + details.band + "dg"] = true;
      }
    }
    if (details.ituz && details.ituz.length > 0)
    {
      GT.tracker.confirmed.ituz[details.ituz + "|" + details.band + details.mode] = true;
      GT.tracker.confirmed.ituz[details.ituz + "|"] = true;
      GT.tracker.confirmed.ituz[details.ituz + "|" + details.mode] = true;
      GT.tracker.confirmed.ituz[details.ituz + "|" + details.band] = true;
      if (isDigi == true)
      {
        GT.tracker.confirmed.ituz[details.ituz + "|dg"] = true;
        GT.tracker.confirmed.ituz[details.ituz + "|" + details.band + "dg"] = true;
      }
    }
    if (details.cqz && details.cqz.length > 0)
    {
      GT.tracker.confirmed.cqz[details.cqz + "|" + details.band + details.mode] = true;
      GT.tracker.confirmed.cqz[details.cqz + "|"] = true;
      GT.tracker.confirmed.cqz[details.cqz + "|" + details.mode] = true;
      GT.tracker.confirmed.cqz[details.cqz + "|" + details.band] = true;
      if (isDigi == true)
      {
        GT.tracker.confirmed.cqz[details.cqz + "|dg"] = true;
        GT.tracker.confirmed.cqz[details.cqz + "|" + details.band + "dg"] = true;
      }
    }

    if (details.dxcc > 0)
    {
      var sDXCC = String(details.dxcc);
      GT.tracker.confirmed.dxcc[sDXCC + "|" + details.band + details.mode] = true;
      GT.tracker.confirmed.dxcc[sDXCC + "|"] = true;
      GT.tracker.confirmed.dxcc[sDXCC + "|" + details.mode] = true;
      GT.tracker.confirmed.dxcc[sDXCC + "|" + details.band] = true;
      if (isDigi == true)
      {
        GT.tracker.confirmed.dxcc[sDXCC + "|dg"] = true;
        GT.tracker.confirmed.dxcc[sDXCC + "|" + details.band + "dg"] = true;
      }
      if (isPhone == true)
      {
        GT.tracker.confirmed.dxcc[sDXCC + "|ph"] = true;
      }
    }

    if (details.state)
    {
      GT.tracker.confirmed.state[details.state] = true;
      GT.tracker.confirmed.state[details.state + details.mode] = true;
      GT.tracker.confirmed.state[details.state + details.band] = true;
      GT.tracker.confirmed.state[details.state + details.band + details.mode] = true;

      if (isDigi)
      {
        GT.tracker.confirmed.state[details.state + "dg"] = true;
        GT.tracker.confirmed.state[details.state + details.band + "dg"] = true;
      }
    }

    if (details.cnty)
    {
      GT.tracker.confirmed.cnty[details.cnty] = true;
      GT.tracker.confirmed.cnty[details.cnty + details.mode] = true;
      GT.tracker.confirmed.cnty[details.cnty + details.band] = true;
      GT.tracker.confirmed.cnty[details.cnty + details.band + details.mode] = true;

      if (isDigi)
      {
        GT.tracker.confirmed.cnty[details.cnty + "dg"] = true;
        GT.tracker.confirmed.cnty[details.cnty + details.band + "dg"] = true;
      }
    }

    if (details.px)
    {
      GT.tracker.confirmed.px[details.px + details.band + details.mode] = true;
      // store the last one
      GT.tracker.confirmed.px[details.px] = details.hash;
      GT.tracker.confirmed.px[details.px + details.mode] = true;
      GT.tracker.confirmed.px[details.px + details.band] = true;
      if (isDigi == true)
      {
        GT.tracker.confirmed.px[details.px + "dg"] = true;
        GT.tracker.confirmed.px[details.px + details.band + "dg"] = true;
      }
      if (isPhone == true)
      {
        GT.tracker.confirmed.px[details.px + "ph"] = true;
        GT.tracker.confirmed.px[details.px + details.band + "ph"] = true;
      }
    }

    if (details.cont)
    {
      GT.tracker.confirmed.cont[details.cont + details.band + details.mode] = true;
      // store the last one
      GT.tracker.confirmed.cont[details.cont] = details.hash;
      GT.tracker.confirmed.cont[details.cont + details.mode] = true;
      GT.tracker.confirmed.cont[details.cont + details.band] = true;
      if (isDigi == true)
      {
        GT.tracker.confirmed.cont[details.cont + "dg"] = true;
        GT.tracker.confirmed.cont[details.cont + details.band + "dg"] = true;
      }
    }
  }
}

/* eslint-disable */

function bitwise(str){
	var hash = 0;
	if (str.length == 0) return hash;
	for (var i = 0; i < str.length; i++) {
		var ch = str.charCodeAt(i);
		hash = ((hash<<5)-hash) + ch;
		hash = hash & hash; // Convert to 32bit integer
	}
	return hash;
}

// convert 10 binary to customized binary, max is 62
function binaryTransfer(integer, binary) {
	binary = binary || 62;
	var stack = [];
	var num;
	var result = '';
	var sign = integer < 0 ? 'Z' : '';

	function table (num) {
		var t = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		return t[num];
	}

	integer = Math.abs(integer);

	while (integer >= binary) {
		num = integer % binary;
		integer = Math.floor(integer / binary);
		stack.push(table(num));
	}

	if (integer > 0) {
		stack.push(table(integer));
	}

	for (var i = stack.length - 1; i >= 0; i--) {
		result += stack[i];
	}

	return sign + result;
}
/**
 * why choose 61 binary, because we need the last element char to replace the minus sign
 * eg: -aGtzd will be ZaGtzd
 */
function unique (text) {
	return binaryTransfer(bitwise(text), 62);
}