// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Core types

pub mod block;
pub mod build;
pub mod hash;
pub mod id;
pub mod pmmr;
pub mod target;
pub mod transaction;
// pub mod txoset;
#[allow(dead_code)]

use rand::{thread_rng, Rng};
use std::{fmt, iter};
use std::cmp::Ordering;
use std::num::ParseFloatError;
use consensus::GRIN_BASE;

use util::{secp, static_secp_instance};
use util::secp::pedersen::*;

pub use self::block::*;
pub use self::transaction::*;
pub use self::id::ShortId;
use core::hash::Hashed;
use ser::{Error, Readable, Reader, Writeable, Writer};
use global;

/// Implemented by types that hold inputs and outputs including Pedersen
/// commitments. Handles the collection of the commitments as well as their
/// summing, taking potential explicit overages of fees into account.
pub trait Committed {
	/// Gathers commitments and sum them.
	fn sum_commitments(&self) -> Result<Commitment, secp::Error> {
		// first, verify each range proof
		let ref outputs = self.outputs_committed();
		for output in *outputs {
			try!(output.verify_proof())
		}

		// then gather the commitments
		let mut input_commits = map_vec!(self.inputs_committed(), |inp| inp.commitment());
		let mut output_commits = map_vec!(self.outputs_committed(), |out| out.commitment());

		// add the overage as output commitment if positive, as an input commitment if
		// negative
		let overage = self.overage();
		if overage != 0 {
			let over_commit = {
				let secp = static_secp_instance();
				let secp = secp.lock().unwrap();
				secp.commit_value(overage.abs() as u64).unwrap()
			};
			if overage < 0 {
				input_commits.push(over_commit);
			} else {
				output_commits.push(over_commit);
			}
		}

		// sum all that stuff
		{
			let secp = static_secp_instance();
			let secp = secp.lock().unwrap();
			secp.commit_sum(output_commits, input_commits)
		}
	}

	/// Vector of committed inputs to verify
	fn inputs_committed(&self) -> &Vec<Input>;

	/// Vector of committed inputs to verify
	fn outputs_committed(&self) -> &Vec<Output>;

	/// The overage amount expected over the commitments. Can be negative (a
	/// fee) or positive (a reward).
	fn overage(&self) -> i64;
}

/// Proof of work
pub struct Proof {
	/// The nonces
	pub nonces: Vec<u32>,

	/// The proof size
	pub proof_size: usize,
}

impl fmt::Debug for Proof {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		try!(write!(f, "Cuckoo("));
		for (i, val) in self.nonces[..].iter().enumerate() {
			try!(write!(f, "{:x}", val));
			if i < self.nonces.len() - 1 {
				try!(write!(f, " "));
			}
		}
		write!(f, ")")
	}
}
impl PartialOrd for Proof {
	fn partial_cmp(&self, other: &Proof) -> Option<Ordering> {
		self.nonces.partial_cmp(&other.nonces)
	}
}
impl PartialEq for Proof {
	fn eq(&self, other: &Proof) -> bool {
		self.nonces[..] == other.nonces[..]
	}
}
impl Eq for Proof {}
impl Clone for Proof {
	fn clone(&self) -> Proof {
		let mut out_nonces = Vec::new();
		for n in self.nonces.iter() {
			out_nonces.push(*n as u32);
		}
		Proof {
			proof_size: out_nonces.len(),
			nonces: out_nonces,
		}
	}
}

impl Proof {
	/// Builds a proof with all bytes zeroed out
	pub fn new(in_nonces: Vec<u32>) -> Proof {
		Proof {
			proof_size: in_nonces.len(),
			nonces: in_nonces,
		}
	}

	/// Builds a proof with all bytes zeroed out
	pub fn zero(proof_size: usize) -> Proof {
		Proof {
			proof_size: proof_size,
			nonces: vec![0; proof_size],
		}
	}

