mod helpers;
mod position_info;

use super::{snap, Editor};

use crate::{
    edit_list::{NodeInstanceId, SelectionList},
    feature_types::{DuplicationType, Grab, GrabKind, MoveType, ViewMode},
    vector::Vector,
};

use pns::{Pid, Tid};

use std::collections::HashMap;

impl Editor {
    /// Grab something.
    ///
    /// * `primary`: specifies if the primary button is pressed.
    /// * `clear_old`: specifies if old selections should be deselected.
    /// * `create`: creates new nodes.
    pub fn grab(&mut self, primary: bool, clear_old: bool, create: bool) {
        if let Some(node_id) = self.click_info(self.relative_mouse_pos()) {
            self.grab_node(node_id, !primary, clear_old, create);
        } else {
            self.grab_empty(!primary, clear_old, create)
        }
    }

    /// Release something.
    pub fn release(&mut self, primary: bool, allow_more: bool, clear_old: bool, select_new: bool) {
        if let Some(Grab {
            kind,
            moved,
            deselect,
            ..
        }) = self.grab
        {
            if primary {
                if let GrabKind::Move(MoveType::Nodes) = kind {
                    let click_pos = self.relative_mouse_pos();
                    if let Some(NodeInstanceId::Transition(parent)) = self.release_info(click_pos) {
                        for &tid in &self.selected.tids {
                            self.nodes.transitions.map.put(tid, parent);
                        }
                    }
                }
            } else {
                match kind {
                    GrabKind::Select => {
                        let node_ids = self.box_info();
                        if clear_old && select_new {
                            self.selected.clear();
                        }
                        for node_id in node_ids {
                            if !select_new {
                                self.selected.remove_node(node_id);
                            } else if !self.selected.contains(&node_id) {
                                self.selected.add(node_id);
                            }
                        }
                    }
                    GrabKind::Connect => self.release_connect(allow_more, clear_old, select_new),
                    GrabKind::Move(_) => (),
                }
            }

            if let (false, GrabKind::Move(_)) = (moved, kind) {
                let click_pos = self.relative_mouse_pos();
                if let Some(node_id) = self.click_info(click_pos) {
                    if clear_old {
                        self.selected.clear();
                        self.selected.add(node_id);
                    } else {
                        if deselect {
                            self.selected.remove_node(node_id);
                        }
                    }
                } else {
                    if clear_old {
                        self.selected.clear()
                    }
                }
            }

            if let Some(snap_size) = self.snapping {
                if let GrabKind::Move(move_type) = kind {
                    match move_type {
                        MoveType::Nodes => {
                            for tid in self
                                .nodes
                                .transitions
                                .map
                                .all_children(self.selected.tids.iter().cloned())
                            {
                                let node = self.nodes.transitions.node_mut(tid);
                                snap(&mut node.position, snap_size);
                            }
                            for (&pid, subindices) in self.selected.pids.iter() {
                                let node = self.nodes.place_mut(pid);
                                for &index in subindices {
                                    snap(&mut node.positions[index], snap_size);
                                }
                            }
                        }
                        MoveType::View => snap(&mut self.view_offset, snap_size),
                    }
                }
            }

            self.grab = None;
        }
    }

