use alloc::{string::ToString, sync::Arc, vec::Vec};
use assembly::{Assembler, Compile};
use vm_core::{
    mast::{MastForest, MastNodeId},
    Program,
};
use super::{Digest, Felt};
use crate::{
    utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
    NoteError,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NoteScript {
    mast: Arc<MastForest>,
    entrypoint: MastNodeId,
}
impl NoteScript {
    pub fn new(code: Program) -> Self {
        Self {
            entrypoint: code.entrypoint(),
            mast: Arc::new(code.into()),
        }
    }
    pub fn compile(source_code: impl Compile, assembler: Assembler) -> Result<Self, NoteError> {
        let program = assembler
            .assemble_program(source_code)
            .map_err(|report| NoteError::NoteScriptAssemblyError(report.to_string()))?;
        Ok(Self::new(program))
    }
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, NoteError> {
        Self::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)
    }
    pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
        assert!(mast.get_node_by_id(entrypoint).is_some());
        Self { mast, entrypoint }
    }
    pub fn hash(&self) -> Digest {
        self.mast[self.entrypoint].digest()
    }
    pub fn mast(&self) -> Arc<MastForest> {
        self.mast.clone()
    }
}
impl From<&NoteScript> for Vec<Felt> {
    fn from(script: &NoteScript) -> Self {
        let mut bytes = script.mast.to_bytes();
        let len = bytes.len();
        let missing = if len % 4 > 0 { 4 - (len % 4) } else { 0 };
        bytes.resize(bytes.len() + missing, 0);
        let final_size = 2 + bytes.len();
        let mut result = Vec::with_capacity(final_size);
        result.push(Felt::from(script.entrypoint.as_u32()));
        result.push(Felt::new(len as u64));
        let mut encoded: &[u8] = &bytes;
        while encoded.len() >= 4 {
            let (data, rest) =
                encoded.split_first_chunk::<4>().expect("The length has been checked");
            let number = u32::from_le_bytes(*data);
            result.push(Felt::new(number.into()));
            encoded = rest;
        }
        result
    }
}
impl From<NoteScript> for Vec<Felt> {
    fn from(value: NoteScript) -> Self {
        (&value).into()
    }
}
impl TryFrom<&[Felt]> for NoteScript {
    type Error = DeserializationError;
    fn try_from(elements: &[Felt]) -> Result<Self, Self::Error> {
        if elements.len() < 2 {
            return Err(DeserializationError::UnexpectedEOF);
        }
        let entrypoint: u32 = elements[0].try_into().map_err(DeserializationError::InvalidValue)?;
        let len = elements[1].as_int();
        let mut data = Vec::with_capacity(elements.len() * 4);
        for &felt in &elements[2..] {
            let v: u32 = felt.try_into().map_err(DeserializationError::InvalidValue)?;
            data.extend(v.to_le_bytes())
        }
        data.shrink_to(len as usize);
        let mast = MastForest::read_from_bytes(&data)?;
        let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)?;
        Ok(NoteScript::from_parts(Arc::new(mast), entrypoint))
    }
}
impl TryFrom<Vec<Felt>> for NoteScript {
    type Error = DeserializationError;
    fn try_from(value: Vec<Felt>) -> Result<Self, Self::Error> {
        value.as_slice().try_into()
    }
}
impl Serializable for NoteScript {
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        self.mast.write_into(target);
        target.write_u32(self.entrypoint.as_u32());
    }
}
impl Deserializable for NoteScript {
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        let mast = MastForest::read_from(source)?;
        let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
        Ok(Self::from_parts(Arc::new(mast), entrypoint))
    }
}
#[cfg(test)]
mod tests {
    use super::{Assembler, Felt, NoteScript, Vec};
    use crate::testing::notes::DEFAULT_NOTE_CODE;
    #[test]
    fn test_note_script_to_from_felt() {
        let assembler = Assembler::default();
        let tx_script_src = DEFAULT_NOTE_CODE;
        let note_script = NoteScript::compile(tx_script_src, assembler).unwrap();
        let encoded: Vec<Felt> = (¬e_script).into();
        let decoded: NoteScript = encoded.try_into().unwrap();
        assert_eq!(note_script, decoded);
    }
}