var utils = require('jsr-util')
  , HashMap = require('../type/hash')
  , Constants = require('jsr-constants')
  , OK = Constants.OK
  , slice = Array.prototype.slice;

// TODO: hincrby
// TODO: hincrbyfloat

// TODO: hscan (much later!)

/**
 *  Set the string value of a hash field.
 */
function hset(key, field, value, req) {
  var val = this.getKey(key, req)
    , exists = this.hexists(key, field, req);
  if(!val) {
    val = new HashMap();
    // add to the database
    // must call set to ensure size is correct
    this.setKey(key, val, undefined, undefined, undefined, req);
  }

  // set on the hashmap
  val.setKey(field, value);
  return exists ? 0 : 1;
}

/**
 *  Sets field in the hash stored at key to value, only
 *  if field does not yet exist.
 */
function hsetnx(key, field, value, req) {
  var val = this.getKey(key, req)
    , exists = this.hexists(key, field, req);
  if(!val) {
    val = new HashMap();
    this.setKey(key, val, undefined, undefined, undefined, req);
  }
  if(!exists) {
    // set on the hashmap
    val.setKey(field, value);
  }
  return exists ? 0 : 1;
}

/**
 *  Get the value of a hash field.
 */
function hget(key, field, req) {
  // get at db level
  var val = this.getKey(key, req);
  if(!val) return null;
  // get at hash level
  val = val.getKey(field)
  if(!val) return null;
  return val;
}

/**
 *  Determine if a hash field exists.
 */
function hexists(key, field, req) {
  var val = this.getKey(key, req);
  if(!val) return 0;
  return val.keyExists(field);
}

/**
 *  Get all the fields and values in a hash.
 */
function hgetall(key, req) {
  var val = this.getKey(key, req)
    , list = [];

  if(!val) return list;
  val.getKeys().forEach(function(k) {
    list.push(k, '' + val.getKey(k));
  });
  return list;
}

/**
 *  Get all the fields in a hash.
 */
function hkeys(key, req) {
  var val = this.getKey(key, req);
  if(!val) return [];
  return val.getKeys();
}

/**
 *  Get the number of fields in a hash.
 */
function hlen(key, req) {
  var val = this.getKey(key, req);
  if(!val) return 0;
  return val.hlen();
}

/**
 *  Get all the values in a hash.
 */
function hvals(key, req) {
  var val = this.getKey(key, req)
    , list = [];
  if(!val) return list;
  val.getKeys().forEach(function(k) {
    list.push('' + val.getKey(k));
  });
  return list;
}

/**
 *  Delete one or more hash fields.
 */
function hdel(key /* field-1, field-N, req */) {
  var args = slice.call(arguments, 1)
    , req = typeof args[args.length - 1] === 'object'
        ? args.pop() : null
    , val = this.getKey(key, req)
    , deleted = 0
    , i;

  // nothing at key
  if(!val) return 0;

  for(i = 0;i < args.length;i++) {
    deleted += val.delKey(args[i]);
  }

  if(val.hlen() === 0) {
    this.delKey(key, req);
  }

  return deleted;
}

/**
 *  Returns the values associated with the specified fields in
 *  the hash stored at key.
 */
function hmget(key /* field-1, field-N, req */) {
  var args = slice.call(arguments, 1)
    , req = typeof args[args.length - 1] === 'object'
        ? args.pop() : null
    , val = this.getKey(key, req)
    , value
    , list = []
    , i;
  for(i = 0;i < args.length;i++) {
    if(!val) {
      list.push(null);
      continue;
    }
    value = val.getKey(args[i])
    list.push(value !== undefined ? value : null);
  }
  return list;
}

/**
 *  Sets the specified fields to their respective values in
 *  the hash stored at key.
 */
function hmset(key /* field-1, value-1, field-N, value-N, req */) {
    , req = typeof args[args.length - 1] === 'object'
        ? args.pop() : null
    , val = this.getKey(key, req)
    , i;

  if(!val) {
    val = new HashMap();
    this.setKey(key, val, undefined, undefined, undefined, req);
  }
  for(i = 0;i < args.length;i+=2) {
    val.setKey(args[i], args[i + 1]);
  }
  return OK;
}

/**
 *  Increment the specified field of hash stored at key, and
 *  representing a floating point number, by the specified increment.
 */
function hincrbyfloat(key, field, amount, req) {
  var map = this.getKey(key, req)
    , val;
  if(!map) {
    map = new HashMap();
    this.setKey(key, map, undefined, undefined, undefined, req);
  }
  val = utils.strtofloat(map.getKey(field));
  amount = utils.strtofloat(amount);
  val += amount;
  map.setKey(field, val);
  // store as number and return a number,
  // the command implementation should coerce the result
  // to a string for a bulk string reply
  return val;
}

/**
 *  Increments the number stored at field in the
 *  hash stored at key by increment.
 */
function hincrby(key, field, amount, req) {
  var map = this.getKey(key, req)
    , val;
  if(!map) {
    map = new HashMap();
    this.setKey(key, map, undefined, undefined, undefined, req);
  }
  val = utils.strtoint(map.getKey(field));
  amount = utils.strtoint(amount);
  val += amount;
  map.setKey(field, val);
  return val;
}

module.exports = {
  hset: hset,
  hsetnx: hsetnx,
  hget: hget,
  hexists: hexists,
  hgetall: hgetall,
  hkeys: hkeys,
  hvals: hvals,
  hlen: hlen,
  hdel: hdel,
  hmget: hmget,
  hmset: hmset,
  hincrby: hincrby,
  hincrbyfloat: hincrbyfloat,
}
  var args = slice.call(arguments, 1)