'use strict';
// @ts-check
// ==================================================================================
// wifi.js
// ----------------------------------------------------------------------------------
// Description:   System Information - library
//                for Node.js
// Copyright:     (c) 2014 - 2021
// Author:        Sebastian Hildebrandt
// ----------------------------------------------------------------------------------
// License:       MIT
// ==================================================================================
// 9. wifi
// ----------------------------------------------------------------------------------

const os = require('os');
const exec = require('child_process').exec;
const execSync = require('child_process').execSync;
const util = require('./util');

let _platform = process.platform;

const _linux = (_platform === 'linux');
const _darwin = (_platform === 'darwin');
const _windows = (_platform === 'win32');

function wifiDBFromQuality(quality) {
  return (parseFloat(quality) / 2 - 100);
}

function wifiQualityFromDB(db) {
  const result = 2 * (parseFloat(db) + 100);
  return result <= 100 ? result : 100;
}

function wifiFrequencyFromChannel(channel) {
  const frequencies = {
    1: 2412,
    2: 2417,
    3: 2422,
    4: 2427,
    5: 2432,
    6: 2437,
    7: 2442,
    8: 2447,
    9: 2452,
    10: 2457,
    11: 2462,
    12: 2467,
    13: 2472,
    14: 2484,
    32: 5160,
    34: 5170,
    36: 5180,
    38: 5190,
    40: 5200,
    42: 5210,
    44: 5220,
    46: 5230,
    48: 5240,
    50: 5250,
    52: 5260,
    54: 5270,
    56: 5280,
    58: 5290,
    60: 5300,
    62: 5310,
    64: 5320,
    68: 5340,
    96: 5480,
    100: 5500,
    102: 5510,
    104: 5520,
    106: 5530,
    108: 5540,
    110: 5550,
    112: 5560,
    114: 5570,
    116: 5580,
    118: 5590,
    120: 5600,
    122: 5610,
    124: 5620,
    126: 5630,
    128: 5640,
    132: 5660,
    134: 5670,
    136: 5680,
    138: 5690,
    140: 5700,
    142: 5710,
    144: 5720,
    149: 5745,
    151: 5755,
    153: 5765,
    155: 5775,
    157: 5785,
    159: 5795,
    161: 5805,
    165: 5825,
    169: 5845,
    173: 5865,
    183: 4915,
    184: 4920,
    185: 4925,
    187: 4935,
    188: 4940,
    189: 4945,
    192: 4960,
    196: 4980
  };
  return {}.hasOwnProperty.call(frequencies, channel) ? frequencies[channel] : null;
}

