#include "../include/ImageViewer.hpp"
#include "../include/Notification.hpp"
#include "../include/Storage.hpp"
#include "../include/ResourceLoader.hpp"
#include "../include/Scale.hpp"
#include "../include/Config.hpp"
#include "../include/Theme.hpp"
#include <mglpp/system/FloatRect.hpp>
#include <malloc.h>
#include <mglpp/window/Event.hpp>
#include <mglpp/window/Window.hpp>
#include <mglpp/graphics/Rectangle.hpp>
#include <mglpp/graphics/Image.hpp>
#include <cmath>

namespace QuickMedia {
    static const int page_text_character_size = 14 * get_config().scale * get_config().font_scale * get_config().font.scale.latin;

    static mgl::vec2d get_no_image_size_scaled(mgl::vec2d window_size, bool fit_image_to_window) {
        mgl::vec2d no_image_page_size(720.0, 1280.0);
        mgl::vec2f no_image_page_size_f(no_image_page_size.x, no_image_page_size.y);
        mgl::vec2f content_size(window_size.x, window_size.y);

        mgl::vec2f image_scale;
        if(fit_image_to_window)
            image_scale = get_ratio(no_image_page_size_f, wrap_to_size_x(no_image_page_size_f, content_size.x));
        else
            image_scale = get_ratio(no_image_page_size_f, clamp_to_size_x(no_image_page_size_f, content_size.x));

        no_image_page_size.x *= image_scale.x;
        no_image_page_size.y *= image_scale.y;
        return no_image_page_size;
    }

    ImageViewer::ImageViewer(mgl::Window *window, int num_pages, const std::string &content_title, const std::string &chapter_title, int current_page, const Path &chapter_cache_dir, bool *fit_image_to_window) :
        window(window),
        current_page(current_page),
        num_pages(num_pages),
        content_title(content_title),
        chapter_title(chapter_title),
        chapter_cache_dir(chapter_cache_dir),
        focused_page(current_page),
        page_text("", *FontLoader::get_font(FontLoader::FontType::LATIN, page_text_character_size)),
        fit_image_to_window(fit_image_to_window)
    {
        current_page = std::min(current_page, num_pages);
        image_data.resize(num_pages);
        page_size.resize(num_pages);
        for(int i = 0; i < num_pages; ++i) {
            image_data[i] = nullptr;
            page_size[i].loaded = false;
        }
        page_text.set_color(get_theme().text_color);

        // TODO: Fix
        //has_default_cursor = default_cursor.loadFromSystem(sf::Cursor::Arrow);
        //has_size_vertical_cursor = size_vertical_cursor.loadFromSystem(sf::Cursor::SizeVertical);
    }

    ImageViewer::~ImageViewer() {
        // TODO: Fix
        //if(has_default_cursor)
        //    window->setMouseCursor(default_cursor);
        if(image_loader_thread.joinable())
            image_loader_thread.join();
    }

    void ImageViewer::load_image_async(const Path &path, std::shared_ptr<ImageData> image_data) {
        image_data->image_status = ImageStatus::LOADING;
        assert(!loading_image);
        loading_image = true;
        if(image_loader_thread.joinable())
            image_loader_thread.join();
        image_loader_thread = std::thread([this, image_data, path]() mutable {
            auto image = std::make_unique<mgl::Image>();
            if(image->load_from_file(path.data.c_str())) {
                image_data->image = std::move(image);
                image_data->image_status = ImageStatus::LOADED;
            } else {
                image_data->image_status = ImageStatus::FAILED_TO_LOAD;
            }
            loading_image = false;
        });
    }

