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

use keeshond::gameloop::GameControl;
use keeshond::renderer::{Sheet, SpriteSlice};
use keeshond_datapack::DataId;

use std::any::Any;
use std::ops::{RangeInclusive, Deref, DerefMut};

use image::{RgbaImage, GrayImage, ImageBuffer, GenericImage, GenericImageView};
use image::math::Rect;
use imgui::{im_str, TabBarFlags, ImStr};

const MAX_DRAW_POINTS : usize = 32768;
const MAX_TEXTURE_TILE_DIMENSIONS : u32 = 2048;
const MAX_TEXTURE_TILE_DIMENSIONS_MANUAL : u32 = 128;

enum SheetAction
{
    SetPixels(Vec<u8>),
    SetPixelsWithSize(Vec<u8>, u32, u32),
    SetTextureFiltering(bool, bool),
    AddSlice(usize, SpriteSlice),
    AddSlices(usize, Vec<SpriteSlice>),
    RenameSlice(usize, String),
    SetSlice(usize, SpriteSlice),
    MoveSlice(usize, usize),
    RemoveSlice(usize),
    RemoveSlices(usize, usize)
}

struct TransparentPixel
{
    x : u32, y : u32, set : bool
}

fn automatic_or_number_printf(number : u32) -> &'static ImStr
{
    if number == 0
    {
        return im_str!("Automatic");
    }

    im_str!("%d")
}

struct TilesetSliceInfo
{
    rows : u32,
    columns : u32,
    tile_walk_x : u32,
    tile_walk_y : u32
}

pub struct SheetDocument
{
    action_history : ActionHistory,
    data_id : DataId,
    show_origin : bool,
    transparent_color : [f32; 3],
    zoom_level : f32,
    current_slice : usize,
    tics : u64,
    draw_tool_on : bool,
    draw_tool_padding : bool,
    focus_slice_name_input : bool,
    scroll_slice_list : bool,
    mouse_drawing_texture : bool,
    draw_points : Vec<(f32, f32)>,
    dragging : bool,
    tilegen_w : u32,
    tilegen_h : u32,
    tilegen_x : u32,
    tilegen_y : u32,
    tilegen_space_x : u32,
    tilegen_space_y : u32,
    tilegen_rows : u32,
    tilegen_columns : u32,
    tilegen_preview : bool,
    on_tilegen : bool
}

impl SheetDocument
{
    fn opaque_pixels(&self, sheet : &Sheet) -> toodee::TooDee<bool>
    {
        let mut pixels = toodee::TooDee::new(sheet.width() as usize, sheet.height() as usize);

        for y in 0..sheet.height()
        {
            for x in 0..sheet.width()
            {
                if let Some(color) = sheet.get_pixel(x, y)
                {
                    if color.a > 0
                    {
                        pixels[(x as usize, y as usize)] = true;
                    }
                }
            }
        }

        pixels
    }

    fn draw_slice_bounds(&mut self, pos: [f32; 2], draw_list: &imgui::WindowDrawList, slice: &SpriteSlice, active : bool)
    {
        let mut x = slice.texture_x * self.zoom_level + pos[0];
        let mut y = slice.texture_y * self.zoom_level + pos[1];
        let mut x2 = x + slice.texture_w * self.zoom_level;
        let mut y2 = y + slice.texture_h * self.zoom_level;

        let color;
        let thickness;

        if active
        {
            x -= 1.0;
            y -= 1.0;
            x2 += 1.0;
            y2 += 1.0;

            let pulse = (((self.tics as f64 * 0.1).sin() + 1.0) / 2.0) as f32;

            let origin_x = x + (slice.origin_x * self.zoom_level) + 1.0;
            let origin_y = y + (slice.origin_y * self.zoom_level) + 1.0;

            color = [1.0, pulse, pulse, 1.0];
            thickness = ((self.tics as f64 * 0.05).sin() + 1.0) as f32;

            if self.show_origin
            {
                self.draw_crosshair(draw_list, origin_x, origin_y);
            }
        }
        else
        {
            thickness = 1.0;
            color = [1.0, 1.0, 1.0, 1.0];
        }

        draw_list.add_rect([x, y], [x2, y2], imgui::ImColor::from(color)).thickness(thickness).build();
    }

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