function getWifiNetworkAlt(iface) {
  const result = [];
  try {
    let iwlistParts = execSync(`export LC_ALL=C; iwlist ${iface} scan 2>&1; unset LC_ALL`).toString().split('        Cell ');
    if (iwlistParts[0].indexOf('resource busy') >= 0) { return -1; }
    if (iwlistParts.length > 1) {
      iwlistParts.shift();
      for (let i = 0; i < iwlistParts.length; i++) {
        const lines = iwlistParts[i].split('\n');
        const channel = util.getValue(lines, 'channel', ':', true);
        const address = (lines && lines.length && lines[0].indexOf('Address:') >= 0 ? lines[0].split('Address:')[1].trim().toLowerCase() : '');
        const mode = util.getValue(lines, 'mode', ':', true);
        const frequency = util.getValue(lines, 'frequency', ':', true);
        const qualityString = util.getValue(lines, 'Quality', '=', true);
        const dbParts = qualityString.toLowerCase().split('signal level=');
        const db = dbParts.length > 1 ? util.toInt(dbParts[1]) : 0;
        const quality = db ? wifiQualityFromDB(db) : 0;
        const ssid = util.getValue(lines, 'essid', ':', true);

        // security and wpa-flags
        const isWpa = iwlistParts[i].indexOf(' WPA ') >= 0;
        const isWpa2 = iwlistParts[i].indexOf('WPA2 ') >= 0;
        const security = [];
        if (isWpa) { security.push('WPA'); }
        if (isWpa2) { security.push('WPA2'); }
        const wpaFlags = [];
        let wpaFlag = '';
        lines.forEach(function (line) {
          const l = line.trim().toLowerCase();
          if (l.indexOf('group cipher') >= 0) {
            if (wpaFlag) {
              wpaFlags.push(wpaFlag);
            }
            const parts = l.split(':');
            if (parts.length > 1) {
              wpaFlag = parts[1].trim().toUpperCase();
            }
          }
          if (l.indexOf('pairwise cipher') >= 0) {
            const parts = l.split(':');
            if (parts.length > 1) {
              if (parts[1].indexOf('tkip')) { wpaFlag = (wpaFlag ? 'TKIP/' + wpaFlag : 'TKIP'); }
              else if (parts[1].indexOf('ccmp')) { wpaFlag = (wpaFlag ? 'CCMP/' + wpaFlag : 'CCMP'); }
              else if (parts[1].indexOf('proprietary')) { wpaFlag = (wpaFlag ? 'PROP/' + wpaFlag : 'PROP'); }
            }
          }
          if (l.indexOf('authentication suites') >= 0) {
            const parts = l.split(':');
            if (parts.length > 1) {
              if (parts[1].indexOf('802.1x')) { wpaFlag = (wpaFlag ? '802.1x/' + wpaFlag : '802.1x'); }
              else if (parts[1].indexOf('psk')) { wpaFlag = (wpaFlag ? 'PSK/' + wpaFlag : 'PSK'); }
            }
          }
        });
        if (wpaFlag) {
          wpaFlags.push(wpaFlag);
        }

        result.push({
          ssid,
          bssid: address,
          mode,
          channel: channel ? util.toInt(channel) : null,
          frequency: frequency ? util.toInt(frequency.replace('.', '')) : null,
          signalLevel: db,
          quality,
          security,
          wpaFlags,
          rsnFlags: []
        });
      }
    }
    return result;
  } catch (e) {
    return -1;
  }
}

