// Copyright 2023 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::cmp::min;
use std::sync::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};

use egui::{Align2, RichText, Rounding, Stroke, Vec2};
use egui::epaint::RectShape;
use lazy_static::lazy_static;

use crate::gui::Colors;
use crate::gui::views::{Root, View};

lazy_static! {
    /// Showing [`Modal`] state to be accessible from different ui parts.
    static ref MODAL_STATE: RwLock<ModalState> = RwLock::new(ModalState::default());
}

#[derive(Default)]
struct ModalState {
    modal: Option<Modal>
}

/// Contains ids to draw current [`Modal`] at this ui container if it's possible.
pub trait ModalContainer {
    /// List of [`Modal`] ids to draw at current ui container.
    fn modal_ids(&self) -> &Vec<&'static str>;

    /// Check if it's possible to draw [`Modal`] at current ui container.
    fn can_draw_modal(&self) -> bool {
        let modal_id = Modal::opened();
        modal_id.is_some() && self.modal_ids().contains(&modal_id.unwrap())
    }
}

/// Position of [`Modal`] on the screen.
pub enum ModalPosition {
    CenterTop,
    Center
}

/// Stores data to draw modal [`egui::Window`] at ui.
pub struct Modal {
    /// Identifier for modal.
    pub(crate) id: &'static str,
    /// Position on the screen.
    position: ModalPosition,
    /// Flag to show the content.
    open: AtomicBool,
    /// To check if it can be closed.
    closeable: AtomicBool,
    /// Title text
    title: Option<String>
}

impl Modal {
    /// Margin from [`Modal`] window at top/left/right.
    const DEFAULT_MARGIN: f32 = 10.0;
    /// Default width of the content.
    const DEFAULT_WIDTH: f32 = Root::SIDE_PANEL_MIN_WIDTH - (2.0 * Self::DEFAULT_MARGIN);

    /// Create opened and closeable Modal with center position.
    pub fn new(id: &'static str) -> Self {
        Self {
            id,
            position: ModalPosition::Center,
            open: AtomicBool::new(true),
            closeable: AtomicBool::new(true),
            title: None
        }
    }

    /// Setup position of [`Modal`] on the screen.
    pub fn position(mut self, position: ModalPosition) -> Self {
        self.position = position;
        self
    }

    /// Check if [`Modal`] is open.
    pub fn is_open(&self) -> bool {
        self.open.load(Ordering::Relaxed)
    }

    /// Mark [`Modal`] closed.
    pub fn close(&self) {
        self.open.store(false, Ordering::Relaxed);
    }

    /// Setup possibility to close [`Modal`].
    pub fn closeable(self, closeable: bool) -> Self {
        self.closeable.store(closeable, Ordering::Relaxed);
        self
    }

    /// Disable possibility to close [`Modal`].
    pub fn disable_closing(&self) {
        self.closeable.store(false, Ordering::Relaxed);
    }

    /// Check if [`Modal`] is closeable.
    pub fn is_closeable(&self) -> bool {
        self.closeable.load(Ordering::Relaxed)
    }

    /// Set title text on [`Modal`] creation.
    pub fn title(mut self, title: String) -> Self {
        self.title = Some(title.to_uppercase());
        self
    }

    /// Set [`Modal`] instance into state to show at ui.
    pub fn show(self) {
        let mut w_nav = MODAL_STATE.write().unwrap();
        w_nav.modal = Some(self);
    }

    /// Remove [`Modal`] from [`MODAL_STATE`] if it's showing and can be closed.
    /// Return `false` if Modal existed in [`MODAL_STATE`] before call.
    pub fn on_back() -> bool {
        let mut w_state = MODAL_STATE.write().unwrap();

        // If Modal is showing and closeable, remove it from state.
        if w_state.modal.is_some() {
            let modal = w_state.modal.as_ref().unwrap();
            if modal.is_closeable() {
                w_state.modal = None;
            }
            return false;
        }
        true
    }

    /// Return id of opened [`Modal`] or remove its instance from [`MODAL_STATE`] if it was closed.
    pub fn opened() -> Option<&'static str> {
        // Check if Modal is showing.
        {
            if MODAL_STATE.read().unwrap().modal.is_none() {
                return None;
            }
        }

        // Check if Modal is open.
        let (is_open, id) = {
            let r_state = MODAL_STATE.read().unwrap();
            let modal = r_state.modal.as_ref().unwrap();
            (modal.is_open(), modal.id)
        };