    fn defringe_sheet(&mut self, sheet : &mut Sheet)
    {
        let mut pixels = sheet.data();
        let mut texture = RgbaImage::from_raw(sheet.width(), sheet.height(), pixels).expect("Raw data to image failed");
        let mut trans_pixels = Vec::new();

        for y in 0..texture.height()
        {
            for x in 0..texture.width()
            {
                let pixel = texture.get_pixel_mut(x, y);

                if pixel[3] == 0
                {
                    trans_pixels.push(TransparentPixel
                    {
                        x, y, set : false
                    })
                }
            }
        }

        let mut colors = Vec::new();

        while trans_pixels.len() > 0
        {
            let mut processed = false;

            for entry in &mut trans_pixels
            {
                colors.clear();
                let (x, y) = (entry.x, entry.y);

                for (offset_x, offset_y) in
                    &[
                        (-1, -1), (0, -1), (1, -1),
                        (-1, 0), (1, 0),
                        (-1, 1), (0, 1), (1, 1)
                    ]
                {
                    let check_x = (x as i32 + offset_x) as u32 % texture.width();
                    let check_y = (y as i32 + offset_y) as u32 % texture.height();

                    let other_pixel = texture.get_pixel(check_x, check_y);

                    if other_pixel[3] > 0
                    {
                        colors.push((other_pixel[0], other_pixel[1], other_pixel[2]));
                    }
                }

                if colors.len() > 0
                {
                    let mut red = 0.0;
                    let mut green = 0.0;
                    let mut blue = 0.0;

                    for (other_r, other_g, other_b) in &colors
                    {
                        red += *other_r as f32;
                        green += *other_g as f32;
                        blue += *other_b as f32;
                    }

                    red /= colors.len() as f32;
                    green /= colors.len() as f32;
                    blue /= colors.len() as f32;

                    let pixel = texture.get_pixel_mut(x, y);

                    pixel[0] = red as u8;
                    pixel[1] = green as u8;
                    pixel[2] = blue as u8;

                    processed = true;
                    entry.set = true;
                }
            }

            trans_pixels.retain(|entry| entry.set == false);

            // Don't get stuck if there are no opaque pixels
            if !processed
            {
                break;
            }
        }

        pixels = texture.into_raw();

        self.queue_action(Box::new(SheetAction::SetPixels(pixels)));
    }

    fn select_slice(&mut self, sheet: &mut Sheet, point : (f32, f32))
    {
        if sheet.slice_count() <= 0
        {
            return;
        }

        let (x, y) = point;
        let old_slice = self.current_slice;
        let mut i = (old_slice + 1) % sheet.slice_count();

        while i != old_slice
        {
            if let Some(slice) = sheet.slice(i)
            {
                if x >= slice.texture_x && y >= slice.texture_y
                    && x < slice.texture_x + slice.texture_w
                    && y < slice.texture_y + slice.texture_h
                {
                    self.current_slice = i;
                    return;
                }
            }

            i = (i + 1) % sheet.slice_count();
        }
    }

    fn add_slice(&mut self, index : usize, sheet: &mut Sheet, slice : SpriteSlice)
    {
        let mut target_slice = 0;

        if sheet.slice_count() > 0
        {
            target_slice = index + 1;
        }

        sheet.insert_slice(target_slice, slice);

        self.current_slice += 1;
    }

    fn add_slices(&mut self, index : usize, sheet: &mut Sheet, slices : &Vec<SpriteSlice>)
    {
        let mut target_slice = 0;

        if sheet.slice_count() > 0
        {
            target_slice = index + 1;
        }

        for slice in slices
        {
            sheet.insert_slice(target_slice, slice.clone());
            target_slice += 1;
        }

        self.current_slice += slices.len();
    }

    fn move_slice(&mut self, sheet : &mut Sheet, old : usize, new : usize) -> bool
    {
        if new >= sheet.slice_count()
        {
            return false;
        }

        if let Some(slice) = sheet.slice(old).cloned()
        {
            let old_name = sheet.slice_name(old);

            sheet.remove_slice(old);
            sheet.insert_slice(new, slice);

            if let Some(name) = old_name
            {
                sheet.rename_slice(new, &name);
            }

            self.current_slice = new;

            return true;
        }

        false
    }