    bool ImageViewer::render_page(mgl::Window &window, int page, double offset_y) {
        const mgl::vec2d image_size = get_page_size(page);
        mgl::vec2d render_pos(floor(window_size.x * 0.5 - image_size.x * 0.5), scroll + offset_y);

        bool scrolling = (std::abs(scroll_speed) > 0.01f);
        if(!scrolling)
            render_pos.y = floor(render_pos.y);

        double top_dist = std::abs(0.0 - render_pos.y);
        if(top_dist < min_page_top_dist && page != -1 && page != num_pages) {
            min_page_top_dist = top_dist;
            page_closest_to_top = page;
        }

        if(render_pos.y + image_size.y <= 0.0 || render_pos.y >= window_size.y) {
            if(page >= 0 && page < num_pages) {
                std::shared_ptr<ImageData> &page_image_data = image_data[page];
                if(page_image_data)
                    page_image_data->visible_on_screen = false;
            }
            return true;
        }

        if(page == -1 || page == num_pages) {
            // TODO: Dont show if first/last chapter
            mgl::Text text(page == -1 ? "Scroll up to go to the previous chapter" : "Scroll down to go to the next chapter", *FontLoader::get_font(FontLoader::FontType::LATIN, 30 * get_config().scale * get_config().font_scale * get_config().font.scale.latin));
            auto text_bounds = text.get_bounds();
            text.set_color(get_theme().text_color);
            mgl::vec2d render_pos_text(floor(window_size.x * 0.5 - text_bounds.size.x * 0.5), image_size.y * 0.5 - text_bounds.size.y * 0.5 + scroll + offset_y);

            if(!scrolling)
                render_pos_text.y = floor(render_pos_text.y);

            mgl::Rectangle background(mgl::vec2f(image_size.x, image_size.y));
            background.set_color(get_theme().selected_color);
            background.set_position(mgl::vec2f(render_pos.x, render_pos.y));
            window.draw(background);

            text.set_position(mgl::vec2f(render_pos_text.x, render_pos_text.y));
            window.draw(text);
            return true;
        }

        std::shared_ptr<ImageData> &page_image_data = image_data[page];
        if(page_image_data) {
            page_image_data->visible_on_screen = true;

            if(page_image_data->image_status == ImageStatus::APPLIED_TO_TEXTURE) {
                auto texture_size = page_image_data->sprite.get_texture()->get_size();
                mgl::vec2f texture_size_f(texture_size.x, texture_size.y);
                mgl::vec2f content_size(window_size.x, window_size.y);

                mgl::vec2f image_scale;
                if(*fit_image_to_window)
                    image_scale = get_ratio(texture_size_f, wrap_to_size_x(texture_size_f, content_size.x));
                else
                    image_scale = get_ratio(texture_size_f, clamp_to_size_x(texture_size_f, content_size.x));

                page_image_data->sprite.set_scale(image_scale);
                page_image_data->sprite.set_position(mgl::vec2f(render_pos.x, render_pos.y));
                window.draw(page_image_data->sprite);
            } else {
                std::string page_str = std::to_string(1 + page);
                std::string msg;
                if(page_image_data->image_status == ImageStatus::WAITING) {
                    if(!loading_image) {
                        Path image_path = chapter_cache_dir;
                        image_path.join(page_str);
                        load_image_async(image_path, page_image_data);
                    }
                    msg = "Loading image for page " + page_str;
                } else if(page_image_data->image_status == ImageStatus::LOADING) {
                    msg = "Loading image for page " + page_str;
                } else if(page_image_data->image_status == ImageStatus::FAILED_TO_LOAD) {
                    msg = "Failed to load image for page " + page_str;
                }

                mgl::Text error_message(std::move(msg), *FontLoader::get_font(FontLoader::FontType::LATIN, 30 * get_config().scale * get_config().font_scale * get_config().font.scale.latin));
                auto text_bounds = error_message.get_bounds();
                error_message.set_color(get_theme().text_color);
                mgl::vec2d render_pos_text(floor(window_size.x * 0.5 - text_bounds.size.x * 0.5), image_size.y * 0.5 - text_bounds.size.y * 0.5 + scroll + offset_y);

                if(!scrolling)
                    render_pos_text.y = floor(render_pos_text.y);

                mgl::Rectangle background(mgl::vec2f(image_size.x, image_size.y));
                background.set_color(get_theme().selected_color);
                background.set_position(mgl::vec2f(render_pos.x, render_pos.y));
                window.draw(background);

                error_message.set_position(mgl::vec2f(render_pos_text.x, render_pos_text.y));
                window.draw(error_message);
            }
        } else {
            std::string page_str = std::to_string(1 + page);

            mgl::Text error_message("Downloading page " + page_str, *FontLoader::get_font(FontLoader::FontType::LATIN, 30 * get_config().scale * get_config().font_scale * get_config().font.scale.latin));
            auto text_bounds = error_message.get_bounds();
            error_message.set_color(get_theme().text_color);
            mgl::vec2d render_pos_text(floor(window_size.x * 0.5 - text_bounds.size.x * 0.5), image_size.y * 0.5 - text_bounds.size.y * 0.5 + scroll + offset_y);

            if(!scrolling)
                render_pos_text.y = floor(render_pos_text.y);

            mgl::Rectangle background(mgl::vec2f(image_size.x, image_size.y));
            background.set_color(get_theme().selected_color);
            background.set_position(mgl::vec2f(render_pos.x, render_pos.y));
            window.draw(background);

            error_message.set_position(mgl::vec2f(render_pos_text.x, render_pos_text.y));
            window.draw(error_message);

            Path image_path = chapter_cache_dir;
            image_path.join(page_str);

            // TODO: Add delay before checking again?
            if(get_file_type(image_path) == FileType::REGULAR) {
                //fprintf(stderr, "ImageViewer: Loaded page %d\n", 1 + page);

                page_image_data = std::make_shared<ImageData>();
                page_image_data->visible_on_screen = true;
                
                page_image_data->image_status = ImageStatus::WAITING;
            }
        }

        return true;
    }

