use super::Editor;

use header_parsing::parse_header;
use pns::Net;
use text_editing::TextLine;

use std::{
    collections::HashMap,
    fs::File,
    io::{BufRead, BufReader, Error as IOError},
    path::{Path, PathBuf},
};

pub enum Error {
    IO(IOError),
    NoDirectorySpecified,
    SubheaderWithoutHeader(usize),
}

impl From<IOError> for Error {
    fn from(err: IOError) -> Self {
        Self::IO(err)
    }
}

fn load_keys(path: &Path) -> Result<Vec<String>, IOError> {
    let mut result = Vec::new();
    if let Ok(file) = File::open(path) {
        for line in BufReader::new(file).lines() {
            result.push(line?)
        }
    }

    Ok(result)
}

impl Editor {
    /// Loads the net from a specified path.
    pub fn load_net(&mut self, folder_path: PathBuf) -> Result<(), Error> {
        let (pns, pnl, pnk, pnkp, story) =
            if let Some(tuple) = self.project_paths(folder_path.as_ref()) {
                tuple
            } else {
                return Err(Error::NoDirectorySpecified);
            };

        let net = if let Ok(net) = Net::load(pns.as_ref()) {
            net
        } else {
            Net::new()
        };

        let mut names: Vec<Vec<String>> = load_keys(&pnk)?
            .into_iter()
            .map(|name| name.split('#').map(|name| name.to_string()).collect())
            .collect();
        let place_names = load_keys(&pnkp)?;

        let mut contents: HashMap<_, Vec<TextLine>> = HashMap::new();
        if let Ok(file) = File::open(story) {
            let mut first = false;
            let mut empty_count = 0;
            let mut current_key: Vec<String> = Vec::new();
            let mut current_content = Vec::new();
            for (line_number, line) in BufReader::new(file).lines().enumerate() {
                let line = line?;
                if let Some(success) = parse_header(&current_key, &line) {
                    if let Ok(changes) = success {
                        first = true;
                        if !current_key.is_empty() {
                            if !names.contains(&current_key) {
                                names.push(current_key.clone());
                            }
                            contents.insert(current_key.clone(), current_content);
                            current_content = Vec::new();
                            empty_count = 0;
                        }

                        changes.apply(&mut current_key);
                    } else {
                        return Err(Error::SubheaderWithoutHeader(line_number));
                    }
                } else if line.is_empty() {
                    if first {
                        first = false;
                    } else {
                        empty_count += 1
                    }
                } else if !current_key.is_empty() {
                    current_content.extend((0..empty_count).map(|_| TextLine::new()));
                    empty_count = 0;
                    current_content.push(TextLine::from_string(line));
                }
            }
            if !current_key.is_empty() {
                if !names.contains(&current_key) {
                    names.push(current_key.clone());
                }
                contents.insert(current_key, current_content);
            }
        }
        self.net_path = Some(folder_path);
        self.reset(net, names, place_names);
        let mut added_contents = HashMap::new();
        for (&tid, _) in self.nodes.transitions.nodes.iter() {
            let path: Vec<_> = self
                .nodes
                .transitions
                .map
                .path(tid)
                .into_iter()
                .map(|tid| self.nodes.transitions.nodes[&tid].name.to_string())
                .collect();
            if let Some(content) = contents.remove(&path) {
                if !content.is_empty() {
                    added_contents.insert(tid, content);
                }
            }
        }
        for (tid, content) in added_contents {
            self.nodes
                .transitions
                .nodes
                .get_mut(&tid)
                .unwrap()
                .content
                .lines = content;
        }
        if let Ok(mut layout) = File::open(pnl) {
            let mut removed_tids = Vec::new();
            for (&tid, node) in self.nodes.transitions.nodes.iter_mut() {
                use std::io::Read;
                let mut buffer: [u8; 8] = unsafe { std::mem::zeroed() };
                let _failed = layout.read(&mut buffer);
                if 0x7FFFFFFFu64 != unsafe { std::mem::transmute(buffer) } {
                    node.position = unsafe { std::mem::transmute(buffer) };
                } else {
                    removed_tids.push(tid);
                }
            }

            let mut removed_pids = Vec::new();
            for (&pid, node) in self.nodes.iter_places_mut() {
                use std::io::Read;
                node.positions.clear();
                let mut buffer: [u8; 8] = unsafe { std::mem::zeroed() };
                let _failed = layout.read(&mut buffer);
                if 0x7FFFFFFFu64 != unsafe { std::mem::transmute(buffer) } {
                    let (first, count): (f32, u32) = unsafe { std::mem::transmute(buffer) };
                    if first.is_nan() {
                        for _i in 0..count {
                            let _failed = layout.read(&mut buffer);
                            node.positions.push(unsafe { std::mem::transmute(buffer) });
                        }
                    } else {
                        node.positions.push(unsafe { std::mem::transmute(buffer) });
                    }
                }
                if node.positions.is_empty() {
                    removed_pids.push(pid);
                }
            }

            for tid in removed_tids {
                self.nodes.transitions.remove(tid);
            }
            for pid in removed_pids {
                self.nodes.remove_place(pid);
            }
        }

        Ok(())
    }

    /// Loads a new net.
    pub fn load(&mut self, force_path: bool) {
        if !self.ensure_saved(false) {
            return;
        }
        let path = if let (Some(path), false) = (&self.net_path, force_path) {
            path.clone()
        } else if let Some(path) = self.file_dialog().pick_folder() {
            path
        } else {
            return;
        };

        let _ = self.load_net(path);
    }
}
