const libmime = require('libmime');
const QRCode = require('qrcode');
const pkijs = require('pkijs');
const asn1js = require('asn1js');
const pvutils = require('pvutils');
const Penpal = require('penpal').default;
window.axios = require('axios');
//*********************************************************************************

const CERTIFIATE_Version_1 = 0;
const CERTIFIATE_Version_3 = 2;

//these bit fields are reversed, WTF!
const KEY_USAGE_DigitalSignature	= 0x80;//01;
const KEY_USAGE_NonRepudiation		= 0x40;//02;
const KEY_USAGE_KeyEncipherment		= 0x20;//04;
const KEY_USAGE_DataEncipherment	= 0x10;//08;
const KEY_USAGE_KeyAgreement		= 0x08;//10;
const KEY_USAGE_KeyCertSign			= 0x04;//20;
const KEY_USAGE_CRLSign				= 0x02;//40;
//const KEY_USAGE_EncipherOnly		= 0x01;//80; // Not used for now. Must be used together with KEY_USAGE_KeyAgreement (maybe should be ORed as a constant directly?)
//const KEY_USAGE_DecipherOnly		= 0x80;//0100; // If used, modify "KeyUsage" extension array buffer size and appropriate bit operators to accomodate for extra byte

const KEY_USAGE_LeafCertificate		= KEY_USAGE_DigitalSignature | KEY_USAGE_NonRepudiation | KEY_USAGE_KeyEncipherment | KEY_USAGE_DataEncipherment;
const KEY_USAGE_CertificateAuthority= KEY_USAGE_DigitalSignature | KEY_USAGE_KeyCertSign | KEY_USAGE_CRLSign;


const OID_EXT_KEY_USAGE_Any     	= "2.5.29.37.0";
const OID_ID_PKIX_ServerAuth		= "1.3.6.1.5.5.7.3.1";
const OID_ID_PKIX_ClientAuth		= "1.3.6.1.5.5.7.3.2";
const OID_ID_PKIX_CodeSigning		= "1.3.6.1.5.5.7.3.3";
const OID_ID_PKIX_EmailProtection	= "1.3.6.1.5.5.7.3.4";
const OID_ID_PKIX_TimeStamping	    = "1.3.6.1.5.5.7.3.8";
const OID_ID_PKIX_OCSPSigning		= "1.3.6.1.5.5.7.3.9";
// const OID_EXT_KEY_USAGE_MS...	= "1.3.6.1.4.1.311.10.3.1"; // Microsoft Certificate Trust List signing
// const OID_EXT_KEY_USAGE_MS...	= "1.3.6.1.4.1.311.10.3.4";  // Microsoft Encrypted File System
const OID_PKCS7_Data                = "1.2.840.113549.1.7.1";
const OID_PKCS7_SignedData          = "1.2.840.113549.1.7.2";
const OID_PKCS7_EnvelopedData       = "1.2.840.113549.1.7.3";
const OID_PKCS9_EmailAddress        = "1.2.840.113549.1.9.1";
const OID_PKCS9_ContentType         = "1.2.840.113549.1.9.3";
const OID_PKCS9_MessageDigest       = "1.2.840.113549.1.9.4";
const OID_PKCS9_SigningTime         = "1.2.840.113549.1.9.5"

const defaultAlgorithms = {
  hashAlg: "SHA-256",
  signAlg: "RSASSA-PKCS1-v1_5",
  keyLength: 2048
}

const AES_encryptionVariant_Password  = 2;
const encryptionAlgorithm = {
  name: "AES-CBC",
  length: 128
};

//*********************************************************************************
// Returns promise, resolved to keyPair object {publicKey, privateKey}
//*********************************************************************************
function generateKeys(algorithms) {
  //region Get a "crypto" extension
  const crypto = pkijs.getCrypto();
  if (typeof crypto === "undefined") {
    return Promise.reject("No WebCrypto extension found");
  }
  //endregion Get a "crypto" extension

  if (!algorithms) {
    algorithms = defaultAlgorithms;
  } else {
    if (!algorithms.hashAlg) {
      algorithms.hashAlg = defaultAlgorithms.hashAlg;
    }
    if (!algorithms.signAlg) {
      algorithms.signAlg = defaultAlgorithms.signAlg;
    }
    if (!algorithms.keyLength) {
      algorithms.keyLength = defaultAlgorithms.keyLength;
    }
  }

  //region Get default algorithm parameters for key generation
  const algorithm = pkijs.getAlgorithmParameters(algorithms.signAlg, "generatekey");
  if("hash" in algorithm.algorithm) {
    algorithm.algorithm.hash.name = algorithms.hashAlg;
  }
  algorithm.algorithm.modulusLength = algorithms.keyLength;
  //endregion

  return crypto.generateKey(algorithm.algorithm, true, algorithm.usages);
}

