use super::Editor;

use crate::{
    feature_types::{Grab, GrabKind, ViewMode},
    node::TransitionNode,
    node_settings::NodeSettings,
    path_map::PathMap,
    renderer::{ArrowKind, Renderer},
    text_content::TextContent,
    vector::Vector,
};

use gapp::Render;
use pns::{Pid, Tid};

use std::collections::BTreeMap;

trait ChildCollector {
    #[inline]
    fn collect(&mut self, _to: Vector) {}
}

impl ChildCollector for () {}

impl ChildCollector for Vec<[f32; 2]> {
    fn collect(&mut self, to: Vector) {
        self.push(to.into());
    }
}

struct Extents {
    left: f32,
    right: f32,
    top: f32,
    bottom: f32,
}

impl Extents {
    fn apply(self, extents: &mut Option<Extents>) {
        if let Some(extents) = extents {
            let Extents {
                left,
                right,
                top,
                bottom,
            } = self;
            if left < extents.left {
                extents.left = left;
            }
            if right > extents.right {
                extents.right = right;
            }
            if top < extents.top {
                extents.top = top;
            }
            if bottom > extents.bottom {
                extents.bottom = bottom;
            }
        } else {
            *extents = Some(self);
        }
    }

    fn expand(&mut self, size: f32) {
        self.left -= size;
        self.right += size;
        self.top -= size;
        self.bottom += size;
    }
}

impl Editor {
    // helpers

    fn is_inside(&self, pos: Vector, size: Vector) -> bool {
        let pos = pos - self.view_offset;
        pos.x + size.x / 2.0 > -self.window_size.x * self.zoom / 2.0
            && pos.x - size.x / 2.0 < self.window_size.x * self.zoom / 2.0
            && pos.y + size.y / 2.0 > -self.window_size.y * self.zoom / 2.0
            && pos.y - size.y / 2.0 < self.window_size.y * self.zoom / 2.0
    }

    fn token_count(&self, pid: Pid) -> usize {
        if let Some(net) = self.net.simulated() {
            net.state(self.simulation).token_count(pid) + self.net.safe().initial_token_count(pid)
        } else {
            self.net.safe().initial_token_count(pid)
        }
    }

    // rendering

