use core::cmp::Ordering;
use core::fmt::Debug;
use core::num::ParseIntError;
use core::ops::Deref;
use core::str::FromStr;
use std::io;
use std::io::Write;
use amplify::hex::{Error, FromHex, ToHex};
use amplify::{hex, Array, Bytes32, Wrapper};
use bp::secp256k1::rand::thread_rng;
use commit_verify::{
    CommitEncode, CommitVerify, CommitmentProtocol, Conceal, Digest, Sha256, UntaggedProtocol,
};
use secp256k1_zkp::rand::{Rng, RngCore};
use secp256k1_zkp::SECP256K1;
use strict_encoding::{
    DecodeError, ReadTuple, StrictDecode, StrictDumb, StrictEncode, TypedRead, TypedWrite,
    WriteTuple,
};
use super::{ConfidentialState, ExposedState};
use crate::{schema, StateCommitment, StateData, StateType, LIB_NAME_RGB};
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
#[display(inner)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB, tags = custom)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum FungibleState {
    #[from]
    #[strict_type(tag = 8)] Bits64(u64),
    }
impl Default for FungibleState {
    fn default() -> Self { FungibleState::Bits64(0) }
}
impl From<RevealedValue> for FungibleState {
    fn from(revealed: RevealedValue) -> Self { revealed.value }
}
impl FromStr for FungibleState {
    type Err = ParseIntError;
    fn from_str(s: &str) -> Result<Self, Self::Err> { s.parse().map(FungibleState::Bits64) }
}
impl From<FungibleState> for u64 {
    fn from(value: FungibleState) -> Self {
        match value {
            FungibleState::Bits64(val) => val,
        }
    }
}
impl FungibleState {
    pub fn fungible_type(&self) -> schema::FungibleType {
        match self {
            FungibleState::Bits64(_) => schema::FungibleType::Unsigned64Bit,
        }
    }
    pub fn as_u64(&self) -> u64 { (*self).into() }
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[display(Self::to_hex)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", from = "secp256k1_zkp::SecretKey")
)]
pub struct BlindingFactor(Bytes32);
impl Deref for BlindingFactor {
    type Target = [u8; 32];
    fn deref(&self) -> &Self::Target { self.0.as_inner() }
}
impl ToHex for BlindingFactor {
    fn to_hex(&self) -> String { self.0.to_hex() }
}
impl FromHex for BlindingFactor {
    fn from_hex(s: &str) -> Result<Self, Error> { Bytes32::from_hex(s).map(Self) }
    fn from_byte_iter<I>(_: I) -> Result<Self, Error>
    where I: Iterator<Item = Result<u8, Error>> + ExactSizeIterator + DoubleEndedIterator {
        unreachable!()
    }
}
impl FromStr for BlindingFactor {
    type Err = hex::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_hex(s) }
}
impl From<secp256k1_zkp::SecretKey> for BlindingFactor {
    fn from(key: secp256k1_zkp::SecretKey) -> Self { Self(Bytes32::from_inner(*key.as_ref())) }
}
impl From<BlindingFactor> for secp256k1_zkp::SecretKey {
    fn from(bf: BlindingFactor) -> Self {
        secp256k1_zkp::SecretKey::from_slice(bf.0.as_inner())
            .expect("blinding factor is an invalid secret key")
    }
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)]
#[display(doc_comments)]
pub struct FieldOrderOverflow;
impl TryFrom<[u8; 32]> for BlindingFactor {
    type Error = FieldOrderOverflow;
    fn try_from(array: [u8; 32]) -> Result<Self, Self::Error> {
        secp256k1_zkp::SecretKey::from_slice(&array)
            .map_err(|_| FieldOrderOverflow)
            .map(Self::from)
    }
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB, rename = "RevealedFungible")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct RevealedValue {
    pub value: FungibleState,
    pub blinding: BlindingFactor,
}
impl RevealedValue {
    pub fn new<R: Rng + RngCore>(value: impl Into<FungibleState>, rng: &mut R) -> Self {
        Self {
            value: value.into(),
            blinding: BlindingFactor::from(secp256k1_zkp::SecretKey::new(rng)),
        }
    }
    pub fn with(value: impl Into<FungibleState>, blinding: impl Into<BlindingFactor>) -> Self {
        Self {
            value: value.into(),
            blinding: blinding.into(),
        }
    }
}
impl ExposedState for RevealedValue {
    type Confidential = ConcealedValue;
    fn state_type(&self) -> StateType { StateType::Fungible }
    fn state_data(&self) -> StateData { StateData::Fungible(*self) }
}
impl Conceal for RevealedValue {
    type Concealed = ConcealedValue;
    fn conceal(&self) -> Self::Concealed { ConcealedValue::commit(self) }
}
impl CommitEncode for RevealedValue {
    fn commit_encode(&self, e: &mut impl Write) {
        let commitment = PedersenCommitment::commit(self);
        commitment.commit_encode(e);
    }
}
impl PartialOrd for RevealedValue {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match self.value.partial_cmp(&other.value) {
            None => None,
            Some(Ordering::Equal) => self.blinding.0.partial_cmp(&other.blinding.0),
            other => other,
        }
    }
}
impl Ord for RevealedValue {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.value.cmp(&other.value) {
            Ordering::Equal => self.blinding.0.cmp(&other.blinding.0),
            other => other,
        }
    }
}
#[derive(Wrapper, Copy, Clone, Eq, PartialEq, Hash, Debug, From)]
#[wrapper(Deref, FromStr, Display, LowerHex)]
#[derive(StrictType)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", transparent)
)]
pub struct PedersenCommitment(secp256k1_zkp::PedersenCommitment);
impl StrictDumb for PedersenCommitment {
    fn strict_dumb() -> Self {
        secp256k1_zkp::PedersenCommitment::from_slice(&[0x08; 33])
            .expect("hardcoded pedersen commitment value")
            .into()
    }
}
impl StrictEncode for PedersenCommitment {
    fn strict_encode<W: TypedWrite>(&self, writer: W) -> io::Result<W> {
        writer.write_tuple::<Self>(|w| Ok(w.write_field(&self.0.serialize())?.complete()))
    }
}
impl StrictDecode for PedersenCommitment {
    fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
        reader.read_tuple(|r| {
            let commitment = r.read_field::<[u8; 33]>()?;
            secp256k1_zkp::PedersenCommitment::from_slice(&commitment)
                .map_err(|_| {
                    DecodeError::DataIntegrityError(s!("invalid pedersen commitment data"))
                })
                .map(PedersenCommitment::from_inner)
        })
    }
}
impl CommitVerify<RevealedValue, UntaggedProtocol> for PedersenCommitment {
    fn commit(revealed: &RevealedValue) -> Self {
        use secp256k1_zkp::{Generator, Tag, Tweak};
        let blinding = Tweak::from_inner(revealed.blinding.0.into_inner())
            .expect("type guarantees of BlindingFactor are broken");
        let FungibleState::Bits64(value) = revealed.value;
        let one_key = secp256k1_zkp::SecretKey::from_slice(&secp256k1_zkp::constants::ONE)
            .expect("secret key from a constant");
        let g = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &one_key);
        let h: [u8; 32] = Sha256::digest(&g.serialize_uncompressed()).into();
        let tag = Tag::from(h);
        let generator = Generator::new_unblinded(SECP256K1, tag);
        secp256k1_zkp::PedersenCommitment::new(SECP256K1, value, blinding, generator).into()
    }
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", transparent)
)]
pub struct NoiseDumb(Array<u8, 512>);
impl Default for NoiseDumb {
    fn default() -> Self {
        let mut dumb = [0u8; 512];
        thread_rng().fill(&mut dumb);
        NoiseDumb(dumb.into())
    }
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB, tags = custom)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum RangeProof {
    #[strict_type(tag = 0xFF)]
    Placeholder(NoiseDumb),
}
impl Default for RangeProof {
    fn default() -> Self { RangeProof::Placeholder(default!()) }
}
pub struct PedersenProtocol;
impl CommitmentProtocol for PedersenProtocol {}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB, rename = "ConcealedFungible")]
#[derive(CommitEncode)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct ConcealedValue {
    pub commitment: PedersenCommitment,
    #[commit_encode(skip)]
    pub range_proof: RangeProof,
}
impl ConfidentialState for ConcealedValue {
    fn state_type(&self) -> StateType { StateType::Fungible }
    fn state_commitment(&self) -> StateCommitment { StateCommitment::Fungible(*self) }
}
impl CommitVerify<RevealedValue, PedersenProtocol> for ConcealedValue {
    #[allow(dead_code, unreachable_code, unused_variables)]
    fn commit(revealed: &RevealedValue) -> Self {
        panic!(
            "Error: current version of RGB Core doesn't support production of bulletproofs; thus, \
             fungible state must be never concealed"
        );
        let commitment = PedersenCommitment::commit(revealed);
        let range_proof = RangeProof::default();
        ConcealedValue {
            commitment,
            range_proof,
        }
    }
}
impl ConcealedValue {
    pub fn verify(&self) -> bool {
        match self.range_proof {
            RangeProof::Placeholder(_) => false,
        }
    }
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Display, Error)]
#[display(doc_comments)]
pub enum RangeProofError {
    InvalidBlinding(BlindingFactor),
    BulletproofsAbsent,
}
impl ConcealedValue {
    pub fn verify_range_proof(&self) -> Result<bool, RangeProofError> {
        Err(RangeProofError::BulletproofsAbsent)
    }
}
#[cfg(test)]
mod test {
    use std::collections::HashSet;
    use super::*;
    #[test]
    fn commitments_determinism() {
        let value = RevealedValue::new(15, &mut thread_rng());
        let generators = (0..10)
            .into_iter()
            .map(|_| {
                let mut val = vec![];
                value.commit_encode(&mut val);
                val
            })
            .collect::<HashSet<_>>();
        assert_eq!(generators.len(), 1);
    }
}