use keeshond::gameloop::GameControl;
use keeshond::scene::{Component, ComponentControl, SceneControl, ImGuiSystem, NullSceneType, SimpleScene, ThinkerSystem};
use keeshond::renderer::Sheet;

use keeshond_datapack::{DataError, DataStoreOk};
use keeshond_datapack::source::{SourceId, FilesystemSource, TrustLevel};

use keeshond_treats::LoadErrorMode;
use keeshond_treats::visual::{Costume, DrawableDrawer, Drawable, ActorThinker, PixelSnap};
use keeshond_treats::tilemap::Tileset;

use std::collections::{HashMap, HashSet};
use std::string::ToString;

use strum::{EnumCount, IntoEnumIterator};
use strum_macros::EnumIter;

use imgui;
use imgui::{im_str, Condition};
use imgui::{ImString, StyleColor};

use palette::{Srgb, Hsv, Hue, Saturate};

use crate::document::Document;
use crate::document::sheetdocument::SheetDocument;
use crate::document::costumedocument::CostumeDocument;
use crate::document::tilesetdocument::TilesetDocument;
use crate::wizard::Wizard;
use crate::wizard::sheetwizard::SheetWizard;
use crate::wizard::costumewizard::CostumeWizard;
use crate::wizard::tilesetwizard::TilesetWizard;
use std::slice::{Iter};
use std::time::Instant;

const FILEPATH_MAXSIZE : usize = 65536;

#[derive(Copy, Clone, EnumCount, EnumIter, Eq, PartialEq, Serialize, Deserialize, Display)]
enum FlavorMain
{
    Vanilla,
    #[strum(to_string="Cookies & Cream")]
    CookiesNCream,
    #[strum(to_string="Milk Chocolate")]
    MilkChocolate,
    #[strum(to_string="Dark Chocolate")]
    DarkChocolate
}

#[derive(Copy, Clone, EnumCount, EnumIter, Eq, PartialEq, Serialize, Deserialize, Display)]
enum FlavorAccent
{
    Raspberry,
    Orange,
    Lime,
    Blueberry,
    Grape
}

#[derive(Copy, Clone, EnumCount, EnumIter, Eq, PartialEq, Serialize, Deserialize, Display)]
enum FlavorFont
{
    Chewy,
    Crispy
}

#[derive(Clone, Serialize, Deserialize)]
struct EditorConfig
{
    source_path: String,
    flavor_main : FlavorMain,
    flavor_accent : FlavorAccent,
    flavor_font : FlavorFont,
    keyboard_navigation : bool,
    drag_window_from_anywhere : bool
}

impl Default for EditorConfig
{
    fn default() -> EditorConfig
    {
        EditorConfig
        {
            source_path : "".to_string(),
            flavor_main : FlavorMain::MilkChocolate,
            flavor_accent : FlavorAccent::Raspberry,
            flavor_font : FlavorFont::Chewy,
            keyboard_navigation : false,
            drag_window_from_anywhere : true
        }
    }
}

#[derive(Copy, Clone, EnumCount, EnumIter, Display)]
pub enum ResourceType
{
    Sheet,
    Costume,
    Tileset
}

impl ResourceType
{
    pub fn folder_name(&self) -> &'static str
    {
        match self
        {
            ResourceType::Sheet => { "sheets" }
            ResourceType::Costume => { "costumes" }
            ResourceType::Tileset => { "tilesets" }
        }
    }

    pub fn load_package(&self, game : &mut GameControl, package_name : &str) -> Result<DataStoreOk, DataError>
    {
        match self
        {
            ResourceType::Sheet => { game.res().store_mut::<Sheet>().load_package(package_name) }
            ResourceType::Costume => { game.res().store_mut::<Costume>().load_package(package_name) }
            ResourceType::Tileset => { game.res().store_mut::<Tileset>().load_package(package_name) }
        }
    }

    pub fn list_package_contents(&self, game : &mut GameControl, package_name : &str) -> Vec<String>
    {
        match self
        {
            ResourceType::Sheet => { game.res().store_mut::<Sheet>().list_package_contents(package_name) }
            ResourceType::Costume => { game.res().store_mut::<Costume>().list_package_contents(package_name) }
            ResourceType::Tileset => { game.res().store_mut::<Tileset>().list_package_contents(package_name) }
        }
    }

    pub fn open_document(&self, game : &mut GameControl, package_name : &str, pathname : &str) -> Box<dyn Document>
    {
        match self
        {
            ResourceType::Sheet => { Box::new(SheetDocument::load(game, package_name, pathname)) }
            ResourceType::Costume => { Box::new(CostumeDocument::load(game, package_name, pathname)) }
            ResourceType::Tileset => { Box::new(TilesetDocument::load(game, package_name, pathname)) }
        }
    }

    pub fn save(&self, game : &mut GameControl, package_name : &str, pathname : &str, source_id : SourceId) -> Result<(), DataError>
    {
        match self
        {
            ResourceType::Sheet => { game.res().store_mut::<Sheet>().save(package_name, pathname, source_id) }
            ResourceType::Costume => { game.res().store_mut::<Costume>().save(package_name, pathname, source_id) }
            ResourceType::Tileset => { game.res().store_mut::<Tileset>().save(package_name, pathname, source_id) }
        }
    }

