
use keeshond::gameloop::GameControl;
use keeshond::renderer::Sheet;

use keeshond_datapack::{DataId, DataStore};
use keeshond_treats::visual::{Costume, Animation, AnimationCel, SheetSource, SliceSource};

use crate::document::{Document, DocumentState, ActionHistory};
use crate::editorgui::{ResourceType, Packages};
use crate::util::BoxFitResult;

use std::any::Any;

use imgui::im_str;

const CEL_BUTTON_SIZE : f32 = 96.0;

enum CostumeAction
{
    AddAnimation(usize, Animation),
    RenameAnimation(usize, String),
    SetAnimation(usize, Animation),
    MoveAnimation(usize, usize),
    RemoveAnimation(usize),
    AddCel(usize, usize, AnimationCel),
    SetCel(usize, usize, AnimationCel),
    MoveCel(usize, usize, usize),
    RemoveCel(usize, usize)
}

pub struct CostumeDocument
{
    action_history : ActionHistory,
    data_id : DataId,
    show_origin : bool,
    transparent_color : [f32; 3],
    zoom_level : f32,
    current_animation : usize,
    current_cel : usize,
    play_animation : bool,
    animation_timer : f64,
    tics : u64,
    focus_animation_name_input : bool,
    scroll_anim_list : bool,
    scroll_cel_list : bool
}

impl CostumeDocument
{
    fn add_animation(&mut self, index : usize, costume: &mut Costume, animation : Animation)
    {
        let mut target_animation = 0;

        if costume.animation_count() > 0
        {
            target_animation = index + 1;
        }

        costume.insert_animation(target_animation, animation);

        self.current_animation += 1;
    }

    fn move_animation(&mut self, costume : &mut Costume, old : usize, new : usize) -> bool
    {
        if new >= costume.animation_count()
        {
            return false;
        }

        if let Some(animation) = costume.animation(old).cloned()
        {
            let old_name = costume.animation_name(old);

            costume.remove_animation(old);
            costume.insert_animation(new, animation);

            if let Some(name) = old_name
            {
                costume.rename_animation(new, &name);
            }

            self.current_animation = new;

            return true;
        }

        false
    }

    fn add_cel(&mut self, anim_index : usize, cel_index : usize, costume : &mut Costume, cel : AnimationCel) -> bool
    {
        if let Some(mut animation) = costume.animation(anim_index).cloned()
        {
            let mut target_cel = 0;

            if animation.cel_count() > 0
            {
                target_cel = cel_index + 1;
            }

            if animation.insert_cel(target_cel, cel.clone())
            {
                self.current_cel += 1;

                return costume.set_animation(anim_index, animation);
            }
        }

        false
    }

    fn cel_image(&mut self, cel : &AnimationCel, sheet_store : &mut DataStore<Sheet>, packages : &mut Packages) -> Option<(imgui::Image, f32, f32, f32, f32)>
    {
        let (package, sheet_name) = (cel.sheet_source.package(), cel.sheet_source.sheet());

        if package.is_empty() || sheet_name.is_empty()
        {
            return None;
        }

        if let Ok(sheet_id) = sheet_store.get_id(package, sheet_name)
        {
            if let Some(sheet) = sheet_store.get(sheet_id)
            {
                if let Some(slice_id) = sheet.slice_id(&cel.slice_source.slice())
                {
                    if let Some(slice) = sheet.slice(slice_id)
                    {
                        let (w, h) = (slice.texture_w, slice.texture_h);
                        let rect = crate::util::slice_to_uv(sheet, slice);

                        return Some((imgui::Image::new(imgui::TextureId::from(sheet_id), [w, h])
                                         .uv0([rect.x1, rect.y1]).uv1([rect.x2, rect.y2]), w, h, slice.origin_x, slice.origin_y));
                    }
                }
            }
        }
        else
        {
            packages.load_package_later(ResourceType::Sheet, package);
        }

        None
    }

