window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;

if (!window.indexedDB) {
  window.alert("Your browser doesn't support a stable version of IndexedDB.")
}

var contacts, groups;
var searchIndex = new FlexSearch();
var receiverID, selfID, recStat, modSuperNode, msgType;
var selfwebsocket, receiverWebSocket;
var privKey;

var floOpt = {

  p: BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16),

  exponent1: function () {
    return floOpt.p.add(BigInteger.ONE).divide(BigInteger("4"))
  },

  calculateY: function (x) {
    let p = this.p;
    let exp = this.exponent1();
    // x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
    return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
  },

  // Insert a compressed public key
  getUncompressedPublicKey: function (compressedPublicKey) {

    const p = this.p;

    // Fetch x from compressedPublicKey
    let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
    const prefix = pubKeyBytes.shift() // remove prefix
    let prefix_modulus = prefix % 2;
    pubKeyBytes.unshift(0) // add prefix 0
    let x = new BigInteger(pubKeyBytes)
    let xDecimalValue = x.toString()

    // Fetch y
    let y = this.calculateY(x);
    let yDecimalValue = y.toString();

    // verify y value
    let resultBigInt = y.mod(BigInteger("2"));

    let check = resultBigInt.toString() % 2;

    if (prefix_modulus !== check) {
      yDecimalValue = y.negate().mod(p).toString();
    }

    return {
      x: xDecimalValue,
      y: yDecimalValue
    };
  },

  getSenderPublicKeyString: function () {
    privateKey = ellipticCurveEncryption.senderRandom();
    senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
    return {
      privateKey: privateKey,
      senderPublicKeyString: senderPublicKeyString
    }
  },

  deriveSharedKeySender: function (receiverCompressedPublicKey, senderPrivateKey) {
    try {
      let receiverPublicKeyString = this.getUncompressedPublicKey(
        receiverCompressedPublicKey);
      var senderDerivedKey = {
        XValue: "",
        YValue: ""
      };
      senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
        receiverPublicKeyString.x,
        receiverPublicKeyString.y, senderPrivateKey);
      return senderDerivedKey;
    } catch (error) {
      return new Error(error);
    }
  },

  deriveReceiverSharedKey: function (senderPublicKeyString, receiverPrivateKey) {
    return ellipticCurveEncryption.receiverSharedKeyDerivation(
      senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString,
      receiverPrivateKey);
  },

  getReceiverPublicKeyString: function (privateKey) {
    return ellipticCurveEncryption.receiverPublicString(privateKey);
  },

  deriveSharedKeyReceiver: function (senderPublicKeyString, receiverPrivateKey) {
    try {
      return ellipticCurveEncryption.receiverSharedKeyDerivation(senderPublicKeyString.XValuePublicString,
        senderPublicKeyString.YValuePublicString, receiverPrivateKey);

    } catch (error) {
      return new Error(error);
    }
  },

  encryptData: function (data, receiverCompressedPublicKey) {
    var senderECKeyData = this.getSenderPublicKeyString();
    var senderDerivedKey = {
      XValue: "",
      YValue: ""
    };
    var senderPublicKeyString = {};
    senderDerivedKey = this.deriveSharedKeySender(
      receiverCompressedPublicKey, senderECKeyData.privateKey);
    console.log("senderDerivedKey", senderDerivedKey);
    let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
    let secret = Crypto.AES.encrypt(data, senderKey);
    return {
      secret: secret,
      pubVal: senderECKeyData.senderPublicKeyString
    };
  },

  decryptData: function (secret, senderPublicKeyString, myPrivateKey) {
    var receiverDerivedKey = {
      XValue: "",
      YValue: ""
    };
    var receiverECKeyData = {};
    if (typeof myPrivateKey !== "string") throw new Error("No private key found.");

    let privateKey = this.wifToDecimal(myPrivateKey, true);
    if (typeof privateKey.privateKeyDecimal !== "string") throw new Error(
      "Failed to detremine your private key.");
    receiverECKeyData.privateKey = privateKey.privateKeyDecimal;

    receiverDerivedKey = this.deriveReceiverSharedKey(senderPublicKeyString,
      receiverECKeyData.privateKey);
    console.log("receiverDerivedKey", receiverDerivedKey);

    let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
    let decryptMsg = Crypto.AES.decrypt(secret, receiverKey);
    return decryptMsg;
  },

  ecparams: EllipticCurve.getSECCurveByName("secp256k1"),
  getPubKeyHex: function (privateKeyHex) {
    var key = new Bitcoin.ECKey(privateKeyHex);
    if (key.priv == null) {
      alert("Invalid Private key");
      return;
    }
    key.setCompressed(true);
    var pubkeyHex = key.getPubKeyHex();
    return pubkeyHex;
  },
  getFLOIDfromPubkeyHex: function (pubkeyHex) {
    var key = new Bitcoin.ECKey().setPub(pubkeyHex);
    var floID = key.getBitcoinAddress();
    return floID;
  },
  signData: function (msg, privateKeyHex) {
    var key = new Bitcoin.ECKey(privateKeyHex);
    key.setCompressed(true);

    var privateKeyArr = key.getBitcoinPrivateKeyByteArray();
    privateKey = BigInteger.fromByteArrayUnsigned(privateKeyArr);
    var messageHash = Crypto.SHA256(msg);

    var messageHashBigInteger = new BigInteger(messageHash);
    var messageSign = Bitcoin.ECDSA.sign(messageHashBigInteger, key.priv);

    var sighex = Crypto.util.bytesToHex(messageSign);
    return sighex;
  },
  verifyData: function (msg, signatureHex, publicKeyHex) {
    var msgHash = Crypto.SHA256(msg);
    var messageHashBigInteger = new BigInteger(msgHash);

    var sigBytes = Crypto.util.hexToBytes(signatureHex);
    var signature = Bitcoin.ECDSA.parseSig(sigBytes);

    var publicKeyPoint = this.ecparams.getCurve().decodePointHex(publicKeyHex);

    var verify = Bitcoin.ECDSA.verifyRaw(messageHashBigInteger, signature.r, signature.s,
      publicKeyPoint);
    return verify;
  },
  wifToDecimal: function (pk_wif, isPubKeyCompressed = false) {
    let pk = Bitcoin.Base58.decode(pk_wif)
    pk.shift()
    pk.splice(-4, 4)
    //If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
    if (isPubKeyCompressed == true) pk.pop()
    pk.unshift(0)
    privateKeyDecimal = BigInteger(pk).toString()
    privateKeyHex = Crypto.util.bytesToHex(pk)
    return {
      privateKeyDecimal: privateKeyDecimal,
      privateKeyHex: privateKeyHex
    }
  },
  genNewIDpair: function () {
    try {
      var key = new Bitcoin.ECKey(false);
      key.setCompressed(true);
      return {
        floID: key.getBitcoinAddress(),
        pubKey: key.getPubKeyHex(),
        privKey: key.getBitcoinWalletImportFormat()
      }
    } catch (e) {
      console.log(e);
    }
  },
  verifyPrivKey: function (privateKeyHex, floID) {
    try {
      var key = new Bitcoin.ECKey(privateKeyHex);
      if (key.priv == null)
        return false;
      key.setCompressed(true);
      if (floID == key.getBitcoinAddress())
        return true;
      else
        return false;
    } catch (e) {
      console.log(e);
    }
  }
}
//Script for AJAX, and register functions
function ajax(method, uri) {
  var request = new XMLHttpRequest();
  var url = `${server}/${uri}`
  console.log(url)
  var result;
  request.open(method, url, false);
  request.onload = function () {
    if (request.readyState == 4 && request.status == 200)
      result = this.response;
    else {
      console.log('error');
      result = false;
    }
  };
  request.send();
  console.log(result);
  return result;
}