//*********************************************************************************
function createCertificate(certData, issuerData = null)
{

  if (typeof certData === "undefined") {
    return Promise.reject("No Certificate data provided");
  }

  if (typeof certData.subject === "undefined") {
    return Promise.reject("No Certificate subject data provided");
  }


  //region Get a "crypto" extension
  const crypto = pkijs.getCrypto();

  if (typeof crypto === "undefined") {
    return Promise.reject("No WebCrypto extension found");
  }
  //endregion Get a "crypto" extension

  //region Initial variables
  let sequence = Promise.resolve();

  const certificate = new pkijs.Certificate();
  let publicKey;
  let privateKey;

  let certificateBuffer;// = new ArrayBuffer(0); // ArrayBuffer with loaded or created CERT
  let privateKeyBuffer;// = new ArrayBuffer(0);
  let publicKeyBuffer;// = new ArrayBuffer(0);

  //endregion Initial variables

  if (certData.keyPair) {
    //region Create a new key pair
    sequence = sequence.then(() =>
    {
      return certData.keyPair;
    });
    //endregion Create a new key pair

  } else {
    //region Create a new key pair
    sequence = sequence.then(() =>
    {
      return generateKeys(certData.algorithms);
    });
    //endregion Create a new key pair
  }

  //region Store new key in an interim variables
  sequence = sequence.then(keyPair =>
  {
    publicKey = keyPair.publicKey;
    privateKey = keyPair.privateKey;
  }, error => Promise.reject(`Error during key generation: ${error}`));
  //endregion Store new key in an interim variables

  //region Exporting public key into "subjectPublicKeyInfo" value of certificate
  sequence = sequence.then(() =>
    certificate.subjectPublicKeyInfo.importKey(publicKey)
  );
  //endregion Exporting public key into "subjectPublicKeyInfo" value of certificate

  sequence = sequence.then(
    () => crypto.digest({ name: "SHA-1" }, certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex),
    error => Promise.reject(`Error during importing public key: ${error}`)
  );

  //region Fill in cert data
  sequence = sequence.then(subjKeyIdBuffer =>
  {

    //region Put a static values
    certificate.version = CERTIFIATE_Version_3;

    const serialNumberBuffer = new ArrayBuffer(20);
    const serialNumberView = new Uint8Array(serialNumberBuffer);
    pkijs.getRandomValues(serialNumberView)
    // noinspection JSUnresolvedFunction
    certificate.serialNumber = new asn1js.Integer({ valueHex: serialNumberView });
    //endregion Put a static values

    //region Subject
    // For reference http://oidref.com/2.5.4.3
    if (certData.subject.commonName) {
      // noinspection JSUnresolvedFunction
      certificate.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({
        type: "2.5.4.3", // Common name
        value: new asn1js.PrintableString({ value: certData.subject.commonName })
      }));
    }

    if (certData.subject.country) {
      // noinspection JSUnresolvedFunction
      certificate.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({
        type: "2.5.4.6", // Country name
        value: new asn1js.PrintableString({ value: certData.subject.country })
      }));
    }

    if (certData.subject.locality) {
      // noinspection JSUnresolvedFunction
      certificate.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({
        type: "2.5.4.7", // Locality Name
        value: new asn1js.PrintableString({ value: certData.subject.locality })
      }));
    }

    if (certData.subject.state) {
      // noinspection JSUnresolvedFunction
      certificate.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({
        type: "2.5.4.8", // State or Province name
        value: new asn1js.PrintableString({ value: certData.subject.state })
      }));
    }

    if (certData.subject.organization) {
      // noinspection JSUnresolvedFunction
      certificate.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({
        type: "2.5.4.10", // Organization name
        value: new asn1js.PrintableString({ value: certData.subject.organization })
      }));
    }

    if (certData.subject.organizationUnit) {
      // noinspection JSUnresolvedFunction
      certificate.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({
        type: "2.5.4.11", // Organization unit name
        value: new asn1js.PrintableString({ value: certData.subject.organizationUnit })
      }));
    }

    if (certData.subject.email) {
      // noinspection JSUnresolvedFunction
      certificate.subject.typesAndValues.push(new pkijs.AttributeTypeAndValue({
        type: OID_PKCS9_EmailAddress, // Email, deprecated but still widely used
        value: new asn1js.IA5String({ value: certData.subject.email })
      }));
    }
    //endregion Subject


    //region Issuer
    if (issuerData && issuerData.certificate) {
      certificate.issuer = issuerData.certificate.subject;
    } else {
      certificate.issuer = certificate.subject;
    }
    //endregion Issuer

    //region Validity
    if (!certData.validity) {
      certData.validity = {}
    }

    if (certData.validity.notBefore) {
      certificate.notBefore.value = certData.validity.notBefore; //date
    } else {
      const tmp = new Date();
      certificate.notBefore.value = new Date(tmp.getFullYear(), tmp.getMonth(), tmp.getDate(), 0, 0, 0);
    }

    if (certData.validity.notAfter) {
      certificate.notAfter.value = certData.validity.notAfter; //date
    } else {
      const tmp = certificate.notBefore.value;
      const validYears = certData.validity.validYears || 1;
      certificate.notAfter.value = new Date(tmp.getFullYear() + validYears, tmp.getMonth(), tmp.getDate(), 23, 59, 59);
    }
    //endregion Validity

    //region Extensions
    certificate.extensions = []; // Extensions are not a part of certificate by default, it's an optional array

    //region "BasicConstraints" extension
    const basicConstr = new pkijs.BasicConstraints({
      cA: !!certData.isCA,
      //pathLenConstraint: 0 //TODO add logic for leaf CA
    });

    certificate.extensions.push(new pkijs.Extension({
      extnID: "2.5.29.19",
      critical: true,
      extnValue: basicConstr.toSchema().toBER(false),
      parsedValue: basicConstr // Parsed value for well-known extensions
    }));
    //endregion "BasicConstraints" extension

    //region "KeyUsage" extension
    const keyUsageBuffer = new ArrayBuffer(1);
    const keyUsageBitView = new Uint8Array(keyUsageBuffer);

    keyUsageBitView[0] = !!certData.isCA ? KEY_USAGE_CertificateAuthority : KEY_USAGE_LeafCertificate;

    // noinspection JSUnresolvedFunction
    const keyUsage = new asn1js.BitString({ valueHex: keyUsageBuffer });

    certificate.extensions.push(new pkijs.Extension({
      extnID: "2.5.29.15",
      critical: true,
      extnValue: keyUsage.toBER(false),
      parsedValue: keyUsage // Parsed value for well-known extensions
    }));
    //endregion "KeyUsage" extension

    //region "ExtKeyUsage" extension
    if (!certData.isCA && certData.subject.email) {
      const extKeyUsage = new pkijs.ExtKeyUsage({
        keyPurposes: [
          OID_ID_PKIX_EmailProtection
        ]
      });

      certificate.extensions.push(new pkijs.Extension({
        extnID: "2.5.29.37",
        critical: false,
        extnValue: extKeyUsage.toSchema().toBER(false),
        parsedValue: extKeyUsage // Parsed value for well-known extensions
      }));
    }
    //endregion "ExtKeyUsage" extension

    //region "SubjAltName" extension
    if (certData.subject.email || certData.subject.url) {

      const names = [];

      if (certData.subject.email) {
        names.push(new pkijs.GeneralName({
          type: 1, // rfc822Name
          value: certData.subject.email
        }));
      }

      if (certData.subject.url) {
        names.push(new pkijs.GeneralName({
          type: 2, // dNSName
          value: certData.subject.url
        }));
      }

      const subjAltNames = new pkijs.GeneralNames({
        names: names
      });

      certificate.extensions.push(new pkijs.Extension({
        extnID: "2.5.29.17",
        critical: false,
        extnValue: subjAltNames.toSchema().toBER(false),
        parsedValue: subjAltNames // Parsed value for well-known extensions
      }));
    }
    //endregion "SubjAltName" extension


    //region "SubjectKeyIdentifier" extension
    const subjKeyId = new asn1js.OctetString({ valueHex: subjKeyIdBuffer });

    certificate.extensions.push(new pkijs.Extension({
      extnID: "2.5.29.14",
      critical: false,
      extnValue: subjKeyId.toBER(false),
      parsedValue: subjKeyId // Parsed value for well-known extensions
    }));
    //endregion "SubjectKeyIdentifier" extension

    /* COULD NOT GET IT WORKING
        //region "AuthorityKeyIdentifier" extension
        if (issuerData && issuerData.certificate) {

          let issuerSubjKeyExt = null;

          let extLength = issuerData.certificate.extensions.length;
          for (var i = 0; i < extLength; i++) {
            let ext = issuerData.certificate.extensions[i];
            if (ext.extnID == "2.5.29.14") {
              issuerSubjKeyExt = ext;
              break;
            }
          }

          if (issuerSubjKeyExt) {

            const asn1 = asn1js.fromBER(issuerSubjKeyExt.extnValue);

            const authKeyIdentifier = new AuthorityKeyIdentifier({
              keyIdentifier: new asn1js.OctetString({
                //isHexOnly: true,
                //valueHex: issuerSubjKeyExt.parsedValue.valueBlock.valueHex
                value: new asn1js.OctetString({ valueHex: subjKeyIdBuffer })
              })
            });
            // const authKeyIdentifier = new AuthorityKeyIdentifier({
            // 	//keyIdentifier: new asn1js.OctetString({ valueHex: subjKeyIdBuffer })

            // });

            certificate.extensions.push(new Extension({
              extnID: "2.5.29.35",
              critical: false,
              extnValue: authKeyIdentifier.toSchema().toBER(false),
              parsedValue: authKeyIdentifier // Parsed value for well-known extensions
            }));
          }
        }
        //endregion "AuthorityKeyIdentifier" extension
    */
    //endregion Extensions
  });
  //region Fill in cert data


  //region Signing final certificate
  sequence = sequence.then(() => {
      let signerKey = (issuerData && issuerData.privateKey) ? issuerData.privateKey : privateKey;
      //console.log(signerKey)
      return certificate.sign(signerKey, (certData.algorithms && certData.algorithms.hashAlg) ? certData.algorithms.hashAlg : defaultAlgorithms.hashAlg)
    },
    error => Promise.reject(`Error during exporting public key: ${error}`));
  //endregion

  //region Encode and store certificate
  sequence = sequence.then(() =>
  {
    //console.log(certificate)
    certificateBuffer = certificate.toSchema(true).toBER(false);
  }, error => Promise.reject(`Error during signing: ${error}`));
  //endregion

  //region Exporting public key
  sequence = sequence.then(() =>
    crypto.exportKey("spki", publicKey)
  );
  //endregion

  //region Store exported public key on Web page
  sequence = sequence.then(result =>
  {
    publicKeyBuffer = result;
  }, error => Promise.reject(`Error during exporting of public key: ${error}`));
  //endregion

  //region Exporting private key
  sequence = sequence.then(() =>
    crypto.exportKey("pkcs8", privateKey)
  );
  //endregion

  //region Store exported key on Web page
  sequence = sequence.then(result =>
  {
    privateKeyBuffer = result;
  }, error => Promise.reject(`Error during exporting of private key: ${error}`));
  //endregion

  return sequence.then(() => {

    const result = {
      certificate: certificate,
      certificatePEM: encodePEM(certificateBuffer, "CERTIFICATE"),
      publicKey: publicKey,
      publicKeyPEM: encodePEM(publicKeyBuffer, "PUBLIC KEY"),
      privateKey: privateKey,
      privateKeyPEM: encodePEM(privateKeyBuffer, "PRIVATE KEY")
    }
    return result;
  });
}

function formatPEM(pemString) {
  const lineWidth = 64;
  let resultString = "";
  let start = 0;
  let piece = "";
  while ( (piece = pemString.substring(start, start + lineWidth)).length > 0 ) {
    start += lineWidth;
    resultString += piece + '\r\n'
  }
  return resultString;
}

function encodePEM(buffer, label) {
  const bufferString = String.fromCharCode.apply(null, new Uint8Array(buffer));

  const header = `-----BEGIN ${label}-----\r\n`;
  const base64formatted = formatPEM(window.btoa(bufferString));
  const footer = `-----END ${label}-----\r\n`;
  const resultString = header + base64formatted + footer;

  return resultString;
}

function decodePEM(pemString) {
  const pemStripped = pemString.replace(/(-----(BEGIN|END) [a-zA-Z ]*-----|\r|\n)/g, '');
  const pemDecoded = window.atob(pemStripped);
  const buffer = pvutils.stringToArrayBuffer(pemDecoded);
  return buffer;
}

//*********************************************************************************
function parseCertificate(certificatePEM) {
  const certificateBuffer = decodePEM(certificatePEM);
  const asn1 = asn1js.fromBER(certificateBuffer);
  const certificate = new pkijs.Certificate({ schema: asn1.result });
  return certificate;
}

//*********************************************************************************
function parsePublicKey(publicKeyPEM) {
  const publicKeyBuffer = decodePEM(publicKeyPEM);
  const crypto = pkijs.getCrypto();
  const publicKeyPromise = crypto.importKey(
    "spki",
    publicKeyBuffer,
    {   //these are the algorithm options
      name: "RSASSA-PKCS1-v1_5",
      hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
    },
    true,
    ["verify"]
  );
  return publicKeyPromise;
}

//*********************************************************************************
function encryptMessage(message, password, label) {
  const buffer = pvutils.stringToArrayBuffer(message);
  const secret = pvutils.stringToArrayBuffer(password);

  const enveloped = new pkijs.EnvelopedData();
  enveloped.addRecipientByPreDefinedData(secret, {}, AES_encryptionVariant_Password);
  return enveloped.encrypt(encryptionAlgorithm, buffer).
  then(
    () => {
      const content = new pkijs.ContentInfo();
      content.contentType = OID_PKCS7_EnvelopedData;
      content.content = enveloped.toSchema();
      const ber = content.toSchema().toBER(false);
      return encodePEM(ber, label)
    },
    error => Promise.reject(`encryption error: ${error}`)
  )
}

//*********************************************************************************
function decryptMessage(message, password) {
  const secret = pvutils.stringToArrayBuffer(password);
  const buffer = decodePEM(message);

  const asn1 = asn1js.fromBER(buffer);
  const content = new pkijs.ContentInfo({schema: asn1.result});
  const enveloped = new pkijs.EnvelopedData({schema: content.content});
  return enveloped.decrypt(0, {preDefinedData: secret}).then(result => {
    return pvutils.arrayBufferToString(result);
  }).catch(() => {
    throw("Wrong pincode")
  })
}

//*********************************************************************************
function parsePrivateKey(privateKeyPEM) {
  const privateKeyBuffer = decodePEM(privateKeyPEM);
  const crypto = pkijs.getCrypto();
  const privateKeyPromise = crypto.importKey(
    "pkcs8",
    privateKeyBuffer,
    {   //these are the algorithm options
      name: "RSASSA-PKCS1-v1_5",
      hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
    },
    true,
    ["sign"]
  );
  return privateKeyPromise;
}