    pub fn reload(&self, game : &mut GameControl, package_name : &str, pathname : &str) -> Result<(), DataError>
    {
        match self
        {
            ResourceType::Sheet => { game.res().store_mut::<Sheet>().reload(package_name, pathname) }
            ResourceType::Costume => { game.res().store_mut::<Costume>().reload(package_name, pathname) }
            ResourceType::Tileset => { game.res().store_mut::<Tileset>().reload(package_name, pathname) }
        }
    }

    pub fn unload_resources(&self, game : &mut GameControl)
    {
        match self
        {
            ResourceType::Sheet => { game.res().store_mut::<Sheet>().unload_all(); }
            ResourceType::Costume => { game.res().store_mut::<Costume>().unload_all(); }
            ResourceType::Tileset => { game.res().store_mut::<Tileset>().unload_all(); }
        }
    }
}

pub struct ResourceContents
{
    packages_to_load : HashSet<String>,
    package_contents : HashMap<String, Vec<String>>
}

impl ResourceContents
{
    pub fn new() -> ResourceContents
    {
        ResourceContents
        {
            packages_to_load : HashSet::new(),
            package_contents : HashMap::new()
        }
    }
}

struct DocumentEntry
{
    document : Box<dyn Document>,
    res_type : ResourceType,
    package_name : String,
    item_name : String,
    dirty : bool
}

pub struct Documents
{
    documents : Vec<DocumentEntry>,
    name_to_document : HashMap<String, usize>,
    document_to_focus : Option<usize>,
    active_document : Option<usize>
}

impl Documents
{
    pub fn open_document(&mut self, game : &mut GameControl, res_type : ResourceType, package_name : &str, item_name : &str)
    {
        let document = res_type.open_document(game, package_name, item_name);
        let full_name = Documents::format_document_name(res_type, package_name, item_name);

        let package_string = package_name.to_string();
        let item_string = item_name.to_string();

        if let Some(window_index) = self.name_to_document.get(&full_name)
        {
            self.document_to_focus = Some(*window_index);
        }
        else
        {
            self.document_to_focus = Some(self.documents.len());

            self.name_to_document.insert(full_name.clone(), self.documents.len());
            self.documents.push(DocumentEntry
            {
                document : document,
                res_type,
                package_name : package_string,
                item_name : item_string,
                dirty : false
            });
        }
    }

    pub fn save_document(&mut self, packages : &mut Packages, game : &mut GameControl, res_type : ResourceType, package_name : &str, item_name : &str)
    {
        let full_name = Documents::format_document_name(res_type, package_name, item_name);
        let document_index = try_opt_or_else!(self.name_to_document.get(&full_name), || ()).clone();

        if !self.documents[document_index].dirty
        {
            return;
        }

        match res_type.save(game, package_name, item_name, packages.source_id)
        {
            Ok(_) =>
            {
                self.documents[document_index].dirty = false;
            },
            Err(error) =>
            {
                let package_error = format!("Failed to save \"{}/{}/{}\": {}",
                                            &package_name, res_type.folder_name(), &item_name, error.to_string());
                error!("{}", package_error);

                packages.package_errors.push(package_error);
            }
        }
    }

    pub fn close_document(&mut self, packages : &mut Packages, game : &mut GameControl, res_type : ResourceType, package_name : &str, item_name : &str)
    {
        let full_name = Documents::format_document_name(res_type, package_name, item_name);
        let document_index = try_opt_or_else!(self.name_to_document.get(&full_name), || ()).clone();

        match res_type.reload(game, package_name, item_name)
        {
            Ok(_) =>
            {
                self.documents.remove(document_index);
                self.refresh_name_lookup();
            },
            Err(error) =>
            {
                let package_error = format!("Failed to revert \"{}/{}/{}\": {}",
                                            &package_name, res_type.folder_name(), &item_name, error.to_string());
                error!("{}", package_error);

                packages.package_errors.push(package_error);
            }
        }
    }

    pub fn close_all_documents(&mut self)
    {
        self.documents.clear();
        self.refresh_name_lookup();
    }

    pub fn refresh_name_lookup(&mut self)
    {
        self.name_to_document.clear();

        for i in 0..self.documents.len()
        {
            let entry = &self.documents[i];
            let full_name = Documents::format_document_name(entry.res_type, &entry.package_name, &entry.item_name);
            self.name_to_document.insert(full_name, i);
        }
    }

    pub fn document_info(&self, index : usize) -> Option<(ResourceType, String, String)>
    {
        if let Some(entry) = self.documents.get(index)
        {
            return Some((entry.res_type.clone(), entry.package_name.to_string(), entry.item_name.to_string()));
        }

        None
    }

    pub fn format_document_name(res_type : ResourceType, package_name : &str, item_name : &str) -> String
    {
        format!("{}/{}/{}", package_name, res_type.folder_name(), item_name)
    }

