/* Libraries / Imports */
const { send_xdr_online, send_to_xdr } = require('./xdr_server');
const RDSDecoder = require("./rds.js");
const { serverConfig } = require('./server_config');

const fetchTx = require('./tx_search.js');
const updateInterval = 75;

// Initialize the data object
var dataToSend = {
  pi: '?',
  freq: (87.500).toFixed(3),
  sig: 0,
  sigRaw: '',
  sigTop: -Infinity,
  bw: 0,
  st: false,
  stForced: false,
  rds: false,
  ps: '',
  tp: 0,
  ta: 0,
  ms: -1,
  pty: 0,
  ecc: null,
  af: [],
  rt0: '',
  rt1: '',
  rt_flag: '',
  ims: 0,
  eq: 0,
  agc: 0,
  ant: 0,
  txInfo: {
    tx: '', // Name
    pol: '', // Polarization
    erp: '', // Power
    city: '',
    itu: '', // Country
    dist: '', // Distance
    azi: '', // Azimuth
    id: 0, // Some number
    reg: false,
    pi: '', // PI
  },
  country_name: '',
  country_iso: 'UN',
  users: 0,
};

const rdsdec = new RDSDecoder(dataToSend);

const filterMappings = {
  'G11': { eq: 1, ims: 1 },
  'G01': { eq: 0, ims: 1 },
  'G10': { eq: 1, ims: 0 },
  'G00': { eq: 0, ims: 0 }
};

var legacyRdsPiBuffer = null;
var lastUpdateTime = Date.now();
const initialData = { ...dataToSend };
const resetToDefault = dataToSend => Object.assign(dataToSend, initialData);

var serialportUpdateTime = process.hrtime();
let checkSerialport = false;
let rdsTimeoutTimer = null;

function rdsReceived() {
  if (rdsTimeoutTimer) {
    clearTimeout(rdsTimeoutTimer);
    rdsTimeoutTimer = null;
  }
  if (serverConfig.webserver.rdsTimeout && serverConfig.webserver.rdsTimeout != 0) rdsTimeoutTimer = setTimeout(rdsReset, serverConfig.webserver.rdsTimeout * 1000);
}

function rdsReset() {
  resetToDefault(dataToSend);
  dataToSend.af.length = 0;
  rdsdec.clear();
  if (rdsTimeoutTimer) {
    clearTimeout(rdsTimeoutTimer);
    rdsTimeoutTimer = null;
  }
}