    static double sign(double value) {
        return value >= 0.0 ? 1.0 : -1.0;
    }

    static bool is_key_scroll_up(const mgl::Event::KeyEvent &key) {
        return (!key.control && key.code == mgl::Keyboard::Up) || (!key.alt && key.code == mgl::Keyboard::K);
    }

    static bool is_key_scroll_down(const mgl::Event::KeyEvent &key) {
        return (!key.control && key.code == mgl::Keyboard::Down) || (!key.alt && key.code == mgl::Keyboard::J);
    }

    void ImageViewer::scroll_to_page(int page) {
        scroll = 0.0;
        scroll_speed = 0.0;

        for(int i = -1; i < num_pages + 1; ++i) {
            const mgl::vec2d current_page_size = get_page_size(i);
            double scroll_diff = 0.0;
            if(i < 0) {
                scroll_diff = current_page_size.y - prev_size_first_page.y;
                prev_size_first_page = current_page_size;
            } else if(i >= num_pages) {
                scroll_diff = current_page_size.y - prev_size_last_page.y;
                prev_size_last_page = current_page_size;
            } else {
                scroll_diff = current_page_size.y - page_size[i].prev_size.y;
                page_size[i].prev_size = current_page_size;
            }

            if(i < page) {
                scroll -= scroll_diff;
                if(scroll_diff < 0.001)
                    scroll -= current_page_size.y;
            }
        }
    }

