use crate::prelude::*;
use anyhow::Result;

use crate::builder::validator::gridinfo::GridInfo;

use std::sync::{
    atomic::{AtomicBool, AtomicU32, Ordering::SeqCst},
    Arc, RwLock, RwLockReadGuard,
};
use std::{ops::Deref, sync::Weak};

use super::fill_type::FillType;

#[derive(Clone)]
enum ChildState {
    Some {
        child: Arc<dyn GuiElementTraits>,
        x: u32,
        y: u32,
    },
    Extend {
        weak_child: Weak<dyn GuiElementTraits>,
        x: u32,
        y: u32,
    },
    None,
}

impl ChildState {
    fn downgrade(&self) -> Self {
        match self {
            Self::Some { child, x, y } => Self::Extend {
                weak_child: Arc::downgrade(child),
                x: *x,
                y: *y,
            },

            _ => panic!(),
        }
    }

    fn is_some(&self) -> bool {
        match self {
            ChildState::Some { .. } => true,
            ChildState::Extend { .. } => false,
            ChildState::None => false,
        }
    }

    fn is_extend(&self) -> bool {
        match self {
            ChildState::Some { .. } => false,
            ChildState::Extend { .. } => true,
            ChildState::None => false,
        }
    }

    fn get_some(&self) -> Option<(&Arc<dyn GuiElementTraits>, u32, u32)> {
        match self {
            ChildState::Some { child, x, y } => Some((child, *x, *y)),
            ChildState::Extend { .. } => None,
            ChildState::None => None,
        }
    }

    fn get(&self) -> Option<(Arc<dyn GuiElementTraits>, u32, u32)> {
        match self {
            ChildState::Some { child, x, y } => Some((child.clone(), *x, *y)),
            ChildState::Extend { weak_child, x, y } => {
                weak_child.upgrade().map(|child| (child, *x, *y))
            }
            ChildState::None => None,
        }
    }

    fn map<C, R>(&self, f: C) -> Option<R>
    where
        C: FnOnce(&Arc<dyn GuiElementTraits>) -> R,
    {
        match self {
            ChildState::Some { child, .. } => Some(f(child)),
            ChildState::Extend { weak_child, .. } => weak_child.upgrade().map(|child| f(&child)),
            ChildState::None => None,
        }
    }
}

pub struct Grid {
    gui_handler: Arc<GuiHandler>,

    pub(crate) framable: Arc<Framable>,
    background: RwLock<Option<FillType>>,

    children: RwLock<Vec<Vec<ChildState>>>,

    dim_x: usize,
    dim_y: usize,

    padding: AtomicU32,
    margin: AtomicU32,

    visible: AtomicBool,
}

impl Grid {
    pub fn new(
        gui_handler: Arc<GuiHandler>,
        dim_x: usize,
        dim_y: usize,
        top_level: bool,
    ) -> Result<Arc<Self>> {
        let grid = Arc::new(Grid {
            gui_handler: gui_handler.clone(),

            framable: Framable::new(gui_handler, top_level)?,
            background: RwLock::new(None),

            children: RwLock::new(vec![vec![ChildState::None; dim_y]; dim_x]),

            dim_x,
            dim_y,

            padding: AtomicU32::new(0),
            margin: AtomicU32::new(10),

            visible: AtomicBool::new(false),
        });

        if top_level {
            let weak_grid = Arc::downgrade(&grid);

            grid.framable.add_callback(
                weak_grid.clone(),
                Box::new(move || {
                    if let Some(grid) = weak_grid.upgrade() {
                        grid.calculate_child_positions()?;
                    }

                    Ok(())
                }),
            );
        }

        Ok(grid)
    }

    pub fn dimensions(&self) -> (usize, usize) {
        (self.dim_x, self.dim_y)
    }

    pub fn set_background(&self, background: impl Into<FillTypeInfo>) -> Result<()> {
        super::set_background(self.visible(), &self.framable, &self.background, background)
    }