function handleData(wss, receivedData, rdsWss) {
  send_to_xdr(receivedData);
  
  // Retrieve the last update time for this client
  const currentTime = Date.now();

  let modifiedData, parsedValue;
  const receivedLines = receivedData.split('\n');

  for (const receivedLine of receivedLines) {
    switch (true) {
      case receivedLine.startsWith('F'): // Bandwidth
        initialData.bw = receivedLine.substring(1);
        dataToSend.bw = receivedLine.substring(1);
        break;
      case receivedLine.startsWith('P'): // PI Code
        rdsReceived();
        modifiedData = receivedLine.slice(1);
        legacyRdsPiBuffer = modifiedData;
        if (dataToSend.pi.length >= modifiedData.length || dataToSend.pi == '?') dataToSend.pi = modifiedData;
        break;
      case receivedLine.startsWith('T'): // Frequency
        modifiedData = receivedLine.substring(1).split(",")[0];

        if((modifiedData / 1000).toFixed(3) == dataToSend.freq) return; // Prevent tune spamming using scrollwheel
        rdsReset();

        parsedValue = parseFloat(modifiedData);

        if (!isNaN(parsedValue)) {
          initialData.freq = (parsedValue / 1000).toFixed(3);
          dataToSend.freq = (parsedValue / 1000).toFixed(3);
          dataToSend.pi = '?';
          dataToSend.txInfo.reg = false;

          rdsWss.clients.forEach((client) => client.send("G:\r\nRESET-------\r\n\r\n"));
        }
        break;
      case receivedLine.startsWith('Z'): // Antenna
        dataToSend.ant = receivedLine.substring(1);
        initialData.ant = receivedLine.substring(1);
        rdsReset();
        break;
      case receivedLine.startsWith('A'): // AGC
        dataToSend.agc = receivedLine.substring(1);
        initialData.agc = receivedLine.substring(1);
        break;
      case receivedLine.startsWith('G'): // EQ / iMS
        const mapping = filterMappings[receivedLine];
        if (mapping) {
          initialData.eq = mapping.eq;
          initialData.ims = mapping.ims;
          dataToSend.eq = mapping.eq;
          dataToSend.ims = mapping.ims;
        }
        break;
      case receivedLine.startsWith('W'): // Bandwidth
        initialData.bw = receivedLine.substring(1);
        dataToSend.bw = receivedLine.substring(1);
        break;
      case receivedLine.startsWith('Sm'):
        processSignal(receivedLine, false, false);
        break;
      case receivedLine.startsWith('Ss'):
        processSignal(receivedLine, true, false);
        break;
      case receivedLine.startsWith('SS'): // ss? oh no
        processSignal(receivedLine, true, true);
        break;
      case receivedLine.startsWith('SM'):
        processSignal(receivedLine, false, true);
        break;
      case receivedLine.startsWith('R'):
        rdsReceived();
        modifiedData = receivedLine.slice(1);
        dataToSend.rds = true;

        if (modifiedData.length == 14) {
          // Handle legacy RDS message
          var errorsNew = 0;
          var pi;

          if(legacyRdsPiBuffer !== null && legacyRdsPiBuffer.length >= 4) {
            pi = legacyRdsPiBuffer.slice(0, 4);
            // PI message does not carry explicit information about
            // error correction, but this is a good substitute.
            errorsNew = (legacyRdsPiBuffer.length - 4) << 6;
          } else {
            pi = '0000';
            errorsNew = (0x03 << 6);
          }

          let errorsOld = parseInt(modifiedData.slice(12), 16);
          errorsNew |= (errorsOld & 0x03) << 4;
          errorsNew |= (errorsOld & 0x0C);
          errorsNew |= (errorsOld & 0x30) >> 4;

          modifiedData = pi + modifiedData.slice(0, 12);
          modifiedData += errorsNew.toString(16).padStart(2, '0');
        }

        const a = modifiedData.slice(0, 4);
        const b = modifiedData.slice(4, 8);
        const c = modifiedData.slice(8, 12);
        const d = modifiedData.slice(12, 16);
        const errors = parseInt(modifiedData.slice(-2), 16);
        rdsWss.clients.forEach((client) => {
          let data = ((((errors >> 6) & 3) < 3) ? a : '----');
          data += ((((errors >> 4) & 3) < 3) ? b : '----');
          data += ((((errors >> 2) & 3) < 3) ? c : '----');
          data += (((errors & 3) < 3) ? d : '----');

          client.send("G:\r\n" + data + "\r\n\r\n");
        });

        rdsdec.decodeGroup(parseInt(a, 16), parseInt(b, 16), parseInt(c, 16), parseInt(d, 16), errors);
        legacyRdsPiBuffer = null;
        break;
      case receivedLine.startsWith("!"):
        wss.clients.forEach((client) => client.send(receivedLine.trim()));
        break;
    }
  }

  // Get the received TX info
  fetchTx(parseFloat(dataToSend.freq).toFixed(1), dataToSend.pi, dataToSend.ps)
  .then((currentTx) => {
      if (currentTx && currentTx.station !== undefined && parseInt(currentTx.distance) < 4000) {
          dataToSend.txInfo = {
              tx: currentTx.station,
              pol: currentTx.pol,
              erp: currentTx.erp,
              city: currentTx.city,
              itu: currentTx.itu,
              dist: currentTx.distance,
              azi: currentTx.azimuth,
              id: currentTx.id,
              pi: currentTx.pi,
              reg: currentTx.reg,
              otherMatches: currentTx.others,
              score: currentTx.score,
          };
      }
  })
  .catch((error) => console.log("Error fetching Tx info:", error));

  // Send the updated data to the client
  const dataToSendJSON = JSON.stringify(dataToSend);
  if (currentTime - lastUpdateTime >= updateInterval) {
    wss.clients.forEach((client) => client.send(dataToSendJSON));
    lastUpdateTime = Date.now();
    serialportUpdateTime = process.hrtime();
  }
}

// Serialport retry code when port is open but communication is lost (additional code in index.js)
let state = {
  isSerialportAlive: true,
  isSerialportRetrying: false,
  lastFrequencyAlive: '87.500'
};

setInterval(() => {
  state.lastFrequencyAlive = initialData.freq;
  const serialportElapsedTime = process.hrtime(serialportUpdateTime)[0];
  // Activate serialport retry if handleData has not been executed for over 8 seconds
  if (checkSerialport && (serialportElapsedTime > 8) && !state.isSerialportRetrying && serverConfig.xdrd.wirelessConnection === false) {
    state.isSerialportAlive = false;
    state.isSerialportRetrying = true;
  }
}, 2000);

// Delay checking Serialport status on startup for 10 seconds
async function checkSerialPortStatus() {
    const ServerStartTime = process.hrtime();

    while (!checkSerialport) {
        const ServerElapsedSeconds = process.hrtime(ServerStartTime)[0];

        if (ServerElapsedSeconds > 10) checkSerialport = true;
        await new Promise(resolve => setTimeout(resolve, 100));
    }
}
checkSerialPortStatus();

function showOnlineUsers(currentUsers) {
  dataToSend.users = currentUsers;
  initialData.users = currentUsers;
  send_xdr_online(currentUsers);
}

let prevFreq = initialData.freq || '87.500';
function processSignal(receivedData, st, stForced) {
  if (initialData.freq !== prevFreq) {
    prevFreq = initialData.freq;
    dataToSend.ps_errors = '';
  }

  const modifiedData = receivedData.substring(2);
  const parsedValue = parseFloat(modifiedData);
  dataToSend.st = st;
  dataToSend.stForced = stForced;
  initialData.st = st;
  initialData.stForced = stForced;

  if (!isNaN(parsedValue)) {
    // Convert parsedValue to a number
    var signal = parseFloat(parsedValue.toFixed(2));
    dataToSend.sig = signal;
    initialData.sig = signal;
    dataToSend.sigRaw = receivedData;
    initialData.sigRaw = receivedData;

    // Convert highestSignal to a number for comparison
    var highestSignal = parseFloat(dataToSend.sigTop);
    if (signal > highestSignal) dataToSend.sigTop = signal.toString(); // Convert back to string for consistency
  }
}

module.exports = { handleData, showOnlineUsers, dataToSend, initialData, resetToDefault, state };
