use std::collections::{btree_map, BTreeMap};
use std::io;
use aluvm::data::encoding::{Decode, Encode};
use aluvm::library::{Lib, LibId, LibSite};
use aluvm::Program;
use amplify::confinement::{Confined, SmallBlob, SmallOrdMap, TinyOrdMap};
use strict_encoding::{
    DecodeError, ReadStruct, StrictDecode, StrictEncode, StrictProduct, StrictStruct, StrictTuple,
    StrictType, TypedRead, TypedWrite, WriteStruct,
};
use crate::vm::RgbIsa;
use crate::{AssignmentType, ExtensionType, GlobalStateType, TransitionType, LIB_NAME_RGB};
pub const LIBS_MAX_TOTAL: usize = 1024;
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
#[derive(StrictDumb)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum EntryPoint {
    #[strict_type(dumb)]
    ValidateGenesis,
    ValidateTransition(TransitionType),
    ValidateExtension(ExtensionType),
    ValidateGlobalState(GlobalStateType),
    ValidateOwnedState(AssignmentType),
}
impl StrictType for EntryPoint {
    const STRICT_LIB_NAME: &'static str = LIB_NAME_RGB;
}
impl StrictProduct for EntryPoint {}
impl StrictTuple for EntryPoint {
    const FIELD_COUNT: u8 = 1;
}
impl StrictEncode for EntryPoint {
    fn strict_encode<W: TypedWrite>(&self, writer: W) -> io::Result<W> {
        let mut val = [0u8; 3];
        let (ty, subty) = match self {
            EntryPoint::ValidateGenesis => (0, 0u16),
            EntryPoint::ValidateTransition(ty) => (1, *ty),
            EntryPoint::ValidateExtension(ty) => (2, *ty),
            EntryPoint::ValidateGlobalState(ty) => (3, *ty),
            EntryPoint::ValidateOwnedState(ty) => (4, *ty),
        };
        val[0] = ty;
        val[1..].copy_from_slice(&subty.to_le_bytes());
        val.strict_encode(writer)
    }
}
impl StrictDecode for EntryPoint {
    fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
        let val = <[u8; 3]>::strict_decode(reader)?;
        let mut ty = [0u8; 2];
        ty.copy_from_slice(&val[1..]);
        let ty = u16::from_le_bytes(ty);
        Ok(match val[0] {
            0 => EntryPoint::ValidateGenesis,
            1 => EntryPoint::ValidateTransition(ty),
            2 => EntryPoint::ValidateExtension(ty),
            3 => EntryPoint::ValidateGlobalState(ty),
            4 => EntryPoint::ValidateOwnedState(ty),
            x => return Err(DecodeError::EnumTagNotKnown(s!("EntryPoint"), x)),
        })
    }
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct AluScript {
    pub libs: Confined<BTreeMap<LibId, Lib>, 0, LIBS_MAX_TOTAL>,
    pub entry_points: SmallOrdMap<EntryPoint, LibSite>,
}
impl StrictType for AluScript {
    const STRICT_LIB_NAME: &'static str = LIB_NAME_RGB;
}
impl StrictProduct for AluScript {}
impl StrictStruct for AluScript {
    const ALL_FIELDS: &'static [&'static str] = &["libs", "entryPoints"];
}
impl StrictEncode for AluScript {
    fn strict_encode<W: TypedWrite>(&self, writer: W) -> io::Result<W> {
        let libs = self.libs.iter().map(|(id, lib)| {
            let lib = SmallBlob::try_from(lib.serialize()).expect(
                "the RGB Core library must not be used to create AluVM library size exceeding 2^16",
            );
            (*id, lib)
        });
        writer.write_struct::<Self>(|w| {
            Ok(w.write_field(
                fname!("libs"),
                &TinyOrdMap::try_from_iter(libs).expect(
                    "the RGB Core library must not be used to create AluVM scripts with more than \
                     255 libraries",
                ),
            )?
            .write_field(fname!("entryPoints"), &self.entry_points)?
            .complete())
        })
    }
}
impl StrictDecode for AluScript {
    fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
        reader.read_struct(|r| {
            let libs = r
                .read_field::<TinyOrdMap<LibId, SmallBlob>>(fname!("libs"))?
                .into_iter()
                .map(|(id, lib)| {
                    let lib = Lib::deserialize(lib)
                        .map_err(|err| DecodeError::DataIntegrityError(err.to_string()))?;
                    Ok((id, lib))
                })
                .collect::<Result<BTreeMap<_, _>, DecodeError>>()?;
            let entry_points = r.read_field(fname!("entryPoints"))?;
            Ok(AluScript {
                libs: Confined::try_from(libs).expect("strict decoder guarantees"),
                entry_points,
            })
        })
    }
}
impl Program for AluScript {
    type Isa = RgbIsa;
    type Iter<'a> = btree_map::Values<'a, LibId, Lib> where Self: 'a;
    fn lib_count(&self) -> u16 { self.libs.len() as u16 }
    fn libs(&self) -> Self::Iter<'_> { self.libs.values() }
    fn lib(&self, id: LibId) -> Option<&Lib> { self.libs.get(&id) }
    fn entrypoint(&self) -> LibSite { panic!("AluScript doesn't have a single entry point") }
}