    pub(crate) fn background(&self) -> RwLockReadGuard<'_, Option<FillType>> {
        self.background.read().unwrap()
    }

    pub fn set_margin(&self, margin: u32) {
        self.margin.store(margin, SeqCst);
    }

    pub fn set_padding(&self, padding: u32) {
        self.padding.store(padding, SeqCst);
    }

    pub fn child_at(&self, x: usize, y: usize) -> Result<Option<Arc<dyn GuiElementTraits>>> {
        if x >= self.dim_x {
            return Err(anyhow::anyhow!(
                "Tried to access Grid at {} while only being {} wide",
                x,
                self.dim_x
            ));
        }

        if y >= self.dim_y {
            return Err(anyhow::anyhow!(
                "Tried to access Grid at {} while only being {} tall",
                y,
                self.dim_y
            ));
        }

        Ok(self.children.read().unwrap()[x][y].map(|c| c.clone()))
    }

    pub fn detach(&self, pos_x: usize, pos_y: usize) -> Result<Option<Arc<dyn GuiElementTraits>>> {
        if cfg!(debug_assertions) {
            if pos_x >= self.dim_x as usize {
                panic!(
                    "x position ({}) must not be bigger than grid x-dimension ({})",
                    pos_x, self.dim_x
                );
            }

            if pos_y >= self.dim_y as usize {
                panic!(
                    "y position ({}) must not be bigger than grid y-dimension ({})",
                    pos_y, self.dim_y
                );
            }
        }

        let current = self.children.write().unwrap()[pos_x][pos_y].clone();

        if current.is_extend() {
            todo!();
        }

        self.children.write().unwrap()[pos_x][pos_y] = ChildState::None;

        match current.get() {
            Some((child, _x, _y)) => {
                if self.visible() {
                    if let Some(child_visibility) = child.visibility() {
                        child_visibility.set_visibility(false)?;
                    }
                }

                Ok(Some(child))
            }
            None => Ok(None),
        }
    }

    /// Returns `true` if item got detached
    pub fn detach_item(&self, item: Arc<dyn GuiElementTraits>) -> Result<bool> {
        let mut grid = self.children.write().unwrap();
        let mut removed = false;

        'outer: for column in grid.iter_mut() {
            for child_opt in column.iter_mut() {
                let mut remove = false;

                if let Some((child, _x, _y)) = child_opt.get() {
                    // ugly workaround because Arc::ptr_eq does not work (7th, April 2020)

                    let item_ptr = Arc::into_raw(item.clone());
                    let child_ptr = Arc::into_raw(child.clone());

                    if std::ptr::eq(item_ptr, child_ptr) {
                        remove = true;

                        if self.visible() {
                            if let Some(child_visibility) = child.visibility() {
                                child_visibility.set_visibility(false)?;
                            }
                        }
                    }

                    unsafe {
                        Arc::from_raw(item_ptr);
                        Arc::from_raw(child_ptr);
                    }
                }

                if remove {
                    *child_opt = ChildState::None;
                    removed = true;

                    break 'outer;
                }
            }
        }

        Ok(removed)
    }

    pub fn set_position(&self, x: i32, y: i32) -> Result<()> {
        // update own position
        self.framable.change_position(x, y)?;

        if let Some(background) = self.background.read().unwrap().as_ref() {
            background.update_frame()?;
        }

        self.calculate_child_positions()?;

        Ok(())
    }

    fn calculate_child_positions(&self) -> Result<()> {
        // recalculate positions of the children
        let children = self.children.read().unwrap();

        for (x, row) in children.iter().enumerate() {
            for (y, child_opt) in row.iter().enumerate() {
                if let Some((child, dim_x, dim_y)) = child_opt.get_some() {
                    if let Some(child_gridable) = child.gridable() {
                        self.child_position(child_gridable, x, y, dim_x, dim_y)?;
                    }
                }
            }
        }

        Ok(())
    }

    pub fn position(&self) -> (i32, i32) {
        (self.framable.left(), self.framable.top())
    }

    pub fn extents(&self) -> (u32, u32) {
        let width = (self.framable.right() as i32 - self.framable.left()) as u32;
        let height = (self.framable.bottom() as i32 - self.framable.top()) as u32;

        (width, height)
    }

    pub fn disallow_position_scale(&self) -> Result<()> {
        self.framable.allow_position_scale(false)
    }

    pub fn disallow_size_scale(&self) -> Result<()> {
        self.framable.allow_size_scale(false)
    }

    pub fn attach(
        &self,
        child: Arc<dyn GuiElementTraits>,
        pos_x: usize,
        pos_y: usize,
        dim_x: u32,
        dim_y: u32,
    ) -> Result<()> {
        if pos_x >= self.dim_x as usize {
            panic!(
                "x postion ({}) must not be bigger than grid x-dimension ({})",
                pos_x, self.dim_x
            );
        }

        if pos_y >= self.dim_y as usize {
            panic!(
                "y postion ({}) must not be bigger than grid y-dimension ({})",
                pos_y, self.dim_y
            );
        }

        assert_ne!(dim_x, 0);
        assert_ne!(dim_y, 0);

        // get grid
        let mut grid = self.children.write().unwrap();

        if let Some(child_gridable) = child.gridable() {
            // check for neighbourships
            if let Some(current_selectable) = child_gridable.selectable() {
                Self::set_neighbours(
                    &*grid,
                    current_selectable,
                    (pos_x, pos_y),
                    (self.dim_x, self.dim_y),
                )?;
            }

            if self.framable.is_framed() {
                self.child_position(child_gridable, pos_x, pos_y, dim_x, dim_y)?;
            }

            child_gridable.set_layer(self.framable.ui_layer())?;
        }

        if self.visible() {
            if let Some(child_visibility) = child.visibility() {
                child_visibility.set_visibility(true)?;
            }
        }

        let child_state = ChildState::Some {
            child,
            x: dim_x,
            y: dim_y,
        };

        let weak = child_state.downgrade();

        // insert the element
        for x in pos_x..(pos_x + dim_x as usize) {
            for y in pos_y..(pos_y + dim_y as usize) {
                grid[x][y] = weak.clone();
            }
        }

        grid[pos_x][pos_y] = child_state;

        Ok(())
    }

    pub fn has_attachment_at(&self, x: usize, y: usize) -> Result<bool> {
        let children = self.children.read().unwrap();

        Ok(children[x][y].is_some())
    }

    pub fn try_from(
        grid_info: &GridInfo,
        gui_handler: &Arc<GuiHandler>,
        top_level: bool,
    ) -> Result<Arc<Self>> {
        let grid = Grid::new(
            gui_handler.clone(),
            grid_info.x_dimension.get()?,
            grid_info.y_dimension.get()?,
            top_level,
        )?;

        grid.set_margin(grid_info.margin);
        grid.set_padding(grid_info.padding);

        if let Some(background) = &grid_info.background_type {
            grid.set_background(background.clone())?;
        }

        Ok(grid)
    }

    pub fn change_position_unscaled(&self, x: i32, y: i32) -> Result<()> {
        self.framable.change_position_unscaled(x, y)?;

        if let Some(background) = self.background.read().unwrap().as_ref() {
            background.update_frame()?;
        }

        self.calculate_child_positions()?;

        Ok(())
    }

    fn child_position(
        &self,
        child: &dyn Gridable,
        pos_x: usize,
        pos_y: usize,
        dim_x: u32,
        dim_y: u32,
    ) -> Result<()> {
        // calculate the extents for the new child
        let single_width = ((self.framable.right() as i32 - self.framable.left())
            - (2 * self.padding.load(SeqCst) as i32
                + (self.dim_x as i32 - 1) * self.margin.load(SeqCst) as i32))
            / self.dim_x as i32;
        let single_height = ((self.framable.bottom() as i32 - self.framable.top())
            - (2 * self.padding.load(SeqCst) as i32
                + (self.dim_y as i32 - 1) * self.margin.load(SeqCst) as i32))
            / self.dim_y as i32;

        let child_width = if dim_x == 1 {
            single_width
        } else {
            single_width + (dim_x - 1) as i32 * (single_width + self.margin.load(SeqCst) as i32)
        };

        let child_height = if dim_y == 1 {
            single_height
        } else {
            single_height + (dim_y - 1) as i32 * (single_height + self.margin.load(SeqCst) as i32)
        };

        let win_width = self.gui_handler.width();
        let win_height = self.gui_handler.height();

        let y_align = match self.framable.vertical_alignment() {
            VerticalAlign::Top => 0,
            VerticalAlign::Middle => win_height / 2,
            VerticalAlign::Bottom => win_height,
            VerticalAlign::NotSet => panic!("grid: vertical alignment is not set"),
        };

        let x_align = match self.framable.horizontal_alignment() {
            HorizontalAlign::Left => 0,
            HorizontalAlign::Middle => win_width / 2,
            HorizontalAlign::Right => win_width,
            HorizontalAlign::NotSet => panic!("grid: horizontal alignment is not set"),
        };

        let child_x = self.framable.left()
            + self.padding.load(SeqCst) as i32
            + pos_x as i32 * (single_width + self.margin.load(SeqCst) as i32);
        let child_y = self.framable.top()
            + self.padding.load(SeqCst) as i32
            + pos_y as i32 * (single_height + self.margin.load(SeqCst) as i32);

        child.set_frame(
            child_x - x_align as i32,
            child_y - y_align as i32,
            child_width as u32,
            child_height as u32,
            self.framable.vertical_alignment(),
            self.framable.horizontal_alignment(),
        )?;

        Ok(())
    }

    pub fn select(&self, x: u32, y: u32) -> Result<()> {
        match self.children.read().unwrap()[x as usize][y as usize].get_some() {
            Some((child, ..)) => match child.gridable() {
                Some(gridable) => match gridable.selectable() {
                    Some(selectable) => {
                        selectable.select()?;
                        Ok(())
                    }
                    None => panic!("gridable at position ({}, {}), is not selectable", x, y),
                },
                None => panic!("element at position ({}, {}) is not a gridable", x, y),
            },

            None => panic!("no element at position ({}, {})", x, y),
        }
    }

    fn disable_tree(&self) -> Result<()> {
        self.framable.delete()?;

        if let Some(background) = self.background.read().unwrap().as_ref() {
            background.disable()?;
        }

        self.set_tree_visibility(false)?;

        Ok(())
    }

    fn set_tree_visibility(&self, visible: bool) -> Result<()> {
        let tree = self.children.read().unwrap();

        for row in tree.deref() {
            for child_state in row {
                if let Some((child, ..)) = child_state.get_some() {
                    if let Some(visibility) = child.visibility() {
                        visibility.set_visibility(visible)?;
                    }
                }
            }
        }

        Ok(())
    }

    #[inline]
    fn set_neighbours(
        grid: &Vec<Vec<ChildState>>,
        current_child: &Arc<Selectable>,
        pos: (usize, usize),
        dim: (usize, usize),
    ) -> Result<()> {
        // set east neighbour
        Self::set_east_neighbour(grid, current_child, pos, dim)?;

        // set west neighbour
        Self::set_west_neighbour(grid, current_child, pos, dim)?;

        // set north neighbour
        Self::set_north_neighbour(grid, current_child, pos, dim)?;

        // set south neighbour
        Self::set_south_neighbour(grid, current_child, pos, dim)?;

        Ok(())
    }

    #[inline]
    fn set_north_neighbour(
        grid: &Vec<Vec<ChildState>>,
        current_child: &Arc<Selectable>,
        pos: (usize, usize),
        dim: (usize, usize),
    ) -> Result<bool> {
        match Self::search_neighbour_in_direction(grid, pos, (0, 1), dim) {
            Some(north_neighbour) => {
                current_child.set_south_neighbour(Some(&north_neighbour));
                north_neighbour.set_north_neighbour(Some(current_child));

                Ok(true)
            }
            None => Ok(false),
        }
    }

    #[inline]
    fn set_south_neighbour(
        grid: &Vec<Vec<ChildState>>,
        current_child: &Arc<Selectable>,
        pos: (usize, usize),
        dim: (usize, usize),
    ) -> Result<bool> {
        match Self::search_neighbour_in_direction(grid, pos, (0, -1), dim) {
            Some(north_neighbour) => {
                current_child.set_north_neighbour(Some(&north_neighbour));
                north_neighbour.set_south_neighbour(Some(current_child));

                Ok(true)
            }
            None => Ok(false),
        }
    }

    #[inline]
    fn set_east_neighbour(
        grid: &Vec<Vec<ChildState>>,
        current_child: &Arc<Selectable>,
        pos: (usize, usize),
        dim: (usize, usize),
    ) -> Result<bool> {
        match Self::search_neighbour_in_direction(grid, pos, (1, 0), dim) {
            Some(north_neighbour) => {
                current_child.set_east_neighbour(Some(&north_neighbour));
                north_neighbour.set_west_neighbour(Some(current_child));

                Ok(true)
            }
            None => Ok(false),
        }
    }

    #[inline]
    fn set_west_neighbour(
        grid: &Vec<Vec<ChildState>>,
        current_child: &Arc<Selectable>,
        pos: (usize, usize),
        dim: (usize, usize),
    ) -> Result<bool> {
        match Self::search_neighbour_in_direction(grid, pos, (-1, 0), dim) {
            Some(north_neighbour) => {
                current_child.set_west_neighbour(Some(&north_neighbour));
                north_neighbour.set_east_neighbour(Some(current_child));

                Ok(true)
            }
            None => Ok(false),
        }
    }

    #[inline]
    fn search_neighbour_in_direction(
        grid: &Vec<Vec<ChildState>>,
        pos: (usize, usize),
        dir: (i32, i32),
        dim: (usize, usize),
    ) -> Option<Arc<Selectable>> {
        let (mut x, mut y) = pos;
        let (x_step, y_step) = dir;
        let (dim_x, dim_y) = dim;

        while ((x as i32 + x_step) < dim_x as i32 && (x as i32 + x_step) >= 0)
            && ((y as i32 + y_step) < dim_y as i32 && (y as i32 + y_step) >= 0)
        {
            x = (x as i32 + x_step) as usize;
            y = (y as i32 + y_step) as usize;

            if let Some((neighbour, ..)) = grid[x][y].get() {
                if let Some(gridable) = neighbour.gridable() {
                    if let Some(selectable) = gridable.selectable() {
                        return Some(selectable.clone());
                    }
                }
            }
        }

        None
    }
}

