text: const bech32CharValues = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
  
  module.exports = {
    decode: function(paymentRequest) {
    	  let input = paymentRequest.toLowerCase();
        let splitPosition = input.lastIndexOf('1');
        let humanReadablePart = input.substring(0, splitPosition);
        let data = input.substring(splitPosition + 1, input.length - 6);
        let checksum = input.substring(input.length - 6, input.length);
  
        if (!this.verify_checksum(humanReadablePart, this.bech32ToFiveBitArray(data + checksum))) {
            return 'error';
        }
  
        return {
            'human_readable_part': this.decodeHumanReadablePart(humanReadablePart),
            'data': this.decodeData(data, humanReadablePart),
            'checksum': checksum
        }
    },
  
    decodeHumanReadablePart: function(humanReadablePart) {
        let prefixes = ['lnbc', 'lntb', 'lnbcrt'];
        let prefix;
        prefixes.forEach(value => {
            if (humanReadablePart.substring(0, value.length) === value) {
                prefix = value;
            }
        });
        if (prefix == null) return 'error'; // A reader MUST fail if it does not understand the prefix.
        let amount = this.decodeAmount(humanReadablePart.substring(prefix.length, humanReadablePart.length));
  
        return {
            'prefix': prefix,
            'amount': amount
        }
    },
  
    decodeData: function(data, humanReadablePart) {
        let date32 = data.substring(0, 7);
        let dateEpoch = this.bech32ToInt(date32);
        let signature = data.substring(data.length - 104, data.length);
        let tagData = data.substring(7, data.length - 104);
        let decodedTags = this.decodeTags(tagData);
        let value = this.bech32ToFiveBitArray(date32 + tagData);
        value = this.fiveBitArrayTo8BitArray(value, true);
        value = this.textToHexString(humanReadablePart).concat(this.byteArrayToHexString(value));
  
    		return {
            'time_stamp': dateEpoch,
            'tags': decodedTags,
            'signature': this.decodeSignature(signature),
            'signing_data': value
        }
    },
  
    decodeSignature: function(signature) {
        let data = this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(signature));
        let recoveryFlag = data[data.length - 1];
        let r = this.byteArrayToHexString(data.slice(0, 32));
        let s = this.byteArrayToHexString(data.slice(32, data.length - 1));
        return {
            'r': r,
            's': s,
            'recovery_flag': recoveryFlag
        }
    },
  
    decodeAmount: function(str) {
        let multiplier = str.charAt(str.length - 1);
        let amount = str.substring(0, str.length - 1);
        if (amount.substring(0, 1) === '0') {
            return 'error';
        }
        amount = Number(amount);
        if (amount < 0 || !Number.isInteger(amount)) {
            return 'error';
        }
  
        switch (multiplier) {
            case '':
                return 'Any amount'; // A reader SHOULD indicate if amount is unspecified
            case 'p':
                return amount / 10;
            case 'n':
                return amount * 100;
            case 'u':
                return amount * 100000;
            case 'm':
                return amount * 100000000;
            default:
                // A reader SHOULD fail if amount is followed by anything except a defined multiplier.
                return 'error';
        }
    },
  
    decodeTags: function(tagData) {
        let tags = this.extractTags(tagData);
        let decodedTags = [];
        tags.forEach(value => decodedTags.push(this.decodeTag(value.type, value.length, value.data)));
        return decodedTags;
    },
  
    extractTags: function(str) {
        let tags = [];
        while (str.length > 0) {
            let type = str.charAt(0);
            let dataLength = this.bech32ToInt(str.substring(1, 3));
            let data = str.substring(3, dataLength + 3);
            tags.push({
                'type': type,
                'length': dataLength,
                'data': data
            });
            str = str.substring(3 + dataLength, str.length);
        }
        return tags;
    },
  
    decodeTag: function(type, length, data) {
        switch (type) {
            case 'p':
                if (length !== 52) break; // A reader MUST skip over a 'p' field that does not have data_length 52
                return {
                    'type': type,
                    'length': length,
                    'description': 'payment_hash',
                    'value': this.byteArrayToHexString(this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(data)))
                };
            case 'd':
                return {
                    'type': type,
                    'length': length,
                    'description': 'description',
                    'value': this.bech32ToUTF8String(data)
                };
            case 'n':
                if (length !== 53) break; // A reader MUST skip over a 'n' field that does not have data_length 53
                return {
                    'type': type,
                    'length': length,
                    'description': 'payee_public_key',
                    'value': this.byteArrayToHexString(this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(data)))
                };
            case 'h':
                if (length !== 52) break; // A reader MUST skip over a 'h' field that does not have data_length 52
                return {
                    'type': type,
                    'length': length,
                    'description': 'description_hash',
                    'value': data
                };
            case 'x':
                return {
                    'type': type,
                    'length': length,
                    'description': 'expiry',
                    'value': this.bech32ToInt(data)
                };
            case 'c':
                return {
                    'type': type,
                    'length': length,
                    'description': 'min_final_cltv_expiry',
                    'value': this.bech32ToInt(data)
                };
            case 'f':
                let version = this.bech32ToFiveBitArray(data.charAt(0))[0];
                if (version < 0 || version > 18) break; // a reader MUST skip over an f field with unknown version.
                data = data.substring(1, data.length);
                return {
                    'type': type,
                    'length': length,
                    'description': 'fallback_address',
                    'value': {
                        'version': version,
                        'fallback_address': data
                    }
                };
            case 'r':
                data = this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(data));
                let pubkey = data.slice(0, 33);
                let shortChannelId = data.slice(33, 41);
                let feeBaseMsat = data.slice(41, 45);
                let feeProportionalMillionths = data.slice(45, 49);
                let cltvExpiryDelta = data.slice(49, 51);
                return {
                    'type': type,
                    'length': length,
                    'description': 'routing_information',
                    'value': {
                        'public_key': this.byteArrayToHexString(pubkey),
                        'short_channel_id': this.byteArrayToHexString(shortChannelId),
                        'fee_base_msat': this.byteArrayToInt(feeBaseMsat),
                        'fee_proportional_millionths': this.byteArrayToInt(feeProportionalMillionths),
                        'cltv_expiry_delta': this.byteArrayToInt(cltvExpiryDelta)
                    }
                };
            default:
            // reader MUST skip over unknown fields
        }
    },
  
    polymod: function(values) {
        let GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
        let chk = 1;
        values.forEach((value) => {
            let b = (chk >> 25);
            chk = (chk & 0x1ffffff) << 5 ^ value;
            for (let i = 0; i < 5; i++) {
                if (((b >> i) & 1) === 1) {
                    chk ^= GEN[i];
                } else {
                    chk ^= 0;
                }
            }
        });
        return chk;
    },
  
    expand: function(str) {
        let array = [];
        for (let i = 0; i < str.length; i++) {
            array.push(str.charCodeAt(i) >> 5);
        }
        array.push(0);
        for (let i = 0; i < str.length; i++) {
            array.push(str.charCodeAt(i) & 31);
        }
        return array;
    },
  
    verify_checksum: function(hrp, data) {
        hrp = this.expand(hrp);
        let all = hrp.concat(data);
        let bool = this.polymod(all);
        return bool === 1;
    },
  
    byteArrayToInt: function(byteArray) {
        let value = 0;
        for (let i = 0; i < byteArray.length; ++i) {
            value = (value << 8) + byteArray[i];
        }
        return value;
    },
  
    bech32ToInt: function(str) {
        let sum = 0;
        for (let i = 0; i < str.length; i++) {
            sum = sum * 32;
            sum = sum + bech32CharValues.indexOf(str.charAt(i));
        }
        return sum;
    },
  
    bech32ToFiveBitArray: function(str) {
        let array = [];
        for (let i = 0; i < str.length; i++) {
            array.push(bech32CharValues.indexOf(str.charAt(i)));
        }
        return array;
    },
  
    fiveBitArrayTo8BitArray: function(int5Array, includeOverflow) {
        let count = 0;
        let buffer = 0;
        let byteArray = [];
        int5Array.forEach((value) => {
            buffer = (buffer << 5) + value;
            count += 5;
            if (count >= 8) {
                byteArray.push(buffer >> (count - 8) & 255);
                count -= 8;
            }
        });
        if (includeOverflow && count > 0) {
            byteArray.push(buffer << (8 - count) & 255);
        }
        return byteArray;
    },
  
    bech32ToUTF8String: function(str) {
        let int5Array = this.bech32ToFiveBitArray(str);
        let byteArray = this.fiveBitArrayTo8BitArray(int5Array);
  
        let utf8String = '';
        for (let i = 0; i < byteArray.length; i++) {
            utf8String += '%' + ('0' + byteArray[i].toString(16)).slice(-2);
        }
        return decodeURIComponent(utf8String);
    },
  
    byteArrayToHexString: function(byteArray) {
        return Array.prototype.map.call(byteArray, function (byte) {
            return ('0' + (byte & 0xFF).toString(16)).slice(-2);
        }).join('');
    },
  
    textToHexString: function(text) {
        let hexString = '';
        for (let i = 0; i < text.length; i++) {
            hexString += text.charCodeAt(i).toString(16);
        }
        return hexString;
    },
  
    epochToDate: function(int) {
        let date = new Date(int * 1000);
        return date.toUTCString();
    }
  }
  