function createPassportCertificate(commonNameArg) {
  const certData = {
    algorithms: {
      hashAlg: "SHA-256",
      signAlg: "RSASSA-PKCS1-v1_5",
      keyLength: 2048
    },
    //keyPair: generateKeys(), //optional , if provided must be object or promise that resolves to object {publicKey, prvateKey}. If it is not provided, new ones are generated automatically
    subject: {
      commonName: commonNameArg + "-userdevice", //optional for leaf, recommended for CA
      country: "CH", //optional for leaf, recommended for CA
      locality: "Zug", //optional for leaf, recommended for CA
      state: "Zug", //optional for leaf, recommended for CA
      organization: "Vereign AG", //optional for leaf, recommended for CA
      organizationUnit:"Business Dep", //optional for leaf, recommended for CA
      //email: "damyan.mitev@vereign.com", // added to DN and Subject Alternative Name extension. Optional for CA. Mandatory for leaf certificate, used for email protection
      //url: "www.vereign.com" // optional url, recommended for CA, added to Subject Alternative Name extension
    },
    validity: {
      //notBefore: new Date() // optional, defaults to today at 00:00:00
      //notAfter: new Date()  // optional, defaults to notBefore + validYears at 23:59:59
      validYears: 5 //optional, defaults to 1
    },
    isCA: true // optional flag denoting if this is CA certificate or leaf certificate, defaults to false
  }

  return createCertificate(certData, null)
}

function createOneTimePassportCertificate(commonNameArg, emailArg, privateKeyIssuerArg, certicateIssuerArg) {
  var certData = null
  if(emailArg != null && emailArg != "") {
    certData = {
      algorithms: {
        hashAlg: "SHA-256",
        signAlg: "RSASSA-PKCS1-v1_5",
        keyLength: 2048
      },
      //keyPair: generateKeys(), //optional , if provided must be object or promise that resolves to object {publicKey, prvateKey}. If it is not provided, new ones are generated automatically
      subject: {
        commonName: commonNameArg + "-onetime", //optional for leaf, recommended for CA
        country: "CH", //optional for leaf, recommended for CA
        locality: "Zug", //optional for leaf, recommended for CA
        state: "Zug", //optional for leaf, recommended for CA
        organization: "Vereign AG", //optional for leaf, recommended for CA
        organizationUnit:"Business Dep", //optional for leaf, recommended for CA
        email: emailArg, // added to DN and Subject Alternative Name extension. Optional for CA. Mandatory for leaf certificate, used for email protection
        //url: "www.vereign.com" // optional url, recommended for CA, added to Subject Alternative Name extension
      },
      validity: {
        //notBefore: new Date() // optional, defaults to today at 00:00:00
        //notAfter: new Date()  // optional, defaults to notBefore + validYears at 23:59:59
        validYears: 5 //optional, defaults to 1
      },
      isCA: false // optional flag denoting if this is CA certificate or leaf certificate, defaults to false
    }
  } else {
    certData = {
      algorithms: {
        hashAlg: "SHA-256",
        signAlg: "RSASSA-PKCS1-v1_5",
        keyLength: 2048
      },
      //keyPair: generateKeys(), //optional , if provided must be object or promise that resolves to object {publicKey, prvateKey}. If it is not provided, new ones are generated automatically
      subject: {
        commonName: commonNameArg + "-onetime", //optional for leaf, recommended for CA
        country: "CH", //optional for leaf, recommended for CA
        locality: "Zug", //optional for leaf, recommended for CA
        state: "Zug", //optional for leaf, recommended for CA
        organization: "Vereign AG", //optional for leaf, recommended for CA
        organizationUnit:"Business Dep", //optional for leaf, recommended for CA
        //email: emailArg, // added to DN and Subject Alternative Name extension. Optional for CA. Mandatory for leaf certificate, used for email protection
        //url: "www.vereign.com" // optional url, recommended for CA, added to Subject Alternative Name extension
      },
      validity: {
        //notBefore: new Date() // optional, defaults to today at 00:00:00
        //notAfter: new Date()  // optional, defaults to notBefore + validYears at 23:59:59
        validYears: 5 //optional, defaults to 1
      },
      isCA: false // optional flag denoting if this is CA certificate or leaf certificate, defaults to false
    }
  }

  return parsePrivateKey(privateKeyIssuerArg).then(privateKeyDecoded => {
    const issuerData = {
      certificate: parseCertificate(certicateIssuerArg),// vereignCACertPEM),
      privateKey: privateKeyDecoded
    }
    return createCertificate(certData, issuerData);
    //console.log(vereignIntermediateKey)
  });
}

function download(filename, contentType, text) {
  var element = document.createElement('a');
  element.setAttribute('href', 'data:' + contentType + ';charset=utf-8,' + encodeURIComponent(text));
  element.setAttribute('download', filename);

  element.style.display = 'none';
  document.body.appendChild(element);

  element.click();

  document.body.removeChild(element);
}

function arrayBufferToBase64Formatted(buffer) {
  const bufferString = String.fromCharCode.apply(null, new Uint8Array(buffer));
  const base64formatted = formatPEM(window.btoa(bufferString));
  return base64formatted;
}

function arrayBufferToBase64(buffer) {
  const bufferString = String.fromCharCode.apply(null, new Uint8Array(buffer));
  const base64 = window.btoa(bufferString);
  return base64;
}

const newline = /\r\n|\r|\n/g;

function capitalizeFirstLetter(string) {
  if(string == "id") {
    return "ID"
  }

  if(string == "mime") {
    return "MIME";
  }

  return string.charAt(0).toUpperCase() + string.slice(1);
}

function capitalizeHeader(string) {
  result = ""
  tokens = string.split("-")
  for(var i = 0; i < tokens.length; i++) {
    result += capitalizeFirstLetter(tokens[i])
    if(i != tokens.length - 1) {
      result += "-"
    }
  }

  return result
}

function signEmail(mime, signingCert, certificateChain, privateKey) {
  signingCertObj = parseCertificate(signingCert)
  certificateChainObj = []
  certificateChainObj[0] = parseCertificate(signingCert)
  for(var i = 0; i < certificateChain.length; i++) {
    certificateChainObj[i + 1] = parseCertificate(certificateChain[i])
  }
  //console.log(certificateChainObj)

  return parsePrivateKey(privateKey).then(privateKeyDecoded => {

    return signEmailObjects(mime, signingCertObj, certificateChainObj, privateKeyDecoded);
    //console.log(vereignIntermediateKey)
  });
}

function signEmailObjects(mime, signingCert, certificateChain, privateKey) {

  //region Get a "crypto" extension
  const crypto = pkijs.getCrypto();
  if (typeof crypto === "undefined") {
    return Promise.reject("No WebCrypto extension found");
  }
  //endregion Get a "crypto" extension

  let template =
    `{{headers}}Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha-256; boundary="{{boundary}}"
MIME-Version: 1.0

This is a cryptographically signed message in MIME format.

--{{boundary}}
{{mime}}
--{{boundary}}
Content-Type: application/pkcs7-signature; name="smime.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
Content-Description: S/MIME Cryptographic Signature

{{signature}}
--{{boundary}}--

Vereign - Authentic Communication
`.replace(newline, '\r\n');

  const detachedSignature = true;
  const addExt = true;
  const hashAlg = "SHA-256";
  let cmsSignedSimpl;

  var mimeHeadersTitles = [
    "Content-Type",
    "Content-Transfer-Encoding",
    "Content-ID",
    "Content-Description",
    "Content-Disposition",
    "Content-Language",
    "Content-Location"
  ]

  mime = mime.replace(newline, '\r\n');

  let newHeaderLines = ""
  let headersEnd = mime.indexOf('\r\n\r\n') //the first empty line

  if (headersEnd < 0 && mime.startsWith('\r\n')) {

    mime = mime.substring(2) //should not happen

  } else
  if (headersEnd >= 0) {

    let mimeHeaders = {}
    let mimeBody = mime.substring(headersEnd + 4)

    let mimeHeadersStr = mime.substring(0, headersEnd)

    let headers = libmime.decodeHeaders(mimeHeadersStr)
    for (var i = 0; i < mimeHeadersTitles.length; i++) {
      let key = mimeHeadersTitles[i].toLowerCase()
      if(key in headers) {
        mimeHeaders[key] = headers[key]
        delete headers[key]
      }
    }

    for(let key in headers) {
      if(!(key === "" || key === "MIME-Version".toLowerCase())) { //we have MIME-Version in the template
        newHeaderLines += capitalizeHeader(key) + ": " + headers[key] + '\r\n';
      }
    }

    let newMimeHeaderLines = ""
    for(let key in mimeHeaders) {
      if(!(key === "")) {
        newMimeHeaderLines += capitalizeHeader(key) + ": " + mimeHeaders[key] + '\r\n';
      }
    }

    if (newMimeHeaderLines === "") {
      newMimeHeaderLines = 'Content-Type: text/plain\r\n' //should not happen
    }

    mime = newMimeHeaderLines + '\r\n' + mimeBody
  }

  let dataBuffer = pvutils.stringToArrayBuffer(mime);

  let sequence = Promise.resolve();

  //region Check if user wants us to include signed extensions
  if(addExt)
  {
    //region Create a message digest
    sequence = sequence.then(
      () => crypto.digest({ name: hashAlg }, new Uint8Array(dataBuffer))
    );
    //endregion

    //region Combine all signed extensions
    sequence = sequence.then(
      messageHash =>
      {
        const signedAttr = [];
        /*
                1.2.840.113549.1.9.1 - e-mailAddress
                1.2.840.113549.1.9.2 - PKCS-9 unstructuredName
                1.2.840.113549.1.9.3 - contentType
                1.2.840.113549.1.9.4 - messageDigest
                1.2.840.113549.1.9.5 - Signing Time
                1.2.840.113549.1.9.6 - counterSignature
        */
        signedAttr.push(new pkijs.Attribute({
          type: OID_PKCS9_ContentType, //contentType
          values: [
            new asn1js.ObjectIdentifier({ value: OID_PKCS7_Data}) //data
          ]
          /*
                      1.2.840.113549.1.7.1 - data
                      1.2.840.113549.1.7.2 - signedData
                      1.2.840.113549.1.7.3 - envelopedData
                      1.2.840.113549.1.7.4 - signedAndEnvelopedData
                      1.2.840.113549.1.7.5 - digestedData
                      1.2.840.113549.1.7.6 - encryptedData
          */
        })); // contentType

        signedAttr.push(new pkijs.Attribute({
          type: OID_PKCS9_SigningTime, //Signing Time
          values: [
            new asn1js.UTCTime({ valueDate: new Date() })
          ]
        })); // signingTime

        signedAttr.push(new pkijs.Attribute({
          type: OID_PKCS9_MessageDigest, //messageDigest
          values: [
            new asn1js.OctetString({ valueHex: messageHash })
          ]
        })); // messageDigest

        return signedAttr;
      }
    );
    //endregion
  }
  //endregion

  //region Initialize CMS Signed Data structures and sign it
  sequence = sequence.then(
    signedAttr =>
    {
      cmsSignedSimpl = new pkijs.SignedData({
        version: 1,
        encapContentInfo: new pkijs.EncapsulatedContentInfo({
          eContentType: OID_PKCS7_Data // "data" content type
        }),
        signerInfos: [
          new pkijs.SignerInfo({
            version: 1,
            sid: new pkijs.IssuerAndSerialNumber({
              issuer: signingCert.issuer,
              serialNumber: signingCert.serialNumber
            })
          })
        ],
        certificates: certificateChain //array
      });

      if(addExt)
      {
        cmsSignedSimpl.signerInfos[0].signedAttrs = new pkijs.SignedAndUnsignedAttributes({
          type: 0,
          attributes: signedAttr
        });
      }

      if(detachedSignature === false)
      {
        const contentInfo = new pkijs.EncapsulatedContentInfo({
          eContent: new asn1js.OctetString({ valueHex: dataBuffer })
        });

        cmsSignedSimpl.encapContentInfo.eContent = contentInfo.eContent;

        return cmsSignedSimpl.sign(privateKey, 0, hashAlg);
      }

      return cmsSignedSimpl.sign(privateKey, 0, hashAlg, dataBuffer);
    }
  );
  //endregion

  //region Create final result
  sequence = sequence.then(
    (result) =>
    {
      const cmsSignedSchema = cmsSignedSimpl.toSchema(true);

      const cmsContentSimp = new pkijs.ContentInfo({
        contentType: OID_PKCS7_SignedData, //signedData
        content: cmsSignedSchema
      });

      const _cmsSignedSchema = cmsContentSimp.toSchema();

      //region Make length of some elements in "indefinite form"
      _cmsSignedSchema.lenBlock.isIndefiniteForm = true;

      const block1 = _cmsSignedSchema.valueBlock.value[1];
      block1.lenBlock.isIndefiniteForm = true;

      const block2 = block1.valueBlock.value[0];
      block2.lenBlock.isIndefiniteForm = true;

      if(detachedSignature === false)
      {
        const block3 = block2.valueBlock.value[2];
        block3.lenBlock.isIndefiniteForm = true;
        block3.valueBlock.value[1].lenBlock.isIndefiniteForm = true;
        block3.valueBlock.value[1].valueBlock.value[0].lenBlock.isIndefiniteForm = true;
      }
      //endregion

      const cmsSignedBuffer = _cmsSignedSchema.toBER(false);
      return cmsSignedBuffer;
    },
    error => Promise.reject(`Erorr during signing of CMS Signed Data: ${error}`)
  );
  //endregion

  sequence = sequence.then(
    (cmsSignedBuffer) =>
    {
      let signature = arrayBufferToBase64Formatted(cmsSignedBuffer);
      let boundary = makeBoundary()

      template = template.replace(/{{boundary}}/g, boundary)
      template = template.replace("{{signature}}", signature)
      template = template.replace("{{headers}}", newHeaderLines)
      template = template.replace("{{mime}}", mime)

      //template = template.replace(newline, '\r\n')
      return template
    }
  );

  return sequence;
}

