const bn128 = require("snarkjs").bn128;
const bigInt = require("snarkjs").bigInt;
const babyJub = require("./babyjub");
const assert = require("assert");
const createBlakeHash = require("blake-hash");

const GENPOINT_PREFIX = "Iden3_PedersenGenerator";
const windowSize = 4;
const nWindowsPerSegment = 50;

exports.hash = pedersenHash;
exports.getBasePoint = getBasePoint;

function pedersenHash(msg) {
    const bitsPerSegment = windowSize*nWindowsPerSegment;
    const bits = buffer2bits(msg);

    const nSegments = Math.floor((bits.length - 1)/(windowSize*nWindowsPerSegment)) +1;

    let accP = [bigInt.zero,bigInt.one];

    for (let s=0; s<nSegments; s++) {
        let nWindows;
        if (s == nSegments-1) {
            nWindows = Math.floor(((bits.length - (nSegments - 1)*bitsPerSegment) - 1) / windowSize) +1;
        } else {
            nWindows = nWindowsPerSegment;
        }
        let escalar = bigInt.zero;
        let exp = bigInt.one;
        for (let w=0; w<nWindows; w++) {
            let o = s*bitsPerSegment + w*windowSize;
            let acc = bigInt.one;
            for (let b=0; ((b<windowSize-1)&&(o<bits.length)) ; b++) {
                if (bits[o]) {
                    acc = acc.add( bigInt.one.shl(b) );
                }
                o++;
            }
            if (o<bits.length) {
                if (bits[o]) {
                    acc = acc.neg();
                }
                o++;
            }
            escalar = escalar.add(acc.mul(exp));
            exp = exp.shl(windowSize+1);
        }

        if (escalar.lesser(bigInt.zero)) {
            escalar = babyJub.subOrder.add(escalar);
        }

        accP = babyJub.addPoint(accP, babyJub.mulPointEscalar(getBasePoint(s), escalar));
    }

    return babyJub.packPoint(accP);
}

let bases = [];

function getBasePoint(pointIdx) {
    if (pointIdx<bases.length) return bases[pointIdx];
    let p= null;
    let tryIdx = 0;
    while (p==null) {
        const S = GENPOINT_PREFIX + "_" + padLeftZeros(pointIdx, 32) + "_" + padLeftZeros(tryIdx, 32);
        const h = createBlakeHash("blake256").update(S).digest();
        h[31] = h[31] & 0xBF;  // Set 255th bit to 0 (256th is the signal and 254th is the last possible bit to 1)
        p = babyJub.unpackPoint(h);
        tryIdx++;
    }

    const p8 = babyJub.mulPointEscalar(p, 8);

    assert(babyJub.inSubgroup(p8), "Point not in curve");

    bases[pointIdx] = p8;
    return p8;
}

function padLeftZeros(idx, n) {
    let sidx = "" + idx;
    while (sidx.length<n) sidx = "0"+sidx;
    return sidx;
}

/*
Input a buffer
Returns an array of booleans. 0 is LSB of first byte and so on.
 */
function buffer2bits(buff) {
    const res = new Array(buff.length*8);
    for (let i=0; i<buff.length; i++) {
        const b = buff[i];
        res[i*8] = b & 0x01;
        res[i*8+1] = b & 0x02;
        res[i*8+2] = b & 0x04;
        res[i*8+3] = b & 0x08;
        res[i*8+4] = b & 0x10;
        res[i*8+5] = b & 0x20;
        res[i*8+6] = b & 0x40;
        res[i*8+7] = b & 0x80;
    }
    return res;
}




