// 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 egui::epaint::{RectShape, Shadow};
use egui::os::OperatingSystem;
use egui::{Align2, Color32, CornerRadius, RichText, Stroke, StrokeKind, UiBuilder, Vec2};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use crate::gui::views::types::{ModalPosition, ModalState};
use crate::gui::views::{Content, View};
use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks;

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

/// Modal [`egui::Window`] container.
#[derive(Clone)]
pub struct Modal {
    /// Identifier for modal.
    pub(crate) id: &'static str,
    /// Position on the screen.
    pub position: ModalPosition,
    /// Flag to check if modal can be closed by keys.
    closeable: Arc<AtomicBool>,
    /// Title text.
    title: Option<String>,
    /// Flag to check first content render.
    first_draw: Arc<AtomicBool>,
    /// Background color.
    fill: Option<Color32>,
}

impl Modal {
    /// Margin from [`Modal`] window at top/left/right.
    const DEFAULT_MARGIN: f32 = 8.0;
    /// Maximum width of the content.
    const DEFAULT_WIDTH: f32 = Content::SIDE_PANEL_WIDTH - (2.0 * Self::DEFAULT_MARGIN);
    /// Modal content [`egui::Window`] id.
    pub const WINDOW_ID: &'static str = "modal_window";

    /// Create closeable [`Modal`] with center position.
    pub fn new(id: &'static str) -> Self {
        Self {
            id,
            position: ModalPosition::Center,
            closeable: Arc::new(AtomicBool::new(true)),
            title: None,
            first_draw: Arc::new(AtomicBool::new(true)),
            fill: None,
        }
    }

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

    /// Change [`Modal`] position on the screen.
    pub fn change_position(position: ModalPosition) {
        let mut w_state = MODAL_STATE.write();
        w_state.modal.as_mut().unwrap().position = position;
    }

    /// Close [`Modal`] by clearing its state.
    pub fn close() {
        let mut w_nav = MODAL_STATE.write();
        w_nav.modal = None;
    }

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

    /// Enable possibility to close [`Modal`].
    pub fn enable_closing(&self) {
        self.closeable.store(true, 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: impl Into<String>) -> Self {
        self.title = Some(title.into().to_uppercase());
        self
    }

    /// Set [`Modal`] instance into state to show at ui.
    pub fn show(self) {
        let mut w_nav = MODAL_STATE.write();
        self.first_draw.store(true, Ordering::Relaxed);
        w_nav.modal = Some(self);
    }

    /// Remove [`Modal`] from [`ModalState`] if it's showing and can be closed.
    /// Return `false` if modal existed in state before call.
    pub fn on_back() -> bool {
        if Self::opened().is_some() {
            if Self::opened_closeable() {
                Self::close();
            }
            return false;
        }
        true
    }

    /// Return identifier of opened [`Modal`].
    pub fn opened() -> Option<&'static str> {
        // Check if modal is showing.
        {
            if MODAL_STATE.read().modal.is_none() {
                return None;
            }
        }

        // Get identifier of opened modal.
        let r_state = MODAL_STATE.read();
        let modal = r_state.modal.as_ref().unwrap();
        Some(modal.id)
    }

    /// Check if [`Modal`] is opened and can be closed.
    pub fn opened_closeable() -> bool {
        // Check if modal is showing.
        {
            if MODAL_STATE.read().modal.is_none() {
                return false;
            }
        }
        let r_state = MODAL_STATE.read();
        let modal = r_state.modal.as_ref().unwrap();
        modal.closeable.load(Ordering::Relaxed)
    }

    /// Set title text for current opened [`Modal`].
    pub fn set_title(title: impl Into<String>) {
        let mut w_state = MODAL_STATE.write();
        if w_state.modal.is_some() {
            let mut modal = w_state.modal.clone().unwrap();
            modal.title = Some(title.into().to_uppercase());
            w_state.modal = Some(modal);
        }
    }

    /// Check for first [`Modal`] content rendering.
    pub fn first_draw() -> bool {
        if Self::opened().is_none() {
            return false;
        }
        let r_state = MODAL_STATE.read();
        let modal = r_state.modal.as_ref().unwrap();
        modal.first_draw.load(Ordering::Relaxed)
    }

    pub fn ui(ctx: &egui::Context,
              cb: &dyn PlatformCallbacks,
              add_content: impl FnOnce(&mut egui::Ui, &Modal, &dyn PlatformCallbacks)) {
        let has_modal = {
            MODAL_STATE.read().modal.is_some()
        };
        if has_modal {
            let modal = {
                let r_state = MODAL_STATE.read();
                r_state.modal.clone().unwrap()
            };
            modal.window_ui(ctx, cb, add_content);
        }
    }