impl GuiElementTraits for Grid {
    fn gridable(&self) -> Option<&dyn Gridable> {
        Some(self)
    }

    fn visibility(&self) -> Option<&dyn Visibility> {
        Some(self)
    }

    fn downcast<'a>(&'a self) -> Option<GuiElement<'a>> {
        Some(GuiElement::Grid(self))
    }
}

impl Visibility for Grid {
    fn visible(&self) -> bool {
        self.visible.load(SeqCst)
    }

    fn set_visibility(&self, visibility: bool) -> Result<()> {
        if visibility != self.visible.load(SeqCst) {
            self.visible.store(visibility, SeqCst);

            if visibility {
                Framable::add(&self.framable)?;

                if let Some(background) = self.background.read().unwrap().as_ref() {
                    background.enable()?;
                }

                self.set_tree_visibility(true)?;
            } else {
                self.disable_tree()?;
            }
        }

        Ok(())
    }
}

impl Gridable for Grid {
    fn set_frame(
        &self,
        x: i32,
        y: i32,
        w: u32,
        h: u32,
        vert_align: VerticalAlign,
        hori_align: HorizontalAlign,
    ) -> Result<()> {
        self.framable.set_frame(x, y, w, h, vert_align, hori_align);

        if let Some(background) = self.background.read().unwrap().as_ref() {
            background.update_frame()?;
        }

        self.calculate_child_positions()?;

        Ok(())
    }

    fn selectable(&self) -> Option<&Arc<Selectable>> {
        None
    }

    fn type_name(&self) -> &str {
        "Grid"
    }

    fn set_layer(&self, layer: i32) -> Result<()> {
        self.framable.set_ui_layer(layer);

        if let Some(background) = self.background.read().unwrap().as_ref() {
            background.set_ui_layer(layer);
        }

        for row in self.children.read().unwrap().iter() {
            for child_state in row.iter() {
                if let Some((child, ..)) = child_state.get_some() {
                    if let Some(gridable) = child.gridable() {
                        gridable.set_layer(layer)?;
                    }
                }
            }
        }

        Ok(())
    }

    fn position_extent(&self) -> (i32, i32, u32, u32) {
        self.framable.position()
    }
}

impl Drop for Grid {
    fn drop(&mut self) {
        if self.visible.load(SeqCst) {
            self.disable_tree().unwrap();
        }
    }
}