    pub fn full_document_name(&mut self, index : usize) -> String
    {
        if let Some(document) = self.documents.get(index)
        {
            return Documents::format_document_name(document.res_type, &document.package_name, &document.item_name);
        }

        String::new()
    }
}

pub struct Packages
{
    need_reload: bool,
    load_source : bool,
    source_id : SourceId,
    packages_listed : bool,
    im_source_path : ImString,
    package_list : Vec<String>,
    package_errors : Vec<String>,
    res : Vec<ResourceContents>
}

impl Packages
{
    pub fn source_id(&self) -> SourceId
    {
        self.source_id
    }

    pub(crate) fn append_error(&mut self, error : String)
    {
        self.package_errors.push(error);
    }

    pub(crate) fn package_iter(&self) -> Iter<String>
    {
        self.package_list.iter()
    }

    pub(crate) fn package_contents(&self, res_type : ResourceType, package_name : &str) -> Iter<String>
    {
        let res = &self.res[res_type as usize];

        if let Some(contents) = res.package_contents.get(package_name)
        {
            return contents.iter();
        }

        [].iter()
    }

    pub fn package_loaded(&self, res_type : ResourceType, package_name : &str) -> bool
    {
        let res = &self.res[res_type as usize];

        res.package_contents.contains_key(package_name)
    }

    pub fn load_package_later(&mut self, res_type : ResourceType, package_name : &str)
    {
        if self.package_loaded(res_type, package_name)
        {
            return;
        }

        let res = &mut self.res[res_type as usize];

        res.packages_to_load.insert(package_name.to_string());
    }

    pub fn load_sources(&mut self, game : &mut GameControl)
    {
        if self.source_id == 0 && self.load_source
        {
            let source_manager = game.source_manager();
            let source = FilesystemSource::new(&self.im_source_path.to_str(), TrustLevel::TrustedSource);

            self.source_id = source_manager.borrow_mut().add_source(Box::new(source));

            self.load_source = false;
            self.need_reload = true;
        }

        if self.need_reload
        {
            self.package_list.clear();

            for res_type in ResourceType::iter()
            {
                res_type.unload_resources(game);

                let res = &mut self.res[res_type as usize];

                for (package, _) in &res.package_contents
                {
                    res.packages_to_load.insert(package.clone());
                }
            }

            self.packages_listed = false;
            self.need_reload = false;
        }

        if self.source_id != 0 && !self.packages_listed
        {
            let source_manager = game.source_manager();
            let mut source_manager_borrow = source_manager.borrow_mut();
            let source_option = source_manager_borrow.source(self.source_id);

            if let Some(source) = source_option
            {
                self.package_list = source.list_packages();
            }

            self.packages_listed = true;
        }

        for res_type in ResourceType::iter()
        {
            let res = &mut self.res[res_type as usize];
            for package_name in res.packages_to_load.iter()
            {
                let mut contents = Vec::new();
                let load_start = Instant::now();

                match res_type.load_package(game, package_name)
                {
                    Ok(_) =>
                    {
                        let load_time = (Instant::now() - load_start).as_millis();
                        contents = res_type.list_package_contents(game, &package_name);

                        info!("Loaded {} package \"{}\" in {} ms", res_type.folder_name(), &package_name, load_time);
                    },
                    Err(error) =>
                    {
                        let package_error = format!("Failed to load {} package \"{}\": {}",
                                                    res_type.folder_name(), &package_name, error.to_string());
                        error!("{}", package_error);

                        self.package_errors.push(package_error);
                    }
                }

                res.package_contents.insert(package_name.clone(), contents);
            }

            res.packages_to_load.clear();
        }
    }

    pub fn reload(&mut self)
    {
        self.need_reload = true;
    }
}

pub struct EditorControl
{
    pub documents : Documents,
    pub packages : Packages,
    pub wizard : Option<Box<dyn Wizard>>,
    wizard_raise : bool,
    config : EditorConfig,
    style_dirty : bool
}

impl EditorControl
{
    pub fn new() -> EditorControl
    {
        let mut res = Vec::new();
        
        for _ in 0 .. ResourceType::count()
        {
            res.push(ResourceContents::new());
        }
        
        EditorControl
        {
            config : confy::load("keeshond_editor").unwrap_or(EditorConfig::default()),
            style_dirty : true,
            packages : Packages
            {
                need_reload: false,
                load_source: false,
                source_id: 0,
                packages_listed: false,
                im_source_path: ImString::with_capacity(FILEPATH_MAXSIZE),
                package_list: Vec::new(),
                package_errors: Vec::new(),
                res
            },
            documents : Documents
            {
                documents: Vec::new(),
                name_to_document: HashMap::new(),
                document_to_focus: None,
                active_document: None
            },
            wizard : None,
            wizard_raise : false
        }
    }

