pub mod actions;
pub mod documents;

use crate::errors::*;

use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha512_256};
use std::{
	borrow::Borrow,
	convert::{TryFrom, TryInto},
};

type ProtocolName = [char; 7];
pub const PROTOCOL_NAME: ProtocolName = ['G', 'M', 'i', 'x', 'e', 'r', '\0'];
pub const PROTOCOL_VERSION: u16 = 0;
pub const TX_ID_SEED_LEN: usize = 32;
pub const TX_ID_LEN: usize = 32;
pub const TX_ID_LIST_HASH_LEN: usize = 32;
pub const TX_ID_COMMENT: &str = "GMixerTxId";
pub const TX_ID_LIST_HASH_COMMENT: &str = "GMixerTxIdListHash";

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ProtocolMark {
	pub name: ProtocolName,
	pub version: u16,
	pub document: u8,
}

impl ProtocolMark {
	pub fn new(document: u8) -> Self {
		Self {
			name: PROTOCOL_NAME,
			version: PROTOCOL_VERSION,
			document,
		}
	}
}

#[derive(Deserialize, Serialize)]
pub struct ProtocolConfig {
	pub currency: String,
	/// Mix confirm validity duration
	pub mix_confirm_expire: u64,
	/// Mix demand validity duration
	pub mix_demand_expire: u64,
	/// Public key delegation validity duration
	pub pubkey_delegation_expire: u64,
}

impl Default for ProtocolConfig {
	fn default() -> Self {
		ProtocolConfig {
			currency: String::from("g1"),
			mix_confirm_expire: 604800,
			mix_demand_expire: 86400,
			pubkey_delegation_expire: 604800,
		}
	}
}

#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct TxIdSeed(pub [u8; TX_ID_SEED_LEN]);

impl TxIdSeed {
	pub fn generate() -> Self {
		Self(rand::random())
	}

	pub fn from_string(raw: &str) -> Option<Self> {
		Some(Self(
			base64::decode_config(raw, base64::URL_SAFE_NO_PAD)
				.ok()?
				.try_into()
				.ok()?,
		))
	}

	pub fn to_string(&self) -> String {
		base64::encode_config(self.0, base64::URL_SAFE_NO_PAD)
	}
}

impl AsRef<[u8]> for TxIdSeed {
	fn as_ref(&self) -> &[u8] {
		&self.0
	}
}

impl Borrow<[u8]> for TxIdSeed {
	fn borrow(&self) -> &[u8] {
		&self.0
	}
}

impl TryFrom<&[u8]> for TxIdSeed {
	type Error = std::array::TryFromSliceError;

	fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
		Ok(Self(v.try_into()?))
	}
}

#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct TxId(pub [u8; TX_ID_LEN]);

impl TxId {
	pub fn generate(tx_id_seeds: &[TxIdSeed; 3]) -> Self {
		let mut hasher = Sha512_256::new();
		hasher.update(tx_id_seeds.concat());
		Self(hasher.finalize().try_into().unwrap())
	}

	pub fn to_comment(&self) -> String {
		format!(
			"{} {}",
			TX_ID_COMMENT,
			base64::encode_config(self.0, base64::URL_SAFE_NO_PAD)
		)
	}
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TxIdListHash(pub [u8; TX_ID_LIST_HASH_LEN]);

impl TxIdListHash {
	pub fn to_comment(&self) -> String {
		format!(
			"{} {}",
			TX_ID_LIST_HASH_COMMENT,
			base64::encode_config(self.0, base64::URL_SAFE_NO_PAD)
		)
	}
}

impl AsRef<[u8]> for TxIdListHash {
	fn as_ref(&self) -> &[u8] {
		&self.0
	}
}

#[derive(Debug, Eq, PartialEq)]
pub enum DecodedComment {
	TxId(TxId),
	TxIdListHash(TxIdListHash),
}

pub fn decode_comment(comment: &str) -> Result<DecodedComment, Error> {
	let mut iter = comment.split(' ');
	let result: Option<DecodedComment> = try {
		match iter.next()? {
			TX_ID_COMMENT => DecodedComment::TxId(TxId(
				base64::decode_config(iter.next()?, base64::URL_SAFE_NO_PAD)
					.ok()?
					.try_into()
					.ok()?,
			)),
			TX_ID_LIST_HASH_COMMENT => DecodedComment::TxIdListHash(TxIdListHash(
				base64::decode_config(iter.next()?, base64::URL_SAFE_NO_PAD)
					.ok()?
					.try_into()
					.ok()?,
			)),
			_ => None?,
		}
	};
	result.ok_or(Error::DeserializationError)
}