	/// Builds a proof with random POW data,
	/// needed so that tests that ignore POW
	/// don't fail due to duplicate hashes
	pub fn random(proof_size: usize) -> Proof {
		let mut rng = thread_rng();
		let v: Vec<u32> = iter::repeat(())
			.map(|()| rng.gen())
			.take(proof_size)
			.collect();
		Proof {
			proof_size: proof_size,
			nonces: v,
		}
	}

	/// Converts the proof to a vector of u64s
	pub fn to_u64s(&self) -> Vec<u64> {
		let mut out_nonces = Vec::with_capacity(self.proof_size);
		for n in self.nonces.iter() {
			out_nonces.push(*n as u64);
		}
		out_nonces
	}

	/// Converts the proof to a vector of u32s
	pub fn to_u32s(&self) -> Vec<u32> {
		self.clone().nonces
	}

	/// Converts the proof to a proof-of-work Target so they can be compared.
	/// Hashes the Cuckoo Proof data.
	pub fn to_difficulty(self) -> target::Difficulty {
		target::Difficulty::from_hash(&self.hash())
	}
}

impl Readable for Proof {
	fn read(reader: &mut Reader) -> Result<Proof, Error> {
		let proof_size = global::proofsize();
		let mut pow = vec![0u32; proof_size];
		for n in 0..proof_size {
			pow[n] = try!(reader.read_u32());
		}
		Ok(Proof::new(pow))
	}
}

impl Writeable for Proof {
	fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
		for n in 0..self.proof_size {
			try!(writer.write_u32(self.nonces[n]));
		}
		Ok(())
	}
}

/// Common method for parsing an amount from human-readable, and converting
/// to internally-compatible u64

pub fn amount_from_hr_string(amount: &str) -> Result<u64, ParseFloatError> {
	let amount = amount.parse::<f64>()?;
	Ok((amount * GRIN_BASE as f64) as u64)
}

/// Common method for converting an amount to a human-readable string

pub fn amount_to_hr_string(amount: u64) -> String {
	let amount = (amount as f64 / GRIN_BASE as f64) as f64;
	let places = (GRIN_BASE as f64).log(10.0) as usize + 1;
	String::from(format!("{:.*}", places, amount))
}

#[cfg(test)]
mod test {
	use super::*;
	use core::target::Difficulty;
	use core::hash::ZERO_HASH;
	use core::build::{initial_tx, input, output, with_excess, with_fee, with_lock_height};
	use core::block::Error::KernelLockHeight;
	use ser;
	use keychain;
	use keychain::{BlindingFactor, Keychain};

	#[test]
	pub fn test_amount_to_hr() {
		assert!(50123456789 == amount_from_hr_string("50.123456789").unwrap());
		assert!(50 == amount_from_hr_string(".000000050").unwrap());
		assert!(1 == amount_from_hr_string(".000000001").unwrap());
		assert!(0 == amount_from_hr_string(".0000000009").unwrap());
		assert!(500_000_000_000 == amount_from_hr_string("500").unwrap());
		assert!(
			5_000_000_000_000_000_000 == amount_from_hr_string("5000000000.00000000000").unwrap()
		);
	}

	#[test]
	pub fn test_hr_to_amount() {
		assert!("50.123456789" == amount_to_hr_string(50123456789));
		assert!("0.000000050" == amount_to_hr_string(50));
		assert!("0.000000001" == amount_to_hr_string(1));
		assert!("500.000000000" == amount_to_hr_string(500_000_000_000));
		assert!("5000000000.000000000" == amount_to_hr_string(5_000_000_000_000_000_000));
	}

	#[test]
	#[should_panic(expected = "InvalidSecretKey")]
	fn test_zero_commit_fails() {
		let keychain = Keychain::from_random_seed().unwrap();
		let key_id1 = keychain.derive_key_id(1).unwrap();

		// blinding should fail as signing with a zero r*G shouldn't work
		build::transaction(
			vec![
				input(10, key_id1.clone()),
				output(9, key_id1.clone()),
				with_fee(1),
			],
			&keychain,
		).unwrap();
	}