    fn new_resource_wizard(&mut self, game : &mut GameControl, res_type : ResourceType)
    {
        match res_type
        {
            ResourceType::Sheet => { self.wizard = Some(Box::new(SheetWizard::load(game))) }
            ResourceType::Costume => { self.wizard = Some(Box::new(CostumeWizard::load(game))) }
            ResourceType::Tileset => { self.wizard = Some(Box::new(TilesetWizard::load(game))) }
        }

        self.wizard_raise = true;
    }

    fn adjust_color(&self, color : [f32; 4]) -> [f32; 4]
    {
        let hue = match self.config.flavor_accent
        {
            FlavorAccent::Raspberry => 350.0,
            FlavorAccent::Orange => 35.0,
            FlavorAccent::Lime => 90.0,
            FlavorAccent::Blueberry => 225.0,
            FlavorAccent::Grape => 260.0
        };

        let saturation = match self.config.flavor_accent
        {
            FlavorAccent::Raspberry => 0.0,
            FlavorAccent::Orange => 0.65,
            FlavorAccent::Lime => 0.1,
            FlavorAccent::Blueberry => 0.0,
            FlavorAccent::Grape => 0.0
        };

        let value = match self.config.flavor_accent
        {
            FlavorAccent::Raspberry => -0.1,
            FlavorAccent::Orange => -0.15,
            FlavorAccent::Lime => -0.5,
            FlavorAccent::Blueberry => 0.0,
            FlavorAccent::Grape => 0.2
        };

        let mut color_hue : Hsv = Srgb::new(color[0], color[1], color[2]).into();

        if color_hue.saturation > 0.1
        {
            color_hue.value *= 1.0 + (value * (color_hue.saturation));
            let new_color = Srgb::from(color_hue.with_hue(hue).saturate(saturation));

            return [new_color.red, new_color.green, new_color.blue, color[3]];
        }

        color
    }

    fn set_flavor_main(&mut self, flavor : FlavorMain)
    {
        self.config.flavor_main = flavor;
        self.style_dirty = true;
    }

    fn set_flavor_accent(&mut self, flavor : FlavorAccent)
    {
        self.config.flavor_accent = flavor;
        self.style_dirty = true;
    }

    fn set_flavor_font(&mut self, flavor : FlavorFont)
    {
        self.config.flavor_font = flavor;
        self.style_dirty = true;
    }

    fn set_keyboard_navigation(&mut self, enabled : bool)
    {
        self.config.keyboard_navigation = enabled;
        self.style_dirty = true;
    }

    fn set_drag_window_from_anywhere(&mut self, enabled : bool)
    {
        self.config.drag_window_from_anywhere = enabled;
        self.style_dirty = true;
    }