function registerID(sender, onionAddr, wif, pubkey, username) {

  var receiver = adminID;

  var trx = bitjs.transaction();
  var utxoAmt = 0.0;
  var x = sendAmt + fee;
  var response = ajax("GET", `api/addr/${sender}/utxo`);
  var utxos = JSON.parse(response);
  for (var x = utxos.length - 1; x >= 0; x--) {
    if (utxoAmt < sendAmt + fee) {
      trx.addinput(utxos[x].txid, utxos[x].vout, utxos[x].scriptPubKey);
      utxoAmt += utxos[x].amount;
    } else
      break;
  }
  console.log(utxoAmt + ":" + (sendAmt + fee));
  if (utxoAmt < sendAmt + fee) {
    alert("Insufficient balance!");
    return;
  }

  trx.addoutput(receiver, sendAmt);
  console.log(receiver + ":" + sendAmt);

  var change = utxoAmt - sendAmt - fee;
  if (change > 0)
    trx.addoutput(sender, change);
  console.log(sender + ":" + change);
  var key = new Bitcoin.ECKey(wif);
  var sendFloData = JSON.stringify({
    FLO_chat: {
      onionAddr: onionAddr,
      name: username,
      pubKey: pubkey
    }
  });;
  trx.addflodata(sendFloData);
  console.log(sendFloData);

  var signedTxHash = trx.sign(wif, 1);
  console.log(signedTxHash);
  return broadcastTx(signedTxHash);
}

function broadcastTx(signedTxHash) {
  var http = new XMLHttpRequest();
  var url = `${server}/api/tx/send`;
  if (signedTxHash.length < 1) {
    alert("Empty Signature");
    return false;
  }

  var params = `{"rawtx":"${signedTxHash}"}`;
  var result;
  http.open('POST', url, false);

  //Send the proper header information along with the request
  http.setRequestHeader('Content-type', 'application/json');

  http.onreadystatechange = () => { //Call a function when the state changes.
    if (http.readyState == 4 && http.status == 200) {
      console.log(http.response);
      var txid = JSON.parse(http.response).txid.result;
      alert("Transaction successful! txid : " + txid);
      result = true;

    } else {
      console.log(http.responseText);
      result = false;
    }
  }
  http.send(params);
  return result;
}

function userDataStartUp() {
  console.log("StartUp");

  document.getElementById("sendMsgInput").addEventListener("keydown", (event) => {
    if (event.keyCode === 13 && !event.shiftKey) {
      event.preventDefault();
      sendMsg();
    }
  });
  document.getElementById("searchContact").addEventListener("input", searchContact, true);
  document.getElementById("searchList").addEventListener("input", searchChecklist, true);
  getDatafromAPI().then(result => {
    console.log(result);
    getContactsfromIDB().then(result => {
      contacts = result;
      getSuperNodeListfromIDB().then(result => {
        console.log(result)
        superNodeList = result;
        kBucketObj.launchKBucket().then(result => {
          console.log(result)
          getuserID().then(result => {
            console.log(result);
            selfID = result;
            if (superNodeList.includes(selfID))
              modSuperNode = true;
            alert(`${selfID}\nWelcome ${contacts[selfID].name}`)
            getGroupsfromIDB().then(result => {
              groups = result;
              readMsgfromIDB().then(result => {
                console.log(result);
                initselfWebSocket();
                pingSuperNodeForAwayMessages();
                displayContacts();
                const createClock = setInterval(checkStatusInterval, 30000);
              }).catch(error => {
                console.log(error);
              });
            }).catch(error => {
              console.log(error.message);
            });
          }).catch(error => {
            console.log(error.message);
          });
        }).catch(error => {
          console.log(error.message);
        });
      }).catch(error => {
        console.log(error.message);
      });
    }).catch(error => {
      console.log(error.message);
    });
  }).catch(error => {
    console.log(error.message);
  });

}

function storeContact(data) {
  return new Promise((resolve, reject) => {
    var idb = indexedDB.open("FLO_Chat");
    idb.onerror = (event) => {
      console.log("Error in opening IndexedDB!");
    };
    idb.onsuccess = (event) => {
      var db = event.target.result;
      var obs = db.transaction('contacts', "readwrite").objectStore('contacts');
      objectRequest = obs.put(data);
      objectRequest.onerror = (event) => {
        reject(Error('Error occured: Unable to store data'));
      };

      objectRequest.onsuccess = (event) => {
        resolve('Data saved OK');
        db.close();
      };
    };
  });
}

function storeSuperNodeData(data) {
  return new Promise((resolve, reject) => {
    var idb = indexedDB.open("FLO_Chat");
    idb.onerror = (event) => {
      reject("Error in opening IndexedDB!");
    };
    idb.onsuccess = (event) => {
      var db = event.target.result;
      var obs = db.transaction('superNodes', "readwrite").objectStore('superNodes');
      if (data.addNodes)
        for (var i = 0; i < data.addNodes.length; i++)
          obs.add(true, data.addNodes[i])
      if (data.removeNodes)
        for (var i = 0; i < data.removeNodes.length; i++)
          obs.delete(data.removeNodes[i])
      db.close();
      resolve('Updated superNodes list in IDB');
    };
  });
}