function makeBoundary() {
  let len = 20 + Math.random() * 20
  return 'W0RyLiBEYW15YW4gTWl0ZXZd--' + makeid(len)
}

function makeid(len) {
  if (typeof len === 'undefined') {
    len = 10
  }
  var text = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  for (var i = 0; i < len; i++)
    text += possible.charAt(Math.floor(Math.random() * possible.length));

  return text;
}

function CryptoData() {
}

CryptoData.prototype.set = function(obj) {
  for(var member in obj) {
    this[member] = JSON.parse(JSON.stringify(obj[member]))
  }
}

CryptoData.prototype.serialize = function() {
  return JSON.stringify(this)
}

CryptoData.prototype.deserialize = function(serialized) {
  var obj = JSON.parse(serialized)
  this.set(obj)
}

CryptoData.prototype.setPublicKey = function(publicKey) {
  this["publicKey"] = publicKey
}

CryptoData.prototype.getPublicKey = function() {
  return this["publicKey"]
}

CryptoData.prototype.setPrivateKey = function(privateKey) {
  this["privateKey"] = privateKey
}

CryptoData.prototype.getPrivateKey = function() {
  return this["privateKey"]
}

CryptoData.prototype.setx509Certificate = function(x509Certificate) {
  this["x509Certificate"] = x509Certificate
}

CryptoData.prototype.getx509Certificate = function() {
  return this["x509Certificate"]
}

CryptoData.prototype.setKeyUUID = function(keyUUID) {
  this["keyUUID"] = keyUUID
}

CryptoData.prototype.getKeyUUID = function() {
  return this["keyUUID"]
}

CryptoData.prototype.setChain = function(chain) {
  this["chain"] = chain
}

CryptoData.prototype.getChain = function() {
  return this["chain"]
}

function Identity() {
}

Identity.prototype.set = function(obj) {
  for(var member in obj) {
    this[member] = JSON.parse(JSON.stringify(obj[member]))
  }
}

Identity.prototype.serialize = function() {
  return JSON.stringify(this)
}

Identity.prototype.deserialize = function(serialized) {
  var obj = JSON.parse(serialized)
  this.set(obj)
}

Identity.prototype.setAuthentication = function(cryptoData) {
  this["authentication"] = cryptoData
}

Identity.prototype.getAuthentication = function() {
  return this["authentication"]
}

Identity.prototype.setPinCode = function(pinCode) {
  this["pinCode"] = pinCode
}

Identity.prototype.getPinCode = function() {
  return this["pinCode"]
}

Identity.prototype.setPassport = function(passportUUID, cryptoData) {
  if(this["passports"] === undefined || this["passports"] === null) {
    this["passports"] = {}
  }

  this["passports"][passportUUID] = cryptoData
}

Identity.prototype.getPassport = function(passportUUID) {
  if(this["passports"] === undefined || this["passports"] === null) {
    this["passports"] = {}
  }

  return this["passports"][passportUUID]
}

var identityColors = ["#994392", "#cb0767", "#e51d31", "#ec671b", "#fab610"]

function getNextColor() {
  var colorIndex = localStorage.getItem("colorIndex");
  if (colorIndex == null || colorIndex == "") {
    colorIndex = 0
  }

  var color = identityColors[colorIndex]

  colorIndex++;

  colorIndex = colorIndex % identityColors.length

  localStorage.setItem("colorIndex", colorIndex)

  return color
}

function setKeyForUUID(uuid, key) {
  var storedIdentityForUuid = localStorage.getItem("keyperuuid/" + uuid)
  if(storedIdentityForUuid != key && storedIdentityForUuid != null && storedIdentityForUuid != "") {
    destroyIdentityFromLocalStorage(storedIdentityForUuid)
  }

  localStorage.setItem("keyperuuid/" + uuid, key)
}

function getColorForIdentity(key) {
  var storedColor = localStorage.getItem("colors/" + key)

  if(storedColor == null || storedColor == "") {
    storedColor = getNextColor()
    console.log("Setting new color: " + storedColor)
    localStorage.setItem("colors/" + key, storedColor)
  }

  return storedColor
}

function setIdentityInLocalStorage(identityToStore, extendKey = true) {
  //console.log(getStack())
  var pinCode = identityToStore.pinCode;
  const serializedIdentity = JSON.stringify(identityToStore);
  const key = identityToStore.authentication.publicKey;

  if(pinCode == null || pinCode == "") {
    pinCode = getPincode(key)
  }

  if(pinCode == null || pinCode == "") {
    console.log("Can not set identity")
    return null;
  }

  return encryptMessage(serializedIdentity, pinCode, "identity").then((encryptedIdentity) => {
    var success = true
    if(extendKey === true) {
      success = extendPinCodeTtl(key, pinCode)
    }
    if (success == true) {
      localStorage.setItem(key, encryptedIdentity);
      let serializedIdentitiesList = localStorage.getItem("identities");
      let identities = JSON.parse(serializedIdentitiesList);
      identities[key] = true;

      localStorage.setItem("identities", JSON.stringify(identities))
    } else {
      console.log("Can not extend pincode ttl")
    }
  });
}

function getProfileData(identity) {
  return new Penpal.Promise(executeResultUpper => {
    executeRestfulFunction("private", viamApi,
      viamApi.identityGetIdentityProfileData).then(executeResult => {
      if(executeResult.code == "200") {
        console.log("In promise")
        console.log(executeResult)
        var listItem = {};

        console.log(identity)
        listItem.identityColor = getColorForIdentity(identity.authentication.publicKey)
        listItem.initials = executeResult.data.initials

        if(listItem.initials === null || listItem.initials === "") {
          listItem.initials = "JD";
        }
        console.log("Item")
        console.log(listItem)
        localStorage.setItem("profiles/" + identity.authentication.publicKey, JSON.stringify(listItem))
        executeResultUpper(listItem)
      } else {
        executeResultUpper({})
      }
    });
  });
}

function getIdentityFromLocalStorage(key, pinCode, extendTtl = true) {
  const encryptedIdentity = localStorage.getItem(key);
  if (encryptedIdentity == null) {
    console.log("No such identity for public key")
    return Promise.resolve(null)
  }
  return decryptMessage(encryptedIdentity, pinCode).then((serializedIdentity) => {
    var parsedIdentity = JSON.parse(serializedIdentity);
    parsedIdentity["pinCode"] = ""
    //console.log(parsedIdentity)
    if(extendTtl === true) {
      var success = extendPinCodeTtl(key, pinCode)
      if (success == true) {
        return parsedIdentity
      } else {
        console.log("Can not extend pincode ttl")
        return null
      }
    } else {
      return parsedIdentity
    }
  });

}

