var async       = require("async");
var extend      = require("extend");

var Amount      = require("ripple-lib").Amount;
var Remote      = require("ripple-lib").Remote;
var Server      = require("./server").Server;
var Transaction = require("ripple-lib").Transaction;

var account_dump = function (remote, account, callback) {
  var self = this;

  async.waterfall([
      function (callback) {
        self.what = "Get latest account_root";

        remote
          .request_ledger_entry('account_root')
          .ledger_hash(remote.ledger_hash())
          .account_root("root")
          .on('success', function (r) {
              //console.log("account_root: %s", JSON.stringify(r, undefined, 2));

              callback();
            })
          .on('error', function(m) {
              console.log("error: %s", m);

              buster.assert(false);
              callback();
            })
          .request();
      },
    ], function (error) {
      callback(error);
    });

  // get closed ledger hash
  // get account root
  // construct a json result
  //
};

/**
 * Helper called by test cases to generate a setUp routine.
 *
 * By default you would call this without options, but it is useful to
 * be able to plug options in during development for quick and easy
 * debugging.
 *
 * @example
 *   buster.testCase("Foobar", {
 *     setUp: testutils.build_setup({verbose: true}),
 *     // ...
 *   });
 *
 * @param opts {Object} These options allow quick-and-dirty test-specific
 *   customizations of your test environment.
 * @param opts.verbose {Bool} Enable all debug output (then cover your ears
 *   and run)
 * @param opts.verbose_ws {Bool} Enable tracing in the Remote class. Prints
 *   websocket traffic.
 * @param opts.verbose_server {Bool} Set the -v option when running rippled.
 * @param opts.no_server {Bool} Don't auto-run rippled.
 * @param host {String} Identifier for the host configuration to be used.
 */
var build_setup = function (opts, host) {
  var config = get_config();

  opts = opts || {};

  // Normalize options
  if (opts.verbose) {
    opts.verbose_ws = true;
    opts.verbose_server = true;
  };

  return function (done) {
    var self = this;
    
    self.compute_fees_amount_for_txs = function(txs) {
        var fee_units = Transaction.fee_units["default"] * txs;
        return self.remote.fee_tx(fee_units);
    };

    self.amount_for = function(options) {
        var reserve = self.remote.reserve(options.ledger_entries || 0);
        var fees = self.compute_fees_amount_for_txs(options.default_transactions || 0)
        return reserve.add(fees)
                      .add(options.extra || 0);
    };

    host = host || config.server_default;

    this.store = this.store || {};

    var data = this.store[host] = this.store[host] || {};

    data.opts = opts;

    async.series([
      function runServerStep(callback) {
        if (opts.no_server) return callback();

        var server_config = extend({}, config.default_server_config,
                                   config.servers[host]);

        data.server = Server
                        .from_config(host, server_config,
                                     !!opts.verbose_server)
                        .on('started', callback)
                        .on('exited', function () {
                            // If know the remote, tell it server is gone.
                            if (self.remote)
                              self.remote.server_fatal();
                          })
                        .start();
      },
      function connectWebsocketStep(callback) {
        self.remote = data.remote =
          Remote
            .from_config(host, !!opts.verbose_ws)
            .once('ledger_closed', callback)
            .connect();
      }
    ], done);
  };
};

/**
 * Generate tearDown routine.
 *
 * @param host {String} Identifier for the host configuration to be used.
 */
var build_teardown = function (host) {
  var config = get_config();

  return function (done) {
    host = host || config.server_default;

    var data = this.store[host];
    var opts = data.opts;

    async.series([
      function disconnectWebsocketStep(callback) {

        data.remote
          .on('disconnected', callback)
          .on('error', function (m) {
              console.log("server error: ", m);
            })
          .connect(false);
      },
      function stopServerStep(callback) {
        if (opts.no_server)
        {
          return callback();
        }

        data.server
          .on('stopped', callback)
          .stop();
      }
    ], done);
  };
};

var create_accounts = function (remote, src, amount, accounts, callback) {
  assert(5 === arguments.length);

  remote.set_account_seq(src, 1);

  async.forEach(accounts, function (account, callback) {
    // Cache the seq as 1.
    // Otherwise, when other operations attempt to opperate async against the account they may get confused.
    remote.set_account_seq(account, 1);

    remote.transaction()
      .payment(src, account, amount)
      .on('proposed', function (m) {
          // console.log("proposed: %s", JSON.stringify(m));

          if (m.result != 'tesSUCCESS') {
            callback(new Error("Payment to create account did not succeed."));
          } else callback(null);
        })
      .on('error', function (m) {
          // console.log("error: %s", JSON.stringify(m));

          callback(m);
        })
      .submit();
    }, callback);
};