function getDatafromAPI() {
  return new Promise((resolve, reject) => {
    var addr = adminID;
    var idb = indexedDB.open("FLO_Chat");
    idb.onerror = (event) => {
      console.log("Error in opening IndexedDB!");
    };
    idb.onupgradeneeded = (event) => {
      var db = event.target.result;
      var objectStore0 = db.createObjectStore("superNodes");
      var objectStore1 = db.createObjectStore("contacts", {
        keyPath: 'floID'
      });
      objectStore1.createIndex('onionAddr', 'onionAddr', {
        unique: false
      });
      objectStore1.createIndex('name', 'name', {
        unique: false
      });
      objectStore1.createIndex('pubKey', 'pubKey', {
        unique: false
      });
      var objectStore2 = db.createObjectStore("lastTx");
      var objectStore3 = db.createObjectStore("messages", {
        keyPath: 'time'
      });
      objectStore3.createIndex('text', 'text', {
        unique: false
      });
      objectStore3.createIndex('floID', 'floID', {
        unique: false
      });
      objectStore3.createIndex('groupID', 'groupID', {
        unique: false
      });
      objectStore3.createIndex('sender', 'sender', {
        unique: false
      });
      objectStore3.createIndex('type', 'type', {
        unique: false
      });
      var objectStore4 = db.createObjectStore("groups", {
        keyPath: 'groupID'
      });
      objectStore4.createIndex('groupInfo', 'groupInfo', {
        unique: false
      });
    };
    idb.onsuccess = (event) => {
      var db = event.target.result;
      var lastTx = db.transaction('lastTx', "readwrite").objectStore('lastTx');
      console.log(addr);
      new Promise((res, rej) => {
        var lastTxReq = lastTx.get(addr);
        lastTxReq.onsuccess = (event) => {
          var lasttx = event.target.result;
          if (lasttx === undefined) {
            lasttx = 0;
          }
          res(lasttx);
        }
      }).then(lasttx => {
        var response = ajax("GET", `api/addrs/${addr}/txs`);
        var nRequired = JSON.parse(response).totalItems - lasttx;
        console.log(nRequired);
        while (true && nRequired) {
          var response = ajax("GET", `api/addrs/${addr}/txs?from=0&to=${nRequired}`);
          response = JSON.parse(response);
          if (nRequired + lasttx != response.totalItems) {
            nRequired = response.totalItems - lasttx;
            continue;
          }
          response.items.reverse().forEach(tx => {
            try {
              if (tx.vin[0].addr == addr) {
                var data = JSON.parse(tx.floData).FLO_chat_SuperNode;
                if (data !== undefined) {
                  storeSuperNodeData(data).then(response => {}).catch(error => {
                    console.log(error.message);
                  });
                }
              } else {
                var data = JSON.parse(tx.floData).FLO_chat;
                if (data !== undefined) {
                  if (floOpt.getFLOIDfromPubkeyHex(data.pubKey) != tx.vin[0].addr)
                    throw ("PublicKey doesnot match with floID")
                  data = {
                    floID: tx.vin[0].addr,
                    onionAddr: data.onionAddr,
                    name: data.name,
                    pubKey: data.pubKey
                  };
                  storeContact(data).then(response => {}).catch(error => {
                    console.log(error.message);
                  });
                }
              }
            } catch (e) {
              console.log(e)
            }
          });

          var obs = db.transaction('lastTx', "readwrite").objectStore('lastTx');
          obs.put(response.totalItems, addr);
          break;
        }
        db.close();
        resolve('retrived data from API');
      });
    };
  });
}

function getuserID() {
  return new Promise((resolve, reject) => {
    privKey = prompt("Enter FLO Private Key : ")
    var key = new Bitcoin.ECKey(privKey);
    while (key.priv == null) {
      privKey = prompt("Invalid FLO Private Key! Retry : ")
      key = Bitcoin.ECKey(privKey);
    }
    key.setCompressed(true);
    var userID = key.getBitcoinAddress();
    if (contacts[userID] === undefined)
      var reg = confirm(`${userID} is not registers to FLO chat!\nRegister FLO ID to this onion?`);
    else if (contacts[userID].onionAddr == window.location.host)
      resolve(userID)
    else
      var reg = confirm(`${userID} is registered to another onion!\nChange to this onion?`);

    if (reg) {
      var name = prompt("Enter your name :");
      var pubKey = key.getPubKeyHex();
      if (registerID(userID, window.location.host, privKey, pubKey, name)) {
        contacts[userID] = {
          onionAddr: window.location.host,
          name: name,
          pubKey: pubKey
        };
        resolve(userID);
      }
    }
    reject(`Unable to bind ${userID} to this onionAddress!\nTry again later!`);
  });
}

function getContactsfromIDB() {
  return new Promise((resolve, reject) => {
    var idb = indexedDB.open("FLO_Chat");
    idb.onerror = (event) => {
      reject("Error in opening IndexedDB!");
    };
    idb.onsuccess = (event) => {
      var db = event.target.result;
      var obs = db.transaction("contacts", "readwrite").objectStore("contacts");
      var getReq = obs.getAll();
      getReq.onsuccess = (event) => {
        var result = {}
        event.target.result.forEach(c => {
          result[c.floID] = c;
          searchIndex.add(c.floID, c.name + ' @' + c.floID);
        });
        resolve(result);
      }
      getReq.onerror = (event) => {
        reject('Unable to read contacts!')
      }
      db.close();
    };
  });
}

function getGroupsfromIDB() {
  return new Promise((resolve, reject) => {
    var idb = indexedDB.open("FLO_Chat");
    idb.onerror = (event) => {
      reject("Error in opening IndexedDB!");
    };
    idb.onsuccess = (event) => {
      var db = event.target.result;
      var obs = db.transaction("groups", "readwrite").objectStore("groups");
      var getReq = obs.getAll();
      getReq.onsuccess = (event) => {
        var result = {}
        event.target.result.forEach(g => {
          var gInfo = JSON.parse(g.groupInfo);
          result[g.groupID] = gInfo;
          searchIndex.add(g.groupID, gInfo.name + ' #' + gInfo.floID);
        });
        resolve(result);
      }
      getReq.onerror = (event) => {
        reject('Unable to read groups!')
      }
      db.close();
    };
  });
}