	#[test]
	fn simple_tx_ser() {
		let tx = tx2i1o();
		let mut vec = Vec::new();
		ser::serialize(&mut vec, &tx).expect("serialization failed");
		let target_len = 954;
		assert_eq!(vec.len(), target_len,);
	}

	#[test]
	fn simple_tx_ser_deser() {
		let tx = tx2i1o();
		let mut vec = Vec::new();
		ser::serialize(&mut vec, &tx).expect("serialization failed");
		let dtx: Transaction = ser::deserialize(&mut &vec[..]).unwrap();
		assert_eq!(dtx.fee(), 2);
		assert_eq!(dtx.inputs.len(), 2);
		assert_eq!(dtx.outputs.len(), 1);
		assert_eq!(tx.hash(), dtx.hash());
	}

	#[test]
	fn tx_double_ser_deser() {
		// checks serializing doesn't mess up the tx and produces consistent results
		let btx = tx2i1o();

		let mut vec = Vec::new();
		assert!(ser::serialize(&mut vec, &btx).is_ok());
		let dtx: Transaction = ser::deserialize(&mut &vec[..]).unwrap();

		let mut vec2 = Vec::new();
		assert!(ser::serialize(&mut vec2, &btx).is_ok());
		let dtx2: Transaction = ser::deserialize(&mut &vec2[..]).unwrap();

		assert_eq!(btx.hash(), dtx.hash());
		assert_eq!(dtx.hash(), dtx2.hash());
	}

	#[test]
	fn build_tx_kernel() {
		let keychain = Keychain::from_random_seed().unwrap();
		let key_id1 = keychain.derive_key_id(1).unwrap();
		let key_id2 = keychain.derive_key_id(2).unwrap();
		let key_id3 = keychain.derive_key_id(3).unwrap();

		// first build a valid tx with corresponding blinding factor
		let tx = build::transaction(
			vec![
				input(10, key_id1),
				output(5, key_id2),
				output(3, key_id3),
				with_fee(2),
			],
			&keychain,
		).unwrap();

		// check the tx is valid
		tx.validate().unwrap();

		// check the kernel is also itself valid
		assert_eq!(tx.kernels.len(), 1);
		let kern = &tx.kernels[0];
		kern.verify().unwrap();

		assert_eq!(kern.features, KernelFeatures::DEFAULT_KERNEL);
		assert_eq!(kern.fee, tx.fee());
	}

	// Combine two transactions into one big transaction (with multiple kernels)
	// and check it still validates.
	#[test]
	fn transaction_cut_through() {
		let tx1 = tx1i2o();
		let tx2 = tx2i1o();

		assert!(tx1.validate().is_ok());
		assert!(tx2.validate().is_ok());

		// now build a "cut_through" tx from tx1 and tx2
		let mut tx3 = tx1.clone();
		tx3.inputs.extend(tx2.inputs.iter().cloned());
		tx3.outputs.extend(tx2.outputs.iter().cloned());
		tx3.kernels.extend(tx2.kernels.iter().cloned());

		// make sure everything is sorted
		tx3.inputs.sort();
		tx3.outputs.sort();
		tx3.kernels.sort();

		// finally sum the offsets up
		// TODO - hide this in a convenience function somewhere
		tx3.offset = {
			let secp = static_secp_instance();
			let secp = secp.lock().unwrap();
			let skey1 = tx1.offset.secret_key(&secp).unwrap();
			let skey2 = tx2.offset.secret_key(&secp).unwrap();
			let skey3 = secp.blind_sum(vec![skey1, skey2], vec![]).unwrap();
			BlindingFactor::from_secret_key(skey3)
		};

		assert!(tx3.validate().is_ok());
	}