function listIdentitiesFromLocalStorage() {
  var serializedIdentitiesList = localStorage.getItem("identities")
  var identities = JSON.parse(serializedIdentitiesList)
  var identitiesResult = {}

  for(var key in identities) {
    var profile = JSON.parse(JSON.stringify(localStorage.getItem("profiles/" + key)))
    console.log("Getting profile")
    console.log(profile)
    if(profile != null && profile != "") {
      console.log("Setting profile for key: " + key)
      //console.log(profile)
      identitiesResult[key] = JSON.parse(profile)
      //console.log(identitiesResult)
    } else {
      console.log("Setting empty key for profile: " + key)
      identitiesResult[key] = {}
      //console.log(identitiesResult)
    }
  }

  console.log("In list identities from local storage")
  console.log(identitiesResult)
  return identitiesResult
}

function getStack() {
  try {
    throw new Error();
  } catch(e) {
    return e.stack;
  }
}

function extendPinCodeTtl(key, pinCode) {
  //console.log("Extending pincode ttl")
  //console.log(getStack())
  //console.log("Extending pincode ttl for key: " + key)
  //console.log(pinCode)
  if(pinCode == null || pinCode == "") {
    var now = new Date();
    var nowMillis = now.getTime();
    var ttl = window.sessionStorage.getItem("pincodettls/" + key);
    if (ttl == null || ttl == "" || nowMillis >= parseInt(ttl)) {
      clearPinCodeTtl(key)
      return false
    } else {
      var ttl = now.getTime() + 10 * 60 * 1000;
      window.sessionStorage.setItem("pincodettls/" + key, ttl);
    }
  } else {
    var now = new Date();
    var ttl = now.getTime() + 10 * 60 * 1000;
    window.sessionStorage.setItem("pincodettls/" + key, ttl);
    window.sessionStorage.setItem("pincodes/" + key, pinCode);
  }

  return true;
}

function clearPinCodeTtl(key) {
  //console.log("Clearing ttl for key: " + key)
  window.sessionStorage.removeItem("pincodettls/" + key)
  window.sessionStorage.removeItem("pincodes/" + key)
}

function getPincode(key) {
  var now = new Date();
  var nowMillis = now.getTime();
  var ttl = window.sessionStorage.getItem("pincodettls/" + key);
  if (ttl == null || ttl == "") {
    return null
  } else {
    if(nowMillis >= parseInt(ttl)) {
      clearPinCodeTtl(key)
      return null
    } else {
      return window.sessionStorage.getItem("pincodes/" + key);
    }
  }
}

function createEvent(actionId, type, payloads) {
  return {
    "actionID": actionId,
    "type": type,
    "stamp": new Date().getTime(),
    "payloads" : payloads
  }
}

function destroyIdentityFromLocalStorage(key) {
  localStorage.removeItem(key)
  localStorage.removeItem("profiles/" + key)
  localStorage.removeItem("colors/" + key)

  var serializedIdentitiesList = localStorage.getItem("identities")

  var identities = JSON.parse(serializedIdentitiesList)

  identities[key] = null

  delete identities[key]

  localStorage.setItem("identities", JSON.stringify(identities))
}

window.loadedIdentities = {}
window.viamApi = new ViamAPI();
window.viamAnonymousApi = new ViamAPI();
window.currentlyAuthenticatedIdentity = null
window.currentlyLoadedIdentity = null
window.lastTimeGetProfile = 0

function executeRestfulFunction(type, that, fn, ...args) {
  if(type == "private") {
    return new Penpal.Promise(executeResult => {
      fn.apply(that, args).then((response) => {
        if (response.data.code == "400" && response.data.status == "Bad session") {
          console.log("Trying to login again")
          if(currentlyAuthenticatedIdentity != "" && currentlyAuthenticatedIdentity != null) {
            viamApi.identityLogin("previousaddeddevice").then((response1) => {
              if (response1.data.code == "200") {
                //console.log(response.data.data)
                var uuid = response1.data.data["Uuid"]
                var token = response1.data.data["Session"]
                //console.log(uuid + " " + token)
                viamApi.setSessionData(uuid, token)
                localStorage.setItem("uuid", uuid)
                localStorage.setItem("token", token)
                localStorage.setItem("authenticatedIdentity", currentlyAuthenticatedIdentity.authentication.publicKey)
                currentlyAuthenticatedIdentity = loadedIdentities[currentlyAuthenticatedIdentity.authentication.publicKey]
                setKeyForUUID(uuid, currentlyAuthenticatedIdentity.authentication.publicKey)
                lastTimeGetProfile = 0;
                fn.apply(null, args).then((response2) => {
                  executeResult(response2.data)
                });
              } else {
                executeResult(response1.data)
              }
            });
          } else {
            if(currentlyLoadedIdentity != "" && currentlyLoadedIdentity != null) {
              viamApi.identityLogin("previousaddeddevice").then((response1) => {
                if (response1.data.code == "200") {
                  //console.log(response.data.data)
                  var uuid = response1.data.data["Uuid"]
                  var token = response1.data.data["Session"]
                  //console.log(uuid + " " + token)
                  viamApi.setSessionData(uuid, token)
                  localStorage.setItem("uuid", uuid)
                  localStorage.setItem("token", token)
                  localStorage.setItem("authenticatedIdentity", currentlyLoadedIdentity.authentication.publicKey)
                  currentlyAuthenticatedIdentity = loadedIdentities[currentlyLoadedIdentity.authentication.publicKey]
                  setKeyForUUID(uuid, currentlyLoadedIdentity.authentication.publicKey)
                  lastTimeGetProfile = 0;
                  fn.apply(null, args).then((response2) => {
                    executeResult(response2.data)
                  });
                } else {
                  executeResult(response1.data)
                }
              });
            } else {
              executeResult(response.data)
            }
          }
        } else {
          executeResult(response.data)
        }
      });
    });
  } else {
    return new Penpal.Promise(executeResult => {
      fn.apply(that, args).then((response) => {
        executeResult(response.data)
      });
    });
  }
}

function loadIdentityInternal(identityKey, pinCode) {
  return new Penpal.Promise(result => {
    console.log("Loading identity with pincode: " + pinCode)
    getIdentityFromLocalStorage(identityKey, pinCode).then((loadedIdentity) => {
      if (loadedIdentity == null) {
        result({
          "data": "",
          "code": "400",
          "status": "Can not load identity"
        })
      }
      var copiedIdentity = JSON.parse(JSON.stringify(loadedIdentity))
      loadedIdentities[identityKey] = loadedIdentity

      if (identityKey === localStorage.getItem("authenticatedIdentity")) {
        currentlyAuthenticatedIdentity = copiedIdentity
        viamApi.setIdentity(identityKey)
        var uuid = localStorage.getItem("uuid")
        var token = localStorage.getItem("token")
        //console.log("Loading " + uuid + " " + token)
        viamApi.setSessionData(uuid, token)
      }

      //console.log("Set loaded identity in load identity")
      currentlyLoadedIdentity = copiedIdentity
      viamAnonymousApi.setIdentity(currentlyLoadedIdentity.authentication.publicKey)

      copiedIdentity.pinCode = ""
      copiedIdentity.authentication.privateKey = ""

      result({
        "data": copiedIdentity,
        "code": "200",
        "status": "Identity loaded"
      })
    }).catch((e) => {
      result({
        "data": "",
        "code": "400",
        "status": "Can not load entity:" + e
      })
    })
  });
}

function changeIdentityPinCodeInternal(key, oldPinCode, newPinCode) {

  return new Penpal.Promise(result => {
    getIdentityFromLocalStorage(key, oldPinCode, false).then((identity) => {

      identity.pinCode = newPinCode;

      console.log("Storing identity with pincode: " + identity.pinCode)
      setIdentityInLocalStorage(identity).then(() => {
      }).catch((e) => {
        result({
          "data": "",
          "code": "400",
          "status": "Cannot store identity " + e
        });
      });
    });
  });
}

function getCertificateForPassport(passportUUID, internal) {

  return new Penpal.Promise(certificateResult => {
    if (currentlyAuthenticatedIdentity === null) {
      return {"data" : "",
        "code" : "400",
        "status" : "Identity not authenticated"
      }
    }

    var passportIdentity = new Identity()
    passportIdentity.set(currentlyAuthenticatedIdentity)
    //console.log("Getting: " + passportUUID)
    //console.log(identity)
    var passport = passportIdentity.getPassport(passportUUID)
    if(passport === undefined || passport === null) {
      createPassportCertificate(passportUUID).then(function(keys){
        var cryptoData = new CryptoData()
        //console.log(keys)
        cryptoData.setPublicKey(keys["publicKeyPEM"])
        cryptoData.setPrivateKey(keys["privateKeyPEM"])
        var certificate = keys["certificatePEM"]
        //download("passportCertificateBeforeSigning.crt", "text/plain", certificate)
        //console.log(certificate)
        //cryptoData.setx509Certificate(keys["certificate"])
        executeRestfulFunction("private", viamApi, viamApi.signSignCertificate, btoa(certificate), passportUUID).then(executeResult => {
          if(executeResult.code == "200") {
            var signedCertificate = atob(executeResult.data["SignedCertificate"])
            //download("passportCertificateAfterSigning.crt", "text/plain", signedCertificate)
            var keyUUID = executeResult.data["CertificateUUID"]
            var encodedChain = executeResult.data["Chain"]
            //download("rootCertificate.crt", "text/plain", atob(encodedChain[0]))

            var chain = []

            for(var i = 0; i < encodedChain.length; i++) {
              chain.push(atob(encodedChain[i]))
            }

            //console.log("Chain from server")
            //console.log(chain)
            //console.log(signedCertificate)
            //console.log(certificate)
            //console.log(keyUUID)
            cryptoData.setx509Certificate(signedCertificate)
            cryptoData.setKeyUUID(keyUUID)
            cryptoData.setChain(chain)

            passportIdentity.setPassport(passportUUID, cryptoData)

            getProfileData(passportIdentity).then(executeResult1 => {
              console.log("Profile updated in set identity")
              setIdentityInLocalStorage(passportIdentity).then(() => {
                currentlyAuthenticatedIdentity = passportIdentity
                lastTimeGetProfile = 0;
                //console.log("Set loaded identity in passport");
                currentlyLoadedIdentity = passportIdentity
                var copyOfCryptoData = JSON.parse(JSON.stringify(cryptoData))

                if (internal === false) {
                  copyOfCryptoData["privateKey"] = ""
                }

                certificateResult({
                  "data": copyOfCryptoData,
                  "code": "200",
                  "status": "Certificate got"
                });
              }).catch((e) => {
                certificateResult({
                  "data": "",
                  "code": "400",
                  "status": "Can not store certificate " + e
                });
              });
            });
          } else {
            certificateResult(executeResult)
          }
        });
      });
    } else {
      //console.log(passport)
      var copyOfCryptoData = JSON.parse(JSON.stringify(passport))

      if(internal === false) {
        copyOfCryptoData["privateKey"] = ""
      }

      certificateResult({"data" : copyOfCryptoData,
        "code" : "200",
        "status" : "Certificate got"
      });
    }
  });
}

