use super::{snap, Editor};

use crate::{node::TransitionNode, path_map::PathMap};

use pns::Tid;
use rfd::{MessageButtons, MessageDialog, MessageLevel};

use std::{
    fs::{remove_file, File},
    io::Write,
};

use std::collections::{BTreeMap, HashSet};

impl Editor {
    fn save_layout(&self, file: &mut File) {
        for tid in self.net.safe().transition_ids() {
            if let Some(node) = self.nodes.transitions.nodes.get(&tid) {
                unsafe {
                    let _failed = file.write_all(&std::mem::transmute::<_, [u8; 8]>(
                        node.position - self.view_offset,
                    ));
                }
            } else {
                unsafe {
                    let _failed = file.write_all(&std::mem::transmute::<_, [u8; 8]>(0x7FFFFFFFu64));
                }
            }
        }

        for pid in self.net.safe().place_ids() {
            if let Some(node) = self.nodes.get_place(pid) {
                if node.positions.len() > 1 {
                    unsafe {
                        let _failed = file.write_all(&std::mem::transmute::<_, [u8; 8]>((
                            std::f32::NAN,
                            node.positions.len() as u32,
                        )));
                    }
                }
                for pos in &node.positions {
                    unsafe {
                        let _failed = file
                            .write_all(&std::mem::transmute::<_, [u8; 8]>(pos - self.view_offset));
                    }
                }
            } else {
                unsafe {
                    let _failed = file.write_all(&std::mem::transmute::<_, [u8; 8]>(0x7FFFFFFFu64));
                }
            }
        }
    }

    fn save_transitions(&self, file: &mut File) {
        for tid in self.net.safe().transition_ids() {
            if let Some(node) = &self.nodes.transitions.nodes.get(&tid) {
                for name in &node.path {
                    let _failed = write!(file, "{}#", name);
                }
                let _failed = writeln!(file, "{}", node.name.as_str());
            } else {
                let _failed = writeln!(file, "");
            }
        }
    }

    fn save_places(&self, file: &mut File) -> bool {
        let mut all_empty = true;
        for pid in self.net.safe().place_ids() {
            if let Some(node) = self.nodes.get_place(pid) {
                let name = node.name.as_str().trim();
                if !name.is_empty() {
                    all_empty = false;
                }
                let _failed = writeln!(file, "{}", name);
            } else {
                let _failed = writeln!(file, "");
            }
        }
        all_empty
    }

    fn save_story(&self, file: &mut File) -> bool {
        fn save_transitions(
            nodes: &BTreeMap<Tid, TransitionNode>,
            paths: &PathMap<String, Tid>,
            level: usize,
            file: &mut File,
        ) -> bool {
            let mut used_paths = HashSet::new();

            let mut all_empty = true;
            for &tid in &paths.values {
                let node = &nodes[&tid];
                let name = node.name.as_str();
                let lines: Vec<_> = node.content.lines.iter().collect();
                if !lines.is_empty() {
                    for _ in 0..level {
                        let _failed = write!(file, "#");
                    }
                    let _failed = writeln!(file, "# {}", name);
                    let _failed = writeln!(file);
                    for line in lines {
                        let line = line.as_str().trim();
                        if !line.is_empty() {
                            all_empty = false;
                        }
                        let _failed = writeln!(file, "{}", &line);
                    }
                    let _failed = writeln!(file);
                }

                if let Some(child_paths) = paths.children.get(name) {
                    used_paths.insert(name);
                    if !save_transitions(nodes, child_paths, level + 1, file) {
                        all_empty = false;
                    }
                }
            }

            for (name, child_paths) in &paths.children {
                if used_paths.contains(name.as_str()) {
                    continue;
                }

                for _ in 0..level {
                    let _failed = write!(file, "#");
                }
                let _failed = writeln!(file, "# {}", name);
                let _failed = writeln!(file);

                if !save_transitions(nodes, child_paths, level + 1, file) {
                    all_empty = false;
                }
            }

            all_empty
        }

        save_transitions(
            &self.nodes.transitions.nodes,
            &self.nodes.transitions.paths,
            0,
            file,
        )
    }

    fn save_net(&self) {
        let folder_path = self.net_path.as_ref().unwrap();
        let (pns, pnl, pnk, pnkp, story) =
            if let Some(tuple) = self.project_paths(folder_path.as_ref()) {
                tuple
            } else {
                return;
            };

        let net = self.net.safe();
        let _ = net.save(pns.as_ref());

        if let Ok(mut layout) = File::create(pnl) {
            self.save_layout(&mut layout);
        }

        if let Ok(mut keys) = File::create(pnk) {
            self.save_transitions(&mut keys);
        }

        if let Ok(mut place_keys) = File::create(&pnkp) {
            if self.save_places(&mut place_keys) {
                let _ = remove_file(&pnkp);
            }
        }

        if let Ok(mut story_file) = File::create(&story) {
            if self.save_story(&mut story_file) {
                let _ = remove_file(&story);
            }
        }
    }

    /// Saves the current net.
    pub fn save(&mut self, force_path: bool) {
        fn check_names<'a>(
            nodes: &'a BTreeMap<Tid, TransitionNode>,
            paths: &PathMap<String, Tid>,
        ) -> Result<(), &'a str> {
            let mut names: HashSet<&str> = HashSet::new();
            for &tid in &paths.values {
                let name = nodes[&tid].name.as_str();
                if names.contains(name) {
                    return Err(name);
                }
                names.insert(name);
            }

            for child_paths in paths.children.values() {
                check_names(nodes, child_paths)?;
            }

            Ok(())
        }

        if let Err(name) = check_names(&self.nodes.transitions.nodes, &self.nodes.transitions.paths)
        {
            MessageDialog::new()
                 .set_level(MessageLevel::Warning)
                 .set_title("Warning!")
                 .set_description(&format!(
                         "Cannot Save!\nTransition name '{}' found multiple times.\nRename transitions to ensure only one has this name.",
                        name
                     )[..],
 )
                 .set_buttons(MessageButtons::Ok)
                 .show();
            return;
        }
        if self.net_path.is_none() || force_path {
            if let Some(path) = self.file_dialog().pick_folder() {
                self.net_path = Some(path);
            } else {
                return;
            }
        }

        if let Some(snap_size) = self.snapping {
            snap(&mut self.view_offset, snap_size);
        }

        self.save_net();

        self.changes = false;
    }
}
