use tract_core::internal::*;
use tract_gpu::fact::DeviceTypedFactExt;
use tract_gpu::ops::change_axes::GpuAxisOp;
use tract_gpu::ops::pulse::GpuDelay;
use tract_gpu::rule_ensure;
use crate::ops::{CudaGgmlGemm, CudaRingGemm};
pub fn fuse_window_into_gemm(
_ctx: &(),
model: &TypedModel,
node: &TypedNode,
_node_name: &str,
delay: &GpuDelay,
) -> TractResult<Option<TypedModelPatch>> {
let op = &delay.inner;
rule_ensure!(op.delay == 0 && op.zero_pad && op.overlap > 0);
let slot = model.node_input_facts(node.id)?[0];
let slot_shape = slot.as_device_fact().map(|f| &f.shape).unwrap_or(&slot.shape);
rule_ensure!(slot_shape[op.axis].is_one());
let mut window_axis_ops = delay.out_axis_ops.clone();
let mut window_wire = node.id;
let gemm = loop {
rule_if_some!(succ = model.single_succ(window_wire)?);
let Some(axis_op) = succ.op_as::<GpuAxisOp>() else { break succ };
window_axis_ops.push(axis_op.clone());
window_wire = succ.id;
};
rule_ensure!(!window_axis_ops.is_empty());
rule_ensure!(gemm.op_is::<CudaGgmlGemm>());
rule_ensure!(gemm.inputs[1] == window_wire.into());
let window = model.node_input_facts(gemm.id)?[1].clone();
let act = model.node_input_facts(gemm.id)?[0];
let (window, act) = (
window.as_device_fact().map(|f| f.clone().into_typed_fact()).unwrap_or(window),
act.as_device_fact().map(|f| f.clone().into_typed_fact()).unwrap_or(act.clone()),
);
rule_ensure!(act.rank() >= 2 && window.rank() == act.rank());
let out = CudaGgmlGemm.resolve_output_facts(&[&act, &window])?;
rule_if_some!(m = out[0].shape[out[0].rank() - 2].as_i64());
rule_if_some!(k = act.shape[act.rank() - 1].as_i64());
rule_if_some!(n = window.shape[window.rank() - 2].as_i64());
rule_ensure!(m <= 8 && k % 2 == 0);
let slots = op.overlap + 1;
let (n, k) = (n as usize, k as usize);
rule_ensure!(
n.is_multiple_of(slots) || (k.is_multiple_of(slots) && (k / slots).is_multiple_of(2))
);
let mut patch = TypedModelPatch::default();
let inputs = patch.taps(model, &[gemm.inputs[0], node.inputs[0]])?;
let wire = patch.wire_node(
gemm.name.clone(),
CudaRingGemm { delay: op.clone(), window_axis_ops },
&inputs,
)?;
patch.shunt_outside(model, gemm.id.into(), wire[0])?;
Ok(Some(patch))
}