function getSuperNodeListfromIDB() {
  return new Promise((resolve, reject) => {
    var idb = indexedDB.open("FLO_Chat");
    idb.onerror = (event) => {
      reject("Error in opening IndexedDB!");
    };
    idb.onsuccess = (event) => {
      var db = event.target.result;
      var obs = db.transaction("superNodes", "readwrite").objectStore("superNodes");
      var getReq = obs.getAllKeys();
      getReq.onsuccess = (event) => {
        resolve(event.target.result);
      }
      getReq.onerror = (event) => {
        reject('Unable to read superNode list!')
      }
      db.close();
    };
  });
}

function readMsgfromIDB() {
  return new Promise((resolve, reject) => {
    var disp = document.getElementById("conversation");
    for (floID in contacts) {
      var createLi = document.createElement('div');
      createLi.setAttribute("id", floID);
      createLi.setAttribute("class", "message-inner");
      createLi.style.display = 'none';
      disp.appendChild(createLi);
    }
    for (floID in groups) {
      var createLi = document.createElement('div');
      createLi.setAttribute("id", floID);
      createLi.setAttribute("class", "message-inner");
      createLi.style.display = 'none';
      disp.appendChild(createLi);
    }
    var idb = indexedDB.open("FLO_Chat");
    idb.onerror = (event) => {
      reject("Error in opening IndexedDB!");
    };
    idb.onsuccess = (event) => {
      var db = event.target.result;
      var obs = db.transaction("messages", "readwrite").objectStore("messages");
      obs.openCursor().onsuccess = (event) => {
        var cursor = event.target.result;
        if (cursor) {
          createMsgElement(cursor.value);
          cursor.continue();
        } else {
          resolve("Read Msg from IDB");
        }
      };
      db.close();
    };
  });
}

function storeMsg(data) {
  var idb = indexedDB.open("FLO_Chat");
  idb.onerror = (event) => {
    console.log("Error in opening IndexedDB!");
  };
  idb.onsuccess = (event) => {
    var db = event.target.result;
    var obs = db.transaction("messages", "readwrite").objectStore("messages");
    obs.add(data);
    db.close();
  };
}

function storeSuperNodeMsg(data) {
  var idb = indexedDB.open("FLO_Chat", 2);
  idb.onerror = (event) => {
    console.log("Error in opening IndexedDB!");
  };
  idb.onupgradeneeded = (event) => {
    var objectStore = event.target.result.createObjectStore("superNodeMsg", {
      keyPath: 'id'
    });
    objectStore.createIndex('from', 'from', {
      unique: false
    });
    objectStore.createIndex('to', 'to', {
      unique: false
    });
    objectStore.createIndex('data', 'data', {
      unique: false
    });
  };
  idb.onsuccess = (event) => {
    var db = event.target.result;
    var obs = db.transaction("superNodeMsg", "readwrite").objectStore("superNodeMsg");
    var parsedData = JSON.parse(data);
    var id = '' + parsedData.from + '_' + parsedData.to + '_' + parsedData.time;
    obs.add({
      id: id,
      from: parsedData.from,
      to: parsedData.to,
      data: data
    });
    db.close();
  };
}

function displayContacts() {
  console.log('displayContacts');
  var listElement = document.getElementById('contact-display');
  for (floID in contacts) {
    var createLi = document.createElement('div');
    createLi.setAttribute("name", floID);
    createLi.setAttribute("onClick", 'changeReceiver(this)');
    createLi.setAttribute("class", "row sideBar-body");
    createLi.innerHTML = `<div class="col-sm-11 col-xs-11 sideBar-main">
                <div class="row">
                  <div class="col-sm-12 col-xs-12 sideBar-name">
                    <span class="name-meta"></span><br/>
                    <span class="time-meta">@${floID}</span>
                  </div>
                </div>
              </div>`
    createLi.querySelector("span.name-meta").textContent = contacts[floID].name;
    listElement.appendChild(createLi);
  }
  for (floID in groups) {
    var createLi = document.createElement('div');
    createLi.setAttribute("name", floID);
    createLi.setAttribute("onClick", 'changeReceiver(this)');
    createLi.setAttribute("class", "row sideBar-body");
    createLi.innerHTML = `<div class="col-sm-11 col-xs-11 sideBar-main">
                <div class="row">
                  <div class="col-sm-12 col-xs-12 sideBar-name">
                    <span class="name-meta"></span><br/>
                    <span class="time-meta">#${floID}</span>
                  </div>
                </div>
              </div>`
    createLi.querySelector("span.name-meta").textContent = groups[floID].name;
    listElement.appendChild(createLi);
  }
}

function initselfWebSocket() {
  var selfwebsocket = new WebSocket("ws://" + location.host + "/ws");
  selfwebsocket.onopen = (evt) => {
    console.log("CONNECTED");
    var pass = prompt("Enter server password :")
    selfwebsocket.send("$" + pass);
  };
  selfwebsocket.onclose = (evt) => {
    console.log("DISCONNECTED");
  };
  selfwebsocket.onmessage = (evt) => {
    console.log(evt.data);
    try {
      var data = JSON.parse(evt.data);
      if (data.to == selfID) {
        console.log('Incoming data')
        processIncomingData(data);
      } else if (modSuperNode) {
        if (data.pingAway !== undefined)
          sendStoredSuperNodeMsgs(data.pingAway)
        else {
          kBucketObj.determineClosestSupernode(data.to).then(result => {
            console.log(result)
            if (result[0].floID == selfID)
              storeSuperNodeMsg(evt.data);
          }).catch(e => {
            console.log(e.message);
          });
        }
      }
    } catch (err) {
      if (evt.data[0] == '$')
        alert(evt.data);
      else
        console.log(err);
    }
  };
  selfwebsocket.onerror = (evt) => {
    console.log(evt);
  };
}

