use super::{check_ok_needed_outputs, gather_operands, ParseNode, ParseNodeFunctional};

use crate::ast_node::{ASTContext, Node, TokenSlice};
use crate::operations::Operation;
use crate::syntax_error::SyntaxError;
use crate::tokens::Token;
use crate::types::{FunctionSignature, IJType};
use anyhow::Result;
use std::rc::Rc;

pub struct UnaryFunction;
impl ParseNode for UnaryFunction {
    fn next_node(
        op: Token,
        slice: TokenSlice,
        context: &mut ASTContext,
    ) -> Result<(Rc<Node>, TokenSlice)> {
        let Token::UnaryFunction(function_name) = op else {
            unreachable!()
        };
        let (operands, rest) = gather_operands(
            vec![vec![IJType::Tensor(None)], vec![IJType::Number(None)]],
            slice,
            context,
        )?;
        let operand = operands[0].clone();

        Ok((
            Rc::new(Node::new_skip_number_inference(
                Operation::UnaryFunction(function_name),
                vec![operand.output_type.clone()],
                operand.output_type.clone(),
                operands,
                context,
            )?),
            rest,
        ))
    }
}
impl ParseNodeFunctional for UnaryFunction {
    fn next_node_functional_impl(
        op: Token,
        slice: TokenSlice,
        context: &mut ASTContext,
        needed_outputs: Option<&[IJType]>,
    ) -> Result<(Vec<Rc<Node>>, TokenSlice)> {
        let Token::UnaryFunction(function_name) = op else {
            unreachable!()
        };
        let rest = slice.move_start(1)?;
        let mut output_tokens = vec![];
        if check_ok_needed_outputs(needed_outputs, &IJType::Tensor(None)) {
            output_tokens.push(Rc::new(Node::new(
                Operation::UnaryFunction(function_name.clone()),
                vec![],
                IJType::Function(FunctionSignature::new(
                    vec![IJType::Tensor(None)],
                    IJType::Tensor(None),
                )),
                vec![],
                context,
            )?));
        }
        if check_ok_needed_outputs(needed_outputs, &IJType::Number(None)) {
            output_tokens.push(Rc::new(Node::new(
                Operation::UnaryFunction(function_name),
                vec![],
                IJType::Function(FunctionSignature::new(
                    vec![IJType::Number(None)],
                    IJType::Number(None),
                )),
                vec![],
                context,
            )?));
        }
        if output_tokens.is_empty() {
            return Err(SyntaxError::RequiredOutputsDoNotMatchFunctionOutputs(
                format!("{:?}", needed_outputs),
                IJType::Tensor(None).to_string(),
            )
            .into());
        }
        Ok((output_tokens, rest))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::function_enums::UnaryMathFunctionEnum;
    use crate::parser::parse_str_no_context;

    #[test]
    fn test_unary_function() -> Result<()> {
        let (node, _) = parse_str_no_context("sin 1.0")?;
        assert_eq!(
            node.op,
            Operation::UnaryFunction(UnaryMathFunctionEnum::Sin)
        );
        assert_eq!(node.operands.len(), 1);

        let (node, _) = parse_str_no_context("sin cos 1.0")?;
        assert_eq!(
            node.op,
            Operation::UnaryFunction(UnaryMathFunctionEnum::Sin)
        );
        assert_eq!(node.operands.len(), 1);
        Ok(())
    }

    #[test]
    fn test_unary_function_functional() -> Result<()> {
        let (node, _) = parse_str_no_context("~sin: Fn(T->T)")?;
        assert_eq!(
            node.op,
            Operation::UnaryFunction(UnaryMathFunctionEnum::Sin)
        );
        assert_eq!(
            node.output_type,
            IJType::Function(FunctionSignature::new(
                vec![IJType::Tensor(None)],
                IJType::Tensor(None)
            ))
        );

        let (node, _) = parse_str_no_context("~exp: Fn(N->N)")?;
        assert_eq!(
            node.op,
            Operation::UnaryFunction(UnaryMathFunctionEnum::Exp)
        );
        assert_eq!(
            node.output_type,
            IJType::Function(FunctionSignature::new(
                vec![IJType::Number(None)],
                IJType::Number(None)
            ))
        );
        Ok(())
    }

    #[test]
    fn test_unary_function_composition() -> Result<()> {
        let (node, _) = parse_str_no_context("@(ln, sin, cos) [1]")?;
        assert_eq!(node.op, Operation::FunctionComposition(3));
        assert_eq!(node.output_type, IJType::Tensor(None));

        Ok(())
    }
    #[test]

    fn test_unary_function_specific_type() -> Result<()> {
        let (node, _) = parse_str_no_context("~sin: Fn(N<f64>->N<f64>)")?;
        assert_eq!(
            node.op,
            Operation::UnaryFunction(UnaryMathFunctionEnum::Sin)
        );
        assert_eq!(
            node.output_type,
            IJType::Function(FunctionSignature::new(
                vec![IJType::Number(Some("f64".to_string()))],
                IJType::Number(Some("f64".to_string()))
            ))
        );

        Ok(())
    }
}
