use rand::rngs::OsRng;
use rust_util::XResult;
use serde::{Deserialize, Serialize};
use secp256k1::{Secp256k1, SecretKey, key::PublicKey};
use sha2::Sha256;
use ripemd160::Ripemd160;
use digest::{ Input, FixedOutput };
use std::str::FromStr;

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonKeyPair {
    pub tag: Option<String>,
    pub identity: String,
    pub pri_key: String,
    pub pub_key: String,
}

impl JsonKeyPair {
    pub fn from(pri_key: SecretKey, pub_key: PublicKey, tag: Option<String>) -> Self {
        JsonKeyPair {
            tag,
            identity: make_btc_address(&pub_key),
            pri_key: format!("{}", pri_key),
            pub_key: format!("{}", pub_key),
        }
    }

    pub fn to_json(&self) -> XResult<String> {
        serde_json::to_string_pretty(self).map_err(|e| e.into())
    }

    pub fn from_json(json: &str) -> XResult<Self> {
        serde_json::from_str(json).map_err(|e| e.into())
    }

    pub fn to_key_pair(&self) -> XResult<(SecretKey, PublicKey)> {
        let pri_key = SecretKey::from_str(&self.pri_key)?;
        let pub_key = PublicKey::from_str(&self.pub_key)?;
        Ok((pri_key, pub_key))
    }
}

pub fn make_key_pair() -> (SecretKey, PublicKey) {
    let secp = Secp256k1::new();
    let mut rng = OsRng::new().expect("OsRng");
    let (secret_key, public_key) = secp.generate_keypair(&mut rng);
    (secret_key, public_key)
}

pub fn make_btc_address(public_key: &PublicKey) -> String {
    let public_key_bytes = public_key.serialize_uncompressed().to_vec();
    let riphemd160_sha256_pub_key = calc_ripemd160(&calc_sha256(&public_key_bytes));
    let mut btc_addr = Vec::<u8>::with_capacity(25);
    btc_addr.push(0x00 as u8);
    btc_addr.extend_from_slice(&riphemd160_sha256_pub_key);
    let checksum = &calc_sha256(&calc_sha256(&btc_addr))[0..4];
    btc_addr.extend_from_slice(checksum);
    bs58::encode(&btc_addr).into_string()
}

#[inline]
pub fn calc_sha256(i: &[u8]) -> Vec<u8> {
    calc_hash(Sha256::default(), i)
}

#[inline]
fn calc_ripemd160(i: &[u8]) -> Vec<u8> {
    calc_hash(Ripemd160::default(), i)
}

#[inline]
fn calc_hash<T>(mut hasher: T, i: &[u8]) -> Vec<u8> where T: Input + FixedOutput {
    hasher.input(&i);
    hasher.fixed_result().to_vec()
}
