use super::{snap, Editor};

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

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

use std::{
    collections::{BTreeMap, HashSet},
    fs::{remove_file, File},
    io::{Error as IOError, Write},
};

#[derive(Default)]
struct SaveResult {
    net: Option<SaveError>,
    transitions: Option<IOError>,
    places: Option<IOError>,
    story: Option<IOError>,
    layout: Option<IOError>,
}

impl SaveResult {
    fn is_error(&self) -> bool {
        self.net.is_some()
            || self.transitions.is_some()
            || self.places.is_some()
            || self.story.is_some()
            || self.layout.is_some()
    }
}

impl Editor {
    fn save_layout(&self, file: &mut File) -> Result<(), IOError> {
        for tid in self.net.safe().transition_ids() {
            if let Some(node) = self.nodes.transitions.nodes.get(&tid) {
                let pos = node.position - self.view_offset;
                file.write_all(&pos.x.to_le_bytes())?;
                file.write_all(&pos.y.to_le_bytes())?;
            } else {
                file.write_all(&0x7FFFFFFFu64.to_le_bytes())?;
            }
        }

        for pid in self.net.safe().place_ids() {
            if let Some(node) = self.nodes.places.get(&pid) {
                if node.positions.len() > 1 {
                    file.write_all(&std::f32::NAN.to_le_bytes())?;
                    file.write_all(&(node.positions.len() as u32).to_le_bytes())?;
                }
                for pos in &node.positions {
                    let pos = pos - self.view_offset;
                    file.write_all(&pos.x.to_le_bytes())?;
                    file.write_all(&pos.y.to_le_bytes())?;
                }
            } else {
                file.write_all(&0x7FFFFFFFu64.to_le_bytes())?;
            }
        }

        Ok(())
    }

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

        Ok(())
    }

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

    fn save_story(&self, file: &mut File) -> Result<bool, IOError> {
        fn save_transitions(
            nodes: &BTreeMap<Tid, TransitionNode>,
            paths: &PathMap<String, Tid>,
            level: usize,
            file: &mut File,
        ) -> Result<bool, IOError> {
            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 {
                        write!(file, "#")?;
                    }
                    writeln!(file, "# {}", name)?;
                    writeln!(file)?;
                    for line in lines {
                        let line = line.as_str().trim();
                        if !line.is_empty() {
                            all_empty = false;
                        }
                        writeln!(file, "{}", &line)?;
                    }
                    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 {
                    write!(file, "#")?;
                }
                writeln!(file, "# {}", name)?;
                writeln!(file)?;

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

            Ok(all_empty)
        }

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

    fn save_net(&self) -> Option<SaveResult> {
        let folder_path = self.net_path.as_ref().unwrap();
        let (pns, pnl, pnk, pnkp, story) = self.project_paths(folder_path.as_ref())?;

        let mut result = SaveResult::default();

        let net = self.net.safe();
        if let Err(err) = net.save(&pns) {
            result.net = Some(err);
        }

        match File::create(pnl) {
            Ok(mut layout) => {
                if let Err(err) = self.save_layout(&mut layout) {
                    result.layout = Some(err);
                }
            }
            Err(err) => result.layout = Some(err),
        }

        match File::create(pnk) {
            Ok(mut keys) => {
                if let Err(err) = self.save_transitions(&mut keys) {
                    result.transitions = Some(err);
                }
            }
            Err(err) => result.transitions = Some(err),
        }

        match File::create(&pnkp) {
            Ok(mut place_keys) => match self.save_places(&mut place_keys) {
                Ok(remove) => {
                    if remove {
                        if let Err(err) = remove_file(&pnkp) {
                            result.places = Some(err);
                        }
                    }
                }
                Err(err) => result.places = Some(err),
            },
            Err(err) => result.places = Some(err),
        }

        match File::create(&story) {
            Ok(mut story_file) => match self.save_story(&mut story_file) {
                Ok(remove) => {
                    if remove {
                        if let Err(err) = remove_file(&story) {
                            result.story = Some(err);
                        }
                    }
                }
                Err(err) => result.story = Some(err),
            },
            Err(err) => result.story = Some(err),
        }

        Some(result)
    }

    /// 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);
        }

        if let Some(result) = self.save_net() {
            if result.is_error() {
                let mut infos = Vec::with_capacity(5);
                if let Some(err) = result.net {
                    infos.push(("net", format!("{err:?}")));
                }
                if let Some(err) = result.transitions {
                    infos.push(("transitions", format!("{err:?}")));
                }
                if let Some(err) = result.places {
                    infos.push(("places", format!("{err:?}")));
                }
                if let Some(err) = result.layout {
                    infos.push(("layout", format!("{err:?}")));
                }
                if let Some(err) = result.story {
                    infos.push(("story", format!("{err:?}")));
                }
                let mut infos = infos.into_iter();

                let (file, message) = infos.next().unwrap();

                let (files, messages) = infos.fold(
                    (file.to_string(), message),
                    |(files, messages), (file, message)| {
                        (format!("{files}, {file}"), format!("{messages}\n{message}"))
                    },
                );

                MessageDialog::new()
                    .set_level(MessageLevel::Error)
                    .set_title("Error saving files!")
                    .set_description(&format!(
                        "Saving failed! Files for {files} might be corrupt!\n\nErrors:\n{messages}",
                    ))
                    .set_buttons(MessageButtons::Ok)
                    .show();
            }
        }

        self.changes = false;
    }
}