    /// Draw [`egui::Window`] with provided content.
    fn window_ui(&self,
                 ctx: &egui::Context,
                 cb: &dyn PlatformCallbacks,
                 add_content: impl FnOnce(&mut egui::Ui, &Modal, &dyn PlatformCallbacks)) {
        let is_fullscreen = ctx.input(|i| {
            i.viewport().fullscreen.unwrap_or(false)
        });

        // Setup background rect.
        let is_win = OperatingSystem::Windows == OperatingSystem::from_target_os();
        let bg_rect = if View::is_desktop() && !is_win {
            let mut r = ctx.content_rect();
            let is_mac = OperatingSystem::Mac == OperatingSystem::from_target_os();
            if !is_mac && !is_fullscreen {
                r = r.shrink(Content::WINDOW_FRAME_MARGIN - 1.0);
            }
            r.min.y += Content::WINDOW_TITLE_HEIGHT;
            r
        } else {
            ctx.content_rect()
        };

        // Draw modal background.
        egui::Window::new("modal_bg_window")
            .title_bar(false)
            .resizable(false)
            .collapsible(false)
            .fixed_rect(bg_rect)
            .frame(egui::Frame {
                fill: Colors::semi_transparent(),
                ..Default::default()
            })
            .show(ctx, |ui| {
                ui.set_min_size(bg_rect.size());
            });

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

        // Show main content window at given position.
        let (content_align, content_offset) = self.modal_position();
        egui::Window::new(Self::WINDOW_ID)
            .title_bar(false)
            .resizable(false)
            .collapsible(false)
            .min_width(width)
            .default_width(width)
            .anchor(content_align, content_offset)
            .frame(egui::Frame {
                shadow: Shadow {
                    offset: Default::default(),
                    blur: 30.0 as u8,
                    spread: 3.0 as u8,
                    color: egui::Color32::from_black_alpha(32),
                },
                corner_radius: CornerRadius::same(8.0 as u8),
                ..Default::default()
            })
            .show(ctx, |ui| {
                if let Some(title) = &self.title {
                    title_ui(title, ui);
                }
                self.content_ui(ui, cb, add_content);
            });

        // Setup first draw flag.
        if Self::first_draw() {
            let r_state = MODAL_STATE.read();
            let modal = r_state.modal.as_ref().unwrap();
            modal.first_draw.store(false, Ordering::Relaxed);
        }
    }

    /// 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 is_mac = OperatingSystem::Mac == OperatingSystem::from_target_os();
        let is_win = OperatingSystem::Windows == OperatingSystem::from_target_os();
        let extra_y = if View::is_desktop() && !is_win {
            Content::WINDOW_TITLE_HEIGHT + if !is_mac {
                Content::WINDOW_FRAME_MARGIN
            } else {
                0.0
            }
        } else {
            0.0
        };
        let y_align = View::get_top_inset() + Self::DEFAULT_MARGIN / 2.0 + extra_y;

        let offset = match self.position {
            ModalPosition::CenterTop => Vec2::new(x_align, y_align),
            ModalPosition::Center => Vec2::new(x_align, 0.0)
        };
        (align, offset)
    }

    /// Set custom background color.
    pub fn set_background_color(&self, color: Color32) {
        let mut w_state = MODAL_STATE.write();
        w_state.modal.as_mut().unwrap().fill = Some(color);
    }

    /// Draw provided content.
    fn content_ui(&self,
                  ui: &mut egui::Ui,
                  cb: &dyn PlatformCallbacks,
                  add_content: impl FnOnce(&mut egui::Ui, &Modal, &dyn PlatformCallbacks)) {
        let mut rect = ui.available_rect_before_wrap();

        // Create background shape.
        let mut bg_shape = RectShape::new(rect, if self.title.is_none() {
            CornerRadius::same(8.0 as u8)
        } else {
            CornerRadius {
                nw: 0.0 as u8,
                ne: 0.0 as u8,
                sw: 8.0 as u8,
                se: 8.0 as u8,
            }
        }, self.fill.unwrap_or(Colors::fill_lite()), Stroke::NONE, StrokeKind::Outside);
        let bg_idx = ui.painter().add(bg_shape.clone());

        rect.min += egui::emath::vec2(6.0, 0.0);
        rect.max -= egui::emath::vec2(6.0, 0.0);
        let resp = ui.scope_builder(UiBuilder::new().max_rect(rect), |ui| {
            (add_content)(ui, self, cb);
        }).response;

        // Setup background size.
        let bg_rect = {
            let mut r = resp.rect.clone();
            r.min -= egui::emath::vec2(6.0, 0.0);
            r.max += egui::emath::vec2(6.0, 0.0);
            r
        };
        bg_shape.rect = bg_rect;
        ui.painter().set(bg_idx, bg_shape);
    }
}

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

    // Create background shape.
    let mut bg_shape = RectShape::new(rect, CornerRadius {
        nw: 8.0 as u8,
        ne: 8.0 as u8,
        sw: 0.0 as u8,
        se: 0.0 as u8,
    }, Colors::yellow(), Stroke::NONE, StrokeKind::Outside);
    let bg_idx = ui.painter().add(bg_shape.clone());

    // Draw title content.
    let resp = ui.vertical_centered(|ui| {
        ui.add_space(Modal::DEFAULT_MARGIN + 2.0);
        ui.label(RichText::new(title)
            .size(19.0)
            .color(Colors::title(true))
        );
        ui.add_space(Modal::DEFAULT_MARGIN + 1.0);
        // Draw line below title.
        View::horizontal_line(ui, Colors::item_stroke());
    }).response;

    // Setup background size.
    bg_shape.rect = resp.rect;
    ui.painter().set(bg_idx, bg_shape);
}