const connection = Penpal.connectToParent({
  // Methods child is exposing to parent
  methods: {
    createIdentity(pinCode) {
      return new Penpal.Promise(result => {
        createPassportCertificate(makeid()).then(function(keys){
          var newIdentity = new Identity()
          var cryptoData = new CryptoData()
          cryptoData.setPublicKey(keys["publicKeyPEM"])
          cryptoData.setPrivateKey(keys["privateKeyPEM"])
          cryptoData.setx509Certificate(keys["certificatePEM"])
          newIdentity.setAuthentication(cryptoData)
          newIdentity.setPinCode(pinCode)

          //console.log("Set loaded identity in createIdentity")
          currentlyLoadedIdentity = newIdentity
          loadedIdentities[newIdentity.authentication.publicKey] = newIdentity
          extendPinCodeTtl(newIdentity.authentication.publicKey, pinCode)


          viamAnonymousApi.setIdentity(newIdentity.authentication.publicKey)

          result({"data" : newIdentity,
            "code" : "200",
            "status" : "Identity created"
          })
        });
      })
    },
    listIdentities() {
      return new Penpal.Promise(result => {
        var identities = listIdentitiesFromLocalStorage()
        console.log("Before return of identities")
        console.log(identities)
        result({"data" : identities,
          "code" : "200",
          "status" : "Identities listed"
        })
      });
    },
    loadIdentity(identityKey, pinCode) {
      return loadIdentityInternal(identityKey, pinCode)
    },
    changeIdentityPinCode(key, oldPinCode, newPinCode) {
      return changeIdentityPinCodeInternal(key, oldPinCode, newPinCode)
    },
    getIdentityProfile(identityKey) {
      return new Penpal.Promise(result => {
        serializedProfile = localStorage.getItem("profiles/" + identityKey)
        if(serializedProfile == null || serializedProfile == "") {
          result({"data" : "",
            "code" : "400",
            "status" : "Profile is empty"
          });
        } else {
          result({"data" : JSON.parse(serializedProfile),
            "code" : "200",
            "status" : "Identities cleared"
          })
        }
      });
    },
    clearIdentities() {
      return new Penpal.Promise(result => {
        var identitiesTemp = listIdentitiesFromLocalStorage()
        //console.log(identitiesTemp.length)
        for(var i in identitiesTemp) {
          destroyIdentityFromLocalStorage(i)
        }
        result({"data" : "",
          "code" : "200",
          "status" : "Identities cleared"
        })
      });
    },
    register(registerIdentity, email, name, surname, family, phoneNumber) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity(registerIdentity.authentication.publicKey)

        executeRestfulFunction("public", viamApi, viamApi.identityRegister, email, name,surname, family, phoneNumber).then(executeResult => {
          console.log("Profile updated in set identity")

          let sequence = Promise.resolve()
          if (executeResult.code === "200") {
            sequence = sequence.then(() => {
                setIdentityInLocalStorage(registerIdentity)
              }
            )
          }
          sequence.then(() => {
            result(executeResult);
          }).catch((e) => {
            result({
              "data": "",
              "code": "400",
              "status": "Can not store identity: " + e
            })
          })
        });
      });
    },
    resendConfirmationCode(identity, identificatorArg) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity(identity.authentication.publicKey)

        executeRestfulFunction("public", viamApi, viamApi.identityResendConfirmationCode,identificatorArg).then(executeResult => {
          result(executeResult);
        });
      });
    },
    login(loginIdentity, mode, code, actionID) {
      return new Penpal.Promise(result => {
        if (loadedIdentities[loginIdentity.authentication.publicKey] === null) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not loaded"
          })
        }

        //console.log("After loaded check")

        viamApi.setIdentity(loginIdentity.authentication.publicKey)

        executeRestfulFunction("public", viamApi, viamApi.identityLogin, mode, code, actionID).then(executeResult => {
          // console.log(executeResult)
          //console.log(mode)
          switch(mode) {
            case "sms" : {
              if (executeResult.code === "200") {
                //console.log("In if")
                var uuid = executeResult.data["Uuid"]
                var token = executeResult.data["Session"]
                viamApi.setSessionData(uuid, token)
                localStorage.setItem("uuid", uuid)
                localStorage.setItem("token", token)
                localStorage.setItem("authenticatedIdentity",
                  loginIdentity.authentication.publicKey)
                setKeyForUUID(uuid, loginIdentity.authentication.publicKey)
                currentlyAuthenticatedIdentity = loadedIdentities[loginIdentity.authentication.publicKey]
                lastTimeGetProfile = 0;
                delete executeResult.data["Uuid"]
                delete executeResult.data["Session"]
                getProfileData(loginIdentity).then(executeResult1 => {
                  result(executeResult);
                });
              } else {
                //console.log("In else")
                result(executeResult);
              }

              break;
            }

            case "previousaddeddevice" : {
              if (executeResult.code === "200") {
                //console.log(response.data.data)
                var uuid = executeResult.data["Uuid"]
                var token = executeResult.data["Session"]
                //console.log(uuid + " " + token)
                viamApi.setSessionData(uuid, token)
                localStorage.setItem("uuid", uuid)
                localStorage.setItem("token", token)
                localStorage.setItem("authenticatedIdentity",
                  loginIdentity.authentication.publicKey)
                setKeyForUUID(uuid, loginIdentity.authentication.publicKey)
                currentlyAuthenticatedIdentity = loadedIdentities[loginIdentity.authentication.publicKey]
                lastTimeGetProfile = 0;
                delete executeResult.data["Uuid"]
                delete executeResult.data["Session"]
                getProfileData(loginIdentity).then(executeResult1 => {
                  result(executeResult);
                });
              } else {
                result(executeResult);
              }

              break;
            }

            case "newdevice" : {
              if (executeResult.code === "200") {
                //console.log(executeResult)
                var actionID = executeResult.data["ActionID"]
                var QrCode = executeResult.data["QrCode"]
                //console.log(uuid + " " + token)
                QRCode.toDataURL(actionID + "," + QrCode, function (err, url) {
                  executeResult.data["image"] = url
                  //console.log(executeResult)
                  result(executeResult);
                })
              } else {
                //console.log(executeResult)
                result(executeResult);
              }
              break;
            }

            default : {
              result(executeResult);
              break;
            }
          }
        });
      });
    },
    identityAddNewDevice() {
      return new Penpal.Promise(result => {
        authenticationPublicKey = localStorage.getItem("authenticatedIdentity")

        if (authenticationPublicKey === null) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        if (loadedIdentities[authenticationPublicKey] === null) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        var success = extendPinCodeTtl(authenticationPublicKey)

        if(success == false) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        executeRestfulFunction("private", viamApi, viamApi.identityAddNewDevice).then(executeResult => {
          if (executeResult.code == "200") {
            //console.log(response.data.data)
            var actionID = executeResult.data["ActionID"]
            var QrCode = executeResult.data["QrCode"]
            //console.log(uuid + " " + token)
            QRCode.toDataURL(actionID + "," + QrCode, function (err, url) {
              executeResult.data["image"] = url
              result(executeResult);
            })
          } else {
            result(executeResult);
          }
        });
      });
    },
    identityDestroyKeysForDevice(authenticationPublicKeyArg) {
      return new Penpal.Promise(result => {
        authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
        if (authenticationPublicKey === null) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }
        if (loadedIdentities[authenticationPublicKey] === null) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        var success = extendPinCodeTtl(authenticationPublicKey)

        if(success == false) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        executeRestfulFunction("private", viamApi, viamApi.identityDestroyKeysForDevice, btoa(authenticationPublicKeyArg)).then(executeResult => {
          result(executeResult);
        });
      });
    },
    logout() {
      authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
      if (authenticationPublicKey === null) {
        return {"data" : "",
          "code" : "400",
          "status" : "Identity not loaded"
        }
      }
      if (loadedIdentities[authenticationPublicKey] === null) {
        return {"data" : "",
          "code" : "400",
          "status" : "Identity not loaded"
        }
      }

      return new Penpal.Promise(result => {
        executeRestfulFunction("private", viamApi, viamApi.identityLogout).then(executeResult => {
          viamApi.setIdentity("")
          viamApi.setSessionData("", "")
          clearPinCodeTtl(authenticationPublicKey)

          authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
          localStorage.removeItem("uuid")
          localStorage.removeItem("token")
          localStorage.removeItem("authenticatedIdentity")
          delete loadedIdentities[authenticationPublicKey]
          //console.log("Set loaded identity in logout")
          currentlyLoadedIdentity = null
          currentlyAuthenticatedIdentity = null
          lastTimeGetProfile = 0;

          result(executeResult);
        });
      });
    },
    identityRestoreAccess(restoreAccessIdentity, identificator) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity(restoreAccessIdentity.authentication.publicKey)

        executeRestfulFunction("public", viamApi, viamApi.identityRestoreAccess, identificator).then(executeResult => {
          if (executeResult.code === "200") {
            setIdentityInLocalStorage(restoreAccessIdentity)
            result(executeResult);
          } else {
            result(executeResult);
          }
        });
      });
    },
    getCurrentlyLoggedInUUID() {
      return new Penpal.Promise(result => {
        authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
        if (authenticationPublicKey === null) {
          return {"data" : "",
            "code" : "400",
            "status" : "Identity not loaded"
          }
        }
        if (loadedIdentities[authenticationPublicKey] === null) {
          return {"data" : "",
            "code" : "400",
            "status" : "Identity not loaded"
          }
        }

        var success = extendPinCodeTtl(authenticationPublicKey)

        if(success == false) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        if(localStorage.getItem("uuid") === null) {
          result({"data" : "",
            "code" : "400",
            "status" : "Not logged in UUID"
          })
        }
        result({"data" : localStorage.getItem("uuid"),
          "code" : "200",
          "status" : "UUID loaded"
        })
      });
    },
    getCertificateByPassport(passportUUID) {
      return new Penpal.Promise(result => {
        authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
        if (authenticationPublicKey === null) {
          return {"data" : "",
            "code" : "400",
            "status" : "Identity not loaded"
          }
        }
        if (loadedIdentities[authenticationPublicKey] === null) {
          return {"data" : "",
            "code" : "400",
            "status" : "Identity not loaded"
          }
        }

        var success = extendPinCodeTtl(authenticationPublicKey)

        if(success == false) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        getCertificateForPassport(passportUUID, false).then(certificateResult => {
          //console.log(certificateResult)
          result(certificateResult)
        })
      });
    },
    getOneTimeCertificateByPassport(passportUUID, emailArg) {
      return new Penpal.Promise(result => {
        authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
        if (authenticationPublicKey === null) {
          return {"data" : "",
            "code" : "400",
            "status" : "Identity not loaded"
          }
        }
        if (loadedIdentities[authenticationPublicKey] === null) {
          return {"data" : "",
            "code" : "400",
            "status" : "Identity not loaded"
          }
        }

        var success = extendPinCodeTtl(authenticationPublicKey)

        if(success == false) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        getCertificateForPassport(passportUUID, true).then(certificateResult => {
          //console.log(certificateResult)
          if(certificateResult.code == "200") {
            var passportCertificate = certificateResult.data["x509Certificate"]
            var passportPrivateKey = certificateResult.data["privateKey"]
            var passportChain = certificateResult.data["chain"]

            createOneTimePassportCertificate(makeid() + "-" + passportUUID, emailArg, passportPrivateKey, passportCertificate).then(function(keys){
              var publicKeyOneTime = keys["publicKeyPEM"]
              var privateKeyOneTime = keys["privateKeyPEM"]
              var certificateOneTime = keys["certificatePEM"]
              passportChain.push(passportCertificate)

              var oneTimeCryptoData = new CryptoData();
              oneTimeCryptoData.setx509Certificate(certificateOneTime)
              oneTimeCryptoData.setPrivateKey(privateKeyOneTime)
              oneTimeCryptoData.setPublicKey(publicKeyOneTime)
              oneTimeCryptoData.setChain(passportChain)

              result({"data" : oneTimeCryptoData,
                "code" : "200",
                "status" : "One time certificate generated"
              })
              // Prints PEM formatted signed certificate
              // -----BEGIN CERTIFICATE-----MIID....7Hyg==-----END CERTIFICATE-----

            });
          } else {
            result({"data" : "",
              "code" : "400",
              "status" : "Can not generate one time certificate"
            })
          }
        })
      });
    },
    signEmail(passportUUID, emailArg, emailMessage) {
      return new Penpal.Promise(result => {
        authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
        if (authenticationPublicKey === null) {
          return {"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          }
        }
        if (loadedIdentities[authenticationPublicKey] === null) {
          return {"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          }
        }

        var success = extendPinCodeTtl(authenticationPublicKey)

        if(success == false) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        getCertificateForPassport(passportUUID, true).then(certificateResult => {
          console.log("Certificate for passport")
          console.log(certificateResult)
          if(certificateResult.code == "200") {
            var passportCertificate = certificateResult.data["x509Certificate"]
            var passportPrivateKey = certificateResult.data["privateKey"]
            var passportChain = certificateResult.data["chain"]

            createOneTimePassportCertificate(makeid() + "-" + passportUUID, emailArg, passportPrivateKey, passportCertificate).then(function(keys){
              var publicKeyOneTime = keys["publicKeyPEM"]
              var privateKeyOneTime = keys["privateKeyPEM"]
              var certificateOneTime = keys["certificatePEM"]
              //download("certificateOneTime.crt", "text/plain", certificateOneTime)

              passportChain.push(passportCertificate)

              //console.log("Before sign email")
              //console.log(certificateOneTime)
              //console.log(passportChain)
              //console.log(privateKeyOneTime)

              executeRestfulFunction("private", viamApi, viamApi.passportGetEmailWithHeaderByPassport,
                passportUUID, window.btoa(emailMessage)).then(executeResult2 => {
                var emailWithHeader = window.atob(executeResult2.data)
                //console.log(emailWithHeader)
                //download("withheader.eml", "message/rfc822", emailWithHeader)
                var signedEmailPromise = signEmail(emailWithHeader,
                  certificateOneTime,
                  passportChain,
                  privateKeyOneTime)

                signedEmailPromise.then(signedEmail => {
                  executeRestfulFunction("private", viamApi, viamApi.signResignEmail,
                    passportUUID, window.btoa(signedEmail)).then(executeResult => {
                    result({"data" : window.atob(executeResult.data),
                      "code" : "200",
                      "status" : "Email signed"
                    })
                  });
                  /*result({"data" : signedEmail,
                      "code" : "200",
                      "status" : "Email signed"
                  })*/
                });
              });
              // Prints PEM formatted signed certificate
              // -----BEGIN CERTIFICATE-----MIID....7Hyg==-----END CERTIFICATE-----

            });
          } else {
            result({"data" : "",
              "code" : "400",
              "status" : "Can not sign email"
            })
          }
        })
      });
    },
    hasSession() {
      return new Penpal.Promise(result => {
        authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
        if (authenticationPublicKey === null) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          });
        }
        if (loadedIdentities[authenticationPublicKey] === null) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          });
        }

        var success = extendPinCodeTtl(authenticationPublicKey)

        if(success == false) {
          result({"data" : "",
            "code" : "400",
            "status" : "Identity not authenticated"
          })
        }

        executeRestfulFunction("private", viamApi, viamApi.identityHasSession).then(executeResult => {
          result(executeResult);
        });
      });
    },
    marketingSignUpIdentificator(identificator, reference) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity("marketingapppublickey")

        executeRestfulFunction("public", viamApi, viamApi.marketingSignUpIdentificator, identificator, reference).then(executeResult => {
          viamApi.setIdentity("")
          viamApi.setSessionData("", "")
          result(executeResult);
        });
      });
    },
    marketingGetIdentificatorProfile(identificator, pincode) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity("marketingapppublickey")

        executeRestfulFunction("public", viamApi, viamApi.marketingGetIdentificatorProfile, identificator, pincode).then(executeResult => {
          viamApi.setIdentity("")
          viamApi.setSessionData("", "")
          result(executeResult);
        });
      });
    },
    marketingЕxecuteEventForIdentificator(identificator, pincode, event) {
      return new Penpal.Promise(result => {
        viamApi.setIdentity("marketingapppublickey")

        executeRestfulFunction("public", viamApi, viamApi.marketingExecuteEventForIdentificator, identificator, pincode, event).then(executeResult => {
          viamApi.setIdentity("")
          viamApi.setSessionData("", "")
          result(executeResult);
        });
      });
    },
    getCurrentlyAuthenticatedIdentity() {
      return new Penpal.Promise(result => {
        result({"data" : currentlyAuthenticatedIdentity,
          "code" : "200",
          "status" : "Currently authenticated identity"
        })
      });
    },
    //{{methods}}
  }
});