    fn make_slice_from_draw_points(&mut self, sheet : &mut Sheet) -> bool
    {
        let mut success = false;
        let mut left = sheet.width() as usize;
        let mut right = 0;
        let mut top = sheet.height() as usize;
        let mut bottom = 0;
        let pixels = self.opaque_pixels(sheet);

        for (point_x, point_y) in &self.draw_points
        {
            let x = *point_x as usize;
            let y = *point_y as usize;

            self.adjust_box_for_pixels(&mut left, &mut right, &mut top, &mut bottom, x, y, pixels.clone());
        }

        let mut left_tex = left as f32;
        let mut right_tex = right as f32 + 1.0;
        let mut top_tex = top as f32;
        let mut bottom_tex = bottom as f32 + 1.0;

        if self.draw_tool_padding
        {
            left_tex -= 1.0;
            top_tex -= 1.0;
            right_tex += 1.0;
            bottom_tex += 1.0;
        }

        if right_tex >= left_tex && bottom_tex >= top_tex
        {
            let width = right_tex - left_tex;
            let height = bottom_tex - top_tex;
            let mut origin_x = width / 2.0;
            let mut origin_y = height / 2.0;

            if sheet.has_palette()
            {
                origin_x = origin_x.floor();
                origin_y = origin_y.floor();
            }

            let slice = SpriteSlice
            {
                texture_x: left_tex,
                texture_y: top_tex,
                texture_w: width,
                texture_h: height,
                origin_x,
                origin_y
            };

            self.queue_action(Box::new(SheetAction::AddSlice(self.current_slice, slice)));

            success = true;
        }

        self.draw_points.clear();

        success
    }

    fn adjust_box_for_pixels(&self, left : &mut usize, right : &mut usize, top : &mut usize, bottom : &mut usize,
                             x : usize, y : usize, mut pixels: toodee::TooDee<bool>)
    {
        if x > *left && x < *right && y > *top && y < *bottom
        {
            return;
        }

        crate::util::flood_fill(&mut pixels, x, y, true, |tiles, x, y|
        {
            tiles[(x, y)] == true
        },
            |tiles, x, y|
            {
                tiles[(x, y)] = false;

                *left = (*left).min(x);
                *right = (*right).max(x);
                *top = (*top).min(y);
                *bottom = (*bottom).max(y);
            });
    }

    fn tileset_slice_info(&self, sheet : &Sheet) -> TilesetSliceInfo
    {
        let width = sheet.width();
        let height = sheet.height();

        let tile_walk_x = self.tilegen_w + self.tilegen_space_x;
        let tile_walk_y = self.tilegen_h + self.tilegen_space_y;
        let mut columns = self.tilegen_columns;
        let mut rows = self.tilegen_rows;

        if columns == 0
        {
            columns = (width as f32 - self.tilegen_x as f32 - self.tilegen_space_x as f32) as u32 / tile_walk_x;
        }
        if rows == 0
        {
            rows = (height as f32 - self.tilegen_y as f32 - self.tilegen_space_y as f32) as u32 / tile_walk_y;
        }

        TilesetSliceInfo
        {
            rows,
            columns,
            tile_walk_x,
            tile_walk_y
        }
    }

    fn draw_tileset_preview(&self, pos: [f32; 2], draw_list: &imgui::WindowDrawList, sheet : &Sheet)
    {
        if self.tilegen_w as f32 * self.zoom_level < 8.0 || self.tilegen_h as f32 * self.zoom_level < 8.0
        {
            return;
        }

        let tileset_info = self.tileset_slice_info(sheet);

        let pulse = (((self.tics as f64 * 0.06).sin() + 1.0) / 2.0) as f32;
        let color = [pulse, pulse, pulse, 1.0];
        let thickness = 1.0;

        for y in 0..tileset_info.rows
        {
            for x in 0..tileset_info.columns
            {
                let x1 = pos[0] + (self.tilegen_x as f32 + (tileset_info.tile_walk_x * x) as f32) * self.zoom_level;
                let y1 = pos[1] + (self.tilegen_y as f32 + (tileset_info.tile_walk_y * y) as f32) * self.zoom_level;
                let x2 = x1 + (self.tilegen_w as f32) * self.zoom_level;
                let y2 = y1 + (self.tilegen_h as f32) * self.zoom_level;

                draw_list.add_rect([x1, y1], [x2, y2], imgui::ImColor::from(color)).thickness(thickness).build();
            }
        }
    }

    fn create_tileset_slices(&mut self, sheet : &Sheet)
    {
        let tileset_info = self.tileset_slice_info(sheet);
        let mut slices = Vec::new();

        for y in 0..tileset_info.rows
        {
            for x in 0..tileset_info.columns
            {
                slices.push(SpriteSlice
                {
                    texture_x: self.tilegen_x as f32 + (tileset_info.tile_walk_x * x) as f32,
                    texture_y: self.tilegen_y as f32 + (tileset_info.tile_walk_y * y) as f32,
                    texture_w: self.tilegen_w as f32,
                    texture_h: self.tilegen_h as f32,
                    origin_x: 0.0,
                    origin_y: 0.0
                });
            }
        }

        if slices.len() > 0
        {
            self.queue_action(Box::new(SheetAction::RemoveSlices(0, sheet.slice_count())));
            self.queue_action(Box::new(SheetAction::AddSlices(0, slices)));
        }
    }

