use super::Evaluable;
use crate::eval::env::Env;
use crate::eval::EvalContext;
use crate::eval::EvalError;
use ergo_chain_types::ADDigest;
use ergotree_ir::mir::avl_tree_data::{AvlTreeData, AvlTreeFlags};
use ergotree_ir::mir::constant::TryExtractInto;
use ergotree_ir::mir::create_avl_tree::CreateAvlTree;
use ergotree_ir::mir::value::Value;
use sigma_util::AsVecU8;
use std::convert::TryFrom;

impl Evaluable for CreateAvlTree {
    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
        let flags_v = self.flags.eval(env, ctx)?.try_extract_into::<i8>()? as u8;
        let digest_v = self.digest.eval(env, ctx)?.try_extract_into::<Vec<i8>>()?;
        let key_length = self.key_length.eval(env, ctx)?.try_extract_into::<i32>()? as u32;
        let value_length_opt = match self.value_length.clone() {
            Some(expr) => Some(Box::new(
                expr.eval(env, ctx)?.try_extract_into::<i32>()? as u32
            )),
            None => None,
        };

        let tree_flags = AvlTreeFlags::parse(flags_v);
        let digest = ADDigest::try_from(digest_v.as_vec_u8()).map_err(map_eval_err)?;

        Ok(Value::AvlTree(Box::new(AvlTreeData {
            tree_flags,
            digest,
            key_length,
            value_length_opt,
        })))
    }
}

fn map_eval_err<T: std::fmt::Debug>(e: T) -> EvalError {
    EvalError::AvlTree(format!("{:?}", e))
}

#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod tests {
    use super::*;
    use crate::eval::tests::eval_out_wo_ctx;

    use ergo_chain_types::ADDigest;
    use ergotree_ir::mir::{
        avl_tree_data::{AvlTreeData, AvlTreeFlags},
        expr::Expr,
    };
    use scorex_crypto_avltree::authenticated_tree_ops::AuthenticatedTreeOps;
    use scorex_crypto_avltree::batch_avl_prover::BatchAVLProver;
    use scorex_crypto_avltree::batch_node::{AVLTree, Node, NodeHeader};
    use sigma_ser::ScorexSerializable;

    #[test]
    fn eval_create_avl_tree() {
        let prover = BatchAVLProver::new(
            AVLTree::new(
                |digest| Node::LabelOnly(NodeHeader::new(Some(*digest), None)),
                1,
                None,
            ),
            true,
        );
        let initial_digest =
            ADDigest::scorex_parse_bytes(&prover.digest().unwrap().into_iter().collect::<Vec<_>>())
                .unwrap();
        let flags = AvlTreeFlags::new(false, false, false);
        let expr: Expr = CreateAvlTree::new(
            Expr::Const(flags.clone().into()),
            Expr::Const(initial_digest.into()),
            1.into(),
            None,
        )
        .unwrap()
        .into();

        let tree = eval_out_wo_ctx::<AvlTreeData>(&expr);
        assert_eq!(tree.digest, initial_digest);
        assert_eq!(tree.key_length, 1);
        assert_eq!(tree.value_length_opt, None);
        assert_eq!(tree.tree_flags, flags);
    }
}