    fn apply_imgui_style(&mut self, imgui: &mut imgui::Context, background : &mut Drawable)
    {
        if !self.style_dirty
        {
            return;
        }

        imgui.io_mut().config_flags.set(imgui::ConfigFlags::NAV_ENABLE_KEYBOARD, self.config.keyboard_navigation);
        imgui.io_mut().config_windows_move_from_title_bar_only = !self.config.drag_window_from_anywhere;

        let mut style = imgui.style_mut();

        match self.config.flavor_main
        {
            FlavorMain::Vanilla | FlavorMain::CookiesNCream =>
            {
                style.use_light_colors();
            },
            FlavorMain::MilkChocolate | FlavorMain::DarkChocolate =>
            {
                style.use_dark_colors();
            }
        }

        style.window_title_align = [0.5, 0.5];
        style.item_spacing = [8.0, 3.0];
        style.frame_rounding = 1.0;
        style.frame_border_size = 1.0;
        style.window_rounding = 2.0;
        style.popup_rounding = 1.0;
        style.child_rounding = 1.0;
        style.tab_rounding = 2.0;
        style.tab_border_size = 1.0;
        style.scrollbar_rounding = 2.0;
        style.indent_spacing = 28.0;

        let colors = &mut style.colors;

        match self.config.flavor_main
        {
            FlavorMain::Vanilla =>
            {
                colors[StyleColor::TitleBgActive as usize] = [0.4, 0.58, 0.92, 1.0];
                colors[StyleColor::Border as usize][3] = 0.4;

                *background = Drawable::with_background_color(0.75, 0.75, 0.75);
            },
            FlavorMain::CookiesNCream =>
            {
                colors[StyleColor::TitleBgActive as usize] = [0.3, 0.6, 0.9, 1.0];
                colors[StyleColor::WindowBg as usize] = [0.82, 0.82, 0.82, 1.0];
                colors[StyleColor::FrameBg as usize] = [0.96, 0.96, 0.96, 1.0];
                colors[StyleColor::PopupBg as usize] = [0.9, 0.9, 0.9, 1.0];
                colors[StyleColor::MenuBarBg as usize] = [0.75, 0.75, 0.75, 1.0];
                colors[StyleColor::TitleBg as usize] = colors[StyleColor::MenuBarBg as usize];
                colors[StyleColor::Border as usize][3] = 0.4;

                *background = Drawable::with_background_color(0.6, 0.6, 0.6);
            },
            FlavorMain::MilkChocolate =>
            {
                colors[StyleColor::WindowBg as usize] = [0.28, 0.28, 0.28, 1.0];
                colors[StyleColor::FrameBg as usize] = [0.2, 0.2, 0.2, 1.0];
                colors[StyleColor::PopupBg as usize] = [0.25, 0.25, 0.25, 1.0];
                colors[StyleColor::MenuBarBg as usize] = [0.22, 0.22, 0.22, 1.0];
                colors[StyleColor::TitleBg as usize] = colors[StyleColor::MenuBarBg as usize];
                colors[StyleColor::TitleBgActive as usize] = [0.16, 0.34, 0.64, 1.0];
                colors[StyleColor::TabActive as usize] = [0.22, 0.44, 0.75, 1.0];
                colors[StyleColor::Border as usize] = [0.0, 0.0, 0.0, 0.7];
                colors[StyleColor::Button as usize][3] = 0.6;
                colors[StyleColor::Header as usize][3] = 0.5;
                colors[StyleColor::Separator as usize] = colors[StyleColor::Border as usize];
                colors[StyleColor::NavWindowingDimBg as usize] = [0.0, 0.0, 0.0, 0.25];
                colors[StyleColor::ModalWindowDimBg as usize] = [0.0, 0.0, 0.0, 0.35];

                *background = Drawable::with_background_color(0.12, 0.12, 0.12);
            },
            FlavorMain::DarkChocolate =>
            {
                colors[StyleColor::Border as usize][3] = 0.5;

                *background = Drawable::with_background_color(0.0, 0.0, 0.0);
            }
        }

        colors[StyleColor::WindowBg as usize][3] = 1.0;
        colors[StyleColor::TitleBgCollapsed as usize] = colors[StyleColor::TitleBg as usize];
        colors[StyleColor::ScrollbarGrab as usize] = colors[StyleColor::Button as usize];
        colors[StyleColor::ScrollbarGrabHovered as usize] = colors[StyleColor::ButtonHovered as usize];
        colors[StyleColor::ScrollbarGrabActive as usize] = colors[StyleColor::ButtonActive as usize];
        colors[StyleColor::NavWindowingHighlight as usize] = colors[StyleColor::TitleBgActive as usize];

        for style_color in [StyleColor::TitleBgActive, StyleColor::Border, StyleColor::FrameBg, StyleColor::FrameBgHovered,
            StyleColor::FrameBgActive, StyleColor::TitleBg, StyleColor::TitleBgCollapsed,
            StyleColor::CheckMark, StyleColor::SliderGrab, StyleColor::SliderGrabActive, StyleColor::Button,
            StyleColor::ButtonHovered, StyleColor::ButtonActive, StyleColor::Header, StyleColor::HeaderHovered,
            StyleColor::HeaderActive, StyleColor::SeparatorHovered, StyleColor::SeparatorActive,
            StyleColor::ScrollbarGrab, StyleColor::ScrollbarGrabHovered, StyleColor::ScrollbarGrabActive,
            StyleColor::ResizeGripHovered, StyleColor::ResizeGripActive, StyleColor::Tab,
            StyleColor::TabHovered, StyleColor::TabActive, StyleColor::TabUnfocusedActive,
            StyleColor::TextSelectedBg, StyleColor::DragDropTarget, StyleColor::NavHighlight,
            StyleColor::ResizeGrip, StyleColor::ResizeGripActive, StyleColor::ResizeGripHovered,
            StyleColor::NavWindowingHighlight].iter()
        {
            colors[*style_color as usize] = self.adjust_color(colors[*style_color as usize]);
        }

        self.style_dirty = false;
    }

    pub fn save_config(&mut self)
    {
        self.config.source_path = self.packages.im_source_path.to_str().to_string();

        match confy::store("keeshond_editor", self.config.clone())
        {
            Ok(_) =>
            {
                info!("Config saved.");
            },
            Err(error) =>
            {
                error!("Error while saving config: {}", error);
            }
        }
    }

    pub fn switch_project(&mut self, game : &mut GameControl)
    {
        game.source_manager().borrow_mut().clear();

        EditorGui::set_scene(game);
    }
}

impl Component for EditorControl {}

pub struct EditorGui
{
    packages_open : bool,
    demo_open : bool,
    metrics_open : bool,
    imgui_guide_open : bool,
    imgui_about_open : bool,
}

impl EditorGui
{
    pub fn set_scene(game : &mut GameControl)
    {
        let mut my_scene = Box::new(SimpleScene::new());
        my_scene.add_thinker_system(Box::new(EditorThinker::new()));
        my_scene.add_thinker_system(Box::new(ActorThinker::new(LoadErrorMode::Warning)));
        my_scene.add_drawer_system(Box::new(DrawableDrawer::new(PixelSnap::SpriteAndCameraSnap)));
        my_scene.set_imgui_system(Box::new(EditorGui::new()));

        game.goto_constructed_scene(my_scene);
    }

    pub fn new() -> EditorGui
    {
        EditorGui
        {
            packages_open : true,
            demo_open : false,
            metrics_open : false,
            imgui_guide_open : false,
            imgui_about_open : false
        }
    }

