Documentation
use crate::frame::context::{Context, ContextUpdate, UpdateType};
use crate::frame::App;
use crate::layout::horizontal::HorizontalLayout;
use crate::layout::popup::Popup;
use crate::layout::vertical::VerticalLayout;
use crate::layout::{Layout, LayoutItem, LayoutKind};
use crate::map::Map;
use crate::render::image::ImageSource;
use crate::size::padding::Padding;
use crate::size::rect::Rect;
use crate::style::Style;
use crate::text::rich::RichText;
use crate::widgets::checkbox::CheckBox;
use crate::widgets::space::Space;
use crate::widgets::{Widget, WidgetChange, WidgetKind};
use crate::window::inner::InnerWindow;
use crate::window::{UserEvent, WindowId, WindowType};
use crate::{Button, Device, Image, Label, NumCastExt, RadioButton, SelectItem, Slider, SpinBox, SAMPLE_COUNT};
use std::fmt::Display;
use std::ops::{AddAssign, Range, SubAssign};
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::thread::{sleep, spawn, JoinHandle};
use std::time::Duration;
use wgpu::{LoadOp, Operations, RenderPassDescriptor};

pub struct AppContext {
    pub(crate) device: Device,
    pub(crate) layout: Option<LayoutKind>,
    pub(crate) popups: Option<Map<String, Popup>>,
    pub(crate) inner_windows: Option<Map<WindowId, InnerWindow>>,
    pub(crate) style: Style,
    pub(crate) context: Context,
    previous_time: u128,
    redraw_thread: JoinHandle<()>,
}

impl AppContext {
    pub fn new(device: Device, context: Context) -> AppContext {
        let layout = VerticalLayout::top_to_bottom().with_size(device.surface_config.width as f32, device.surface_config.height as f32)
            .with_space(5.0).with_padding(Padding::same(5.0));
        AppContext {
            device,
            layout: Some(LayoutKind::new(layout)),
            popups: Some(Map::new()),
            inner_windows: Some(Map::new()),
            style: Style::light_style(),
            context,
            previous_time: 0,
            redraw_thread: spawn(|| {}),
        }
    }

    pub fn draw(&mut self, app: &mut Box<dyn App>) {
        let draw_rect = Rect::new().with_size(self.device.surface_config.width as f32, self.device.surface_config.height as f32);
        let mut ui = Ui {
            device: &self.device,
            context: &mut self.context,
            app: None,
            pass: None,
            layout: Some(self.layout.take().unwrap()),
            popups: self.popups.take(),
            update_type: UpdateType::Init,
            can_offset: false,
            inner_windows: None,
            request_update: None,
            draw_rect,
            widget_changed: WidgetChange::None,
        };
        app.draw(&mut ui);
        self.layout = ui.layout.take();
        self.popups = ui.popups.take();
    }

    #[cfg(not(feature = "winit"))]
    pub fn user_update(&mut self, app: &mut Box<dyn App>) {
        let draw_rect = Rect::new().with_size(self.device.surface_config.width as f32, self.device.surface_config.height as f32);
        let mut ui = Ui {
            device: &self.device,
            context: &mut self.context,
            app: None,
            pass: None,
            layout: self.layout.take(),
            popups: None,
            update_type: UpdateType::None,
            can_offset: false,
            inner_windows: None,
            request_update: None,
            draw_rect,
            widget_changed: WidgetChange::None,
        };
        app.update(&mut ui);
        self.layout = ui.layout.take();
    }

    pub fn update(&mut self, ut: UpdateType, app: &mut Box<dyn App>) {
        let draw_rect = Rect::new().with_size(self.device.surface_config.width as f32, self.device.surface_config.height as f32);
        let mut ui = Ui {
            device: &self.device,
            context: &mut self.context,
            app: None,
            pass: None,
            layout: self.layout.take(),
            popups: None,
            update_type: ut,
            can_offset: false,
            inner_windows: None,
            request_update: None,

            draw_rect,
            widget_changed: WidgetChange::None,
        };
        app.update(&mut ui);
        ui.app = Some(app);
        let mut event_win = None;
        let inner_windows = self.inner_windows.as_ref().unwrap();
        for i in 0..inner_windows.len() {
            let win = &inner_windows[inner_windows.len() - i - 1];
            if self.device.device_input.hovered_at(&win.fill_render.param.rect) || win.press_title {
                event_win = Some(win.id);
                break;
            }
        }

        if let Some(wid) = event_win {
            let inner_win = &mut self.inner_windows.as_mut().unwrap()[&wid];
            inner_win.update(&mut ui);
            if inner_win.top {
                let win = self.inner_windows.as_mut().unwrap().remove(&wid).unwrap();
                if win.request_close.load(Ordering::SeqCst) {
                    if let Some(win) = self.inner_windows.as_mut().unwrap().last_mut() {
                        win.top = true;
                    }
                } else {
                    self.inner_windows.as_mut().unwrap().iter_mut().for_each(|x| x.top = false);
                    self.inner_windows.as_mut().unwrap().insert(win.id, win);
                }
            }
        };

        ui.inner_windows = self.inner_windows.take();
        for popup in self.popups.as_mut().unwrap().iter_mut() {
            popup.update(&mut ui);
        }
        ui.popups = self.popups.take();
        self.layout = ui.layout.take();
        self.layout.as_mut().unwrap().update(&mut ui);
        self.popups = ui.popups.take();
        self.inner_windows = ui.inner_windows.take();
    }