connection.promise.then(parent => {
  var identities = localStorage.getItem("identities")

  console.log("Library loaded at: " + new Date().toISOString())

  if (identities === "" || identities === null) {
    //console.log("Setting up empty version")
    localStorage.setItem("identities", JSON.stringify({}))
  }

  if (localStorage.getItem("uuid") === null || localStorage.getItem("token") === null
    || localStorage.getItem("authenticatedIdentity") === null) {
    localStorage.removeItem("uuid")
    localStorage.removeItem("token")
    localStorage.removeItem("authenticatedIdentity")
  } else {
    authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
    var pinCode = getPincode(authenticationPublicKey)

    if(pinCode == "" || pinCode == null) {
      loadIdentityInternal(authenticationPublicKey, "00000000").then(result => {
        if(result.code != "200") {
          console.log(result)
          //var event = createEvent("CanNotLoadIdentity", "ErrorDuringLoadingIdentity", [authenticationPublicKey])
          //parent.onEvent(event)
          var event = createEvent("CanNotGetPincodeForAuthenticatedIdentity", "IdentityNotLoaded", [authenticationPublicKey])
          parent.onEvent(event)
        }
      });
      //var event = createEvent("CanNotGetPincodeForAuthenticatedIdentity", "IdentityNotLoaded", [authenticationPublicKey])
      //parent.onEvent(event)
    } else {
      loadIdentityInternal(authenticationPublicKey, pinCode).then(result => {
        if(result.code != "200") {
          var event = createEvent("CanNotLoadIdentity", "ErrorDuringLoadingIdentity", [authenticationPublicKey])
          parent.onEvent(event)
        }
      });
    }
  }

  var anynomousDeviceKeyEventsProcessing = false
  var maxDeviceKeyAnonymousEventTime = 0

  var eventsDeviceEventsProcessing = false
  var maxDeviceKeyEventTime = 0

  var eventsEntityEventsProcessing = false
  var maxEntityEventTime = 0

  var identityLoadedEvent = false
  var identityAuthenticatedEvent = false

  setInterval(function() {
    if(currentlyAuthenticatedIdentity != null) {
      var pinCode = getPincode(currentlyAuthenticatedIdentity.authentication.publicKey)
      if(pinCode != null && pinCode != "") {
        getIdentityFromLocalStorage(currentlyAuthenticatedIdentity.authentication.publicKey,
          pinCode, false).then((gotIdentity) => {
          currentlyAuthenticatedIdentity = gotIdentity
          //console.log("Set loaded identity in pincode check interval")
          currentlyLoadedIdentity = gotIdentity
          if(identityAuthenticatedEvent === false && gotIdentity != null) {
            var event = createEvent("IdentityAuthenticated", "Authenticated", [gotIdentity.authentication.publicKey])
            parent.onEvent(event)
            identityAuthenticatedEvent = true
          }
        })
      } else {
        authenticationPublicKey = localStorage.getItem("authenticatedIdentity")

        if(authenticationPublicKey != null && authenticationPublicKey != "") {
          /*var event = createEvent("CanNotLoadPincodeForAuthenticatedIdentity", "IdentityNotLoaded", [currentlyAuthenticatedIdentity.authentication.publicKey])
          parent.onEvent(event)
          clearPinCodeTtl(authenticationPublicKey)
          currentlyAuthenticatedIdentity = null*/
          loadIdentityInternal(authenticationPublicKey, "00000000").then(result => {
            if(result.code != "200") {
              console.log(result)
              var event = createEvent("CanNotGetPincodeForAuthenticatedIdentity", "IdentityNotLoaded", [authenticationPublicKey])
              parent.onEvent(event)
              clearPinCodeTtl(authenticationPublicKey)
              currentlyAuthenticatedIdentity = null
            }
          });
        }

        identityAuthenticatedEvent = false
        //console.log("Set loaded identity in cycle for not authenticated value")
        currentlyLoadedIdentity = null
      }
    }

    if(currentlyLoadedIdentity != null) {
      var pinCode = getPincode(currentlyLoadedIdentity.authentication.publicKey)
      if(pinCode == "" || pinCode == null) {
        if(identityLoadedEvent === false) {
          /*var event = createEvent("CanNotLoadPincodeForLoadedIdentity", "IdentityNotLoaded", [currentlyLoadedIdentity.authentication.publicKey])
          parent.onEvent(event)
          identityLoadedEvent = true*/
          loadIdentityInternal(currentlyLoadedIdentity.authentication.publicKey, "00000000").then(result => {
            if(result.code != "200") {
              var event = createEvent("CanNotLoadPincodeForLoadedIdentity", "IdentityNotLoaded", [currentlyLoadedIdentity.authentication.publicKey])
              parent.onEvent(event)
              identityLoadedEvent = true
            }
          });
        }
      } else {
        identityLoadedEvent = false
      }
    }


    if (currentlyAuthenticatedIdentity != null) {
      var now = new Date().getTime()
      if(now - lastTimeGetProfile > 30000) {
        var identityToStore = currentlyAuthenticatedIdentity
        getProfileData(identityToStore).then(executeResult => {
          console.log("Profile updated in cycle")
          console.log(executeResult)
        });
        lastTimeGetProfile = now
      }
    }
  }, 50)

  setInterval(function() {

    if (currentlyLoadedIdentity != null && anynomousDeviceKeyEventsProcessing == false) {
      anynomousDeviceKeyEventsProcessing = true
      executeRestfulFunction("public", viamAnonymousApi, viamAnonymousApi.eventGetNewEventsWithoutSession, "devicekey").then(executeResult => {
        if(executeResult.code == "200") {
          var eventsLen = executeResult.data.length
          changedMaxDeviceKeyAnonymousEventTime = false
          for(var i = 0; i < eventsLen; i++) {
            var event = executeResult.data[i]
            //console.log("Received event anynomous: " + event)
            switch(event.type) {
              case "Authenticated" : {
                console.log("Sending authenticated event")
                var uuid = event.payloads[0]
                var token = event.payloads[1]
                viamApi.setSessionData(uuid, token)
                localStorage.setItem("uuid", uuid)
                localStorage.setItem("token", token)
                localStorage.setItem("authenticatedIdentity",
                  currentlyLoadedIdentity.authentication.publicKey)
                setKeyForUUID(uuid, currentlyLoadedIdentity.authentication.publicKey)

                currentlyAuthenticatedIdentity = currentlyLoadedIdentity
                lastTimeGetProfile = 0;

                var identityToStore = currentlyAuthenticatedIdentity
                event.payloads = []
                console.log(identityToStore)
                setIdentityInLocalStorage(identityToStore).then(() => {
                  console.log(identityToStore)
                  getProfileData(identityToStore).then(executeResult2 => {
                    console.log("Profile updated in setInterval function")
                    console.log(executeResult2)
                    parent.onEvent(event)
                  });
                });
                break;
              }

              case "QRCodeUpdated" : {
                var actionID = event["actionID"]
                var QrCode = event["payloads"][1]

                var eventCopy = JSON.parse(JSON.stringify(event))

                QRCode.toDataURL(actionID + "," + QrCode, function (err, url) {
                  eventCopy["payloads"].push(url)
                  parent.onEvent(eventCopy)
                })
                break
              }

              case "KeyDeleted" : {
                authenticationPublicKey = localStorage.getItem("authenticatedIdentity")
                clearPinCodeTtl(authenticationPublicKey)
                localStorage.removeItem("uuid")
                localStorage.removeItem("token")
                localStorage.removeItem("authenticatedIdentity")
                delete loadedIdentities[authenticationPublicKey]
                //console.log("Set loaded identity in key deleted")
                currentlyLoadedIdentity = null
                currentlyAuthenticatedIdentity = null
                lastTimeGetProfile = 0;

                destroyIdentityFromLocalStorage(authenticationPublicKey)
                break
              }

              default : {
                parent.onEvent(event)
              }
            }
            changedMaxDeviceKeyAnonymousEventTime = true
            maxDeviceKeyAnonymousEventTime = Math.max(maxDeviceKeyAnonymousEventTime, event.stamp)
          }

          if(changedMaxDeviceKeyAnonymousEventTime) {
            //console.log("Updating max time anonymous device with " + maxDeviceKeyAnonymousEventTime )
            executeRestfulFunction("public", viamAnonymousApi, viamAnonymousApi.eventUpdateLastViewedWithoutSession,
              "devicekey", maxDeviceKeyAnonymousEventTime.toString()).then(executeResult1 => {
              anynomousDeviceKeyEventsProcessing = false
            })
          } else {
            anynomousDeviceKeyEventsProcessing = false
          }
        } else {
          anynomousDeviceKeyEventsProcessing = false
          //console.log("Error during query anynomous device")
        }
      });
    }

    /*if(currentlyAuthenticatedIdentity === null) {
        console.log("Currently authenticated identity is null")
    }*/

    if (currentlyAuthenticatedIdentity != null && eventsDeviceEventsProcessing == false) {
      eventsDeviceEventsProcessing = true
      executeRestfulFunction("private", viamApi, viamApi.eventGetNewEvents, "devicekey").then(executeResult => {
        if(executeResult.code == "200") {
          var eventsLen = executeResult.data.length
          changedMaxDeviceKeyEventTime = false
          for(var i = 0; i < eventsLen; i++) {
            var event = executeResult.data[i]
            //console.log("Received event device key: " + event)
            if(event.type == "QRCodeUpdated") {
              var actionID = event["actionID"]
              var QrCode = event["payloads"][1]

              var eventCopy = JSON.parse(JSON.stringify(event))

              QRCode.toDataURL(actionID + "," + QrCode, function (err, url) {
                eventCopy["payloads"].push(url)
                parent.onEvent(eventCopy)
              })
            } else {
              parent.onEvent(event)
            }
            maxDeviceKeyEventTime = Math.max(maxDeviceKeyEventTime, event.stamp)
          }
          if(changedMaxDeviceKeyEventTime) {
            //console.log("Updating max time authenticated device")
            executeRestfulFunction("private", viamApi, viamApi.eventUpdateLastViewed, "devicekey",
              maxDeviceKeyEventTime.toString()).then(executeResult1 => {
              eventsDeviceEventsProcessing = false
            })
          } else {
            eventsDeviceEventsProcessing = false
          }
        } else {
          //console.log("Error during query device")
          eventsDeviceEventsProcessing = false
        }
      });
    }

    if (currentlyAuthenticatedIdentity != null && eventsEntityEventsProcessing == false) {
      eventsEntityEventsProcessing = true
      executeRestfulFunction("private", viamApi, viamApi.eventGetNewEvents, "entity").then(executeResult => {
        if(executeResult.code == "200") {
          var eventsLen = executeResult.data.length
          changedMaxEntityEventTime = false
          for(var i = 0; i < eventsLen; i++) {
            event = executeResult.data[i]
            if(event.type == "QRCodeUpdated") {
              var actionID = event["actionID"]
              var QrCode = event["payloads"][1]

              var eventCopy = JSON.parse(JSON.stringify(event))

              QRCode.toDataURL(actionID + "," + QrCode, function (err, url) {
                eventCopy["payloads"].push(url)
                parent.onEvent(eventCopy)
              })

              continue
            }

            //console.log("Received event entity: " + event)
            parent.onEvent(event)
            changedMaxEntityEventTime = true
            //console.log(maxEntityEventTime + " " + event.stamp)
            maxEntityEventTime = Math.max(maxEntityEventTime, event.stamp)
          }
          if(changedMaxEntityEventTime) {
            //console.log("Updating max time entity" + maxEntityEventTime)
            executeRestfulFunction("private", viamApi, viamApi.eventUpdateLastViewed, "entity",
              maxEntityEventTime.toString()).then(executeResult1 => {
              eventsEntityEventsProcessing = false
            })
          } else {
            eventsEntityEventsProcessing = false
          }
        } else {
          //console.log("Error during query entity")
          eventsEntityEventsProcessing = false
        }
      });
    }
  }, 1000);
});