    fn main_menubar(&mut self, editor : &mut EditorControl, ui : &mut imgui::Ui, game : &mut GameControl)
    {
        ui.main_menu_bar(||
        {
            ui.menu(im_str!("File"), true, ||
            {
                ui.menu(im_str!("New Resource"), true, ||
                {
                    for res_type in ResourceType::iter()
                    {
                        if imgui::MenuItem::new(&im_str!("{}", res_type.to_string())).build(ui)
                        {
                            editor.new_resource_wizard(game, res_type);
                        }
                    }
                });

                ui.separator();

                let mut document_name = String::from("Document");
                let mut package_name = String::new();
                let mut item_name = String::new();
                let mut res_type = ResourceType::Sheet;
                let mut dirty = false;

                if let Some(active_index) = editor.documents.active_document
                {
                    if let Some(document) = editor.documents.documents.get(active_index)
                    {
                        package_name = document.package_name.clone();
                        item_name = document.item_name.clone();
                        res_type = document.res_type;
                        dirty = document.dirty;

                        document_name = format!("{}/{}/{}", package_name, res_type.folder_name(), item_name);
                    }
                }
                if imgui::MenuItem::new(&im_str!("Save {}", document_name)).enabled(dirty).build(ui)
                {
                    editor.documents.save_document(&mut editor.packages, game, res_type, &package_name, &item_name);
                }

                ui.separator();

                if imgui::MenuItem::new(im_str!("Close All Documents")).build(ui)
                {
                    editor.documents.close_all_documents();
                }

                ui.separator();

                if imgui::MenuItem::new(im_str!("Reload Packages")).build(ui)
                {
                    editor.packages.reload();
                }
                if imgui::MenuItem::new(im_str!("Switch Project")).build(ui)
                {
                    editor.switch_project(game);
                }
                ui.separator();
                if imgui::MenuItem::new(im_str!("Quit")).build(ui)
                {
                    game.quit();
                }
            });
            
            ui.menu(im_str!("View"), true, ||
            {
                if imgui::MenuItem::new(im_str!("Packages")).selected(self.packages_open).build(ui)
                {
                    self.packages_open = !self.packages_open;
                }
            });

            ui.menu(im_str!("Settings"), true, ||
            {
                ui.menu(im_str!("Flavor"), true, ||
                {
                    for flavor_main in FlavorMain::iter()
                    {
                        if imgui::MenuItem::new(&im_str!("{}", flavor_main)).selected(editor.config.flavor_main == flavor_main).build(ui)
                        {
                            editor.set_flavor_main(flavor_main);
                        }
                    }

                    ui.separator();

                    for flavor_accent in FlavorAccent::iter()
                    {
                        if imgui::MenuItem::new(&im_str!("{}", flavor_accent)).selected(editor.config.flavor_accent == flavor_accent).build(ui)
                        {
                            editor.set_flavor_accent(flavor_accent);
                        }
                    }

                    ui.separator();

                    for flavor_font in FlavorFont::iter()
                    {
                        if imgui::MenuItem::new(&im_str!("{}", flavor_font)).selected(editor.config.flavor_font == flavor_font).build(ui)
                        {
                            editor.set_flavor_font(flavor_font);
                        }
                    }
                });

                ui.separator();

                if imgui::MenuItem::new(im_str!("Keyboard Navigation")).selected(editor.config.keyboard_navigation).build(ui)
                {
                    editor.set_keyboard_navigation(!editor.config.keyboard_navigation);
                }

                if imgui::MenuItem::new(im_str!("Drag Windows From Anywhere")).selected(editor.config.drag_window_from_anywhere).build(ui)
                {
                    editor.set_drag_window_from_anywhere(!editor.config.drag_window_from_anywhere);
                }
            });

            if ui.io().key_ctrl && ui.io().key_alt && ui.io().key_shift
            {
                ui.menu(im_str!("Debug"), true, ||
                {
                    if imgui::MenuItem::new(im_str!("ImGui Metrics")).selected(self.metrics_open).build(ui)
                    {
                        self.metrics_open = !self.metrics_open;
                    }
                    if imgui::MenuItem::new(im_str!("ImGui Demo Window")).selected(self.demo_open).build(ui)
                    {
                        self.demo_open = !self.demo_open;
                    }
                });
            }
            
            ui.menu(im_str!("Help"), true, ||
            {
                if imgui::MenuItem::new(im_str!("ImGui User Guide")).selected(self.imgui_guide_open).build(ui)
                {
                    self.imgui_guide_open = !self.imgui_guide_open;
                }
                if imgui::MenuItem::new(im_str!("About ImGui")).selected(self.imgui_about_open).build(ui)
                {
                    self.imgui_about_open = !self.imgui_about_open;
                }
            });
        });
    }
    
