// type Var = {
//   name: String
//   type: "uint256" | "bytes32" | ...
// }
//
// type Method = {
//   name: String
//   inputs: [Var]
//   output: [Var]
//   constant: Bool
//   payable: Bool
// }

const Bytes = require("./bytes");
const Nat = require("./nat");
const keccak256s = require("./hash").keccak256s;

// (type : String), JSType(type) -> {data: Bytes, dynamic: Bool}
//   ABI-encodes a single term.
const encode = (type, value) => {
  if (type === "bytes") {
    const length = Bytes.length(value);
    const nextMul32 = (((length - 1) / 32 | 0) + 1) * 32;
    const lengthEncoded = encode("uint256", Nat.fromNumber(length)).data;
    const bytesEncoded = Bytes.padRight(nextMul32, value);
    return { data: Bytes.concat(lengthEncoded, bytesEncoded), dynamic: true };
  } else if (type === "uint256" || type === "bytes32" || type === "address") {
    return { data: Bytes.pad(32, value), dynamic: false };
  } else {
    throw "Eth-lib can't encode ABI type " + type + " yet.";
  }
};

// (method : Method), [JSType(method.inputs[i].type)] -> Bytes
//   ABI-encodes the transaction data to call a method.
const methodData = (method, params) => {
  const methodSig = method.name + "(" + method.inputs.map(i => i.type).join(",") + ")";
  const methodHash = keccak256s(methodSig).slice(0, 10);
  let encodedParams = params.map((param, i) => encode(method.inputs[i].type, param));
  var headBlock = "0x";
  let dataBlock = "0x";
  for (var i = 0; i < encodedParams.length; ++i) {
    if (encodedParams[i].dynamic) {
      var dataLoc = encodedParams.length * 32 + Bytes.length(dataBlock);
      headBlock = Bytes.concat(headBlock, Bytes.pad(32, Nat.fromNumber(dataLoc)));
      dataBlock = Bytes.concat(dataBlock, encodedParams[i].data);
    } else {
      headBlock = Bytes.concat(headBlock, encodedParams[i].data);
    }
  }
  return Bytes.flatten([methodHash, headBlock, dataBlock]);
};

module.exports = {
  encode,
  methodData
};