function wifiNetworks(callback) {

  return new Promise((resolve) => {
    process.nextTick(() => {
      let result = [];
      if (_linux) {
        let cmd = 'nmcli --terse --fields active,ssid,bssid,mode,chan,freq,signal,security,wpa-flags,rsn-flags device wifi list 2>/dev/null';
        exec(cmd, { maxBuffer: 1024 * 20000 }, function (error, stdout) {

          const parts = stdout.toString().split('ACTIVE:');
          parts.shift();
          parts.forEach(part => {
            part = 'ACTIVE:' + part;
            const lines = part.split(os.EOL);
            const channel = util.getValue(lines, 'CHAN');
            const frequency = util.getValue(lines, 'FREQ').toLowerCase().replace('mhz', '').trim();
            const security = util.getValue(lines, 'SECURITY').replace('(', '').replace(')', '');
            const wpaFlags = util.getValue(lines, 'WPA-FLAGS').replace('(', '').replace(')', '');
            const rsnFlags = util.getValue(lines, 'RSN-FLAGS').replace('(', '').replace(')', '');
            result.push({
              ssid: util.getValue(lines, 'SSID'),
              bssid: util.getValue(lines, 'BSSID').toLowerCase(),
              mode: util.getValue(lines, 'MODE'),
              channel: channel ? parseInt(channel, 10) : null,
              frequency: frequency ? parseInt(frequency, 10) : null,
              signalLevel: wifiDBFromQuality(util.getValue(lines, 'SIGNAL')),
              quality: parseFloat(util.getValue(lines, 'SIGNAL')),
              security: security && security !== 'none' ? security.split(' ') : [],
              wpaFlags: wpaFlags && wpaFlags !== 'none' ? wpaFlags.split(' ') : [],
              rsnFlags: rsnFlags && rsnFlags !== 'none' ? rsnFlags.split(' ') : []
            });
          });

          if (result.length === 0) {
            try {
              const iwconfigParts = execSync('export LC_ALL=C; iwconfig 2>/dev/null; unset LC_ALL').toString().split('\n\n');
              let iface = '';
              for (let i = 0; i < iwconfigParts.length; i++) {
                if (iwconfigParts[i].indexOf('no wireless') === -1) {
                  iface = iwconfigParts[i].split(' ')[0];
                }
              }
              if (iface) {
                const res = getWifiNetworkAlt(iface);
                if (res === -1) {
                  // try again after 4 secs
                  setTimeout(function (iface) {
                    const res = getWifiNetworkAlt(iface);
                    if (res != -1) { result = res; }
                    if (callback) {
                      callback(result);
                    }
                    resolve(result);
                  }, 4000);
                } else {
                  result = res;
                  if (callback) {
                    callback(result);
                  }
                  resolve(result);
                }
              } else {
                if (callback) {
                  callback(result);
                }
                resolve(result);
              }
            } catch (e) {
              if (callback) {
                callback(result);
              }
              resolve(result);
            }
          } else {
            if (callback) {
              callback(result);
            }
            resolve(result);
          }
        });
      } else if (_darwin) {
        let cmd = '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s';
        exec(cmd, { maxBuffer: 1024 * 20000 }, function (error, stdout) {
          const lines = stdout.toString().split(os.EOL);
          if (lines && lines.length > 1) {
            const parsedhead = util.parseHead(lines[0], 1);
            if (parsedhead.length >= 7) {
              lines.shift();
              lines.forEach(line => {
                if (line.trim()) {
                  const channelStr = line.substring(parsedhead[3].from, parsedhead[3].to).trim();
                  const channel = channelStr ? parseInt(channelStr, 10) : null;
                  const signalLevel = line.substring(parsedhead[2].from, parsedhead[2].to).trim();
                  const securityAll = line.substring(parsedhead[6].from, 1000).trim().split(' ');
                  let security = [];
                  let wpaFlags = [];
                  securityAll.forEach(securitySingle => {
                    if (securitySingle.indexOf('(') > 0) {
                      const parts = securitySingle.split('(');
                      security.push(parts[0]);
                      wpaFlags = wpaFlags.concat(parts[1].replace(')', '').split(','));
                    }
                  });
                  wpaFlags = Array.from(new Set(wpaFlags));
                  result.push({
                    ssid: line.substring(parsedhead[0].from, parsedhead[0].to).trim(),
                    bssid: line.substring(parsedhead[1].from, parsedhead[1].to).trim().toLowerCase(),
                    mode: '',
                    channel,
                    frequency: wifiFrequencyFromChannel(channel),
                    signalLevel: signalLevel ? parseInt(signalLevel, 10) : null,
                    quality: wifiQualityFromDB(signalLevel),
                    security,
                    wpaFlags,
                    rsnFlags: []
                  });
                }
              });
            }
          }
          if (callback) {
            callback(result);
          }
          resolve(result);
        });
      } else if (_windows) {
        let cmd = 'chcp 65001 && netsh wlan show networks mode=Bssid';
        exec(cmd, util.execOptsWin, function (error, stdout) {

          const parts = stdout.toString('utf8').split(os.EOL + os.EOL + 'SSID ');
          parts.shift();

          parts.forEach(part => {
            const lines = part.split(os.EOL);
            if (lines && lines.length >= 8 && lines[0].indexOf(':') >= 0) {
              let bssid = lines[4].split(':');
              bssid.shift();
              bssid = bssid.join(':').trim().toLowerCase();
              const channel = lines[7].split(':').pop().trim();
              const quality = lines[5].split(':').pop().trim();
              result.push({
                ssid: lines[0].split(':').pop().trim(),
                bssid,
                mode: '',
                channel: channel ? parseInt(channel, 10) : null,
                frequency: wifiFrequencyFromChannel(channel),
                signalLevel: wifiDBFromQuality(quality),
                quality: quality ? parseInt(quality, 10) : null,
                security: [lines[2].split(':').pop().trim()],
                wpaFlags: [lines[3].split(':').pop().trim()],
                rsnFlags: []
              });
            }
          });

          if (callback) {
            callback(result);
          }
          resolve(result);
        });
      } else {
        if (callback) {
          callback(result);
        }
        resolve(result);
      }
    });
  });
}

exports.wifiNetworks = wifiNetworks;