function processIncomingData(data) {
  if (data.directMsg !== undefined) {
    var msg = floOpt.decryptData(data.directMsg.msgCipher.secret, data.directMsg.msgCipher.pubVal, privKey)
    if (!floOpt.verifyData(msg, data.directMsg.sign, contacts[data.from].pubKey))
      return
    var msgInfo = {
      time: Date.now(),
      floID: data.from,
      text: msg,
      type: "R"
    }
    createMsgElement(msgInfo);
    storeMsg(msgInfo);
  } else if (data.groupMsg !== undefined && data.groupMsg.group in groups) {
    if (!(groups[data.groupMsg.group].members.includes(data.from)))
      return
    var msg = floOpt.decryptData(data.groupMsg.msgCipher.secret, data.groupMsg.msgCipher.pubVal, groups[data.groupMsg.group].privKey);
    if (!floOpt.verifyData(msg, data.groupMsg.sign, contacts[data.from].pubKey))
      return
    var msgInfo = {
      time: Date.now(),
      groupID: data.groupMsg.group,
      sender: data.from,
      text: msg,
      type: "R"
    }
    createMsgElement(msgInfo);
    storeMsg(msgInfo)
  } else if (data.newGroup !== undefined) {
    var groupInfoStr = floOpt.decryptData(data.newGroup.groupInfo.secret, data.newGroup.groupInfo.pubVal, privKey)
    var groupInfo = JSON.parse(groupInfoStr);
    if (floOpt.verifyData(groupInfoStr, data.newGroup.sign, contacts[groupInfo.creator].pubKey) && floOpt.verifyPrivKey(groupInfo.privKey, groupInfo.floID)) {
      groups[groupInfo.floID] = groupInfo;
      searchIndex.add(groupInfo.floID, groupInfo.name + ' #' + groupInfo.floID);
      storeGroup(groupInfoStr, groupInfo.floID);
      createGroupDisplay(groupInfo);
    }
  } else if (data.deleteGroup !== undefined && data.deleteGroup.group in groups) {
    if (data.from != groups[data.deleteGroup.group].creator && !groups[data.deleteGroup.group].admins.includes(data.from))
      return
    if (floOpt.verifyData('deleteGroup:' + data.deleteGroup.group, data.deleteGroup.sign, contacts[data.from].pubKey)) {
      delete groups[data.deleteGroup.group];
      searchIndex.remove(data.deleteGroup.group);
      deleteGroupFromIDB(data.deleteGroup.group);
    }
  } else if (data.addGroupMembers !== undefined && data.addGroupMembers.group in groups) {
    if (data.from != groups[data.addGroupMembers.group].creator && !groups[data.addGroupMembers.group].admins.includes(data.from))
      return
    if (floOpt.verifyData('addGroupMembers:' + data.addGroupMembers.group + data.addGroupMembers.members.join('|'), data.addGroupMembers.sign, contacts[data.from].pubKey)) {
      groups[data.addGroupMembers.group].members = groups[data.addGroupMembers.group].members.concat(data.addGroupMembers.members);
      var groupInfoStr = JSON.stringify(groups[data.addGroupMembers.group]);
      storeGroup(groupInfoStr, data.addGroupMembers.group);
    }
  } else if (data.rmGroupMembers !== undefined && data.rmGroupMembers.group in groups) {
    if (data.from != groups[data.rmGroupMembers.group].creator && !groups[data.rmGroupMembers.group].admins.includes(data.from))
      return
    if (floOpt.verifyData('rmGroupMembers:' + data.rmGroupMembers.group + data.rmGroupMembers.members.join('|'), data.rmGroupMembers.sign, contacts[data.from].pubKey)) {
      groups[data.rmGroupMembers.group].members = groups[data.rmGroupMembers.group].members.filter(x => !data.rmGroupMembers.members.includes(x)); //remove member from group
      var groupInfoStr = JSON.stringify(groups[data.rmGroupMembers.group]);
      storeGroup(groupInfoStr, data.rmGroupMembers.group);
    }
  } else if (data.addGroupAdmins !== undefined && data.addGroupAdmins.group in groups) {
    if (data.from != groups[data.addGroupAdmins.group].creator)
      return
    if (floOpt.verifyData('addGroupAdmins:' + data.addGroupAdmins.group + data.addGroupAdmins.admins.join('|'), data.addGroupAdmins.sign, contacts[data.from].pubKey)) {
      groups[data.addGroupAdmins.group].admins = groups[data.addGroupAdmins.group].admins.concat(data.addGroupAdmins.admins);
      var groupInfoStr = JSON.stringify(groups[data.addGroupAdmins.group]);
      storeGroup(groupInfoStr, data.addGroupAdmins.group);
    }
  } else if (data.rmGroupAdmins !== undefined && data.rmGroupAdmins.group in groups) {
    if (data.from != groups[data.rmGroupAdmins.group].creator)
      return
    if (floOpt.verifyData('rmGroupAdmins:' + data.rmGroupAdmins.group + data.rmGroupAdmins.admins.join('|'), data.rmGroupAdmins.sign, contacts[data.from].pubKey)) {
      groups[data.rmGroupAdmins.group].admins = groups[data.rmGroupAdmins.group].admins.filter(x => !data.rmGroupAdmins.admins.includes(x)); //remove member from group
      var groupInfoStr = JSON.stringify(groups[data.rmGroupAdmins.group]);
      storeGroup(groupInfoStr, data.rmGroupAdmins.group);
    }
  }
}

function createMsgElement(msgInfo) {
  try {
    const type = {
      S: 'sender',
      R: 'receiver'
    };
    if (!msgInfo.groupID) {
      var msgEl = document.getElementById(msgInfo.floID);
      var msghd = '';
    } else {
      var msgEl = document.getElementById(msgInfo.groupID);
      var msghd = `<b>${msgInfo.sender}</b><br/>`;
    }
    if (!msgEl)
      return;
    var msgdiv = document.createElement('div');
    msgdiv.setAttribute("class", "row message-body");
    msgdiv.innerHTML = `<div class="col-sm-12 message-main-${type[msgInfo.type]}">
                    <div class="${type[msgInfo.type]}">
                      <span class="message-text">
                        ${msghd}<pre></pre>
                      </span>
                      <span class="message-time pull-right">
                        ${getTime(msgInfo.time)}
                      </span>
                    </div>
                  </div>`;
    msgdiv.querySelector("pre").textContent = msgInfo.text;
    msgEl.appendChild(msgdiv);
  } catch (e) {
    console.log(e);
  }
}