    /// Set cursor position.
    pub fn set_cursor(&mut self, x: f32, y: f32) {
        if let Some(Grab {
            moved, duplicate, ..
        }) = &mut self.grab
        {
            if !*moved {
                *moved = true;

                if let Some(duplication_type) = duplicate {
                    let linked = *duplication_type == DuplicationType::Linked;

                    struct TransitionInfo {
                        name: String,
                        tid: Tid,
                        original_tid: Tid,
                        position: Vector,
                    }

                    struct PlaceInfo {
                        name: String,
                        pid: Pid,
                        positions: Vec<Vector>,
                    }

                    let mut transition_infos: Vec<TransitionInfo> = Vec::new();
                    let mut place_infos: Vec<PlaceInfo> = Vec::new();

                    match self.view_mode {
                        ViewMode::Default => {
                            let pids = &self.selected.pids;
                            let tids = &self.selected.tids;

                            let mut place_mappings = HashMap::new();
                            let mut connecting_tids = Vec::new();
                            for (&pid, subindices) in pids.iter() {
                                let new_pid = if !linked {
                                    let place = &self.net.safe_mut().place(pid);
                                    let mut duplicate = true;
                                    for &tid in place.prev().iter().chain(place.next()) {
                                        if tids.contains(&tid) {
                                            duplicate = false;
                                        } else {
                                            connecting_tids.push(tid);
                                        }
                                    }
                                    if duplicate {
                                        self.net.safe_mut().duplicate_place(pid)
                                    } else {
                                        let net = self.net.safe_mut();
                                        let index = net.add_place();
                                        net.start(index, net.initial_token_count(pid));
                                        place_mappings.insert(pid, index);
                                        index
                                    }
                                } else {
                                    pid
                                };
                                let node = &self.nodes.place(pid);
                                place_infos.push(PlaceInfo {
                                    name: node.name.to_string(),
                                    pid: new_pid,
                                    positions: subindices
                                        .iter()
                                        .map(|&subindex| node.positions[subindex])
                                        .collect(),
                                });
                            }
                            for &tid in tids.iter() {
                                let new_tid = if !linked {
                                    let transition = &self.net.safe_mut().transition(tid);
                                    let mut prev_ids = Vec::new();
                                    let mut next_ids = Vec::new();
                                    for &pid in transition.prev() {
                                        let mapped_pid =
                                            if let Some(&pid) = place_mappings.get(&pid) {
                                                pid
                                            } else {
                                                pid
                                            };
                                        prev_ids.push(mapped_pid);
                                    }
                                    for &pid in transition.next() {
                                        let mapped_pid =
                                            if let Some(&pid) = place_mappings.get(&pid) {
                                                pid
                                            } else {
                                                pid
                                            };
                                        next_ids.push(mapped_pid);
                                    }
                                    let index =
                                        self.net.safe_mut().add_connected_transition(&prev_ids[..]);

                                    for pid in next_ids {
                                        self.net.safe_mut().connect_out(index, pid);
                                    }
                                    index
                                } else {
                                    tid
                                };
                                let node = &self.nodes.transitions.node(tid);
                                transition_infos.push(TransitionInfo {
                                    name: node.name.to_string(),
                                    tid: new_tid,
                                    original_tid: tid,
                                    position: node.position,
                                });
                            }
                            if !linked {
                                for tid in connecting_tids {
                                    let transition = &self.net.safe_mut().transition(tid);
                                    let mut prev_ids = Vec::new();
                                    let mut next_ids = Vec::new();
                                    for &pid in transition.prev() {
                                        let mapped_pid =
                                            if let Some(&pid) = place_mappings.get(&pid) {
                                                pid
                                            } else {
                                                pid
                                            };
                                        prev_ids.push(mapped_pid);
                                    }
                                    for &pid in transition.next() {
                                        let mapped_pid =
                                            if let Some(&pid) = place_mappings.get(&pid) {
                                                pid
                                            } else {
                                                pid
                                            };
                                        next_ids.push(mapped_pid);
                                    }
                                    if let Some(net) = self.net.default_mut() {
                                        for pid in prev_ids {
                                            net.connect_in(tid, pid);
                                        }
                                    }
                                    for pid in next_ids {
                                        self.net.safe_mut().connect_out(tid, pid);
                                    }
                                }
                            }
                        }
                        ViewMode::State => {
                            for (&pid, subindices) in self.selected.pids.iter() {
                                let new_pid = if linked {
                                    pid
                                } else {
                                    self.net.safe_mut().duplicate_place(pid)
                                };
                                let node = &self.nodes.place(pid);
                                place_infos.push(PlaceInfo {
                                    name: node.name.to_string(),
                                    pid: new_pid,
                                    positions: subindices
                                        .iter()
                                        .map(|&subindex| node.positions[subindex])
                                        .collect(),
                                });
                            }
                        }
                        ViewMode::Actions => {
                            for &tid in &self.selected.tids {
                                let new_tid = if linked {
                                    tid
                                } else {
                                    self.net.safe_mut().duplicate_transition(tid)
                                };
                                let node = &self.nodes.transitions.node(tid);
                                transition_infos.push(TransitionInfo {
                                    name: node.name.to_string(),
                                    tid: new_tid,
                                    original_tid: tid,
                                    position: node.position,
                                });
                            }
                        }
                    }

                    let mut new_nodes = Vec::new();

                    if self.view_mode != ViewMode::State {
                        for TransitionInfo {
                            name,
                            tid,
                            original_tid,
                            position,
                        } in transition_infos
                        {
                            let (new_tid, parent) = if linked {
                                (self.net.safe_mut().add_transition(), Some(tid))
                            } else {
                                let mut path = self.nodes.transitions.map.path(original_tid);
                                path.pop();
                                (tid, path.pop())
                            };
                            new_nodes.push(self.add_transition(new_tid, position, Some(name)));
                            if let Some(parent) = parent {
                                self.nodes.transitions.map.put(new_tid, parent);
                            }
                        }
                    }

                    if self.view_mode != ViewMode::Actions {
                        for PlaceInfo {
                            name,
                            pid,
                            positions,
                        } in place_infos
                        {
                            let place = if linked {
                                self.add_place_linked(pid, &positions[..])
                            } else {
                                self.add_place(pid, &positions[..], Some(name))
                            };
                            new_nodes.extend(place);
                        }
                    }

                    let mut new_edit = SelectionList::new();
                    for node in new_nodes {
                        new_edit.add(node);
                    }
                    self.selected = new_edit;
                }
            }
        }

        self.mouse_pos = Vector::new(x, y);
    }

    /// Fire node.
    pub fn fire_node(&mut self, forward: bool) {
        if let Some(node_id) = self.click_info(self.relative_mouse_pos()) {
            if let Some(net) = self.net.simulated_mut() {
                if let NodeInstanceId::Transition(tid) = node_id {
                    let state = &mut net.state_mut(self.simulation);
                    let info = &mut self.state_infos[self.simulation];
                    let fire = if forward {
                        state.fire()
                    } else {
                        state.unfire()
                    };
                    for (i, id) in fire.transitions.iter().enumerate() {
                        if *id == tid {
                            fire.finish(i);
                            info.changes = true;
                            info.update_state(&mut state.state);
                            break;
                        }
                    }
                }
            }
        }
    }
}