	#[test]
	fn hash_output() {
		let keychain = Keychain::from_random_seed().unwrap();
		let key_id1 = keychain.derive_key_id(1).unwrap();
		let key_id2 = keychain.derive_key_id(2).unwrap();
		let key_id3 = keychain.derive_key_id(3).unwrap();

		let tx = build::transaction(
			vec![
				input(75, key_id1),
				output(42, key_id2),
				output(32, key_id3),
				with_fee(1),
			],
			&keychain,
		).unwrap();
		let h = tx.outputs[0].hash();
		assert!(h != ZERO_HASH);
		let h2 = tx.outputs[1].hash();
		assert!(h != h2);
	}

	#[ignore]
	#[test]
	fn blind_tx() {
		let btx = tx2i1o();
		assert!(btx.validate().is_ok());

		// Ignored for bullet proofs, because calling range_proof_info
		// with a bullet proof causes painful errors

		// checks that the range proof on our blind output is sufficiently hiding
		let Output { proof, .. } = btx.outputs[0];

		let secp = static_secp_instance();
		let secp = secp.lock().unwrap();
		let info = secp.range_proof_info(proof);

		assert!(info.min == 0);
		assert!(info.max == u64::max_value());
	}

	#[test]
	fn tx_hash_diff() {
		let btx1 = tx2i1o();
		let btx2 = tx1i1o();

		if btx1.hash() == btx2.hash() {
			panic!("diff txs have same hash")
		}
	}

	/// Simulate the standard exchange between 2 parties when creating a basic
	/// 2 inputs, 2 outputs transaction.
	#[test]
	fn tx_build_exchange() {
		let keychain = Keychain::from_random_seed().unwrap();
		let key_id1 = keychain.derive_key_id(1).unwrap();
		let key_id2 = keychain.derive_key_id(2).unwrap();
		let key_id3 = keychain.derive_key_id(3).unwrap();
		let key_id4 = keychain.derive_key_id(4).unwrap();

		let (tx_alice, blind_sum) = {
			// Alice gets 2 of her pre-existing outputs to send 5 coins to Bob, they
			// become inputs in the new transaction
			let (in1, in2) = (input(4, key_id1), input(3, key_id2));

			// Alice builds her transaction, with change, which also produces the sum
			// of blinding factors before they're obscured.
			let (tx, sum) = build::partial_transaction(
				vec![in1, in2, output(1, key_id3), with_fee(2)],
				&keychain,
			).unwrap();

			(tx, sum)
		};

		// From now on, Bob only has the obscured transaction and the sum of
		// blinding factors. He adds his output, finalizes the transaction so it's
		// ready for broadcast.
		let tx_final = build::transaction(
			vec![
				initial_tx(tx_alice),
				with_excess(blind_sum),
				output(4, key_id4),
			],
			&keychain,
		).unwrap();

		tx_final.validate().unwrap();
	}

	#[test]
	fn reward_empty_block() {
		let keychain = keychain::Keychain::from_random_seed().unwrap();
		let key_id = keychain.derive_key_id(1).unwrap();

		let previous_header = BlockHeader::default();

		let b = Block::new(
			&previous_header,
			vec![],
			&keychain,
			&key_id,
			Difficulty::one(),
		).unwrap();
		b.cut_through().validate(&previous_header).unwrap();
	}

	#[test]
	fn reward_with_tx_block() {
		let keychain = keychain::Keychain::from_random_seed().unwrap();
		let key_id = keychain.derive_key_id(1).unwrap();

		let mut tx1 = tx2i1o();
		tx1.validate().unwrap();

		let previous_header = BlockHeader::default();

		let block = Block::new(
			&previous_header,
			vec![&mut tx1],
			&keychain,
			&key_id,
			Difficulty::one(),
		).unwrap();
		block.cut_through().validate(&previous_header).unwrap();
	}