function pingSuperNodeForAwayMessages() {
  kBucketObj.determineClosestSupernode(selfID).then(result => {
    var selfSuperNodeWS = new WebSocket("ws://" + contacts[result[0].floID].onionAddr + "/ws");
    selfSuperNodeWS.onopen = (evt) => {
      var data = JSON.stringify({
        pingAway: selfID
      });
      selfSuperNodeWS.send(data)
      console.log('Pinged selfSupernode for new messages')
    };
    selfSuperNodeWS.onerror = (ev) => {
      console.log('Unable to ping superNode for new messages');
    };
    selfSuperNodeWS.onclose = (ev) => {
      console.log('Connection with selfSupernode is closed')
    };
  }).catch(err => {
    console.log(err.message);
  });
}

function checkStatusInterval() {
  try {
    if (receiverWebSocket !== undefined && receiverWebSocket.readyState !== WebSocket.OPEN) {
      receiverWebSocket.close()
      receiverWebSocket = new WebSocket("ws://" + contacts[receiverID].onionAddr + "/ws");
      receiverWebSocket.onopen = (evt) => {
        receiverWebSocket.send('#')
      };
      receiverWebSocket.onerror = (ev) => {
        receiverStatus(false);
      };
      receiverWebSocket.onclose = (ev) => {
        receiverStatus(false);
      };
      receiverWebSocket.onmessage = (evt) => {
        console.log(evt.data);
        if (evt.data[0] == '#') {
          if (evt.data[1] == '+')
            receiverStatus(true);
          else if (evt.data[1] == '-')
            receiverStatus(false);
        }
      }
    }
  } catch (e) {
    console.log(e);
  }
}

function changeReceiver(param) {
  if (receiverID !== undefined)
    document.getElementById(receiverID).style.display = 'none';
  //console.log(param.getAttribute("name"));
  receiverID = param.getAttribute("name");
  document.getElementById('recipient-floID').textContent = receiverID;
  receiverStatus(false)
  document.getElementById(receiverID).style.display = 'block';
  document.getElementById("groupOptions").style.display = 'none';

  if (receiverID in contacts) {
    msgType = 'direct';

    try {
      if (receiverWebSocket !== undefined && receiverWebSocket.readyState === WebSocket.OPEN)
        receiverWebSocket.close()
      receiverWebSocket = new WebSocket("ws://" + contacts[receiverID].onionAddr + "/ws");
      receiverWebSocket.onopen = (ev) => {
        receiverWebSocket.send('#');
      };
      receiverWebSocket.onerror = (ev) => {
        receiverStatus(false);
      };
      receiverWebSocket.onclose = (ev) => {
        receiverStatus(false);
      };
      receiverWebSocket.onmessage = (evt) => {
        console.log(evt.data);
        if (evt.data[0] == '#') {
          if (evt.data[1] == '+')
            receiverStatus(true);
          else if (evt.data[1] == '-')
            receiverStatus(false);
        }
      }
    } catch (e) {
      console.log(e);
    }
  } else if (receiverID in groups) {
    msgType = 'group';
    if (receiverWebSocket !== undefined && receiverWebSocket.readyState === WebSocket.OPEN)
      receiverWebSocket.close()
    receiverWebSocket = undefined;
    if (selfID == groups[receiverID].creator) {
      var grpOpt = document.getElementById("groupOptions");
      grpOpt.style.display = 'block';
      var optList = grpOpt.querySelectorAll('li');
      for (var i = 0; i < optList.length; i++)
        optList[i].style.display = 'block';
    } else if (groups[receiverID].admins.includes(selfID)) {
      var grpOpt = document.getElementById("groupOptions");
      grpOpt.style.display = 'block';
      var optList = grpOpt.querySelectorAll('li');
      for (var i = 0; i < 2; i++)
        optList[i].style.display = 'block';
      for (var i = 2; i < optList.length; i++)
        optList[i].style.display = 'none';
    }
  }
}

function receiverStatus(status) {
  if (status)
    document.getElementById('recipient-status').style.color = "#4CC94C";
  else
    document.getElementById('recipient-status').style.color = "#CD5C5C";
  recStat = status;
}

function getTime(time) {
  var t = new Date(time);
  var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  var fn = (n) => {
    if (n < 10)
      return '0' + n;
    else
      return n;
  };
  var tmp = `${months[t.getMonth()]} ${fn(t.getDate())} ${t.getFullYear()} ${fn(t.getHours())}:${fn(t.getMinutes())}`;
  return tmp;
}

function sendMsg() {
  if (receiverID === undefined) {
    alert("Select a contact and send message");
    return;
  }
  var inp = document.getElementById('sendMsgInput')
  var msg = inp.value;
  inp.value = "";
  console.log(msg);
  var time = Date.now();
  var sign = floOpt.signData(msg, privKey);
  if (msgType === 'direct')
    sendDirectMsg(msg, time, sign);
  else if (msgType === 'group')
    sendGroupMsg(msg, time, sign);
}

function sendDirectMsg(msg, time, sign) {
  var data = JSON.stringify({
    from: selfID,
    to: receiverID,
    directMsg: {
      time: time,
      msgCipher: floOpt.encryptData(msg, contacts[receiverID].pubKey),
      sign: sign
    }
  });
  if (recStat)
    receiverWebSocket.send(data);
  else
    sendDataToSuperNode(receiverID, data);

  var msgInfo = {
    time: time,
    floID: receiverID,
    text: msg,
    type: "S"
  }
  createMsgElement(msgInfo);
  storeMsg(msgInfo);
}

function sendGroupMsg(msg, time, sign) {
  var data = {
    from: selfID,
    groupMsg: {
      group: receiverID,
      time: time,
      msgCipher: floOpt.encryptData(msg, groups[receiverID].pubKey),
      sign: sign
    }
  };
  console.log(data);

  groups[receiverID].members.forEach(floID => {
    if (floID == selfID) //dont send to self
      return;
    data.to = floID;
    sendData(floID, JSON.stringify(data));
  });
  var msgInfo = {
    time: time,
    sender: selfID,
    groupID: receiverID,
    text: msg,
    type: "S"
  }
  createMsgElement(msgInfo);
  storeMsg(msgInfo);
}

