import { createVarint } from "../varint";
import { hashLeaf, Merkle } from "./merkle";

/**
 * This implements "Merkelized Maps", documented at
 * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/merkle.md#merkleized-maps
 *
 * A merkelized map consist of two merkle trees, one for the keys of
 * a map and one for the values of the same map, thus the two merkle
 * trees have the same shape. The commitment is the number elements
 * in the map followed by the keys' merkle root followed by the
 * values' merkle root.
 */
export class MerkleMap {
  keys: Buffer[];
  keysTree: Merkle;
  values: Buffer[];
  valuesTree: Merkle;
  /**
   * @param keys Sorted list of (unhashed) keys
   * @param values values, in corresponding order as the keys, and of equal length
   */
  constructor(keys: Buffer[], values: Buffer[]) {
    if (keys.length != values.length) {
      throw new Error("keys and values should have the same length");
    }

    // Sanity check: verify that keys are actually sorted and with no duplicates
    for (let i = 0; i < keys.length - 1; i++) {
      if (keys[i].toString("hex") >= keys[i + 1].toString("hex")) {
        throw new Error("keys must be in strictly increasing order");
      }
    }

    this.keys = keys;
    this.keysTree = new Merkle(keys.map(k => hashLeaf(k)));
    this.values = values;
    this.valuesTree = new Merkle(values.map(v => hashLeaf(v)));
  }

  commitment(): Buffer {
    // returns a buffer between 65 and 73 (included) bytes long
    return Buffer.concat([
      createVarint(this.keys.length),
      this.keysTree.getRoot(),
      this.valuesTree.getRoot(),
    ]);
  }
}
