/*!
 * sigcache.js - signature cache for bcoin
 * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
 * https://github.com/bcoin-org/bcoin
 */

'use strict';

const assert = require('assert');
const util = require('../utils/util');
const secp256k1 = require('../crypto/secp256k1');

/**
 * Signature cache.
 * @alias module:script.SigCache
 * @constructor
 * @param {Number} [size=10000]
 * @property {Number} size
 * @property {Hash[]} keys
 * @property {Object} valid
 */

function SigCache(size) {
  if (!(this instanceof SigCache))
    return new SigCache(size);

  if (size == null)
    size = 10000;

  assert(util.isU32(size));

  this.size = size;
  this.keys = [];
  this.valid = new Map();
}

/**
 * Resize the sigcache.
 * @param {Number} size
 */

SigCache.prototype.resize = function resize(size) {
  assert(util.isU32(size));

  this.size = size;
  this.keys.length = 0;
  this.valid.clear();
};

/**
 * Add item to the sigcache.
 * Potentially evict a random member.
 * @param {Hash} hash - Sig hash.
 * @param {Buffer} sig
 * @param {Buffer} key
 */

SigCache.prototype.add = function add(hash, sig, key) {
  if (this.size === 0)
    return;

  this.valid.set(hash, new SigCacheEntry(sig, key));

  if (this.keys.length >= this.size) {
    const i = Math.floor(Math.random() * this.keys.length);
    const k = this.keys[i];
    this.valid.delete(k);
    this.keys[i] = hash;
  } else {
    this.keys.push(hash);
  }
};

/**
 * Test whether the sig exists.
 * @param {Hash} hash - Sig hash.
 * @param {Buffer} sig
 * @param {Buffer} key
 * @returns {Boolean}
 */

SigCache.prototype.has = function has(hash, sig, key) {
  const entry = this.valid.get(hash);

  if (!entry)
    return false;

  return entry.equals(sig, key);
};

/**
 * Verify a signature, testing
 * it against the cache first.
 * @param {Buffer} msg
 * @param {Buffer} sig
 * @param {Buffer} key
 * @returns {Boolean}
 */

SigCache.prototype.verify = function verify(msg, sig, key) {
  if (this.size === 0)
    return secp256k1.verify(msg, sig, key);

  const hash = msg.toString('hex');

  if (this.has(hash, sig, key))
    return true;

  const result = secp256k1.verify(msg, sig, key);

  if (!result)
    return false;

  this.add(hash, sig, key);

  return true;
};

/**
 * Signature cache entry.
 * @constructor
 * @ignore
 * @param {Buffer} sig
 * @param {Buffer} key
 * @property {Buffer} sig
 * @property {Buffer} key
 */

function SigCacheEntry(sig, key) {
  this.sig = Buffer.from(sig);
  this.key = Buffer.from(key);
}

/**
 * Compare an entry to a sig and key.
 * @param {Buffer} sig
 * @param {Buffer} key
 * @returns {Boolean}
 */

SigCacheEntry.prototype.equals = function equals(sig, key) {
  return this.sig.equals(sig) && this.key.equals(key);
};

/*
 * Expose
 */

module.exports = SigCache;