function sendStoredSuperNodeMsgs(floID) {
  var receiverWS = new WebSocket("ws://" + contacts[floID].onionAddr + "/ws");
  receiverWS.onopen = (ev) => {
    var idb = indexedDB.open("FLO_Chat", 2);
    idb.onerror = (event) => {
      console.log("Error in opening IndexedDB!");
    };
    idb.onupgradeneeded = (event) => {
      var objectStore = event.target.result.createObjectStore("superNodeMsg", {
        keyPath: 'id'
      });
      objectStore.createIndex('from', 'from', {
        unique: false
      });
      objectStore.createIndex('to', 'to', {
        unique: false
      });
      objectStore.createIndex('data', 'data', {
        unique: false
      });
    };
    idb.onsuccess = (event) => {
      var db = event.target.result;
      var obs = db.transaction("superNodeMsg", "readwrite").objectStore("superNodeMsg");
      obs.openCursor().onsuccess = (event) => {
        var cursor = event.target.result;
        if (cursor) {
          if (cursor.value.to == floID) {
            receiverWS.send(cursor.value.data);
            cursor.delete();
          }
          cursor.continue();
        } else {
          console.log('Sent All messages to ' + floID)
        }
      }
      db.close();
    };
  };
  receiverWS.onerror = (ev) => {
    console.log('Connection Error to ' + floID)
  };
  receiverWS.onclose = (ev) => {
    console.log('Disconnected from ' + floID)
  };
}

async function sendData(floID, data) {
  try {
    var recipientWS = new WebSocket("ws://" + contacts[floID].onionAddr + "/ws");
    recipientWS.onopen = (ev) => {
      recipientWS.send('#');
    };
    recipientWS.onerror = (ev) => {
      sendDataToSuperNode(floID, data);
    };
    recipientWS.onclose = (ev) => {
      console.log("Closed")
    };
    recipientWS.onmessage = (evt) => {
      console.log(evt.data);
      if (evt.data[0] == '#') {
        if (evt.data[1] == '+')
          recipientWS.send(data);
        else if (evt.data[1] == '-')
          sendDataToSuperNode(floID, data);
      }
    }
  } catch (e) {
    console.log(e);
  }
}

function sendDataToSuperNode(floID, data) {
  kBucketObj.determineClosestSupernode(floID).then(result => {
    var superNodeWS = new WebSocket("ws://" + contacts[result[0].floID].onionAddr + "/ws");
    superNodeWS.onopen = (ev) => {
      console.log(`Connected to ${floID}'s SuperNode!`);
      superNodeWS.send(data);
    };
    superNodeWS.onerror = (ev) => {
      console.log(`${floID}'s SuperNode is offline!`);
    };
    superNodeWS.onclose = (ev) => {
      console.log(`Disconnected from ${floID}'s SuperNode!`);
    };
  }).catch(e => {
    console.log(e.message);
  });
}

function createGroupDisplay(groupInfo) {
  var createLi = document.createElement('div');
  createLi.setAttribute("name", groupInfo.floID);
  createLi.setAttribute("onClick", 'changeReceiver(this)');
  createLi.setAttribute("class", "row sideBar-body");
  createLi.innerHTML = `<div class="col-sm-11 col-xs-11 sideBar-main">
              <div class="row">
                <div class="col-sm-12 col-xs-12 sideBar-name">
                  <span class="name-meta"></span><br/>
                  <span class="time-meta">#${groupInfo.floID}</span>
                </div>
              </div>
            </div>`;
  createLi.querySelector("span.name-meta").textContent = groupInfo.name;
  document.getElementById('contact-display').appendChild(createLi);

  var createEl = document.createElement('div');
  createEl.setAttribute("id", groupInfo.floID);
  createEl.setAttribute("class", "message-inner");
  createEl.style.display = 'none';
  document.getElementById("conversation").appendChild(createEl);
}

function storeGroup(groupInfoStr, groupID) {
  var idb = indexedDB.open("FLO_Chat");
  idb.onerror = (event) => {
    console.log("Error in opening IndexedDB!");
  };
  idb.onsuccess = (event) => {
    var db = event.target.result;
    console.log(groupID, groupInfoStr);
    var obs = db.transaction('groups', "readwrite").objectStore('groups');
    obs.put({
      groupID: groupID,
      groupInfo: groupInfoStr
    });
    db.close();
  };
}

function deleteGroupFromIDB(groupID) {
  var idb = indexedDB.open("FLO_Chat");
  idb.onerror = (event) => {
    console.log("Error in opening IndexedDB!");
  };
  idb.onsuccess = (event) => {
    var db = event.target.result;
    console.log('Delete Group:', groupID);
    var obs = db.transaction('groups', "readwrite").objectStore('groups');
    obs.delete(groupID);
    db.close();
  };
}

function createGroup() {
  customCheckList(Object.keys(contacts), [selfID], 'Create Group', 'success').then(result => {
    var grpInfo = floOpt.genNewIDpair();
    grpInfo.name = result.grpName;
    grpInfo.members = result.members;
    grpInfo.members.push(selfID)
    grpInfo.creator = selfID;
    grpInfo.admins = [];
    var grpInfoStr = JSON.stringify(grpInfo);
    console.log(grpInfoStr);
    var data = {
      from: selfID,
      newGroup: {
        sign: floOpt.signData(grpInfoStr, privKey)
      }
    }
    grpInfo.members.forEach(floID => {
      data.to = floID;
      data.newGroup.groupInfo = floOpt.encryptData(grpInfoStr, contacts[floID].pubKey),
        sendData(floID, JSON.stringify(data));
    });
  }).catch(error => {
    console.log(error);
  })
}

function deleteGroup() {
  var flag = confirm("Are you sure you want to delete this group?");
  if (flag) {
    var data = {
      from: selfID,
      deleteGroup: {
        group: receiverID,
        sign: floOpt.signData('deleteGroup:' + receiverID, privKey)
      }
    };
    groups[receiverID].members.forEach(floID => {
      data.to = floID;
      sendData(floID, JSON.stringify(data));
    });
  }
}

function addGroupMembers() {
  customCheckList(Object.keys(contacts), groups[receiverID].members, 'Add Members', 'success').then(result => {
    var newMembers = result.members;
    var data1 = {
      from: selfID,
      addGroupMembers: {
        group: receiverID,
        members: newMembers,
        sign: floOpt.signData('addGroupMembers:' + receiverID + newMembers.join('|'), privKey)
      }
    }
    groups[receiverID].members.forEach(floID => {
      if (floID == selfID) //dont send to self
        return;
      data1.to = floID;
      sendData(floID, JSON.stringify(data1));
    });
    groups[receiverID].members = groups[receiverID].members.concat(newMembers);
    var grpInfoStr = JSON.stringify(groups[receiverID]);
    console.log(grpInfoStr);
    var data2 = {
      from: selfID,
      newGroup: {
        sign: floOpt.signData(grpInfoStr, privKey)
      }
    }
    newMembers.forEach(floID => {
      data2.to = floID;
      data2.newGroup.groupInfo = floOpt.encryptData(grpInfoStr, contacts[floID].pubKey),
        sendData(floID, JSON.stringify(data2));
    });
    storeGroup(grpInfoStr, receiverID);
  }).catch(error => {
    console.log(error);
  })
}

