use glyph_brush::{HorizontalAlign, VerticalAlign};
use serde::{Deserialize, Serialize};
#[cfg(feature = "profiler")]
use thread_profiler::profile_scope;
use amethyst_core::{
    ecs::prelude::{
        BitSet, ComponentEvent, Join, ReadExpect, ReadStorage, ReaderId, System, SystemData, World,
        WriteStorage,
    },
    HierarchyEvent, Parent, ParentHierarchy, SystemDesc,
};
use amethyst_window::ScreenDimensions;
use super::UiTransform;
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub enum ScaleMode {
    
    Pixel,
    
    Percent,
}
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub enum Anchor {
    
    TopLeft,
    
    TopMiddle,
    
    TopRight,
    
    MiddleLeft,
    
    Middle,
    
    MiddleRight,
    
    BottomLeft,
    
    BottomMiddle,
    
    BottomRight,
}
impl Anchor {
    
    
    
    pub fn norm_offset(&self) -> (f32, f32) {
        match self {
            Anchor::TopLeft => (-0.5, 0.5),
            Anchor::TopMiddle => (0.0, 0.5),
            Anchor::TopRight => (0.5, 0.5),
            Anchor::MiddleLeft => (-0.5, 0.0),
            Anchor::Middle => (0.0, 0.0),
            Anchor::MiddleRight => (0.5, 0.0),
            Anchor::BottomLeft => (-0.5, -0.5),
            Anchor::BottomMiddle => (0.0, -0.5),
            Anchor::BottomRight => (0.5, -0.5),
        }
    }
    
    pub(crate) fn vertical_align(&self) -> VerticalAlign {
        match self {
            Anchor::TopLeft => VerticalAlign::Top,
            Anchor::TopMiddle => VerticalAlign::Top,
            Anchor::TopRight => VerticalAlign::Top,
            Anchor::MiddleLeft => VerticalAlign::Center,
            Anchor::Middle => VerticalAlign::Center,
            Anchor::MiddleRight => VerticalAlign::Center,
            Anchor::BottomLeft => VerticalAlign::Bottom,
            Anchor::BottomMiddle => VerticalAlign::Bottom,
            Anchor::BottomRight => VerticalAlign::Bottom,
        }
    }
    
    pub(crate) fn horizontal_align(&self) -> HorizontalAlign {
        match self {
            Anchor::TopLeft => HorizontalAlign::Left,
            Anchor::TopMiddle => HorizontalAlign::Center,
            Anchor::TopRight => HorizontalAlign::Right,
            Anchor::MiddleLeft => HorizontalAlign::Left,
            Anchor::Middle => HorizontalAlign::Center,
            Anchor::MiddleRight => HorizontalAlign::Right,
            Anchor::BottomLeft => HorizontalAlign::Left,
            Anchor::BottomMiddle => HorizontalAlign::Center,
            Anchor::BottomRight => HorizontalAlign::Right,
        }
    }
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum Stretch {
    
    NoStretch,
    
    X {
        
        x_margin: f32,
    },
    
    Y {
        
        y_margin: f32,
    },
    
    XY {
        
        x_margin: f32,
        
        y_margin: f32,
        
        keep_aspect_ratio: bool,
    },
}
#[derive(Default, Debug)]
pub struct UiTransformSystemDesc;
impl<'a, 'b> SystemDesc<'a, 'b, UiTransformSystem> for UiTransformSystemDesc {
    fn build(self, world: &mut World) -> UiTransformSystem {
        <UiTransformSystem as System<'_>>::SystemData::setup(world);
        let parent_events_id = world.fetch_mut::<ParentHierarchy>().track();
        let mut transforms = WriteStorage::<UiTransform>::fetch(&world);
        let transform_events_id = transforms.register_reader();
        UiTransformSystem::new(transform_events_id, parent_events_id)
    }
}
#[derive(Debug)]
pub struct UiTransformSystem {
    transform_modified: BitSet,
    transform_events_id: ReaderId<ComponentEvent>,
    parent_events_id: ReaderId<HierarchyEvent>,
    screen_size: (f32, f32),
}
impl UiTransformSystem {
    