    fn draw_arcs<R: Renderer>(&self, renderer: &mut R) {
        fn nearest_position(positions: &[Vector], from: &Vector) -> Option<Vector> {
            if let Some((pos, _)) = positions.iter().fold(None, |result, pos| {
                let new_distance = (from - pos).norm_squared();
                if let Some((_, distance)) = result {
                    if new_distance >= distance {
                        return result;
                    }
                }
                Some((pos, new_distance))
            }) {
                Some(*pos)
            } else {
                None
            }
        }

        use ViewMode::*;
        match self.view_mode {
            Actions => {
                let net = self.net.safe();
                for pid in net.place_ids() {
                    let place = net.place(pid);
                    if place.next_count == 0 || place.prev_count == 0 {
                        continue;
                    }
                    let mut next_offset = Vector::new(0.0, 0.0);
                    let mut prev_offset = Vector::new(0.0, 0.0);

                    for &tid in place.next() {
                        next_offset += self.nodes.transitions.node(tid).position;
                    }

                    for &tid in place.prev() {
                        prev_offset += self.nodes.transitions.node(tid).position;
                    }

                    let offset = (next_offset / place.next_count as f32
                        + prev_offset / place.prev_count as f32)
                        / 2.0;

                    for &tid in place.next() {
                        let next_transform = &self.nodes.transitions.node(tid).position;
                        next_offset += next_transform;
                        let from = offset - self.view_offset + self.window_size / 2.0 * self.zoom;
                        let to =
                            next_transform - self.view_offset + self.window_size / 2.0 * self.zoom;
                        renderer.draw_arrow(
                            from.into(),
                            to.into(),
                            self.node_settings.radius,
                            self.node_settings.width,
                            self.node_settings.height,
                            self.zoom,
                            ArrowKind::PointToTransition,
                        );
                    }

                    for &tid in place.prev() {
                        let prev_transform = &self.nodes.transitions.node(tid).position;
                        prev_offset += prev_transform - offset;
                        let from =
                            prev_transform - self.view_offset + self.window_size / 2.0 * self.zoom;
                        let to = offset - self.view_offset + self.window_size / 2.0 * self.zoom;
                        renderer.draw_arrow(
                            from.into(),
                            to.into(),
                            self.node_settings.radius,
                            self.node_settings.width,
                            self.node_settings.height,
                            self.zoom,
                            ArrowKind::TransitionToPoint,
                        );
                    }
                }
            }

            State => {
                let net = self.net.safe();
                for tid in net.transition_ids() {
                    let transition = net.transition(tid);
                    if transition.next_count == 0 || transition.prev_count == 0 {
                        continue;
                    }
                    let mut next_offset = Vector::new(0.0, 0.0);
                    let mut prev_offset = Vector::new(0.0, 0.0);

                    for &pid in transition.next() {
                        let positions = &self.nodes.place(pid).positions;
                        let mut current_offset = Vector::new(0.0, 0.0);
                        for next_transform in positions {
                            current_offset += next_transform;
                        }
                        next_offset += current_offset / positions.len() as f32;
                    }

                    for &pid in transition.prev() {
                        let positions = &self.nodes.place(pid).positions;
                        let mut current_offset = Vector::new(0.0, 0.0);
                        for prev_transform in positions {
                            current_offset += prev_transform;
                        }
                        prev_offset += current_offset / positions.len() as f32;
                    }

                    let offset = (next_offset / transition.next_count as f32
                        + prev_offset / transition.prev_count as f32)
                        / 2.0;

                    for &pid in transition.next() {
                        for next_transform in &self.nodes.place(pid).positions {
                            next_offset += next_transform - offset;
                            let from =
                                offset - self.view_offset + self.window_size / 2.0 * self.zoom;
                            let to = next_transform - self.view_offset
                                + self.window_size / 2.0 * self.zoom;
                            renderer.draw_arrow(
                                from.into(),
                                to.into(),
                                self.node_settings.radius,
                                self.node_settings.width,
                                self.node_settings.height,
                                self.zoom,
                                ArrowKind::PointToPlace,
                            );
                        }
                    }

                    for &pid in transition.prev() {
                        for prev_transform in &self.nodes.place(pid).positions {
                            prev_offset += prev_transform - offset;
                            let from = prev_transform - self.view_offset
                                + self.window_size / 2.0 * self.zoom;
                            let to = offset - self.view_offset + self.window_size / 2.0 * self.zoom;
                            renderer.draw_arrow(
                                from.into(),
                                to.into(),
                                self.node_settings.radius,
                                self.node_settings.width,
                                self.node_settings.height,
                                self.zoom,
                                ArrowKind::PlaceToPoint,
                            );
                        }
                    }
                }
            }

            Default => {
                let net = self.net.safe();
                for (&pid, node) in self.nodes.iter_places() {
                    let place = net.place(pid);
                    for &tid in place.next() {
                        let next_pos = &self.nodes.transitions.node(tid).position;
                        if let Some(pos) = nearest_position(&node.positions, next_pos) {
                            let from = pos - self.view_offset + self.window_size / 2.0 * self.zoom;
                            let to =
                                next_pos - self.view_offset + self.window_size / 2.0 * self.zoom;
                            renderer.draw_arrow(
                                from.into(),
                                to.into(),
                                self.node_settings.radius,
                                self.node_settings.width,
                                self.node_settings.height,
                                self.zoom,
                                ArrowKind::PlaceToTransition,
                            );
                        }
                    }

                    for &tid in place.prev() {
                        let prev_pos = &self.nodes.transitions.node(tid).position;
                        if let Some(pos) = nearest_position(&node.positions, prev_pos) {
                            let from =
                                prev_pos - self.view_offset + self.window_size / 2.0 * self.zoom;
                            let to = pos - self.view_offset + self.window_size / 2.0 * self.zoom;
                            renderer.draw_arrow(
                                from.into(),
                                to.into(),
                                self.node_settings.radius,
                                self.node_settings.width,
                                self.node_settings.height,
                                self.zoom,
                                ArrowKind::TransitionToPlace,
                            );
                        }
                    }
                }
            }
        }
    }

    fn render_action_contexts<R: Renderer>(&self, renderer: &mut R) {
        fn render_submap<R: Renderer>(
            zoom: f32,
            view_offset: Vector,
            name: &str,
            nodes: &BTreeMap<Tid, TransitionNode>,
            node_settings: &NodeSettings,
            submap: &PathMap<String, Tid>,
            renderer: &mut R,
        ) -> Option<Extents> {
            let mut extents = None;
            for (name, submap) in &submap.children {
                if let Some(result) = render_submap(
                    zoom,
                    view_offset,
                    name,
                    nodes,
                    node_settings,
                    submap,
                    renderer,
                ) {
                    result.apply(&mut extents);
                }
            }
            for &tid in &submap.values {
                let node = &nodes[&tid];

                let pos = node.position - view_offset;

                Extents {
                    left: pos.x - node_settings.width / 2.0,
                    right: pos.x + node_settings.width / 2.0,
                    top: pos.y - node_settings.height / 2.0,
                    bottom: pos.y + node_settings.height / 2.0,
                }
                .apply(&mut extents);
            }

            if let Some(extents) = &mut extents {
                extents.expand(node_settings.expand);

                let &mut Extents {
                    left,
                    right,
                    top,
                    bottom,
                } = extents;

                renderer.draw_transition_frame(left, right, top, bottom, zoom, name);
            }

            extents
        }

        for (name, submap) in &self.nodes.transitions.paths.children {
            render_submap(
                self.zoom,
                self.view_offset - self.window_size / 2.0 * self.zoom,
                name,
                &self.nodes.transitions.nodes,
                &self.node_settings,
                submap,
                renderer,
            );
        }
    }

