extern crate time;

use indy::api::ErrorCode;
use indy::api::pool::{indy_create_pool_ledger_config, indy_delete_pool_ledger_config};
#[cfg(feature = "local_nodes_pool")]
use indy::api::pool::{indy_close_pool_ledger, indy_open_pool_ledger, indy_refresh_pool_ledger};
use indy::api::ledger::indy_submit_request;

use utils::callback::CallbackUtils;
use utils::environment::EnvironmentUtils;
use utils::timeout::TimeoutUtils;

use std::fs;
use std::ffi::CString;
use std::io::Write;
#[cfg(feature = "local_nodes_pool")]
use std::ptr::null;
use std::path::PathBuf;
use std::sync::mpsc::channel;

pub struct PoolUtils {}

impl PoolUtils {
    pub fn create_pool_ledger_config(pool_name: &str, nodes: Option<String>, pool_config: Option<String>, gen_txn_file_name: Option<&str>) -> Result<(), ErrorCode> {
        let (sender, receiver) = channel();


        let cb = Box::new(move |err| {
            sender.send(err).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_create_pool_ledger_cb(cb);

        PoolUtils::create_genesis_txn_file(gen_txn_file_name.unwrap_or(format!("{}.txn", pool_name).as_str()), nodes);
        let pool_config = pool_config.unwrap_or(PoolUtils::create_default_pool_config(pool_name));
        let pool_config = CString::new(pool_config).unwrap();
        let pool_name = CString::new(pool_name).unwrap();

        let err = indy_create_pool_ledger_config(command_handle,
                                                   pool_name.as_ptr(),
                                                   pool_config.as_ptr(),
                                                   cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let err = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(())
    }

    #[cfg(feature = "local_nodes_pool")]
    pub fn open_pool_ledger(pool_name: &str, config: Option<&str>) -> Result<i32, ErrorCode> {
        let (sender, receiver) = channel();


        let cb = Box::new(move |err, pool_handle| {
            sender.send((err, pool_handle)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_open_pool_ledger_cb(cb);

        let pool_name = CString::new(pool_name).unwrap();
        let config_str = config.map(|s| CString::new(s).unwrap()).unwrap_or(CString::new("").unwrap());

        let err = indy_open_pool_ledger(command_handle,
                                          pool_name.as_ptr(),
                                          if config.is_some() { config_str.as_ptr() } else { null() },
                                          cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, pool_handle) = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(pool_handle)
    }

    pub fn create_and_open_pool_ledger_config(pool_name: &str) -> Result<i32, ErrorCode> {
        PoolUtils::create_pool_ledger_config(pool_name, None, None, None)?;
        PoolUtils::open_pool_ledger(pool_name, None)
    }

    pub fn refresh(pool_handle: i32) -> Result<(), ErrorCode> {
        let (sender, receiver) = channel();
        let (command_handle, cb) = CallbackUtils::closure_to_refresh_pool_ledger_cb(
            Box::new(move |res| sender.send(res).unwrap()));

        let res = indy_refresh_pool_ledger(command_handle, pool_handle, cb);
        if res != ErrorCode::Success {
            return Err(res);
        }
        let res = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();
        if res != ErrorCode::Success {
            return Err(res);
        }

        Ok(())
    }

    pub fn close(pool_handle: i32) -> Result<(), ErrorCode> {
        let (sender, receiver) = channel();
        let (command_handle, cb) = CallbackUtils::closure_to_close_pool_ledger_cb(
            Box::new(move |res| sender.send(res).unwrap()));

        let res = indy_close_pool_ledger(command_handle, pool_handle, cb);
        if res != ErrorCode::Success {
            return Err(res);
        }
        let res = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();
        if res != ErrorCode::Success {
            return Err(res);
        }

        Ok(())
    }

    pub fn delete(pool_name: &str) -> Result<(), ErrorCode> {
        let (sender, receiver) = channel();
        let (cmd_id, cb) = CallbackUtils::closure_to_delete_pool_ledger_config_cb(Box::new(
            move |res| sender.send(res).unwrap()));

        let res = indy_delete_pool_ledger_config(cmd_id, CString::new(pool_name).unwrap().as_ptr(), cb);
        if res != ErrorCode::Success {
            return Err(res)
        }
        let res = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();
        if res != ErrorCode::Success {
            return Err(res)
        }
        Ok(())
    }

    pub fn send_request(pool_handle: i32, request: &str) -> Result<String, ErrorCode> {
        let (sender, receiver) = channel();
        let cb_send = Box::new(move |err, resp| {
            sender.send((err, resp)).unwrap();
        });
        let req = CString::new(request).unwrap();
        let (command_handle, callback) = CallbackUtils::closure_to_send_tx_cb(cb_send);

        let err = indy_submit_request(command_handle,
                                        pool_handle,
                                        req.as_ptr(),
                                        callback);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, resp) = receiver.recv_timeout(TimeoutUtils::medium_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(resp)
    }

    pub fn create_genesis_txn_file(txn_file_name: &str, predefined_data: Option<String>) -> PathBuf {
        let path = EnvironmentUtils::tmp_file_path(txn_file_name);

        if !path.parent().unwrap().exists() {
            fs::DirBuilder::new()
                .recursive(true)
                .create(path.parent().unwrap()).unwrap();
        }

        let mut f = fs::File::create(path.clone()).unwrap();
        let data = format!("{}\n{}\n{}\n{}\n",
                           "{\"data\":{\"alias\":\"Node1\",\"client_ip\":\"10.0.0.2\",\"client_port\":9702,\"node_ip\":\"10.0.0.2\",\"node_port\":9701,\"services\":[\"VALIDATOR\"]},\"dest\":\"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv\",\"identifier\":\"Th7MpTaRZVRYnPiabds81Y\",\"txnId\":\"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62\",\"type\":\"0\"}",
                           "{\"data\":{\"alias\":\"Node2\",\"client_ip\":\"10.0.0.2\",\"client_port\":9704,\"node_ip\":\"10.0.0.2\",\"node_port\":9703,\"services\":[\"VALIDATOR\"]},\"dest\":\"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb\",\"identifier\":\"EbP4aYNeTHL6q385GuVpRV\",\"txnId\":\"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc\",\"type\":\"0\"}",
                           "{\"data\":{\"alias\":\"Node3\",\"client_ip\":\"10.0.0.2\",\"client_port\":9706,\"node_ip\":\"10.0.0.2\",\"node_port\":9705,\"services\":[\"VALIDATOR\"]},\"dest\":\"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya\",\"identifier\":\"4cU41vWW82ArfxJxHkzXPG\",\"txnId\":\"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4\",\"type\":\"0\"}",
                           "{\"data\":{\"alias\":\"Node4\",\"client_ip\":\"10.0.0.2\",\"client_port\":9708,\"node_ip\":\"10.0.0.2\",\"node_port\":9707,\"services\":[\"VALIDATOR\"]},\"dest\":\"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA\",\"identifier\":\"TWwCRQRZ2ZHMJFn9TzLp7W\",\"txnId\":\"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008\",\"type\":\"0\"}");

        let data = predefined_data.unwrap_or(data);

        f.write_all(data.as_bytes()).unwrap();
        f.flush().unwrap();
        f.sync_all().unwrap();
        path
    }

    pub fn create_default_pool_config(pool_name: &str) -> String {
        let txn_file_path = EnvironmentUtils::tmp_file_path(format!("{}.txn", pool_name).as_str());
        format!("{{\"genesis_txn\": \"{}\"}}", txn_file_path.to_string_lossy())
    }

    pub fn get_req_id() -> u64 {
        time::get_time().sec as u64 * (1e9 as u64) + time::get_time().nsec as u64
    }
}