    fn create_tileset_texture_internal<P, C>(&mut self, texture : &ImageBuffer<P, C>, new_texture : &mut ImageBuffer<P, C>, tileset_info : TilesetSliceInfo)
        where
            P: image::Pixel + 'static,
            P::Subpixel: 'static,
            C: Deref<Target = [P::Subpixel]> + DerefMut,
    {
        let new_tile_width = self.tilegen_w + 2;
        let new_tile_height = self.tilegen_h + 2;
        let (width, _) = new_texture.dimensions();
        let new_columns = width / new_tile_width;

        info!("new columns {}", new_columns);

        for y in 0..tileset_info.rows
        {
            for x in 0..tileset_info.columns
            {
                let source_x = self.tilegen_x + (tileset_info.tile_walk_x * x);
                let source_y = self.tilegen_y + (tileset_info.tile_walk_y * y);
                let index = y * tileset_info.columns + x;
                let dest_x = (index % new_columns) * new_tile_width;
                let dest_y = (index / new_columns) * new_tile_height;

                let mut tile_image = ImageBuffer::new(new_tile_width, new_tile_height);
                let texture_view = texture.view(source_x, source_y, self.tilegen_w, self.tilegen_h);

                // Main tile image
                tile_image.copy_from(&texture_view, 1, 1).expect("Image copy failed");

                // Tile image edge padding
                tile_image.copy_within(Rect { x : 1, y : 1, width : 1, height : self.tilegen_h }, 0, 1);
                tile_image.copy_within(Rect { x : self.tilegen_w, y : 1, width : 1, height : self.tilegen_h }, self.tilegen_w + 1, 1);
                tile_image.copy_within(Rect { x : 0, y : 1, width : new_tile_width, height : 1 }, 0, 0);
                tile_image.copy_within(Rect { x : 0, y : self.tilegen_h, width : new_tile_width, height : 1 }, 0, self.tilegen_h + 1);

                new_texture.copy_from(&tile_image, dest_x, dest_y).expect("Image copy failed");
            }
        }
    }

    fn create_tileset_texture_and_slices(&mut self, sheet : &Sheet)
    {
        let tileset_info = self.tileset_slice_info(sheet);

        let width = (sheet.width() as f32).max(self.tilegen_w as f32 + 2.0).log2().ceil().exp2() as u32;
        let total_tiles = tileset_info.columns * tileset_info.rows;
        let new_columns = (width / (self.tilegen_w + 2)).min(MAX_TEXTURE_TILE_DIMENSIONS);
        let new_rows = ((total_tiles as f32 / new_columns as f32).ceil() as u32).min(MAX_TEXTURE_TILE_DIMENSIONS);
        let height = ((new_rows * (self.tilegen_h + 2)) as f32).log2().ceil().exp2() as u32;

        let pixels = sheet.data();

        if sheet.has_palette()
        {
            let texture = GrayImage::from_raw(sheet.width(), sheet.height(), pixels).expect("Raw data to image failed");
            let mut new_texture = GrayImage::new(width, height);

            self.create_tileset_texture_internal(&texture, &mut new_texture, tileset_info);

            self.queue_action(Box::new(SheetAction::SetPixelsWithSize(new_texture.into_raw(), width, height)));
        }
        else
        {
            let texture = RgbaImage::from_raw(sheet.width(), sheet.height(), pixels).expect("Raw data to image failed");
            let mut new_texture = RgbaImage::new(width, height);

            self.create_tileset_texture_internal(&texture, &mut new_texture, tileset_info);

            self.queue_action(Box::new(SheetAction::SetPixelsWithSize(new_texture.into_raw(), width, height)));
        }

        self.tilegen_x = 1;
        self.tilegen_y = 1;
        self.tilegen_space_x = 2;
        self.tilegen_space_y = 2;
        self.tilegen_columns = new_columns;
        self.tilegen_rows = new_rows;

        self.create_tileset_slices(sheet);
    }
}

impl Document for SheetDocument
{
    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::<Sheet>().get_id_mut(package_name, item_name)
        {
            data_id = id;
        }