var credit_limit = function (remote, src, amount, callback) {
  assert(4 === arguments.length);

  var _m      = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/);
  if (!_m) {
    console.log("credit_limit: parse error: %s", amount);

    callback('parse_error');
  }
  else
  {
    // console.log("credit_limit: parsed: %s", JSON.stringify(_m, undefined, 2));
    var _account_limit  = _m[1];
    var _quality_in     = _m[2];
    var _quality_out    = _m[3];

    remote.transaction()
      .ripple_line_set(src, _account_limit, _quality_in, _quality_out)
      .on('proposed', function (m) {
          // console.log("proposed: %s", JSON.stringify(m));

          callback(m.result != 'tesSUCCESS');
        })
      .on('error', function (m) {
          // console.log("error: %s", JSON.stringify(m));

          callback(m);
        })
      .submit();
  }
};

function get_config() {
  var cfg = require('./config-example');

  // See if the person testing wants to override the configuration by creating a
  // file called test/config.js.
  try {
    cfg = extend({}, cfg, require('./config'));
  } catch (e) { }

  return cfg;
}

function init_config() {
  var cfg = get_config();

  return require('ripple-lib').config.load(cfg);
}

var verify_limit = function (remote, src, amount, callback) {
  assert(4 === arguments.length);

  var _m      = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/);
  if (!_m) {
    // console.log("credit_limit: parse error: %s", amount);

    callback('parse_error');
  }
  else
  {
    // console.log("_m", _m.length, _m);
    // console.log("verify_limit: parsed: %s", JSON.stringify(_m, undefined, 2));
    var _account_limit  = _m[1];
    var _quality_in     = _m[2];
    var _quality_out    = _m[3];

    var _limit          = Amount.from_json(_account_limit);

    remote.request_ripple_balance(src, _limit.issuer().to_json(), _limit.currency().to_json(), 'CURRENT')
      .once('ripple_state', function (m) {
          buster.assert(m.account_limit.equals(_limit));
          buster.assert('undefined' === _quality_in || m.account_quality_in == _quality_in);
          buster.assert('undefined' === _quality_out || m.account_quality_out == _quality_out);

          callback();
        })
      .once('error', function (m) {
          // console.log("error: %s", JSON.stringify(m));

          callback(m);
        })
      .request();
  }
};