    fn cel_image_fit(&mut self, cel : &AnimationCel, sheet_store : &mut DataStore<Sheet>, packages : &mut Packages) -> Option<(imgui::Image, BoxFitResult<f32>)>
    {
        if let Some((mut image, w, h, _, _)) = self.cel_image(cel, sheet_store, packages)
        {
            let fit = crate::util::box_fit(w, h, CEL_BUTTON_SIZE, CEL_BUTTON_SIZE);

            image = image.size([fit.width, fit.height]);
            return Some((image, fit));
        }

        None
    }

    fn draw_crosshair(&mut self, draw_list : &imgui::WindowDrawList, x : f32, y : f32)
    {
        let pulse = (((self.tics as f64 * 0.1).sin() + 1.0) / 2.0) as f32;

        let cross_size = 4.1 + self.zoom_level.min(8.0);
        let cross_color = [pulse, pulse, pulse, 1.0];

        draw_list.add_line([x - cross_size, y], [x + cross_size, y], imgui::ImColor::from(cross_color)).thickness(1.0).build();
        draw_list.add_line([x, y - cross_size], [x, y + cross_size], imgui::ImColor::from(cross_color)).thickness(1.0).build();
    }
}

impl Document for CostumeDocument
{
    fn load(game: &mut GameControl, package_name: &str, item_name: &str) -> Self where Self: Sized
    {
        let mut data_id = 0;

        if let Ok(id) = game.res().store_mut::<Costume>().get_id_mut(package_name, item_name)
        {
            data_id = id;
        }

        CostumeDocument
        {
            action_history : ActionHistory::new(),
            data_id,
            show_origin : true,
            transparent_color : [0.5, 0.5001, 0.5002],
            zoom_level : 1.0,
            current_animation : 0,
            current_cel : 0,
            play_animation : false,
            animation_timer : 0.0,
            tics : 0,
            focus_animation_name_input : false,
            scroll_anim_list : false,
            scroll_cel_list : false
        }
    }

    fn action_history(&mut self) -> &mut ActionHistory
    {
        &mut self.action_history
    }

    fn apply_action(&mut self, game: &mut GameControl, action: &Box<dyn Any>) -> bool
    {
        if let Some(action) = action.downcast_ref::<CostumeAction>()
        {
            let mut costume_store = game.res().store_mut::<Costume>();

            if let Some(costume) = costume_store.get_mut(self.data_id)
            {
                match action
                {
                    CostumeAction::AddAnimation(index, animation) =>
                    {
                        self.scroll_anim_list = true;
                        self.add_animation(*index, costume, animation.clone());
                        return true;
                    }
                    CostumeAction::RenameAnimation(index, name) =>
                    {
                        self.scroll_anim_list = true;
                        return costume.rename_animation(*index, name);
                    }
                    CostumeAction::SetAnimation(index, animation) =>
                    {
                        self.scroll_anim_list = true;
                        return costume.set_animation(*index, animation.clone());
                    }
                    CostumeAction::MoveAnimation(old_index, new_index) =>
                    {
                        self.scroll_anim_list = true;
                        return self.move_animation(costume, *old_index, *new_index);
                    }
                    CostumeAction::RemoveAnimation(index) =>
                    {
                        self.scroll_anim_list = true;
                        return costume.remove_animation(*index).is_some();
                    }
                    CostumeAction::AddCel(anim_index, cel_index, cel) =>
                    {
                        self.scroll_cel_list = true;
                        return self.add_cel(*anim_index, *cel_index, costume, cel.clone());
                    }
                    CostumeAction::SetCel(anim_index, cel_index, cel) =>
                    {
                        if let Some(mut animation) = costume.animation(*anim_index).cloned()
                        {
                            if animation.set_cel(*cel_index, cel.clone())
                            {
                                self.scroll_cel_list = true;
                                return costume.set_animation(*anim_index, animation);
                            }
                        }
                    }
                    CostumeAction::MoveCel(anim_index, old_index, new_index) =>
                    {
                        if let Some(mut animation) = costume.animation(*anim_index).cloned()
                        {
                            if let Some(old_cel) = animation.remove_cel(*old_index)
                            {
                                if animation.insert_cel(*new_index, old_cel)
                                {
                                    self.current_cel = *new_index;
                                    self.scroll_cel_list = true;
                                    return costume.set_animation(*anim_index, animation);
                                }
                            }
                        }
                    }
                    CostumeAction::RemoveCel(anim_index, cel_index) =>
                    {
                        if let Some(mut animation) = costume.animation(*anim_index).cloned()
                        {
                            if animation.remove_cel(*cel_index).is_some()
                            {
                                self.scroll_cel_list = true;
                                return costume.set_animation(*anim_index, animation);
                            }
                        }
                    }
                }
            }
        }

        false
    }