        SheetDocument
        {
            action_history : ActionHistory::new(),
            data_id,
            show_origin : true,
            transparent_color : [0.5, 0.5001, 0.5002],
            zoom_level : 1.0,
            current_slice : 0,
            tics : 0,
            draw_tool_on : false,
            draw_tool_padding : false,
            focus_slice_name_input : false,
            scroll_slice_list : false,
            mouse_drawing_texture : false,
            draw_points : Vec::new(),
            dragging : false,
            tilegen_w : 32,
            tilegen_h : 32,
            tilegen_x : 0,
            tilegen_y : 0,
            tilegen_space_x : 0,
            tilegen_space_y : 0,
            tilegen_rows : 0,
            tilegen_columns : 0,
            tilegen_preview : true,
            on_tilegen : 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::<SheetAction>()
        {
            let mut sheet_store = game.res().store_mut::<Sheet>();

            if let Some(sheet) = sheet_store.get_mut(self.data_id)
            {
                match action
                {
                    SheetAction::SetPixels(pixels) =>
                    {
                        if sheet.set_data(pixels.clone())
                        {
                            sheet_store.reprepare(self.data_id);
                            return true;
                        }
                    }
                    SheetAction::SetPixelsWithSize(pixels, width, height) =>
                    {
                        let palette_opt = sheet.palette();
                        let success;

                        if let Some(palette) = palette_opt
                        {
                            success = sheet.set_indexed_data_with_size(pixels.clone(), *width, *height, palette);
                        }
                        else
                        {
                            success = sheet.set_rgba_data_with_size(pixels.clone(), *width, *height);
                        }

                        if success
                        {
                            sheet_store.reprepare(self.data_id);
                        }

                        return success;
                    }
                    SheetAction::SetTextureFiltering(force, force_on) =>
                    {
                        sheet.set_texture_filtering(crate::util::double_bool_to_option_bool(*force, *force_on));
                        sheet_store.reprepare(self.data_id);
                        return true;
                    }
                    SheetAction::AddSlice(index, slice) =>
                    {
                        self.scroll_slice_list = true;
                        self.add_slice(*index, sheet, slice.clone());
                        return true;
                    }
                    SheetAction::AddSlices(index, slices) =>
                    {
                        self.scroll_slice_list = true;
                        self.add_slices(*index, sheet, slices);
                        return true;
                    }
                    SheetAction::RenameSlice(index, name) =>
                    {
                        self.scroll_slice_list = true;
                        return sheet.rename_slice(*index, name);
                    }
                    SheetAction::SetSlice(index, slice) =>
                    {
                        self.scroll_slice_list = true;
                        return sheet.set_slice(*index, slice.clone());
                    }
                    SheetAction::MoveSlice(old_index, new_index) =>
                    {
                        self.scroll_slice_list = true;
                        return self.move_slice(sheet, *old_index, *new_index);
                    }
                    SheetAction::RemoveSlice(index) =>
                    {
                        self.scroll_slice_list = true;
                        return sheet.remove_slice(*index).is_some();
                    }
                    SheetAction::RemoveSlices(index, count) =>
                    {
                        let mut removed = false;

                        for _ in 0..*count
                        {
                            removed |= sheet.remove_slice(*index).is_some();
                        }

                        self.scroll_slice_list = true;

                        return removed;
                    }
                }
            }
        }

        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 sheet_store = game.res().store_mut::<Sheet>();

            if let Some(sheet) = sheet_store.get_mut(self.data_id)
            {
                let (mut force, mut force_on) = crate::util::option_bool_to_double_bool(sheet.texture_filtering());
                let mut update_filtering = false;

                ui.columns(2, im_str!("##sheet_columns"), false);

                imgui::ChildWindow::new("sheet_controls").size([240.0, 0.0]).draw_background(true).border(false).build(ui, ||
                {
                    imgui::TabBar::new(im_str!("controls_tabbar"))
                        .flags(TabBarFlags::FITTING_POLICY_SCROLL).build(ui, ||
                    {
                        imgui::TabItem::new(im_str!("Sheet")).build(ui, ||
                        {
                            ui.text_wrapped(&im_str!("Size: {}x{}", sheet.width(), sheet.height()));

                            update_filtering |= ui.checkbox(im_str!("Force texture filter setting"), &mut force);

                            if force
                            {
                                ui.indent();
                                update_filtering |= ui.checkbox(im_str!("Enabled"), &mut force_on);
                                ui.unindent();
                            }

                            if update_filtering
                            {
                                self.queue_action(Box::new(SheetAction::SetTextureFiltering(force, force_on)));
                            }

                            if ui.button(im_str!("De-fringe"), [0.0, 0.0])
                            {
                                if sheet.has_palette()
                                {
                                    ui.open_popup(im_str!("Can't Defringe"));
                                }
                                else
                                {
                                    self.defringe_sheet(sheet);
                                }
                            }

                            ui.popup_modal(im_str!("Can't Defringe")).resizable(false).build(||
                            {
                                ui.text(im_str!("De-fringing requires averaging pixels,\nwhich can't be done for paletted images!"));
                                if ui.button(im_str!("Oh well!"), [0.0, 0.0])
                                {
                                    ui.close_current_popup();
                                }
                            });

                            ui.same_line(0.0);
                            ui.text_disabled(im_str!("(?)"));

                            if ui.is_item_hovered()
                            {
                                ui.tooltip_text("Use this if you see an undesired halo around the edges of your graphics\nwhen zooming and texture filtering is enabled.");
                            }

                            ui.separator();
                            ui.spacing();
                            ui.text(&im_str!("Editing Slice {}", self.current_slice));
                            ui.spacing();

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

                            self.current_slice = self.current_slice.min(sheet.slice_count().max(1) - 1);

                            if let Some(name) = sheet.slice_name(self.current_slice)
                            {
                                slice_name.push_str(&name);
                            }

                            if let Some(slice) = sheet.slice(self.current_slice)
                            {
                                let mut update_slice = false;

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

                                if ui.input_text(im_str!("Name"), &mut slice_name).enter_returns_true(true).build()
                                {
                                    self.queue_action(Box::new(SheetAction::RenameSlice(self.current_slice, slice_name.to_string())));
                                }

                                let mut pos = [slice.texture_x, slice.texture_y];
                                let mut size = [slice.texture_w, slice.texture_h];
                                let mut origin = [slice.origin_x, slice.origin_y];

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

                                update_slice |= imgui::Drag::new(im_str!("Pos")).display_format(im_str!("%.4f")).build_array(ui, &mut pos);
                                update_slice |= imgui::Drag::new(im_str!("Size")).display_format(im_str!("%.4f")).build_array(ui, &mut size);

                                style_var.pop(ui);

                                update_slice |= imgui::Drag::new(im_str!("Origin")).display_format(im_str!("%.4f")).build_array(ui, &mut origin);

                                if update_slice
                                {
                                    let mut slice = slice.clone();

                                    slice.texture_x = pos[0];
                                    slice.texture_y = pos[1];
                                    slice.texture_w = size[0];
                                    slice.texture_h = size[1];
                                    slice.origin_x = origin[0];
                                    slice.origin_y = origin[1];

                                    self.queue_action(Box::new(SheetAction::SetSlice(self.current_slice, slice)));
                                }
                            }

                            ui.spacing();
                            ui.separator();
                            ui.spacing();

                            ui.checkbox(im_str!("Draw Tool"), &mut self.draw_tool_on);

                            if self.draw_tool_on
                            {
                                ui.indent();
                                ui.checkbox(im_str!("Add Padding"), &mut self.draw_tool_padding);
                                ui.unindent();
                            }

                            if ui.button(im_str!("Add"), [0.0, 0.0])
                            {
                                self.queue_action(Box::new(SheetAction::AddSlice(self.current_slice, SpriteSlice
                                {
                                    texture_x: 0.0,
                                    texture_y: 0.0,
                                    texture_w: 32.0,
                                    texture_h: 32.0,
                                    origin_x: 16.0,
                                    origin_y: 16.0
                                })));
                            }
                            ui.same_line_with_spacing(0.0, 4.0);
                            if ui.button(im_str!("Up"), [0.0, 0.0]) && self.current_slice > 0
                            {
                                self.queue_action(Box::new(SheetAction::MoveSlice(self.current_slice, self.current_slice - 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(SheetAction::MoveSlice(self.current_slice, self.current_slice + 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(SheetAction::RemoveSlice(self.current_slice)));
                            }

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

                            imgui::ChildWindow::new("slice_list").border(true).draw_background(true).build(ui, ||
                            {
                                let num_slices = sheet.slice_count();

                                for i in 0..num_slices
                                {
                                    let slice_name;

                                    if let Some(name) = sheet.slice_name(i)
                                    {
                                        slice_name = im_str!("{}", name);
                                    }
                                    else
                                    {
                                        slice_name = im_str!("<slice {}>", i);
                                    }

                                    if imgui::Selectable::new(&slice_name).selected(self.current_slice == i).build(ui)
                                    {
                                        self.current_slice = i;
                                    }

                                    if self.current_slice == i && self.scroll_slice_list
                                    {
                                        ui.set_scroll_here_y();
                                        self.scroll_slice_list = false;
                                    }
                                }
                            });

                            colors_slice_list.pop(ui);
                            style_slice_list.pop(ui);
                        });

                        self.on_tilegen = false;

                        imgui::TabItem::new(im_str!("Tileset Gen")).build(ui, ||
                        {
                            self.on_tilegen = true;

                            let mut size = [self.tilegen_w, self.tilegen_h];
                            let mut offset = [self.tilegen_x, self.tilegen_y];
                            let mut spacing = [self.tilegen_space_x, self.tilegen_space_y];

                            imgui::Drag::new(im_str!("Tile Size")).display_format(im_str!("%d px")).build_array(ui, &mut size);
                            imgui::Drag::new(im_str!("Start Offset")).display_format(im_str!("%d px")).build_array(ui, &mut offset);
                            imgui::Drag::new(im_str!("Tile Spacing")).display_format(im_str!("%d px")).build_array(ui, &mut spacing);
                            imgui::Drag::new(im_str!("Rows")).range(RangeInclusive::new(0, MAX_TEXTURE_TILE_DIMENSIONS_MANUAL))
                                .display_format(automatic_or_number_printf(self.tilegen_rows)).build(ui, &mut self.tilegen_rows);
                            imgui::Drag::new(im_str!("Columns")).range(RangeInclusive::new(0, MAX_TEXTURE_TILE_DIMENSIONS_MANUAL))
                                .display_format(automatic_or_number_printf(self.tilegen_columns)).build(ui, &mut self.tilegen_columns);

                            self.tilegen_w = size[0];
                            self.tilegen_h = size[1];
                            self.tilegen_x = offset[0];
                            self.tilegen_y = offset[1];
                            self.tilegen_space_x = spacing[0];
                            self.tilegen_space_y = spacing[1];

                            ui.spacing();
                            ui.spacing();

                            ui.checkbox(im_str!("Preview"), &mut self.tilegen_preview);

                            ui.spacing();
                            ui.spacing();
                            ui.separator();
                            ui.spacing();
                            ui.spacing();

                            if ui.button(im_str!("Create Slices and Optimize Texture"), [0.0, 0.0])
                            {
                                self.create_tileset_texture_and_slices(sheet);
                            }

                            ui.text_wrapped(im_str!("Click this to get the sheet ready for use as a tileset. \
                                    Warning: This will modify the texture and replace any existing slices on this sheet!"));

                            ui.spacing();
                            ui.spacing();

                            if ui.button(im_str!("Create Slices Only"), [0.0, 0.0])
                            {
                                self.create_tileset_slices(sheet);
                            }

                            ui.text_wrapped(im_str!("Only use this if you know what you are doing."));
                        });
                    });
                });

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

                imgui::TabBar::new(im_str!("texture_tabbar")).build(ui, ||
                {
                    imgui::TabItem::new(im_str!("Sheet View")).build(ui, ||
                    {
                        let style_var_outer = ui.push_style_var(imgui::StyleVar::WindowPadding([0.0, 0.0]));

                        imgui::ChildWindow::new("texture_view").size([0.0, 0.0]).draw_background(true).border(true).horizontal_scrollbar(true).movable(false).build(ui, ||
                        {
                            let mut delta = [0.0, 0.0];

                            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]);

                            let width = sheet.width() as f32 * self.zoom_level;
                            let height = sheet.height() as f32 * self.zoom_level;

                            imgui::ChildWindow::new("texture_item").size([width + 2.0, height + 2.0])
                                .scrollable(false).draw_background(true).border(true).build(ui, ||
                            {
                                let pos = ui.cursor_screen_pos();

                                ui.invisible_button(im_str!("texture_drag"), [width, height]);

                                let hovering = ui.is_item_hovered();

                                if ui.is_mouse_clicked(imgui::MouseButton::Left) && hovering && !self.on_tilegen
                                {
                                    if self.draw_tool_on
                                    {
                                        self.mouse_drawing_texture = true;
                                    }
                                    else
                                    {
                                        let mouse_pos = ui.io().mouse_pos;
                                        let new_point = (((mouse_pos[0] - pos[0]) / self.zoom_level).floor(), ((mouse_pos[1] - pos[1]) / self.zoom_level).floor());

                                        self.select_slice(sheet, new_point);
                                    }
                                }

                                if self.mouse_drawing_texture && ui.is_mouse_down(imgui::MouseButton::Left) && hovering
                                {
                                    let mouse_pos = ui.io().mouse_pos;
                                    let new_point = (((mouse_pos[0] - pos[0]) / self.zoom_level).floor(), ((mouse_pos[1] - pos[1]) / self.zoom_level).floor());
                                    let mut push = true;

                                    if let Some(last) = self.draw_points.last()
                                    {
                                        push = *last != new_point;
                                    }

                                    if push
                                    {
                                        self.draw_points.push(new_point);

                                        if self.draw_points.len() > MAX_DRAW_POINTS
                                        {
                                            self.draw_points.remove(0);
                                        }
                                    }
                                }
                                else if !ui.is_mouse_down(imgui::MouseButton::Left)
                                {
                                    self.mouse_drawing_texture = false;

                                    if self.draw_points.len() > 0
                                    {
                                        self.focus_slice_name_input = self.make_slice_from_draw_points(sheet);
                                    }
                                }

                                if ui.is_mouse_clicked(imgui::MouseButton::Right) && hovering
                                {
                                    self.dragging = true;
                                }

                                if self.dragging && ui.is_mouse_down(imgui::MouseButton::Right)
                                {
                                    delta = ui.io().mouse_delta;
                                }
                                else if !ui.is_mouse_down(imgui::MouseButton::Right)
                                {
                                    self.dragging = false;
                                }

                                ui.same_line(1.0);
                                imgui::Image::new(imgui::TextureId::from(self.data_id), [width, height]).build(ui);

                                if !self.on_tilegen
                                {
                                    let draw_list = ui.get_window_draw_list();

                                    for i in 0..sheet.slice_count()
                                    {
                                        if let Some(slice) = sheet.slice(i)
                                        {
                                            self.draw_slice_bounds(pos, &draw_list, slice, false);
                                        }
                                    }

                                    if let Some(slice) = sheet.slice(self.current_slice)
                                    {
                                        self.draw_slice_bounds(pos, &draw_list, slice, true);
                                    }

                                    for point in 1..self.draw_points.len()
                                    {
                                        let (mut x1, mut y1) = self.draw_points[point - 1];
                                        let (mut x2, mut y2) = self.draw_points[point];

                                        if self.zoom_level >= 3.0
                                        {
                                            x1 += 0.5;
                                            y1 += 0.5;
                                            x2 += 0.5;
                                            y2 += 0.5;
                                        }

                                        draw_list.add_line([x1 * self.zoom_level + pos[0], y1 * self.zoom_level + pos[1]],
                                                           [x2 * self.zoom_level + pos[0], y2 * self.zoom_level + pos[1]],
                                                           imgui::ImColor::from([1.0, 1.0, 1.0, 1.0])).build();
                                    }
                                }
                                else if self.tilegen_preview
                                {
                                    let draw_list = ui.get_window_draw_list();

                                    self.draw_tileset_preview(pos, &draw_list, sheet);
                                }
                            });

                            if delta[0] != 0.0 || delta[1] != 0.0
                            {
                                ui.set_scroll_x(ui.scroll_x() - delta[0]);
                                ui.set_scroll_y(ui.scroll_y() - delta[1]);
                            }

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

                        style_var_outer.pop(ui);
                    });

                    if !self.on_tilegen
                    {
                        imgui::TabItem::new(im_str!("Slice View")).build(ui, ||
                        {
                            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(slice) = sheet.slice(self.current_slice)
                                {
                                    let base_width = sheet.width() as f32;
                                    let base_height = sheet.height() as f32;
                                    let x1 = slice.texture_x / base_width;
                                    let y1 = slice.texture_y / base_height;
                                    let x2 = (slice.texture_x + slice.texture_w) / base_width;
                                    let y2 = (slice.texture_y + slice.texture_h) / base_height;

                                    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 - slice.origin_x * self.zoom_level).round();
                                    let image_y = (center_y - slice.origin_y * self.zoom_level).round();

                                    ui.set_cursor_pos([image_x, image_y]);

                                    imgui::Image::new(imgui::TextureId::from(self.data_id), [slice.texture_w * self.zoom_level, slice.texture_h * self.zoom_level])
                                        .uv0([x1, y1]).uv1([x2, y2]).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 slice selected!"));
                                }
                            });

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

        result
    }
}