var credit_limits = function (remote, balances, callback) {
  assert(3 === arguments.length);

  var limits = [];

  for (var src in balances) {
    var values_src  = balances[src];
    var values      = 'string' === typeof values_src ? [ values_src ] : values_src;

    for (var index in values) {
      limits.push( { "source" : src, "amount" : values[index] } );
    }
  }

  async.every(limits,
    function (limit, callback) {
      credit_limit(remote, limit.source, limit.amount,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

var ledger_close = function (remote, callback) {
  remote.once('ledger_closed', function (m) { callback(); }).ledger_accept();
}

var payment = function (remote, src, dst, amount, callback) {
  assert(5 === arguments.length);

  remote.transaction()
    .payment(src, dst, amount)
    .on('proposed', function (m) {
        // console.log("proposed: %s", JSON.stringify(m));

        callback(m.result != 'tesSUCCESS');
      })
    .on('error', function (m) {
        // console.log("error: %s", JSON.stringify(m));

        callback(m);
      })
    .submit();
};

var payments = function (remote, balances, callback) {
  assert(3 === arguments.length);

  var sends = [];

  for (var src in balances) {
    var values_src  = balances[src];
    var values      = 'string' === typeof values_src ? [ values_src ] : values_src;

    for (var index in values) {
      var amount_json = values[index];
      var amount      = Amount.from_json(amount_json);

      sends.push( { "source" : src, "destination" : amount.issuer().to_json(), "amount" : amount_json } );
    }
  }

  async.every(sends,
    function (send, callback) {
      payment(remote, send.source, send.destination, send.amount,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

var transfer_rate = function (remote, src, billionths, callback) {
  assert(4 === arguments.length);

  remote.transaction()
    .account_set(src)
    .transfer_rate(billionths)
    .on('proposed', function (m) {
        // console.log("proposed: %s", JSON.stringify(m));

        callback(m.result != 'tesSUCCESS');
      })
    .on('error', function (m) {
        // console.log("error: %s", JSON.stringify(m));

        callback(m);
      })
    .submit();
};

var verify_balance = function (remote, src, amount_json, callback) {
  assert(4 === arguments.length);
  var amount_req  = Amount.from_json(amount_json);

  if (amount_req.is_native()) {
    remote.request_account_balance(src, 'CURRENT')
      .once('account_balance', function (amount_act) {
          if (!amount_act.equals(amount_req, true)) {
            console.log("verify_balance: failed: %s / %s",
              amount_act.to_text_full(),
              amount_req.to_text_full());
          }

          callback(!amount_act.equals(amount_req, true));
        })
      .request();
  }
  else {
    remote.request_ripple_balance(src, amount_req.issuer().to_json(), amount_req.currency().to_json(), 'CURRENT')
      .once('ripple_state', function (m) {
        // console.log("BALANCE: %s", JSON.stringify(m));
        // console.log("account_balance: %s", m.account_balance.to_text_full());
        // console.log("account_limit: %s", m.account_limit.to_text_full());
        // console.log("issuer_balance: %s", m.issuer_balance.to_text_full());
        // console.log("issuer_limit: %s", m.issuer_limit.to_text_full());

          var account_balance = Amount.from_json(m.account_balance);

          if (!account_balance.equals(amount_req, true)) {
            console.log("verify_balance: failed: %s vs %s / %s: %s",
                        src,
                        account_balance.to_text_full(),
                        amount_req.to_text_full(),
                        account_balance.not_equals_why(amount_req, true));
          }

          callback(!account_balance.equals(amount_req, true));
        })
      .request();
  }
};

var verify_balances = function (remote, balances, callback) {
  var tests = [];

  for (var src in balances) {
    var values_src  = balances[src];
    var values      = 'string' === typeof values_src ? [ values_src ] : values_src;

    for (var index in values) {
      tests.push( { "source" : src, "amount" : values[index] } );
    }
  }

  async.every(tests,
    function (check, callback) {
      verify_balance(remote, check.source, check.amount,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

// --> owner: account
// --> seq: sequence number of creating transaction.
// --> taker_gets: json amount
// --> taker_pays: json amount
var verify_offer = function (remote, owner, seq, taker_pays, taker_gets, callback) {
  assert(6 === arguments.length);

  remote.request_ledger_entry('offer')
    .offer_id(owner, seq)
    .on('success', function (m) {
        var wrong = !Amount.from_json(m.node.TakerGets).equals(Amount.from_json(taker_gets), true)
          || !Amount.from_json(m.node.TakerPays).equals(Amount.from_json(taker_pays), true);

        if (wrong)
          console.log("verify_offer: failed: %s", JSON.stringify(m));

        callback(wrong);
      })
    .request();
};

var verify_offer_not_found = function (remote, owner, seq, callback) {
  assert(4 === arguments.length);

  remote.request_ledger_entry('offer')
    .offer_id(owner, seq)
    .on('success', function (m) {
        console.log("verify_offer_not_found: found offer: %s", JSON.stringify(m));

        callback('entryFound');
      })
    .on('error', function (m) {
        // console.log("verify_offer_not_found: success: %s", JSON.stringify(m));

        callback('remoteError' !== m.error
          || 'entryNotFound' !== m.remote.error);
      })
    .request();
};

var verify_owner_count = function (remote, account, value, callback) {
  assert(4 === arguments.length);

  remote.request_owner_count(account, 'CURRENT')
    .once('owner_count', function (owner_count) {
        if (owner_count !== value)
          console.log("owner_count: %s/%d", owner_count, value);

        callback(owner_count !== value);
      })
    .request();
};

var verify_owner_counts = function (remote, counts, callback) {
  var tests = [];

  for (var src in counts) {
      tests.push( { "source" : src, "count" : counts[src] } );
  }

  async.every(tests,
    function (check, callback) {
      verify_owner_count(remote, check.source, check.count,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

exports.account_dump            = account_dump;
exports.build_setup             = build_setup;
exports.build_teardown          = build_teardown;
exports.create_accounts         = create_accounts;
exports.credit_limit            = credit_limit;
exports.credit_limits           = credit_limits;
exports.get_config              = get_config;
exports.init_config             = init_config;
exports.ledger_close            = ledger_close;
exports.payment                 = payment;
exports.payments                = payments;
exports.transfer_rate           = transfer_rate;
exports.verify_balance          = verify_balance;
exports.verify_balances         = verify_balances;
exports.verify_limit            = verify_limit;
exports.verify_offer            = verify_offer;
exports.verify_offer_not_found  = verify_offer_not_found;
exports.verify_owner_count      = verify_owner_count;
exports.verify_owner_counts     = verify_owner_counts;

// vim:sw=2:sts=2:ts=8:et
