use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;

use knok_core::{CallOp, Expr};

use super::tensor::{BoolTensor, TraceOperand, TraceTensor};

static NEXT_NODE_ID: AtomicU64 = AtomicU64::new(1);
static NEXT_TUPLE_ID: AtomicU64 = AtomicU64::new(1);

pub(crate) fn call_output<Output: TraceTensor>(op: CallOp, args: Vec<Expr>) -> Output {
    Output::from_expr(node_expr(Expr::Call { op, args }))
}

pub(crate) fn node_expr(value: Expr) -> Expr {
    Expr::Node {
        node_id: NEXT_NODE_ID.fetch_add(1, Ordering::Relaxed),
        value: Arc::new(value),
    }
}

pub(crate) fn next_tuple_id() -> u64 {
    NEXT_TUPLE_ID.fetch_add(1, Ordering::Relaxed)
}

pub(crate) fn unary<T: TraceTensor>(op: CallOp, value: T) -> T {
    T::from_expr(node_expr(Expr::Call {
        op,
        args: vec![value.into_expr()],
    }))
}

pub(crate) fn unary_bool<T: BoolTensor>(op: CallOp, value: T) -> T::Bool {
    <T::Bool as TraceTensor>::from_expr(node_expr(Expr::Call {
        op,
        args: vec![value.into_expr()],
    }))
}

pub(crate) fn binary_same<L, R>(op: CallOp, lhs: L, rhs: R) -> L
where
    L: TraceTensor,
    R: TraceOperand<L::Elem>,
{
    L::from_expr(node_expr(Expr::Call {
        op,
        args: vec![lhs.into_expr(), rhs.into_operand_expr()],
    }))
}

pub(crate) fn binary_bool<L, R>(op: CallOp, lhs: L, rhs: R) -> L::Bool
where
    L: BoolTensor,
    R: TraceOperand<L::Elem>,
{
    <L::Bool as TraceTensor>::from_expr(node_expr(Expr::Call {
        op,
        args: vec![lhs.into_expr(), rhs.into_operand_expr()],
    }))
}
