use crate::{
    error::Error,
    interpreter::{
        data::{ArcisField, ArcisValue, BoolData, CurveData, Data, ValData},
        data_store::SimpleAddress,
        generic_context::MatchKind,
        pointer::Pointer,
        supported_type::{IntegerInfo, PointerDepth, RustType, SupportedType},
        Interpreter,
    },
};
use arcis_compiler::{
    core::expressions::field_expr::FieldExpr,
    utils::used_field::UsedField,
    ArcisFloatValue,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConversionKind {
    Implicit,
    Explicit,
}

impl Interpreter<'_> {
    pub fn conversion(
        &mut self,
        pointer: Pointer,
        new_type: &SupportedType,
        kind: ConversionKind,
        span: impl quote::ToTokens,
    ) -> Result<Pointer, Error> {
        let data = self.memory.compute(pointer)?;
        let res = self.conversion_data(data, new_type, kind, span)?;
        Ok(res.as_pointer())
    }

    fn conversion_data(
        &mut self,
        data: Data,
        new_type: &SupportedType,
        kind: ConversionKind,
        span: impl quote::ToTokens,
    ) -> Result<SimpleAddress, Error> {
        let data = match (new_type, &data) {
            (SupportedType::ImplAny, _) => data,
            (SupportedType::Var(a), _) => match self.generic_context.get_type_var(*a) {
                None => {
                    let ty = data.get_type(&self.memory);
                    self.generic_context
                        .set_type_var(*a, ty)
                        .expect("We just checked it was empty.");
                    data
                }
                Some(s) => {
                    return self.conversion_data(data, &s, kind, span);
                }
            },
            (SupportedType::FieldValue(new_r), Data::Val(ValData(v, old_r))) => {
                if new_r == old_r {
                    data
                } else if *old_r == RustType::Integer(None)
                    && matches!(new_r, RustType::Integer(Some(_)))
                {
                    Data::Val(ValData(*v, *new_r))
                } else if *new_r == RustType::Integer(None)
                    && matches!(old_r, RustType::Integer(Some(_)))
                {
                    Data::Val(ValData(*v, *old_r))
                } else if kind == ConversionKind::Explicit {
                    if new_r.is_float() && !old_r.is_float() {
                        Data::Val(ValData(
                            *v * ArcisField::power_of_two(ArcisFloatValue::MINUS_PLSB),
                            *new_r,
                        ))
                    } else if !new_r.is_float() && old_r.is_float() {
                        return Err(Error::new_spanned(
                            span,
                            "Conversion from float to integer is not yet implemented.",
                        ));
                    } else if *new_r == RustType::Integer(None) {
                        Data::Val(ValData(*v, *old_r))
                    } else if let RustType::Integer(Some(IntegerInfo { signed, width })) = *new_r {
                        Data::Val(ValData(
                            ArcisValue::new(FieldExpr::KeepLsBits(*v, width, signed)),
                            *new_r,
                        ))
                    } else {
                        Data::Val(ValData(*v, *new_r))
                    }
                } else {
                    return Err(Error::new_spanned(
                        &span,
                        format!(
                            "Implicit conversion impossible in `{}` from `{old_r}` to `{new_r}`.",
                            span.to_token_stream()
                        ),
                    ));
                }
            }
            (SupportedType::FieldValue(new_r), Data::Bool(BoolData(b)))
                if kind == ConversionKind::Explicit =>
            {
                Data::Val(ValData(ArcisValue::from(*b), *new_r))
            }
            (SupportedType::Bool, Data::Bool(b)) => Data::Bool(*b),
            (SupportedType::Curve(new_r), Data::Curve(CurveData(_, old_r))) => {
                if new_r == old_r {
                    data
                } else {
                    return Err(Error::new_spanned(
                        &span,
                        format!(
                            "Implicit conversion impossible in `{}` from `{old_r}` to `{new_r}`.",
                            span.to_token_stream()
                        ),
                    ));
                }
            }
            (
                SupportedType::Array(..)
                | SupportedType::Slice(..)
                | SupportedType::Struct(..)
                | SupportedType::Tuple(..),
                Data::Collection(..),
            ) => {
                let data_ty = data.get_type(&self.memory);
                let success = self.generic_context.includes_type(
                    &mut self.memory,
                    new_type,
                    &data_ty,
                    MatchKind::NoDeref,
                );
                if success {
                    data
                } else {
                    return Err(Error::new_spanned(
                        &span,
                        format!(
                            "Array conversion impossible in `{}` from {data_ty} to {new_type}.",
                            span.to_token_stream()
                        ),
                    ));
                }
            }
            (SupportedType::Unit, Data::Unit) => data,
            (SupportedType::Fn, Data::Fn(_)) => data,
            (SupportedType::Ref(_), Data::Pointer(_)) => {
                self.conversion_data_ptr(data, new_type, span)?
            }
            (_, Data::Pointer(_)) if kind == ConversionKind::Implicit => {
                // Only used in receivers.
                self.conversion_data_ptr(data, new_type, span)?
            }
            _ => {
                let data_ty = data.get_type(&self.memory);
                return if data_ty == *new_type {
                    Err(Error::new_spanned(
                        &span,
                        format!(
                            "Unimplemented conversion in `{}` from `{}` to itself.",
                            span.to_token_stream(),
                            data_ty,
                        ),
                    ))
                } else {
                    Err(Error::new_spanned(
                        &span,
                        format!(
                            "Unsupported conversion in `{}` from `{}` to `{}`.",
                            span.to_token_stream(),
                            data_ty,
                            new_type
                        ),
                    ))
                };
            }
        };

        let addr = self.memory.push(data);
        Ok(addr)
    }
    fn conversion_data_ptr(
        &mut self,
        data_ptr: Data,
        new_type: &SupportedType,
        span: impl quote::ToTokens,
    ) -> Result<Data, Error> {
        let new_depth = new_type.pointer_depth();
        let orig_data_type = data_ptr.get_type(&self.memory);
        let old_depth = orig_data_type.pointer_depth();
        let PointerDepth::FromNonPtr(old_depth) = old_depth else {
            return Err(Error::new_spanned(
                span,
                "Converted value has undefined type.",
            ));
        };
        let data = match new_depth {
            PointerDepth::FromNonPtr(new_depth) => match new_depth.cmp(&old_depth) {
                std::cmp::Ordering::Equal => data_ptr,
                std::cmp::Ordering::Less => {
                    let mut data_ptr = data_ptr;
                    for _ in 0..(old_depth - new_depth) {
                        let Data::Pointer(ptr) = data_ptr else {
                            return Err(Error::new_spanned(span, "Pointer should be a pointer."));
                        };
                        data_ptr = self.memory.compute(ptr)?;
                    }
                    data_ptr
                }
                std::cmp::Ordering::Greater => {
                    return Err(Error::new_spanned(
                        span,
                        "Conversion to a deeper pointer is impossible.",
                    ));
                }
            },
            PointerDepth::FromAny(new_depth) => {
                if old_depth >= new_depth {
                    data_ptr
                } else {
                    return Err(Error::new_spanned(
                        span,
                        "Conversion to a deeper pointer is not possible.",
                    ));
                }
            }
        };
        let converted_data_type = data.get_type(&self.memory);
        let success = self.generic_context.includes_type(
            &mut self.memory,
            new_type,
            &converted_data_type,
            MatchKind::NoDeref,
        );
        if !success {
            return Err(Error::new_spanned(
                span,
                format!("Reference conversion impossible from `{orig_data_type}` to `{new_type}`."),
            ));
        }
        Ok(data)
    }
}