	#[test]
	fn simple_block() {
		let keychain = keychain::Keychain::from_random_seed().unwrap();
		let key_id = keychain.derive_key_id(1).unwrap();

		let mut tx1 = tx2i1o();
		let mut tx2 = tx1i1o();

		let previous_header = BlockHeader::default();

		let b = Block::new(
			&previous_header,
			vec![&mut tx1, &mut tx2],
			&keychain,
			&key_id,
			Difficulty::one(),
		).unwrap();
		b.validate(&previous_header).unwrap();
	}

	#[test]
	fn test_block_with_timelocked_tx() {
		let keychain = keychain::Keychain::from_random_seed().unwrap();

		let key_id1 = keychain.derive_key_id(1).unwrap();
		let key_id2 = keychain.derive_key_id(2).unwrap();
		let key_id3 = keychain.derive_key_id(3).unwrap();

		// first check we can add a timelocked tx where lock height matches current
		// block height and that the resulting block is valid
		let tx1 = build::transaction(
			vec![
				input(5, key_id1.clone()),
				output(3, key_id2.clone()),
				with_fee(2),
				with_lock_height(1),
			],
			&keychain,
		).unwrap();

		let previous_header = BlockHeader::default();

		let b = Block::new(
			&previous_header,
			vec![&tx1],
			&keychain,
			&key_id3.clone(),
			Difficulty::one(),
		).unwrap();
		b.validate(&previous_header).unwrap();

		// now try adding a timelocked tx where lock height is greater than current
		// block height
		let tx1 = build::transaction(
			vec![
				input(5, key_id1.clone()),
				output(3, key_id2.clone()),
				with_fee(2),
				with_lock_height(2),
			],
			&keychain,
		).unwrap();

		let previous_header = BlockHeader::default();

		let b = Block::new(
			&previous_header,
			vec![&tx1],
			&keychain,
			&key_id3.clone(),
			Difficulty::one(),
		).unwrap();
		match b.validate(&previous_header) {
			Err(KernelLockHeight(height)) => {
				assert_eq!(height, 2);
			}
			_ => panic!("expecting KernelLockHeight error here"),
		}
	}

	#[test]
	pub fn test_verify_1i1o_sig() {
		let tx = tx1i1o();
		tx.validate().unwrap();
	}

	#[test]
	pub fn test_verify_2i1o_sig() {
		let tx = tx2i1o();
		tx.validate().unwrap();
	}

	// utility producing a transaction with 2 inputs and a single outputs
	pub fn tx2i1o() -> Transaction {
		let keychain = keychain::Keychain::from_random_seed().unwrap();
		let key_id1 = keychain.derive_key_id(1).unwrap();
		let key_id2 = keychain.derive_key_id(2).unwrap();
		let key_id3 = keychain.derive_key_id(3).unwrap();

		build::transaction_with_offset(
			vec![
				input(10, key_id1),
				input(11, key_id2),
				output(19, key_id3),
				with_fee(2),
			],
			&keychain,
		).unwrap()
	}

	// utility producing a transaction with a single input and output
	pub fn tx1i1o() -> Transaction {
		let keychain = keychain::Keychain::from_random_seed().unwrap();
		let key_id1 = keychain.derive_key_id(1).unwrap();
		let key_id2 = keychain.derive_key_id(2).unwrap();

		build::transaction_with_offset(
			vec![input(5, key_id1), output(3, key_id2), with_fee(2)],
			&keychain,
		).unwrap()
	}

	// utility producing a transaction with a single input
	// and two outputs (one change output)
	// Note: this tx has an "offset" kernel
	pub fn tx1i2o() -> Transaction {
		let keychain = keychain::Keychain::from_random_seed().unwrap();
		let key_id1 = keychain.derive_key_id(1).unwrap();
		let key_id2 = keychain.derive_key_id(2).unwrap();
		let key_id3 = keychain.derive_key_id(3).unwrap();

		build::transaction_with_offset(
			vec![
				input(6, key_id1),
				output(3, key_id2),
				output(1, key_id3),
				with_fee(2),
			],
			&keychain,
		).unwrap()
	}
}