function rmGroupMembers() {
  customCheckList(groups[receiverID].members, [], 'Remove Members', 'danger').then(result => {
    var rmMembers = result.members;
    var data1 = {
      from: selfID,
      rmGroupMembers: {
        group: receiverID,
        members: rmMembers,
        sign: floOpt.signData('rmGroupMembers:' + receiverID + rmMembers.join('|'), privKey)
      }
    }
    groups[receiverID].members = groups[receiverID].members.filter(x => !rmMembers.includes(x)); //remove member from group
    storeGroup(JSON.stringify(groups[receiverID]), receiverID);
    groups[receiverID].members.forEach(floID => {
      if (floID == selfID)
        return;
      data1.to = floID;
      sendData(floID, JSON.stringify(data1));
    });
    var data2 = {
      from: selfID,
      deleteGroup: {
        group: receiverID,
        sign: floOpt.signData('deleteGroup:' + receiverID, privKey)
      }
    };
    rmMembers.forEach(floID => {
      data2.to = floID;
      sendData(floID, JSON.stringify(data2));
    });
  }).catch(error => {
    console.log(error);
  })
}

function addGroupAdmins() {
  customCheckList(groups[receiverID].members, groups[receiverID].admins, 'Add Admins', 'success').then(result => {
    var newAdmins = result.members;
    var data = {
      from: selfID,
      addGroupAdmins: {
        group: receiverID,
        admins: newAdmins,
        sign: floOpt.signData('addGroupAdmins:' + receiverID + newAdmins.join('|'), privKey)
      }
    }
    groups[receiverID].members.forEach(floID => {
      if (floID == selfID) //dont send to self
        return;
      data.to = floID;
      sendData(floID, JSON.stringify(data));
    });
    groups[receiverID].admins = groups[receiverID].admins.concat(newAdmins);
    var grpInfoStr = JSON.stringify(groups[receiverID]);
    storeGroup(grpInfoStr, receiverID);
  }).catch(error => {
    console.log(error);
  })
}

function rmGroupAdmins() {
  customCheckList(groups[receiverID].admins, [], 'Remove Admins', 'danger').then(result => {
    var rmAdmins = result.members;
    var data = {
      from: selfID,
      rmGroupAdmins: {
        group: receiverID,
        admins: rmAdmins,
        sign: floOpt.signData('rmGroupAdmins:' + receiverID + rmAdmins.join('|'), privKey)
      }
    }
    groups[receiverID].members.forEach(floID => {
      if (floID == selfID) //dont send to self
        return;
      data.to = floID;
      sendData(floID, JSON.stringify(data));
    });
    groups[receiverID].admins = groups[receiverID].admins.filter(x => !rmAdmins.includes(x)); //remove admins
    var grpInfoStr = JSON.stringify(groups[receiverID]);
    storeGroup(grpInfoStr, receiverID);
  }).catch(error => {
    console.log(error);
  })
}

function searchContact() {
  try {
    var searchKey = this.value;
    if (!searchKey)
      var searchResults = Object.keys(contacts).concat(Object.keys(groups));
    else
      var searchResults = searchIndex.search(searchKey);
    var contactList = document.getElementById('contact-display').children;
    for (var i = 0; i < contactList.length; i++) {
      if (searchResults.includes(contactList[i].getAttribute("name")))
        contactList[i].style.display = 'block';
      else
        contactList[i].style.display = 'none';
    };
  } catch (e) {
    console.log(e);
  }
}

function customCheckList(userList, ignoreList, okBtnVal = "Ok", okBtnType = "success") {
  var dialog = document.getElementById('overlay');
  dialog.style.display = "block";
  var okButton = dialog.querySelector('button.ok');
  var cancelButton = dialog.querySelector('button.cancel');
  okButton.setAttribute("class", `ok btn btn-${okBtnType}`);
  okButton.textContent = okBtnVal;
  var grpNameInput = dialog.querySelector('input.grpName')
  grpNameInput.style.display = (okBtnVal === "Create Group" ? "block" : "none");
  grpNameInput.value = '';
  var userChecklist = document.getElementById('userChecklist');
  for (var i = 0; i < userList.length; i++) {
    if (ignoreList.includes(userList[i]))
      continue;
    var listEl = document.createElement('label');
    listEl.setAttribute('class', "btn btn-default listLabel");
    listEl.setAttribute('name', userList[i]);
    listEl.innerHTML = `
          <span></span><br/>
          <sub>@${userList[i]}</sub>
          <input type="checkbox" class="badgebox" value="${userList[i]}">
          <span class="badge">&check;</span>`;
    listEl.querySelector("span").textContent = contacts[userList[i]].name;
    userChecklist.appendChild(listEl);
  }
  return new Promise((resolve, reject) => {
    dialog.addEventListener('click', function handleButtonClicks(e) {
      if (e.target.tagName !== 'BUTTON') {
        return;
      }
      dialog.removeEventListener('click', handleButtonClicks);
      dialog.style.display = 'none';
      if (e.target === okButton) {
        var selectedList = [];
        var checklist = dialog.querySelectorAll('input.badgebox');
        for (var i = 0; i < checklist.length; i++)
          if (checklist[i].checked)
            selectedList.push(checklist[i].value);
        if (selectedList.length == 0)
          reject('User Didnt select Any Users!');
        else
          resolve({
            grpName: grpNameInput.value,
            members: selectedList
          });
      } else if (e.target === cancelButton) {
        reject('User cancelled!');
      } else {
        reject('Some other button was clicked!');
      }
    });
  });
}

function searchChecklist() {
  try {
    var searchKey = this.value;
    if (!searchKey)
      var searchResults = Object.keys(contacts);
    else
      var searchResults = searchIndex.search(searchKey);
    var checklist = document.getElementById('userChecklist').children;
    for (var i = 0; i < checklist.length; i++) {
      if (searchResults.includes(checklist[i].getAttribute("name")))
        checklist[i].style.display = 'block';
      else
        checklist[i].style.display = 'none';
    };
  } catch (e) {
    console.log(e);
  }
}