    fn do_ui(&mut self, packages : &mut Packages, game: &mut GameControl, window_builder : imgui::Window, ui : &mut imgui::Ui) -> DocumentState
    {
        self.tics = self.tics.wrapping_add(1);

        let mut result = DocumentState::new();

        window_builder.menu_bar(true).build(ui, ||
        {
            ui.menu_bar(||
            {
                ui.menu(im_str!("View"), true, ||
                {
                    if imgui::MenuItem::new(im_str!("Show Origin")).selected(self.show_origin).build(ui)
                    {
                        self.show_origin = !self.show_origin;
                    }

                    ui.menu(im_str!("Background Color"), true, ||
                    {
                        imgui::ColorPicker::new(im_str!("##background_color_picker"), imgui::EditableColor::Float3(&mut self.transparent_color)).build(ui);
                    });
                });
                ui.menu(im_str!("Zoom"), true, ||
                {
                    for level in &[0.25, 0.5, 1.0, 2.0, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 16.0]
                    {
                        if imgui::MenuItem::new(&im_str!("{}%", (level * 100.0) as i32)).selected(self.zoom_level == *level).build(ui)
                        {
                            self.zoom_level = *level;
                        }
                    }
                });
            });

            result.active = ui.is_window_focused();

            let mut costume_store = game.res().store_mut::<Costume>();
            let mut sheet_store = game.res().store_mut::<Sheet>();

            let main_style = ui.clone_style();

            if let Some(costume) = costume_store.get_mut(self.data_id)
            {
                ui.columns(2, im_str!("##costume_columns"), false);

                imgui::ChildWindow::new("costume_controls").size([240.0, 0.0]).draw_background(true).border(false).horizontal_scrollbar(true).build(ui, ||
                {
                    imgui::ChildWindow::new("animation_view").size([0.0, 0.0]).movable(false).build(ui, ||
                    {
                        self.current_animation = self.current_animation.min(costume.animation_count().max(1) - 1);

                        if let Some(animation) = costume.animation(self.current_animation)
                        {
                            if let Some(cel) = animation.cel(self.current_cel)
                            {
                                let mut update_cel = false;

                                ui.text(&im_str!("Editing Cel {}", self.current_cel));
                                ui.spacing();

                                let mut package_name = imgui::ImString::with_capacity(4096);
                                let mut sheet_name = imgui::ImString::with_capacity(4096);
                                let mut slice_name = imgui::ImString::with_capacity(4096);

                                package_name.push_str(cel.sheet_source.package());
                                sheet_name.push_str(cel.sheet_source.sheet());
                                slice_name.push_str(&cel.slice_source.slice());

                                let mut delay = cel.delay as f32;
                                let mut offset = [cel.x, cel.y];

                                let style_var = ui.push_style_var(imgui::StyleVar::ItemSpacing([-1.0, -1.0]));

                                update_cel |= ui.input_text(im_str!("Package"), &mut package_name).enter_returns_true(true).build();
                                update_cel |= ui.input_text(im_str!("Sheet"), &mut sheet_name).enter_returns_true(true).build();

                                style_var.pop(ui);

                                update_cel |= ui.input_text(im_str!("Slice"), &mut slice_name).enter_returns_true(true).build();

                                update_cel |= imgui::Drag::new(im_str!("Delay")).display_format(im_str!("%.4f")).build(ui, &mut delay);
                                update_cel |= imgui::Drag::new(im_str!("Offset")).display_format(im_str!("%.4f")).build_array(ui, &mut offset);

                                ui.spacing();

                                if update_cel
                                {
                                    let new_cel = AnimationCel
                                    {
                                        sheet_source : SheetSource::new(package_name.to_str(), sheet_name.to_str()),
                                        slice_source : SliceSource::new(slice_name.to_str()),
                                        delay : delay as f64,
                                        x: offset[0],
                                        y: offset[1]
                                    };

                                    self.queue_action(Box::new(CostumeAction::SetCel(self.current_animation, self.current_cel, new_cel)));
                                }

                                if self.play_animation
                                {
                                    self.animation_timer += 1.0;

                                    if self.animation_timer >= cel.delay
                                    {
                                        self.current_cel += 1;

                                        if animation.looping()
                                        {
                                            self.current_cel = self.current_cel % animation.cel_count();
                                        }
                                        
                                        self.animation_timer -= cel.delay;
                                    }
                                }
                            }

                            ui.text(&im_str!("Editing Animation {}", self.current_animation));
                            ui.spacing();

                            let mut update_animation = false;

                            let mut animation_name = imgui::ImString::with_capacity(4096);

                            if let Some(name) = costume.animation_name(self.current_animation)
                            {
                                animation_name.push_str(&name);
                            }

                            if self.focus_animation_name_input
                            {
                                ui.set_keyboard_focus_here(imgui::FocusedWidget::Offset(0));
                                self.focus_animation_name_input = false;
                            }

                            if ui.input_text(im_str!("Name"), &mut animation_name).enter_returns_true(true).build()
                            {
                                self.queue_action(Box::new(CostumeAction::RenameAnimation(self.current_animation, animation_name.to_string())));
                            }

                            let mut looping = animation.looping();

                            update_animation |= ui.checkbox(im_str!("Looping"), &mut looping);

                            if update_animation
                            {
                                let mut animation_clone = animation.clone();
                                animation_clone.set_looping(looping);

                                self.queue_action(Box::new(CostumeAction::SetAnimation(self.current_animation, animation_clone)));
                            }
                        }

                        ui.spacing();

                        if ui.button(im_str!("Add"), [0.0, 0.0])
                        {
                            self.queue_action(Box::new(CostumeAction::AddAnimation(self.current_animation, Animation::from_cels(Vec::new(), false))));
                        }
                        ui.same_line_with_spacing(0.0, 4.0);
                        if ui.button(im_str!("Up"), [0.0, 0.0]) && self.current_animation > 0
                        {
                            self.queue_action(Box::new(CostumeAction::MoveAnimation(self.current_animation, self.current_animation - 1)));
                        }
                        ui.same_line_with_spacing(0.0, 4.0);
                        if ui.button(im_str!("Down"), [0.0, 0.0])
                        {
                            self.queue_action(Box::new(CostumeAction::MoveAnimation(self.current_animation, self.current_animation + 1)));
                        }
                        ui.same_line_with_spacing(0.0, 4.0);
                        if ui.button(im_str!("Remove"), [0.0, 0.0])
                        {
                            self.queue_action(Box::new(CostumeAction::RemoveAnimation(self.current_animation)));
                        }

                        let style_animation_list = ui.push_style_var(imgui::StyleVar::WindowPadding([3.0, 2.0]));
                        let colors_animation_list = ui.push_style_color(imgui::StyleColor::ChildBg, main_style.colors[imgui::StyleColor::FrameBg as usize]);

                        imgui::ChildWindow::new("animation_list").border(true).draw_background(true).build(ui, ||
                        {
                            let num_animations = costume.animation_count();

                            for i in 0..num_animations
                            {
                                let animation_name;

                                if let Some(name) = costume.animation_name(i)
                                {
                                    animation_name = im_str!("{}", name);
                                }
                                else
                                {
                                    animation_name = im_str!("<animation {}>", i);
                                }

                                if imgui::Selectable::new(&animation_name).selected(self.current_animation == i).build(ui)
                                {
                                    self.current_animation = i;
                                    self.current_cel = 0;
                                    self.animation_timer = 0.0;
                                }

                                if self.current_animation == i && self.scroll_anim_list
                                {
                                    ui.set_scroll_here_y();
                                    self.scroll_anim_list = false;
                                }
                            }
                        });

                        colors_animation_list.pop(ui);
                        style_animation_list.pop(ui);
                    });
                });

                ui.set_current_column_width(ui.item_rect_size()[0] + 10.0);
                ui.next_column();

                if let Some(animation) = costume.animation(self.current_animation)
                {
                    let style_filmstrip = ui.push_style_vars(&[imgui::StyleVar::ItemSpacing([-1.0, 2.0]), imgui::StyleVar::WindowPadding([4.0, 4.0])]);
                    let colors_filmstrip = ui.push_style_color(imgui::StyleColor::ChildBg, main_style.colors[imgui::StyleColor::FrameBg as usize]);

                    self.current_cel = self.current_cel.min((animation.cel_count()).max(1) - 1);
                    let transparent_color = [self.transparent_color[0], self.transparent_color[1], self.transparent_color[2], 1.0];

                    // FIXME ImGui currently has a bug where adding a row of images extends the vertical scroll when it shouldn't.
                    //  For now, turn off the vertical scrolling controls and force scroll Y to 0.
                    imgui::ChildWindow::new("filmstrip").size([0.0, 104.0 + main_style.scrollbar_size]).draw_background(true).border(true)
                        .horizontal_scrollbar(true).always_horizontal_scrollbar(true).scroll_bar(false).scrollable(false).build(ui, ||
                    {
                        ui.set_scroll_y(0.0);

                        for i in 0..animation.cel_count()
                        {
                            if let Some(cel) = animation.cel(i)
                            {
                                let cursor_pos = ui.cursor_pos();
                                let mut main_button_color = main_style.colors[imgui::StyleColor::Button as usize];

                                if self.current_cel != i
                                {
                                    main_button_color = transparent_color;
                                }

                                let cel_button_color = ui.push_style_colors(&[
                                    (imgui::StyleColor::Border, crate::util::color_to_border_color(transparent_color)),
                                    (imgui::StyleColor::Button, main_button_color)]);

                                if ui.button(&im_str!("###cel{}", i), [CEL_BUTTON_SIZE, CEL_BUTTON_SIZE])
                                {
                                    self.current_cel = i;
                                }

                                let (button_min, button_max) = (ui.item_rect_min(), ui.item_rect_max());

                                ui.same_line(0.0);
                                let new_cursor_pos = ui.cursor_pos();

                                if self.current_cel == i && self.scroll_cel_list
                                {
                                    ui.set_scroll_here_x();
                                    self.scroll_cel_list = false;
                                }

                                let draw_list = ui.get_window_draw_list();

                                if ui.is_rect_visible(button_min, button_max)
                                {
                                    if let Some((image, fit)) = self.cel_image_fit(cel, &mut sheet_store, packages)
                                    {
                                        draw_list.with_clip_rect_intersect(button_min, button_max, ||
                                        {
                                            ui.set_cursor_pos([cursor_pos[0] + fit.x, cursor_pos[1] + fit.y]);
                                            image.build(ui);
                                        });
                                    }
                                }

                                ui.set_cursor_pos(new_cursor_pos);

                                cel_button_color.pop(ui);
                            }
                        }
                    });

                    colors_filmstrip.pop(ui);
                    style_filmstrip.pop(ui);

                    ui.popup(im_str!("Add Cel Menu"), ||
                    {
                        if imgui::Selectable::new(im_str!("Empty Cel")).close_popups(false).build(ui)
                        {
                            self.queue_action(Box::new(CostumeAction::AddCel(self.current_animation, self.current_cel, AnimationCel::new())));
                        }
                        if let Some(old_cel) = animation.cel(self.current_cel)
                        {

                            if imgui::Selectable::new(im_str!("Clone Cel")).close_popups(false).build(ui)
                            {
                                self.queue_action(Box::new(CostumeAction::AddCel(self.current_animation, self.current_cel, old_cel.clone())));
                            }
                        }

                        ui.separator();

                        let mut package_to_load : Option<String> = None;

                        // TODO: It's scary how many levels of indentation are needed here and I can't
                        //  put these in a separate function or Rust's borrow checker complains!

                        for package in packages.package_iter()
                        {
                            ui.menu(&im_str!("{}", package), true, ||
                            {
                                if !packages.package_loaded(ResourceType::Sheet, package)
                                {
                                    package_to_load = Some(package.clone());
                                }

                                for sheet_name in packages.package_contents(ResourceType::Sheet, package)
                                {
                                    ui.menu(&im_str!("{}", sheet_name), true, ||
                                    {
                                        let sheet_id = sheet_store.get_id(package, sheet_name).unwrap_or(0);

                                        if let Some(sheet) = sheet_store.get(sheet_id)
                                        {
                                            for slice_id in 0..sheet.slice_count()
                                            {
                                                if let Some(slice_name) = sheet.slice_name(slice_id)
                                                {
                                                    if imgui::Selectable::new(&im_str!("{}", slice_name)).close_popups(false).build(ui)
                                                    {
                                                        let mut cel = AnimationCel::new();

                                                        cel.sheet_source = SheetSource::new(package, sheet_name);
                                                        cel.slice_source = SliceSource::new(&slice_name);

                                                        self.queue_action(Box::new(CostumeAction::AddCel(self.current_animation, self.current_cel, cel)));
                                                    }
                                                }
                                            }
                                        }
                                    });
                                }
                            });
                        }

                        if let Some(package) = package_to_load
                        {
                            packages.load_package_later(ResourceType::Sheet, &package);
                        }
                    });

                    if ui.button(im_str!("Add###Add Cel"), [0.0, 0.0])
                    {
                        ui.open_popup(im_str!("Add Cel Menu"));
                    }
                    ui.same_line_with_spacing(0.0, 4.0);
                    if ui.button(im_str!("Left"), [0.0, 0.0]) && self.current_cel > 0
                    {
                        self.queue_action(Box::new(CostumeAction::MoveCel(self.current_animation, self.current_cel, self.current_cel - 1)));
                    }
                    ui.same_line_with_spacing(0.0, 4.0);
                    if ui.button(im_str!("Right"), [0.0, 0.0])
                    {
                        self.queue_action(Box::new(CostumeAction::MoveCel(self.current_animation, self.current_cel, self.current_cel + 1)));
                    }
                    ui.same_line_with_spacing(0.0, 4.0);
                    if ui.button(im_str!("Remove###Remove Cel"), [0.0, 0.0])
                    {
                        self.queue_action(Box::new(CostumeAction::RemoveCel(self.current_animation, self.current_cel)));
                    }

                    ui.same_line_with_spacing(0.0, 40.0);
                    ui.checkbox(im_str!("Play Animation"), &mut self.play_animation);

                    let style_var = ui.push_style_var(imgui::StyleVar::WindowPadding([1.0, 1.0]));
                    let style_color = ui.push_style_color(imgui::StyleColor::ChildBg, [self.transparent_color[0], self.transparent_color[1], self.transparent_color[2], 1.0]);

                    imgui::ChildWindow::new("texture_slice_item").size([0.0, 0.0])
                        .scrollable(false).scroll_bar(false).draw_background(true).border(true).build(ui, ||
                    {
                        if let Some(cel) = animation.cel(self.current_cel)
                        {
                            if let Some((image, w, h, origin_x, origin_y)) = self.cel_image(cel, &mut sheet_store, packages)
                            {
                                let slice_space = ui.window_size();
                                let center_x = (slice_space[0] / 2.0).round();
                                let center_y = (slice_space[1] / 2.0).round();
                                let image_x = (center_x - (origin_x - cel.x) * self.zoom_level).round();
                                let image_y = (center_y - (origin_y - cel.y) * self.zoom_level).round();

                                ui.set_cursor_pos([image_x, image_y]);

                                image.size([w * self.zoom_level, h * self.zoom_level]).build(ui);

                                ui.set_cursor_pos([center_x, center_y]);

                                let draw_list = ui.get_window_draw_list();
                                let cursor_pos = ui.cursor_screen_pos();

                                if self.show_origin
                                {
                                    self.draw_crosshair(&draw_list, cursor_pos[0], cursor_pos[1]);
                                }
                            }
                        }
                        else
                        {
                            ui.text(im_str!("No cel selected!"));
                        }
                    });

                    style_color.pop(ui);
                    style_var.pop(ui);
                }
            }
        });

        result
    }
}