    fn render_actions<R: Renderer>(&self, renderer: &mut R) {
        for (&tid, node) in self.nodes.transitions.nodes.iter() {
            let (forward, backwards) = if let Some(_) = self.net.simulated() {
                let info = &self.state_infos[self.simulation];
                info.callables[&tid]
            } else {
                (false, false)
            };
            let show_cursor = self.selected.tids.contains(&tid)
                && (!self.text_content_shown || self.selected.tids.len() != 1);
            let pos = node.position - self.view_offset + self.window_size / 2.0 * self.zoom;
            if self.is_inside(
                node.position,
                Vector::new(self.node_settings.width, self.node_settings.height),
            ) {
                renderer.draw_transition(
                    pos.into(),
                    self.node_settings.width,
                    self.node_settings.height,
                    self.zoom,
                    &node.name,
                    node.name_cursor,
                    show_cursor,
                    self.selected.tids.contains(&tid),
                    forward,
                    backwards,
                );
            }
        }

        if let Some(Grab {
            kind: GrabKind::Connect,
            ..
        }) = self.grab
        {
            for &tid in &self.selected.tids {
                let node = &self.nodes.transitions.node(tid);
                let from = node.position - self.view_offset + self.window_size / 2.0 * self.zoom;
                let to = self.mouse_pos * self.zoom;
                renderer.draw_arrow(
                    from.into(),
                    to.into(),
                    self.node_settings.radius,
                    self.node_settings.width,
                    self.node_settings.height,
                    self.zoom,
                    ArrowKind::TransitionToPoint,
                );
            }
        }
    }

    fn render_states<R: Renderer>(&self, renderer: &mut R) {
        for (&pid, node) in self.nodes.iter_places() {
            let show_cursor = self.selected.pids.contains_key(&pid)
                && (!self.text_content_shown || self.selected.tids.len() != 1);

            for (subindex, pos) in node.positions.iter().enumerate() {
                let count = self.token_count(pid);
                if self.is_inside(
                    *pos,
                    Vector::new(
                        self.node_settings.radius * 2.0,
                        self.node_settings.radius * 2.0,
                    ),
                ) {
                    let pos = pos - self.view_offset + self.window_size / 2.0 * self.zoom;
                    renderer.draw_place(
                        pos.into(),
                        self.node_settings.radius,
                        self.zoom,
                        &node.name,
                        node.name_cursor,
                        show_cursor,
                        count,
                        self.selected.pids.contains(&pid, &subindex),
                    );
                }
            }
        }

        if let Some(Grab {
            kind: GrabKind::Connect,
            ..
        }) = self.grab
        {
            for (&pid, subindices) in self.selected.pids.iter() {
                let node = &self.nodes.place(pid);
                for &subindex in subindices {
                    let pos = &node.positions[subindex];
                    let from = pos - self.view_offset + self.window_size / 2.0 * self.zoom;
                    let to = self.mouse_pos * self.zoom;
                    renderer.draw_arrow(
                        from.into(),
                        to.into(),
                        self.node_settings.radius,
                        self.node_settings.width,
                        self.node_settings.height,
                        self.zoom,
                        ArrowKind::PlaceToPoint,
                    );
                }
            }
        }
    }
}

impl<R: Renderer> Render<R> for Editor {
    fn render(&self, renderer: &mut R) {
        renderer.draw_simulated(self.net.simulated().is_some());

        if self.view_mode != ViewMode::State {
            self.render_action_contexts(renderer);
        }

        if self.view_mode != ViewMode::Actions {
            for (&pid, subindices) in self.selected.pids.iter() {
                let node = &self.nodes.place(pid);
                let mut center = Vector::new(0.0, 0.0);
                for subindex in subindices {
                    center += node.positions[*subindex];
                }
                center /= subindices.len() as f32;

                for pos in &node.positions {
                    let from = pos - self.view_offset + self.window_size / 2.0 * self.zoom;
                    let to = center - self.view_offset + self.window_size / 2.0 * self.zoom;
                    renderer.draw_line(from.into(), to.into(), self.zoom);
                }
            }
        }

        self.draw_arcs(renderer);

        if self.view_mode != ViewMode::Actions {
            self.render_states(renderer)
        }

        if self.view_mode != ViewMode::State {
            self.render_actions(renderer)
        }

        if let Some(Grab {
            pos: grab_pos,
            kind: grab_kind,
            ..
        }) = self.grab
        {
            if grab_kind == GrabKind::Select {
                let local_grab_pos =
                    grab_pos - self.view_offset + self.window_size / 2.0 * self.zoom;
                let real_mouse_pos = self.mouse_pos * self.zoom;
                renderer.draw_select_box(local_grab_pos.into(), real_mouse_pos.into(), self.zoom);
            }
        }

        if let Some(string_searcher) = &self.string_searcher {
            renderer.draw_field(&string_searcher.name, string_searcher.name_cursor);
        }

        if self.text_content_shown && self.selected.tids.len() == 1 {
            let TextContent {
                lines,
                cursor,
                offset,
            } = &self
                .nodes
                .transitions
                .node(*self.selected.tids.iter().next().unwrap())
                .content;
            renderer.draw_text_box(lines, cursor.column, cursor.row, *offset);
        }

        renderer.finish();
    }
}