    fn view_packages(&mut self, editor : &mut EditorControl, ui : &mut imgui::Ui, game : &mut GameControl)
    {
        let mut load_res = ResourceType::Sheet;
        let mut load_package_name = String::new();
        let mut load_item_name = String::new();

        if self.packages_open
        {
            let main_style = ui.clone_style();
            let colors_slice_list = ui.push_style_color(imgui::StyleColor::WindowBg, main_style.colors[imgui::StyleColor::FrameBg as usize]);

            imgui::Window::new(im_str!("Packages"))
                .position([12.0, 38.0], imgui::Condition::Appearing)
                .size([250.0, 600.0], imgui::Condition::Appearing)
                .opened(&mut self.packages_open).build(ui, ||
            {
                let mut package_list = Vec::new();
                
                std::mem::swap(&mut editor.packages.package_list, &mut package_list);

                let style_var = ui.push_style_vars(&[imgui::StyleVar::IndentSpacing(16.0),
                    imgui::StyleVar::ItemSpacing([3.0, 0.0]),
                    imgui::StyleVar::FramePadding([3.0, 2.0])]);
                
                for package_name in &package_list
                {
                    imgui::TreeNode::new(&im_str!("{}", package_name)).flags(imgui::TreeNodeFlags::SPAN_AVAIL_WIDTH)
                        .frame_padding(true).default_open(package_list.len() <= 4).build(ui, ||
                    {
                        for res_type in ResourceType::iter()
                        {
                            imgui::TreeNode::new(&im_str!("{}", res_type.folder_name())).flags(imgui::TreeNodeFlags::SPAN_AVAIL_WIDTH)
                                .frame_padding(true).build(ui, ||
                            {
                                if !editor.packages.package_loaded(res_type, package_name)
                                {
                                    editor.packages.load_package_later(res_type, package_name);
                                }
                                else
                                {
                                    let res = &mut editor.packages.res[res_type as usize];

                                    for item in res.package_contents[package_name].iter()
                                    {
                                        imgui::TreeNode::new(&im_str!("{}", item)).flags(imgui::TreeNodeFlags::SPAN_AVAIL_WIDTH)
                                            .frame_padding(true).bullet(true).opened(false, Condition::Always).build(ui, ||
                                        {
                                            load_res = res_type;
                                            load_package_name = package_name.clone();
                                            load_item_name = item.clone();
                                        });
                                    }
                                }
                            });
                        }
                    });
                }

                style_var.pop(ui);
                
                std::mem::swap(&mut editor.packages.package_list, &mut package_list);
            });

            colors_slice_list.pop(ui);
        }

        if !load_package_name.is_empty()
        {
            editor.documents.open_document(game, load_res, &load_package_name, &load_item_name);
        }
    }

    fn document_windows(&mut self, editor : &mut EditorControl, ui : &mut imgui::Ui, game : &mut GameControl)
    {
        let mut documents_to_close = Vec::new();

        for i in 0..editor.documents.documents.len()
        {
            let document_window_name = editor.documents.full_document_name(i);
            let dirty = editor.documents.documents[i].dirty;
            let document = &mut editor.documents.documents[i].document;
            let raise = editor.documents.document_to_focus == Some(i);
            let mut window_open = true;
            let new_window_w = ui.io().display_size[0] - 294.0;
            let new_window_h = ui.io().display_size[1] - 60.0;
            let window_name;

            if dirty
            {
                window_name = im_str!("{} (modified)###{}", document_window_name, document_window_name);
            }
            else
            {
                window_name = im_str!("{}###{}", document_window_name, document_window_name);
            }

            let window = imgui::Window::new(&window_name).focused(raise).opened(&mut window_open)
                .position([280.0, 38.0], imgui::Condition::Appearing)
                .size([new_window_w, new_window_h], imgui::Condition::Appearing)
                .collapsed(false, imgui::Condition::Appearing);

            let result = document.do_ui(&mut editor.packages, game, window, ui);
            let dirty = document.do_action_queue(game);

            editor.documents.documents[i].dirty |= dirty;

            if !window_open
            {
                documents_to_close.push(i);
            }
            if result.active
            {
                editor.documents.active_document = Some(i);
            }
        }

        for i in &documents_to_close
        {
            if let Some((res_type, package_name, item_name)) = editor.documents.document_info(*i)
            {
                editor.documents.close_document(&mut editor.packages, game, res_type, &package_name, &item_name);
            }
        }

        editor.documents.document_to_focus = None;
    }
    
