use std::cmp::Ordering;
use std::collections::{btree_map, btree_set};
use std::fmt::{self, Display, Formatter};
use std::iter;
use std::str::FromStr;
use amplify::confinement::{SmallBlob, TinyOrdMap, TinyOrdSet};
use amplify::hex::{FromHex, ToHex};
use amplify::{hex, Bytes32, RawArray, Wrapper};
use baid58::{Baid58ParseError, Chunking, FromBaid58, ToBaid58, CHUNKING_32CHECKSUM};
use bp::Chain;
use commit_verify::{mpc, CommitmentId, Conceal};
use strict_encoding::{StrictDeserialize, StrictEncode, StrictSerialize};
use crate::schema::{self, ExtensionType, OpFullType, OpType, SchemaId, TransitionType};
use crate::{
    AssignmentType, Assignments, AssignmentsRef, Ffv, GenesisSeal, GlobalState, GraphSeal, Opout,
    ReservedByte, TypedAssigns, LIB_NAME_RGB,
};
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From)]
#[wrapper(Deref)]
#[wrapper_mut(DerefMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Valencies(TinyOrdSet<schema::ValencyType>);
impl<'a> IntoIterator for &'a Valencies {
    type Item = schema::ValencyType;
    type IntoIter = iter::Copied<btree_set::Iter<'a, schema::ValencyType>>;
    fn into_iter(self) -> Self::IntoIter { self.0.iter().copied() }
}
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From)]
#[wrapper(Deref)]
#[wrapper_mut(DerefMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Redeemed(TinyOrdMap<schema::ValencyType, OpId>);
impl<'a> IntoIterator for &'a Redeemed {
    type Item = (&'a schema::ValencyType, &'a OpId);
    type IntoIter = btree_map::Iter<'a, schema::ValencyType, OpId>;
    fn into_iter(self) -> Self::IntoIter { self.0.iter() }
}
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From)]
#[wrapper(Deref)]
#[wrapper_mut(DerefMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Inputs(TinyOrdSet<Input>);
impl<'a> IntoIterator for &'a Inputs {
    type Item = Input;
    type IntoIter = iter::Copied<btree_set::Iter<'a, Input>>;
    fn into_iter(self) -> Self::IntoIter { self.0.iter().copied() }
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
#[display("{prev_out}")]
pub struct Input {
    pub prev_out: Opout,
    reserved: ReservedByte,
}
impl Input {
    pub fn with(prev_out: Opout) -> Input {
        Input {
            prev_out,
            reserved: default!(),
        }
    }
}
#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
#[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", transparent)
)]
pub struct OpId(
    #[from]
    #[from([u8; 32])]
    Bytes32,
);
impl FromStr for OpId {
    type Err = hex::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_hex(s) }
}
impl OpId {
    pub fn from_slice(slice: impl AsRef<[u8]>) -> Option<Self> {
        Bytes32::from_slice(slice).map(Self)
    }
}
#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", transparent)
)]
pub struct ContractId(
    #[from]
    #[from([u8; 32])]
    Bytes32,
);
impl PartialEq<OpId> for ContractId {
    fn eq(&self, other: &OpId) -> bool { self.to_raw_array() == other.to_raw_array() }
}
impl PartialEq<ContractId> for OpId {
    fn eq(&self, other: &ContractId) -> bool { self.to_raw_array() == other.to_raw_array() }
}
impl ContractId {
    pub fn from_slice(slice: impl AsRef<[u8]>) -> Option<Self> {
        Bytes32::from_slice(slice).map(Self)
    }
}
impl ToBaid58<32> for ContractId {
    const HRI: &'static str = "rgb";
    const CHUNKING: Option<Chunking> = CHUNKING_32CHECKSUM;
    fn to_baid58_payload(&self) -> [u8; 32] { self.to_raw_array() }
    fn to_baid58_string(&self) -> String { self.to_string() }
}
impl FromBaid58<32> for ContractId {}
impl Display for ContractId {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            write!(f, "{::^}", self.to_baid58())
        } else {
            write!(f, "{::^.3}", self.to_baid58())
        }
    }
}
impl FromStr for ContractId {
    type Err = Baid58ParseError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::from_baid58_maybe_chunked_str(s, ':', '#')
    }
}
impl From<mpc::ProtocolId> for ContractId {
    fn from(id: mpc::ProtocolId) -> Self { ContractId(id.into_inner()) }
}
impl From<ContractId> for mpc::ProtocolId {
    fn from(id: ContractId) -> Self { mpc::ProtocolId::from_inner(id.into_inner()) }
}
pub trait Operation {
    fn op_type(&self) -> OpType;
    fn full_type(&self) -> OpFullType;
    fn id(&self) -> OpId;
    fn contract_id(&self) -> ContractId;
    fn transition_type(&self) -> Option<TransitionType>;
    fn extension_type(&self) -> Option<ExtensionType>;
    fn metadata(&self) -> &SmallBlob;
    fn globals(&self) -> &GlobalState;
    fn valencies(&self) -> &Valencies;
    fn assignments(&self) -> AssignmentsRef;
    fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>>;
    fn inputs(&self) -> Inputs;
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Genesis {
    pub ffv: Ffv,
    pub schema_id: SchemaId,
    pub chain: Chain,
    pub metadata: SmallBlob,
    pub globals: GlobalState,
    pub assignments: Assignments<GenesisSeal>,
    pub valencies: Valencies,
}
impl StrictSerialize for Genesis {}
impl StrictDeserialize for Genesis {}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Extension {
    pub ffv: Ffv,
    pub contract_id: ContractId,
    pub extension_type: ExtensionType,
    pub metadata: SmallBlob,
    pub globals: GlobalState,
    pub assignments: Assignments<GenesisSeal>,
    pub redeemed: Redeemed,
    pub valencies: Valencies,
}
impl StrictSerialize for Extension {}
impl StrictDeserialize for Extension {}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Transition {
    pub ffv: Ffv,
    pub contract_id: ContractId,
    pub transition_type: TransitionType,
    pub metadata: SmallBlob,
    pub globals: GlobalState,
    pub inputs: Inputs,
    pub assignments: Assignments<GraphSeal>,
    pub valencies: Valencies,
}
impl StrictSerialize for Transition {}
impl StrictDeserialize for Transition {}
impl Ord for Transition {
    fn cmp(&self, other: &Self) -> Ordering { self.id().cmp(&other.id()) }
}
impl PartialOrd for Transition {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Conceal for Genesis {
    type Concealed = Genesis;
    fn conceal(&self) -> Self::Concealed {
        let mut concealed = self.clone();
        concealed
            .assignments
            .keyed_values_mut()
            .for_each(|(_, a)| *a = a.conceal());
        concealed
    }
}
impl CommitmentId for Genesis {
    const TAG: [u8; 32] = *b"urn:lnpbp:rgb:genesis:v02#202304";
    type Id = ContractId;
}
impl Conceal for Transition {
    type Concealed = Transition;
    fn conceal(&self) -> Self::Concealed {
        let mut concealed = self.clone();
        concealed
            .assignments
            .keyed_values_mut()
            .for_each(|(_, a)| *a = a.conceal());
        concealed
    }
}
impl CommitmentId for Transition {
    const TAG: [u8; 32] = *b"urn:lnpbp:rgb:transition:v02#23B";
    type Id = OpId;
}
impl Conceal for Extension {
    type Concealed = Extension;
    fn conceal(&self) -> Self::Concealed {
        let mut concealed = self.clone();
        concealed
            .assignments
            .keyed_values_mut()
            .for_each(|(_, a)| *a = a.conceal());
        concealed
    }
}
impl CommitmentId for Extension {
    const TAG: [u8; 32] = *b"urn:lnpbp:rgb:extension:v02#2304";
    type Id = OpId;
}
impl Transition {
    pub fn prev_state(&self) -> &Inputs { &self.inputs }
}
impl Extension {
    pub fn redeemed(&self) -> &Redeemed { &self.redeemed }
}
impl Operation for Genesis {
    #[inline]
    fn op_type(&self) -> OpType { OpType::Genesis }
    #[inline]
    fn full_type(&self) -> OpFullType { OpFullType::Genesis }
    #[inline]
    fn id(&self) -> OpId { OpId(self.commitment_id().into_inner()) }
    #[inline]
    fn contract_id(&self) -> ContractId { ContractId::from_inner(self.id().into_inner()) }
    #[inline]
    fn transition_type(&self) -> Option<TransitionType> { None }
    #[inline]
    fn extension_type(&self) -> Option<ExtensionType> { None }
    #[inline]
    fn metadata(&self) -> &SmallBlob { &self.metadata }
    #[inline]
    fn globals(&self) -> &GlobalState { &self.globals }
    #[inline]
    fn valencies(&self) -> &Valencies { &self.valencies }
    #[inline]
    fn assignments(&self) -> AssignmentsRef { (&self.assignments).into() }
    #[inline]
    fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>> {
        self.assignments
            .get(&t)
            .map(TypedAssigns::transmutate_seals)
    }
    #[inline]
    fn inputs(&self) -> Inputs { empty!() }
}
impl Operation for Extension {
    #[inline]
    fn op_type(&self) -> OpType { OpType::StateExtension }
    #[inline]
    fn full_type(&self) -> OpFullType { OpFullType::StateExtension(self.extension_type) }
    #[inline]
    fn id(&self) -> OpId { self.commitment_id() }
    #[inline]
    fn contract_id(&self) -> ContractId { self.contract_id }
    #[inline]
    fn transition_type(&self) -> Option<TransitionType> { None }
    #[inline]
    fn extension_type(&self) -> Option<ExtensionType> { Some(self.extension_type) }
    #[inline]
    fn metadata(&self) -> &SmallBlob { &self.metadata }
    #[inline]
    fn globals(&self) -> &GlobalState { &self.globals }
    #[inline]
    fn valencies(&self) -> &Valencies { &self.valencies }
    #[inline]
    fn assignments(&self) -> AssignmentsRef { (&self.assignments).into() }
    #[inline]
    fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>> {
        self.assignments
            .get(&t)
            .map(TypedAssigns::transmutate_seals)
    }
    #[inline]
    fn inputs(&self) -> Inputs { empty!() }
}
impl Operation for Transition {
    #[inline]
    fn op_type(&self) -> OpType { OpType::StateTransition }
    #[inline]
    fn full_type(&self) -> OpFullType { OpFullType::StateTransition(self.transition_type) }
    #[inline]
    fn id(&self) -> OpId { self.commitment_id() }
    #[inline]
    fn contract_id(&self) -> ContractId { self.contract_id }
    #[inline]
    fn transition_type(&self) -> Option<TransitionType> { Some(self.transition_type) }
    #[inline]
    fn extension_type(&self) -> Option<ExtensionType> { None }
    #[inline]
    fn metadata(&self) -> &SmallBlob { &self.metadata }
    #[inline]
    fn globals(&self) -> &GlobalState { &self.globals }
    #[inline]
    fn valencies(&self) -> &Valencies { &self.valencies }
    #[inline]
    fn assignments(&self) -> AssignmentsRef { (&self.assignments).into() }
    #[inline]
    fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>> {
        self.assignments.get(&t).cloned()
    }
    fn inputs(&self) -> Inputs { self.inputs.clone() }
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, From)]
pub enum OpRef<'op> {
    #[from]
    Genesis(&'op Genesis),
    #[from]
    Transition(&'op Transition),
    #[from]
    Extension(&'op Extension),
}
impl<'op> Operation for OpRef<'op> {
    fn op_type(&self) -> OpType {
        match self {
            OpRef::Genesis(op) => op.op_type(),
            OpRef::Transition(op) => op.op_type(),
            OpRef::Extension(op) => op.op_type(),
        }
    }
    fn full_type(&self) -> OpFullType {
        match self {
            OpRef::Genesis(op) => op.full_type(),
            OpRef::Transition(op) => op.full_type(),
            OpRef::Extension(op) => op.full_type(),
        }
    }
    fn id(&self) -> OpId {
        match self {
            OpRef::Genesis(op) => op.id(),
            OpRef::Transition(op) => op.id(),
            OpRef::Extension(op) => op.id(),
        }
    }
    fn contract_id(&self) -> ContractId {
        match self {
            OpRef::Genesis(op) => op.contract_id(),
            OpRef::Transition(op) => op.contract_id(),
            OpRef::Extension(op) => op.contract_id(),
        }
    }
    fn transition_type(&self) -> Option<TransitionType> {
        match self {
            OpRef::Genesis(op) => op.transition_type(),
            OpRef::Transition(op) => op.transition_type(),
            OpRef::Extension(op) => op.transition_type(),
        }
    }
    fn extension_type(&self) -> Option<ExtensionType> {
        match self {
            OpRef::Genesis(op) => op.extension_type(),
            OpRef::Transition(op) => op.extension_type(),
            OpRef::Extension(op) => op.extension_type(),
        }
    }
    fn metadata(&self) -> &SmallBlob {
        match self {
            OpRef::Genesis(op) => op.metadata(),
            OpRef::Transition(op) => op.metadata(),
            OpRef::Extension(op) => op.metadata(),
        }
    }
    fn globals(&self) -> &GlobalState {
        match self {
            OpRef::Genesis(op) => op.globals(),
            OpRef::Transition(op) => op.globals(),
            OpRef::Extension(op) => op.globals(),
        }
    }
    fn valencies(&self) -> &Valencies {
        match self {
            OpRef::Genesis(op) => op.valencies(),
            OpRef::Transition(op) => op.valencies(),
            OpRef::Extension(op) => op.valencies(),
        }
    }
    fn assignments(&self) -> AssignmentsRef<'op> {
        match self {
            OpRef::Genesis(op) => (&op.assignments).into(),
            OpRef::Transition(op) => (&op.assignments).into(),
            OpRef::Extension(op) => (&op.assignments).into(),
        }
    }
    fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>> {
        match self {
            OpRef::Genesis(op) => op.assignments_by_type(t),
            OpRef::Transition(op) => op.assignments_by_type(t),
            OpRef::Extension(op) => op.assignments_by_type(t),
        }
    }
    fn inputs(&self) -> Inputs {
        match self {
            OpRef::Genesis(op) => op.inputs(),
            OpRef::Transition(op) => op.inputs(),
            OpRef::Extension(op) => op.inputs(),
        }
    }
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn contract_id_display() {
        const ID: &str = "rgb:pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSehE-WvfhF9";
        let id = ContractId::from_raw_array([0x6c; 32]);
        assert_eq!(ID.len(), 58);
        assert_eq!(ID.replace('-', ""), format!("{id:#}"));
        assert_eq!(ID, id.to_string());
        assert_eq!(ID, id.to_baid58_string());
    }
    #[test]
    fn contract_id_from_str() {
        let id = ContractId::from_raw_array([0x6c; 32]);
        assert_eq!(
            Ok(id),
            ContractId::from_str("rgb:pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSehE-WvfhF9")
        );
        assert_eq!(
            Ok(id),
            ContractId::from_str("pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSehE-WvfhF9")
        );
        assert_eq!(
            Ok(id),
            ContractId::from_str("rgb:pkXwpsbaemTWhtSgVDGF25hEijtTAnPjzhB63ZwSehEWvfhF9")
        );
        assert_eq!(
            Ok(id),
            ContractId::from_str("pkXwpsbaemTWhtSgVDGF25hEijtTAnPjzhB63ZwSehEWvfhF9")
        );
        assert!(
            ContractId::from_str("rgb:pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSeh-EWvfhF9")
                .is_err()
        );
        assert!(
            ContractId::from_str("rgb:pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSehEWvfhF9")
                .is_err()
        );
    }
}