        // If Modal is not open, remove it from navigator state.
        if !is_open {
            let mut w_state = MODAL_STATE.write().unwrap();
            w_state.modal = None;
            return None;
        }
        Some(id)
    }

    /// Draw opened [`Modal`] content.
    pub fn ui(ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
        if let Some(modal) = &MODAL_STATE.read().unwrap().modal {
            if modal.is_open() {
                modal.window_ui(ui, add_content);
            }
        }
    }

    /// Draw [`egui::Window`] with provided content.
    fn window_ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
        let rect = ui.ctx().screen_rect();
        egui::Window::new("modal_bg_window")
            .title_bar(false)
            .resizable(false)
            .collapsible(false)
            .fixed_rect(rect)
            .frame(egui::Frame {
                fill: Colors::SEMI_TRANSPARENT,
                ..Default::default()
            })
            .show(ui.ctx(), |ui| {
                ui.set_min_size(rect.size());
            });

        // Setup width of modal content.
        let side_insets = View::get_left_inset() + View::get_right_inset();
        let available_width = (ui.available_width() - (side_insets + Self::DEFAULT_MARGIN)) as i64;
        let width = min(available_width, Self::DEFAULT_WIDTH as i64) as f32;

        // Show main content Window at given position.
        let (content_align, content_offset) = self.modal_position();
        let layer_id = egui::Window::new(format!("modal_window_{}", self.id))
            .title_bar(false)
            .resizable(false)
            .collapsible(false)
            .default_width(width)
            .anchor(content_align, content_offset)
            .frame(egui::Frame {
                rounding: Rounding::same(8.0),
                fill: Colors::YELLOW,
                ..Default::default()
            })
            .show(ui.ctx(), |ui| {
                if self.title.is_some() {
                    self.title_ui(ui);
                }
                self.content_ui(ui, add_content);
            }).unwrap().response.layer_id;

        // Always show main content Window above background Window.
        ui.ctx().move_to_top(layer_id);

    }

    /// Get [`egui::Window`] position based on [`ModalPosition`].
    fn modal_position(&self) -> (Align2, Vec2) {
        let align = match self.position {
            ModalPosition::CenterTop => Align2::CENTER_TOP,
            ModalPosition::Center => Align2::CENTER_CENTER
        };
        let x_align = View::get_left_inset() - View::get_right_inset();
        let y_align = View::get_top_inset() + Self::DEFAULT_MARGIN;
        let offset = match self.position {
            ModalPosition::CenterTop => Vec2::new(x_align, y_align),
            ModalPosition::Center => Vec2::new(x_align, 0.0)
        };
        (align, offset)
    }

    /// Draw provided content.
    fn content_ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
        let mut rect = ui.available_rect_before_wrap();
        rect.min += egui::emath::vec2(6.0, 0.0);
        rect.max -= egui::emath::vec2(6.0, 0.0);

        // Create background shape.
        let rounding = if self.title.is_some() {
            Rounding {
                nw: 0.0,
                ne: 0.0,
                sw: 8.0,
                se: 8.0,
            }
        } else {
            Rounding::same(8.0)
        };
        let mut bg_shape = RectShape {
            rect,
            rounding,
            fill: Colors::FILL,
            stroke: Stroke::NONE,
        };
        let bg_idx = ui.painter().add(bg_shape);

        // Draw main content.
        let mut content_rect = ui.allocate_ui_at_rect(rect, |ui| {
            (add_content)(ui, self);
        }).response.rect;

        // Setup background shape to be painted behind main content.
        content_rect.min -= egui::emath::vec2(6.0, 0.0);
        content_rect.max += egui::emath::vec2(6.0, 0.0);
        bg_shape.rect = content_rect;
        ui.painter().set(bg_idx, bg_shape);
    }

    /// Draw the title.
    fn title_ui(&self, ui: &mut egui::Ui) {
        let rect = ui.available_rect_before_wrap();

        // Create background shape.
        let mut bg_shape = RectShape {
            rect,
            rounding: Rounding {
                nw: 8.0,
                ne: 8.0,
                sw: 0.0,
                se: 0.0,
            },
            fill: Colors::YELLOW,
            stroke: Stroke::NONE,
        };
        let bg_idx = ui.painter().add(bg_shape);

        // Draw title content.
        let title_resp = ui.allocate_ui_at_rect(rect, |ui| {
            ui.vertical_centered_justified(|ui| {
                ui.add_space(8.0);
                ui.label(RichText::new(self.title.as_ref().unwrap())
                    .size(19.0)
                    .color(Colors::TITLE)
                );
                ui.add_space(8.0);
            });
        }).response;

        // Setup background shape to be painted behind title content.
        bg_shape.rect = title_resp.rect;
        ui.painter().set(bg_idx, bg_shape);

        // Draw line below title.
        View::horizontal_line(ui, Colors::STROKE);
    }
}