use crate::{
    core::{
        actually_used_field::ActuallyUsedField,
        expressions::{expr::Expr, field_expr::FieldExpr},
        global_value::global_expr_store::with_global_expr_store_as_local,
    },
    utils::field::{BaseField, ScalarField},
};
use std::marker::PhantomData;

#[derive(Clone, Copy, Debug)]
pub struct FieldValue<F: ActuallyUsedField> {
    pub(super) id: usize,
    pub(super) marker: PhantomData<F>,
}

impl<F: ActuallyUsedField> FieldValue<F> {
    pub fn new(expr: FieldExpr<F, FieldValue<F>>) -> FieldValue<F> {
        let id = with_global_expr_store_as_local(|expr_store| {
            expr_store.new_expr(F::field_expr_to_expr(
                expr.clone().apply(|dep: FieldValue<F>| dep.id),
            ))
        });
        FieldValue {
            id,
            marker: PhantomData,
        }
    }

    pub fn get_id(&self) -> usize {
        self.id
    }

    pub fn from_id(id: usize) -> FieldValue<F> {
        #[cfg(debug_assertions)]
        with_global_expr_store_as_local(|expr_store| {
            assert!(expr_store.contains_expr_id(id));
            let _ = F::bounds_to_field_bounds(*expr_store.get_bounds(id));
        });
        FieldValue {
            id,
            marker: PhantomData,
        }
    }
    pub fn from_expr(expr: Expr<usize>) -> FieldValue<F> {
        let id = with_global_expr_store_as_local(|expr_store| {
            let id = expr_store.new_expr(expr);
            #[cfg(debug_assertions)]
            let _ = F::bounds_to_field_bounds(*expr_store.get_bounds(id));
            id
        });
        FieldValue {
            id,
            marker: PhantomData,
        }
    }
}

impl From<FieldValue<BaseField>> for BaseField {
    fn from(value: FieldValue<BaseField>) -> Self {
        if let Expr::Base(FieldExpr::Val(v)) = value.expr() {
            v
        } else {
            panic!("non-constant instance of FieldValue<BaseField>");
        }
    }
}

impl From<FieldValue<ScalarField>> for ScalarField {
    fn from(value: FieldValue<ScalarField>) -> Self {
        if let Expr::Scalar(FieldExpr::Val(v)) = value.expr() {
            v
        } else {
            panic!("non-constant instance of FieldValue<ScalarField>");
        }
    }
}