    pub fn new(
        transform_events_id: ReaderId<ComponentEvent>,
        parent_events_id: ReaderId<HierarchyEvent>,
    ) -> Self {
        Self {
            transform_modified: BitSet::default(),
            transform_events_id,
            parent_events_id,
            screen_size: (0.0, 0.0),
        }
    }
}
impl<'a> System<'a> for UiTransformSystem {
    type SystemData = (
        WriteStorage<'a, UiTransform>,
        ReadStorage<'a, Parent>,
        ReadExpect<'a, ScreenDimensions>,
        ReadExpect<'a, ParentHierarchy>,
    );
    fn run(&mut self, data: Self::SystemData) {
        #[cfg(feature = "profiler")]
        profile_scope!("ui_transform_system");
        let (mut transforms, parents, screen_dim, hierarchy) = data;
        self.transform_modified.clear();
        let self_transform_modified = &mut self.transform_modified;
        let self_transform_events_id = &mut self.transform_events_id;
        transforms
            .channel()
            .read(self_transform_events_id)
            .for_each(|event| match event {
                ComponentEvent::Inserted(id) | ComponentEvent::Modified(id) => {
                    self_transform_modified.add(*id);
                }
                ComponentEvent::Removed(_id) => {}
            });
        for event in hierarchy.changed().read(&mut self.parent_events_id) {
            if let HierarchyEvent::Modified(entity) = *event {
                self_transform_modified.add(entity.id());
            }
        }
        let current_screen_size = (screen_dim.width(), screen_dim.height());
        let screen_resized = current_screen_size != self.screen_size;
        self.screen_size = current_screen_size;
        if screen_resized {
            process_root_iter(
                (&mut transforms, !&parents).join().map(|i| i.0),
                &*screen_dim,
            );
        } else {
            
            let self_transform_modified = &*self_transform_modified;
            process_root_iter(
                (&mut transforms, !&parents, self_transform_modified)
                    .join()
                    .map(|i| i.0),
                &*screen_dim,
            );
        }
        
        transforms
            .channel()
            .read(self_transform_events_id)
            .for_each(|event| {
                if let ComponentEvent::Modified(id) = event {
                    self_transform_modified.add(*id);
                }
            });
        
        for entity in hierarchy.all() {
            {
                let self_dirty = self_transform_modified.contains(entity.id());
                let parent_entity = match parents.get(*entity) {
                    Some(p) => p.entity,
                    None => continue, 
                };
                let parent_dirty = self_transform_modified.contains(parent_entity.id());
                if parent_dirty || self_dirty || screen_resized {
                    let parent_transform_copy = transforms.get(parent_entity).cloned();
                    let transform = transforms.get_mut(*entity);
                    let (transform, parent_transform_copy) =
                        match (transform, parent_transform_copy) {
                            (Some(v1), Some(v2)) => (v1, v2),
                            _ => continue,
                        };
                    let norm = transform.anchor.norm_offset();
                    transform.pixel_x =
                        parent_transform_copy.pixel_x + parent_transform_copy.pixel_width * norm.0;
                    transform.pixel_y =
                        parent_transform_copy.pixel_y + parent_transform_copy.pixel_height * norm.1;
                    transform.global_z = parent_transform_copy.global_z + transform.local_z;
                    let new_size = match transform.stretch {
                        Stretch::NoStretch => (transform.width, transform.height),
                        Stretch::X { x_margin } => (
                            parent_transform_copy.pixel_width - x_margin * 2.0,
                            transform.height,
                        ),
                        Stretch::Y { y_margin } => (
                            transform.width,
                            parent_transform_copy.pixel_height - y_margin * 2.0,
                        ),
                        Stretch::XY {
                            keep_aspect_ratio: false,
                            x_margin,
                            y_margin,
                        } => (
                            parent_transform_copy.pixel_width - x_margin * 2.0,
                            parent_transform_copy.pixel_height - y_margin * 2.0,
                        ),
                        Stretch::XY {
                            keep_aspect_ratio: true,
                            x_margin,
                            y_margin,
                        } => {
                            let scale = f32::min(
                                (parent_transform_copy.pixel_width - x_margin * 2.0)
                                    / transform.width,
                                (parent_transform_copy.pixel_height - y_margin * 2.0)
                                    / transform.height,
                            );
                            (transform.width * scale, transform.height * scale)
                        }
                    };
                    transform.width = new_size.0;
                    transform.height = new_size.1;
                    match transform.scale_mode {
                        ScaleMode::Pixel => {
                            transform.pixel_x += transform.local_x;
                            transform.pixel_y += transform.local_y;
                            transform.pixel_width = transform.width;
                            transform.pixel_height = transform.height;
                        }
                        ScaleMode::Percent => {
                            transform.pixel_x +=
                                transform.local_x * parent_transform_copy.pixel_width;
                            transform.pixel_y +=
                                transform.local_y * parent_transform_copy.pixel_height;
                            transform.pixel_width =
                                transform.width * parent_transform_copy.pixel_width;
                            transform.pixel_height =
                                transform.height * parent_transform_copy.pixel_height;
                        }
                    }
                    let pivot_norm = transform.pivot.norm_offset();
                    transform.pixel_x += transform.pixel_width * -pivot_norm.0;
                    transform.pixel_y += transform.pixel_height * -pivot_norm.1;
                }
            }
            
            transforms
                .channel()
                .read(self_transform_events_id)
                .for_each(|event| {
                    if let ComponentEvent::Modified(id) = event {
                        self_transform_modified.add(*id);
                    }
                });
        }
        
        
        transforms
            .channel()
            .read(self_transform_events_id)
            .for_each(|event| match event {
                ComponentEvent::Inserted(id) | ComponentEvent::Modified(id) => {
                    self_transform_modified.add(*id);
                }
                ComponentEvent::Removed(_id) => {}
            });
    }
}
fn process_root_iter<'a, I>(iter: I, screen_dim: &ScreenDimensions)
where
    I: Iterator<Item = &'a mut UiTransform>,
{
    for transform in iter {
        let norm = transform.anchor.norm_offset();
        transform.pixel_x = screen_dim.width() / 2.0 + screen_dim.width() * norm.0;
        transform.pixel_y = screen_dim.height() / 2.0 + screen_dim.height() * norm.1;
        transform.global_z = transform.local_z;
        let new_size = match transform.stretch {
            Stretch::NoStretch => (transform.width, transform.height),
            Stretch::X { x_margin } => (screen_dim.width() - x_margin * 2.0, transform.height),
            Stretch::Y { y_margin } => (transform.width, screen_dim.height() - y_margin * 2.0),
            Stretch::XY {
                keep_aspect_ratio: false,
                x_margin,
                y_margin,
            } => (
                screen_dim.width() - x_margin * 2.0,
                screen_dim.height() - y_margin * 2.0,
            ),
            Stretch::XY {
                keep_aspect_ratio: true,
                x_margin,
                y_margin,
            } => {
                let scale = f32::min(
                    (screen_dim.width() - x_margin * 2.0) / transform.width,
                    (screen_dim.height() - y_margin * 2.0) / transform.height,
                );
                (transform.width * scale, transform.height * scale)
            }
        };
        transform.width = new_size.0;
        transform.height = new_size.1;
        match transform.scale_mode {
            ScaleMode::Pixel => {
                transform.pixel_x += transform.local_x;
                transform.pixel_y += transform.local_y;
                transform.pixel_width = transform.width;
                transform.pixel_height = transform.height;
            }
            ScaleMode::Percent => {
                transform.pixel_x += transform.local_x * screen_dim.width();
                transform.pixel_y += transform.local_y * screen_dim.height();
                transform.pixel_width = transform.width * screen_dim.width();
                transform.pixel_height = transform.height * screen_dim.height();
            }
        }
        let pivot_norm = transform.pivot.norm_offset();
        transform.pixel_x += transform.pixel_width * -pivot_norm.0;
        transform.pixel_y += transform.pixel_height * -pivot_norm.1;
    }
}