    pub fn redraw(&mut self, app: &mut Box<dyn App>) {
        if !self.redraw_thread.is_finished() { return; }
        if crate::time_ms() - self.previous_time < 10 {
            let window = self.context.window.clone();
            let t = self.previous_time;
            self.redraw_thread = spawn(move || {
                sleep(Duration::from_millis(crate::time_ms() as u64 - t as u64));
                window.request_redraw();
            });
            return;
        }
        let surface_texture = match self.device.surface.get_current_texture() {
            Ok(res) => res,
            Err(e) => {
                println!("{}", e.to_string());
                return;
            }
        };
        let view = surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default());
        let msaa_texture = self.device.device.create_texture(&wgpu::TextureDescriptor {
            label: None,
            size: wgpu::Extent3d {
                width: self.device.surface_config.width,
                height: self.device.surface_config.height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: SAMPLE_COUNT,
            dimension: wgpu::TextureDimension::D2,
            format: self.device.texture_format,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });
        let msaa_view = msaa_texture.create_view(&wgpu::TextureViewDescriptor::default());


        let mut encoder = self.device.device.create_command_encoder(&Default::default());
        let render_pass_desc = RenderPassDescriptor {
            label: None,
            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                view: &msaa_view,
                resolve_target: Some(&view),
                ops: Operations {
                    load: LoadOp::Clear(self.style.window.fill.as_wgpu_color()),
                    store: wgpu::StoreOp::Store,
                },
            })],
            depth_stencil_attachment: None,
            timestamp_writes: None,
            occlusion_query_set: None,
        };
        let pass = encoder.begin_render_pass(&render_pass_desc);
        let draw_rect = Rect::new().with_size(self.device.surface_config.width as f32, self.device.surface_config.height as f32);
        let mut ui = Ui {
            device: &self.device,
            context: &mut self.context,
            app: None,
            pass: Some(pass),
            layout: self.layout.take(),
            popups: self.popups.take(),
            update_type: UpdateType::Draw,
            can_offset: false,
            inner_windows: None,
            request_update: None,
            // offset: Offset::new(Pos::new()),
            draw_rect,
            widget_changed: WidgetChange::None,
        };
        app.update(&mut ui);
        ui.app = Some(app);
        self.layout = ui.layout.take();
        self.layout.as_mut().unwrap().update(&mut ui);
        self.popups = ui.popups.take();
        for popup in self.popups.as_mut().unwrap().iter_mut() {
            popup.update(&mut ui);
            if popup.open && self.device.device_input.hovered_at(popup.rect()) { ui.update_type = UpdateType::None; }
        }
        if let Some(u) = ui.request_update.take() {
            ui.context.user_update = u;
            ui.context.window.request_update_event(UserEvent::ReqUpdate);
        }
        self.inner_windows.as_mut().unwrap().sort_by_key(|x| x.value().top);
        for inner_window in self.inner_windows.as_mut().unwrap().iter_mut() {
            inner_window.redraw(&mut ui);
        }
        drop(ui);
        self.device.queue.submit([encoder.finish()]);
        surface_texture.present();
        self.previous_time = crate::time_ms();
    }
}

pub struct Ui<'a, 'p> {
    pub(crate) device: &'a Device,
    pub(crate) context: &'a mut Context,
    pub(crate) app: Option<&'a mut Box<dyn App>>,
    pub(crate) pass: Option<wgpu::RenderPass<'p>>,
    pub(crate) layout: Option<LayoutKind>,
    pub(crate) popups: Option<Map<String, Popup>>,
    pub(crate) update_type: UpdateType,
    pub(crate) can_offset: bool,
    pub(crate) inner_windows: Option<Map<WindowId, InnerWindow>>,
    pub(crate) request_update: Option<(WindowId, UpdateType)>,
    pub(crate) draw_rect: Rect,
    pub(crate) widget_changed: WidgetChange,
}


impl<'a, 'p> Ui<'a, 'p> {
    pub(crate) fn layout(&mut self) -> &mut LayoutKind {
        self.layout.as_mut().expect("仅能在App::update中调用")
    }

