use ir::{
    id,
    instruction::{Filter, Kind},
    GraphIr, Instruction, Map,
};

use crate::value::Variant;
mod instr;

#[derive(Debug, Default)]
pub struct Evaluator {
    ir: GraphIr,

    /// What the output of each individual streamer, and as result its output sockets, is.
    /// Grows larger as evaluation continues,
    /// as there's no mechanism for purging never-to-be-used-anymore instructions yet.
    evaluated: Map<id::Output, Variant>,
}

impl crate::Evaluator for Evaluator {
    fn feed(&mut self, ir: GraphIr) {
        self.ir = ir;
        self.evaluated.clear();
    }

    fn eval_full(&mut self) {
        // GraphIr::topological_sort returns InstructionRefs, which are mostly cool
        // but we'd like to have them owned, so we can call Self::step without lifetime hassle
        let queue: Vec<Instruction> = self
            .ir
            .topological_sort()
            .into_iter()
            .map(Into::into)
            .collect();

        for instr in queue {
            self.step(instr);
        }
    }
}

impl Evaluator {
    #[allow(clippy::needless_pass_by_value)]
    fn step(&mut self, instr: Instruction) {
        // what inputs does this instr need? fetch them
        let inputs: Vec<_> = instr
            .input_sources()
            .iter()
            .map(|source| {
                let source_socket = source
                    .as_ref()
                    .expect("all inputs to be connected when an instruction is ran");
                self.evaluated
                    .get(source_socket)
                    .expect("toposort to yield later instrs only after previous ones")
            })
            .collect();

        // then actually do whatever the instruction should do
        // NOTE: makes heavy use of index slicing,
        // on the basis that ir::instruction::Kind::socket_count is correct
        // TODO: make this a more flexible dispatch-ish arch
        let output = match instr.kind {
            Kind::Read(details) => Some(Variant::Image(instr::read::read(details))),
            Kind::Write(details) => {
                #[allow(irrefutable_let_patterns)] // will necessarily change
                let Variant::Image(input) = inputs[0] else {
                    panic!("cannot only write images, but received: `{:?}`", inputs[0]);
                };
                instr::write::write(details, input);
                None
            }
            Kind::Math(_) => todo!(),
            Kind::Blend(_) => todo!(),
            Kind::Noise(_) => todo!(),
            Kind::Filter(filter_instruction) => match filter_instruction {
                Filter::Invert => {
                    #[allow(irrefutable_let_patterns)]
                    let Variant::Image(input) = inputs[0] else {
                        panic!(
                            "cannot only filter invert images, but received: `{:?}`",
                            inputs[0]
                        );
                    };

                    Some(Variant::Image(instr::filters::invert::invert(
                        input.clone(),
                    )))
                }
            },
        };

        if let Some(output) = output {
            // TODO: very inaccurate, actually respect individual instructions.
            //       should be implied by a different arch
            // TODO: all of those should not be public, offer some methods to get this on
            //       `Instruction` instead (can infer short-term based on Kind::socket_count)
            let socket = id::Output(id::Socket {
                belongs_to: instr.id,
                idx: id::SocketIdx(0),
            });
            self.evaluated.insert(socket, output);
        }
    }
}