    ImageViewerAction ImageViewer::draw() {
        const double frame_delta = frame_timer.restart();
        const double scroll_speed_key_input = 200.0;
        const double scroll_speed_mouse_wheel = 800.0;
        const double scroll_speed_autoscroll = 10.0;
        const double scroll_deaccel = 6.0; // higher value = faster stop

        if(!window_size_set) {
            auto window_size_i = window->get_size();
            window_size.x = window_size_i.x;
            window_size.y = window_size_i.y;
            window_size_set = true;
            scroll_to_page(current_page);
        }

        // TODO: Only redraw when scrolling and when image has finished downloading
        mgl::Event event;
        while(window->poll_event(event)) {
            if(event.type == mgl::Event::KeyPressed) {
                if(event.key.code == mgl::Keyboard::Q && event.key.control)
                    window->close();
            }

            if(event.type == mgl::Event::Resized) {
                window_size.x = event.size.width;
                window_size.y = event.size.height;
            } else if(event.type == mgl::Event::KeyPressed) {
                if(is_key_scroll_up(event.key))
                    up_pressed = true;
                if(is_key_scroll_down(event.key))
                    down_pressed = true;

                if(event.key.code == mgl::Keyboard::Escape)
                    return ImageViewerAction::RETURN;
                
                if(event.key.code == mgl::Keyboard::I)
                    return ImageViewerAction::SWITCH_TO_SINGLE_IMAGE_MODE;

                if(event.key.code == mgl::Keyboard::Home)
                    scroll_to_page(0);

                if(event.key.code == mgl::Keyboard::End)
                    scroll_to_page(num_pages - 1);

                if(event.key.code == mgl::Keyboard::F)
                    *fit_image_to_window = !*fit_image_to_window;

                if(event.key.code == mgl::Keyboard::B)
                    show_progress_bar = !show_progress_bar;
            } else if(event.type == mgl::Event::KeyReleased) {
                if(is_key_scroll_up(event.key))
                    up_pressed = false;
                if(is_key_scroll_down(event.key))
                    down_pressed = false;
            } else if(event.type == mgl::Event::MouseWheelScrolled/* && event.mouse_wheel_scroll.wheel == mgl::Mouse::VerticalWheel*/) {
                scroll_speed += scroll_speed_mouse_wheel * event.mouse_wheel_scroll.delta * frame_delta;
            } else if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Button::Middle) {
                middle_mouse_scrolling = true;
                autoscroll_start_y = window->get_mouse_position().y;
                // TODO: Fix
                //if(has_size_vertical_cursor)
                //    window->setMouseCursor(size_vertical_cursor);
            } else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Button::Middle) {
                middle_mouse_scrolling = false;
                scroll_speed = 0.0;
                // TODO: Fix
                //if(has_default_cursor)
                //    window->setMouseCursor(default_cursor);
            }
        }

        if(up_pressed)
            scroll_speed += scroll_speed_key_input * frame_delta;
        if(down_pressed)
            scroll_speed -= scroll_speed_key_input * frame_delta;

        if(middle_mouse_scrolling) {
            double distance_to_start_y = (double)window->get_mouse_position().y - autoscroll_start_y;
            double dist_abs = std::abs(distance_to_start_y);
            dist_abs -= 20.0;
            if(dist_abs < 0.0)
                dist_abs = 0.0;
            scroll_speed = -(dist_abs * sign(distance_to_start_y)) * scroll_speed_autoscroll * frame_delta;
            const double max_speed = 100.0;
            if(scroll_speed > max_speed)
                scroll_speed = max_speed;
            if(scroll_speed < -max_speed)
                scroll_speed = -max_speed;
            scroll += scroll_speed;
        } else {
            const double max_speed = 100.0;
            if(scroll_speed > max_speed)
                scroll_speed = max_speed;
            if(scroll_speed < -max_speed)
                scroll_speed = -max_speed;

            scroll_speed *= (1.0 - std::min(1.0, scroll_deaccel * frame_delta));
            if(std::abs(scroll_speed) < 0.01) {
                scroll_speed = 0.0;
                scroll = std::round(scroll); /* Better image quality! */
            } else {
                scroll += scroll_speed;
            }
        }

        min_page_top_dist = 9999999.0;
        page_closest_to_top = -1;

        bool loaded_textures_changed = false;
        int page_i = 0;
        for(auto &page_data : image_data) {
            if(page_data) {
                if(page_data->image_status == ImageStatus::LOADED && page_data->image->get_size().x > 0 && page_data->image->get_size().y > 0) {
                    if(page_data->texture.load_from_image(*page_data->image)) {
                        //page_data->texture.generateMipmap();
                        page_data->image_status = ImageStatus::APPLIED_TO_TEXTURE;
                        page_data->sprite.set_texture(&page_data->texture);
                        mgl::vec2i texture_size = image_data[page_i]->texture.get_size();
                        page_size[page_i].size = mgl::vec2d(texture_size.x, texture_size.y);
                        page_size[page_i].loaded = true;
                    } else {
                        page_data->image_status = ImageStatus::FAILED_TO_LOAD;
                    }
                    page_data->image.reset();
                    loaded_textures_changed = true;
                }
                page_data->visible_on_screen = false;
            }
            ++page_i;
        }

        double page_offset = 0.0;
        for(int i = -1; i < num_pages + 1; ++i) {
            const mgl::vec2d current_page_size = get_page_size(i);
            double scroll_diff = 0.0;
            if(i < 0) {
                scroll_diff = current_page_size.y - prev_size_first_page.y;
                prev_size_first_page = current_page_size;
            } else if(i >= num_pages) {
                scroll_diff = current_page_size.y - prev_size_last_page.y;
                prev_size_last_page = current_page_size;
            } else {
                scroll_diff = current_page_size.y - page_size[i].prev_size.y;
                page_size[i].prev_size = current_page_size;
            }

            if(page_offset < -scroll)
                scroll -= scroll_diff;

            render_page(*window, i, page_offset);
            page_offset += current_page_size.y;
        }

        const double first_image_height = get_page_size(-1).y;
        const double last_image_height = get_page_size(image_data.size()).y;

        // TODO: Do not allow scrolling if all images height (page_offset) is smaller than window height

        const double top_scroll = std::max(0.0, -first_image_height + window_size.y);
        const double bottom_scroll = std::min(window_size.y, window_size.y + last_image_height - window_size.y);
        if(scroll > top_scroll) {
            scroll = top_scroll;
            return ImageViewerAction::PREVIOUS_CHAPTER;
        } else if(scroll + page_offset < bottom_scroll && page_offset > window_size.y) {
            scroll = -page_offset + bottom_scroll;
            return ImageViewerAction::NEXT_CHAPTER;
        }

        if(page_closest_to_top != -1)
            focused_page = page_closest_to_top;

        if(focused_page != prev_focused_page) {
            prev_focused_page = focused_page;
            page_text.set_string(content_title + " | " + chapter_title + " | Page " + std::to_string(1 + focused_page) + "/" + std::to_string(num_pages));
        }

        const float font_height = page_text_character_size + 8.0f;
        const float background_height = font_height + 6.0f;

        if(show_progress_bar) {
            mgl::Rectangle page_text_background(mgl::vec2f(window_size.x, background_height));
            mgl::Color text_background_color = get_theme().shade_color;
            text_background_color.a = 225;
            page_text_background.set_color(text_background_color);
            page_text_background.set_position(mgl::vec2f(0.0f, window_size.y - background_height));
            window->draw(page_text_background);

            auto page_text_bounds = page_text.get_bounds();
            page_text.set_position(mgl::vec2f(floor(window_size.x * 0.5f - page_text_bounds.size.x * 0.5f), floor(window_size.y - background_height * 0.5f - font_height * 0.55f)));
            window->draw(page_text);
        }

        // Free pages that are not visible on the screen
        int i = 0;
        for(auto &page_data : image_data) {
            if(page_data && !page_data->visible_on_screen) {
                //fprintf(stderr, "ImageViewer: Unloaded page %d\n", 1 + i);
                page_data.reset();
                loaded_textures_changed = true;
            }
            ++i;
        }

        if(loaded_textures_changed)
            malloc_trim(0);

        return ImageViewerAction::NONE;
    }

    int ImageViewer::get_focused_page() const {
        return std::max(0, std::min(1 + focused_page, num_pages));
    }

    mgl::vec2d ImageViewer::get_page_size(int page) {
        mgl::vec2d no_image_page_size = get_no_image_size_scaled(window_size, *fit_image_to_window);

        if(page < 0 || page >= (int)image_data.size())
            return no_image_page_size;

        if(page_size[page].loaded) {
            mgl::vec2f texture_size_f(page_size[page].size.x, page_size[page].size.y);
            mgl::vec2f content_size(window_size.x, window_size.y);

            mgl::vec2f image_scale;
            if(*fit_image_to_window)
                image_scale = get_ratio(texture_size_f, wrap_to_size_x(texture_size_f, content_size.x));
            else
                image_scale = get_ratio(texture_size_f, clamp_to_size_x(texture_size_f, content_size.x));

            return mgl::vec2d(page_size[page].size.x * image_scale.x, page_size[page].size.y * image_scale.y);
        }

        if(!image_data[page] || image_data[page]->image_status != ImageStatus::APPLIED_TO_TEXTURE)
            return no_image_page_size;

        // Do not scale here, because this will be used to set page_size[page].size
        mgl::vec2i texture_size = image_data[page]->texture.get_size();
        return mgl::vec2d(texture_size.x, texture_size.y);
    }
}