    fn main_gui(&mut self, editor : &mut EditorControl, ui : &mut imgui::Ui, game : &mut GameControl)
    {
        self.main_menubar(editor, ui, game);
        self.view_packages(editor, ui, game);
        self.document_windows(editor, ui, game);

        if let Some(wizard) = &mut editor.wizard
        {
            let size = ui.io().display_size;
            let mut window_open = true;
            let title = im_str!("{}", wizard.window_title());

            let window = imgui::Window::new(&title).collapsible(false).save_settings(false)
                .position([size[0] / 2.0, size[1] / 2.0], imgui::Condition::Appearing)
                .size([440.0, 0.0], Condition::Appearing)
                .position_pivot([0.5, 0.5]).focused(editor.wizard_raise).opened(&mut window_open);

            let state = wizard.do_ui(&mut editor.packages, game, window, ui);

            window_open &= state.open;
            editor.wizard_raise = false;

            if !window_open
            {
                editor.wizard = None;
            }
        }
        
        if editor.packages.package_errors.len() > 0
        {
            imgui::Window::new(im_str!("Package Errors")).position([640.0, 360.0], imgui::Condition::Appearing)
                .position_pivot([0.5, 0.5])
                .size([640.0, 360.0], imgui::Condition::Appearing).build(ui, ||
            {
                imgui::ChildWindow::new("package_error_list").size([0.0, -40.0]).border(true).build(ui, ||
                {
                    for error in &editor.packages.package_errors
                    {
                        ui.text_wrapped(&im_str!("{}", error));
                    }
                });
                
                ui.spacing();
                
                if ui.button(im_str!("Dismiss"), [100.0, 30.0])
                {
                    editor.packages.package_errors.clear();
                }
            });
        }
        
        if self.demo_open
        {
            ui.show_demo_window(&mut self.demo_open);
        }

        if self.metrics_open
        {
            ui.show_metrics_window(&mut self.metrics_open);
        }
        
        if self.imgui_guide_open
        {
            imgui::Window::new(im_str!("ImGui User Guide")).always_auto_resize(true)
                .opened(&mut self.imgui_guide_open).build(ui, ||
            {
                ui.show_user_guide();
            });
        }
        
        if self.imgui_about_open
        {
            ui.show_about_window(&mut self.imgui_about_open);
        }
    }
}

impl ImGuiSystem<NullSceneType> for EditorGui
{
    fn start(&mut self, _components : &mut ComponentControl, scene : &mut SceneControl<NullSceneType>, game : &mut GameControl)
    {
        let mut editor = EditorControl::new();
        let mut background = Drawable::new();

        let bg_entity = scene.add_entity_later();
        let editor_entity = scene.add_entity_later();

        if let Some(imgui) = game.imgui_mut()
        {
            editor.apply_imgui_style(imgui, &mut background);
        }

        editor.packages.im_source_path.push_str(&editor.config.source_path);

        scene.add_component_later(&bg_entity, background);
        scene.add_component_later(&editor_entity, editor);
    }

    fn end(&mut self, components : &mut ComponentControl, _scene : &mut SceneControl<NullSceneType>, _game : &mut GameControl)
    {
        let mut comp_editor_control = components.store_mut::<EditorControl>();
        let (_, editor) = comp_editor_control.iter_mut().next().unwrap();

        editor.save_config();
    }

    fn imgui_think(&mut self, ui : &mut imgui::Ui, components : &mut ComponentControl, _scene : &mut SceneControl<NullSceneType>, game : &mut GameControl)
    {
        let mut comp_editor_control = components.store_mut::<EditorControl>();
        let (_, editor) = comp_editor_control.iter_mut().next().unwrap();

        let mut font_token = None;

        if let Some(font_id) = ui.fonts().fonts().get(editor.config.flavor_font as usize)
        {
            font_token = Some(ui.push_font(*font_id));
        }

        editor.packages.load_sources(game);

        if editor.packages.source_id == 0
        {
            let size = ui.io().display_size;

            imgui::Window::new(im_str!("Select Package Source")).collapsible(false).save_settings(false)
                .size([370.0, 0.0], imgui::Condition::Always).position([size[0] / 2.0, size[1] / 3.0], imgui::Condition::Appearing)
                .position_pivot([0.5, 0.5]).build(ui, ||
            {
                ui.text_wrapped(im_str!("Please enter the path to your game's data folder."));

                ui.spacing();
                ui.spacing();
                
                let push = ui.push_item_width(350.0);
                if ui.input_text(im_str!(""), &mut editor.packages.im_source_path)
                    .enter_returns_true(true).build()
                {
                    editor.packages.load_source = true;
                }
                push.pop(ui);

                ui.spacing();
                
                if ui.button(im_str!("Next"), [100.0, 30.0])
                {
                    editor.packages.load_source = true;
                }
            });
        }
        else
        {
            self.main_gui(editor, ui, game);
        }

        if let Some(some_token) = font_token
        {
            some_token.pop(ui);
        }
    }
}

struct EditorThinker
{

}

impl EditorThinker
{
    fn new() -> EditorThinker { EditorThinker {} }
}

impl ThinkerSystem<NullSceneType> for EditorThinker
{
    fn think(&mut self, components: &mut ComponentControl, _scene: &mut SceneControl<NullSceneType>, game: &mut GameControl)
    {
        let mut comp_editor_control = components.store_mut::<EditorControl>();
        let (_, editor) = comp_editor_control.iter_mut().next().unwrap();

        let mut drawable_store = components.store_mut();

        if let Some((_, drawable)) = drawable_store.iter_mut().next()
        {
            if let Some(imgui) = game.imgui_mut()
            {
                editor.apply_imgui_style(imgui, drawable);
            }
        }
    }
}