    pub(crate) fn send_updates(&mut self, ids: &Vec<String>, ct: ContextUpdate) {
        for id in ids {
            self.context.updates.insert(id.to_string(), ct.clone());
        }
    }
}

impl<'a, 'p> Ui<'a, 'p> {
    pub fn window(&self) -> Arc<WindowType> {
        self.context.window.clone()
    }
    pub fn add_space(&mut self, space: f32) {
        let space = Space::new(space);
        self.add(space);
    }

    pub fn add<T: Widget>(&mut self, widget: T) -> Option<&mut T> {
        let widget = WidgetKind::new(self, widget);
        let wid = widget.id().to_owned();
        let layout = self.layout.as_mut()?;
        layout.add_item(LayoutItem::Widget(widget));
        layout.get_item_mut(&wid)?.widget_mut()
    }

    pub fn get_widget<T: Widget>(&mut self, id: impl ToString) -> Option<&mut T> {
        let layout = self.layout.as_mut()?;
        layout.get_widget(&id.to_string())
    }

    pub fn request_update(&mut self, ut: UpdateType) {
        let wid = self.context.window.id();
        self.request_update = Some((wid, ut));
    }

    pub fn add_layout(&mut self, layout: impl Layout + 'static, context: impl FnOnce(&mut Ui)) {
        let layout = LayoutKind::new(layout);
        let previous_layout = self.layout.replace(layout).unwrap();
        context(self);
        let mut current_layout = self.layout.replace(previous_layout).unwrap();
        current_layout.update(self);
        self.layout().add_item(LayoutItem::Layout(current_layout));
    }

    pub fn horizontal(&mut self, context: impl FnOnce(&mut Ui)) {
        let current_layout = HorizontalLayout::left_to_right().with_padding(Padding::same(0.0));
        let previous_layout = self.layout.replace(LayoutKind::new(current_layout)).unwrap();
        context(self);
        let mut current_layout = self.layout.replace(previous_layout).unwrap();
        current_layout.update(self);
        self.layout().add_item(LayoutItem::Layout(current_layout));
    }

    pub fn vertical(&mut self, mut context: impl FnMut(&mut Ui)) {
        let current_layout = VerticalLayout::top_to_bottom().with_padding(Padding::same(0.0));
        let previous_layout = self.layout.replace(LayoutKind::new(current_layout)).unwrap();
        context(self);
        let mut current_layout = self.layout.replace(previous_layout).unwrap();
        current_layout.update(self);
        self.layout().add_item(LayoutItem::Layout(current_layout));
    }

    pub fn create_inner_window<W: App>(&mut self, w: W) -> &mut InnerWindow {
        let mut inner_window = InnerWindow::new(w, self);
        inner_window.top = true;
        self.inner_windows.as_mut().unwrap().iter_mut().for_each(|x| x.top = false);
        let id = inner_window.id.clone();
        self.inner_windows.as_mut().unwrap().insert(inner_window.id.clone(), inner_window);
        self.inner_windows.as_mut().unwrap().get_mut(&id).unwrap()
    }

    pub fn create_window<W: App>(&mut self, w: W) {
        let attr = w.window_attributes();
        let app = Box::new(w);
        self.context.new_window = Some((app, attr));
        self.context.window.request_update_event(UserEvent::CreateChild);
    }

    pub fn label(&mut self, text: impl Into<RichText>) {
        let label = Label::new(text);
        self.add(label);
    }

    pub fn button(&mut self, text: impl Into<RichText>) -> &mut Button {
        let btn = Button::new(text);
        self.add(btn).unwrap()
    }

    pub fn radio(&mut self, v: bool, l: impl Into<RichText>) -> &mut RadioButton {
        let radio = RadioButton::new(v, l);
        self.add(radio).unwrap()
    }

    pub fn checkbox(&mut self, v: bool, l: impl Into<RichText>) -> &mut CheckBox {
        let checkbox = CheckBox::new(v, l);
        self.add(checkbox).unwrap()
    }

    pub fn slider(&mut self, v: f32, r: Range<f32>) -> &mut Slider {
        let slider = Slider::new(v).with_range(r);
        self.add(slider).unwrap()
    }

    pub fn image(&mut self, source: impl Into<ImageSource>, size: (f32, f32)) -> &mut Image {
        let image = Image::new(source).with_size(size.0, size.1);
        self.add(image).unwrap()
    }

    pub fn spinbox<T: Display + NumCastExt + PartialOrd + AddAssign + SubAssign + Copy + 'static>(&mut self, v: T, g: T, r: Range<T>) -> &mut SpinBox<T> {
        let spinbox = SpinBox::new(v, g, r);
        self.add(spinbox).unwrap()
    }

    pub fn select_value<T: Display + PartialEq + 'static>(&mut self, t: T) -> &mut SelectItem<T> {
        let select_value = SelectItem::new(t);
        self.add(select_value).unwrap()
    }
}