use anyhow::{bail, Result};
use engine::prelude::*;
use std::sync::Arc;

pub struct FittingResult {
    pub fit: bool,
    pub start: i32,
    pub extent: u32,
}

pub struct Tooltip {
    grid: Arc<Grid>,
    gui: Arc<GuiBuilder>,
    gui_handler: Arc<GuiHandler>,
}

impl Tooltip {
    pub fn new(grid: Arc<Grid>, gui: Arc<GuiBuilder>, gui_handler: Arc<GuiHandler>) -> Self {
        Self {
            grid,
            gui,
            gui_handler,
        }
    }

    pub fn enable(&self) -> Result<()> {
        self.gui.enable()
    }

    pub fn check_fitting(&self) -> Result<(FittingResult, FittingResult)> {
        if !self.grid.visible() {
            bail!("enable item tooltip first");
        }

        let (x, y, w, h) = self.grid.position_extent();

        Ok((
            FittingResult {
                fit: (x + w as i32) <= self.gui_handler.width() as i32,
                start: x,
                extent: w,
            },
            FittingResult {
                fit: (y + h as i32) <= self.gui_handler.height() as i32,
                start: y,
                extent: h,
            },
        ))
    }

    pub fn move_to(&self, x: i32, y: i32) -> Result<()> {
        self.grid.change_position_unscaled(x, y)
    }

    pub fn position_extent(&self) -> (i32, i32, u32, u32) {
        self.grid.position_extent()
    }

    pub fn perform_single_check(&self, x: i32, y: i32) -> Result<()> {
        let (width_fitting, height_fitting) = self.check_fitting()?;

        let mut width_shift = None;
        let mut height_shift = None;

        let gui_pos = self.position_extent();

        if !width_fitting.fit {
            width_shift = Some(x - gui_pos.2 as i32);
        }

        if !height_fitting.fit {
            height_shift = Some(y - gui_pos.3 as i32);
        }

        if width_shift.is_some() || height_shift.is_some() {
            self.move_to(
                width_shift.unwrap_or(gui_pos.0),
                height_shift.unwrap_or(gui_pos.1),
            )?;
        }

        Ok(())
    }

    pub fn perform_double_check(&self, other: &Self, x: i32, spacing: u32) -> Result<()> {
        let (_left_width_fitting, left_height_fitting) = self.check_fitting()?;
        let (right_width_fitting, right_height_fitting) = other.check_fitting()?;

        let mut width_shift = None;
        let mut height_shift = None;

        let left_gui_pos = self.position_extent();
        let right_gui_pos = other.position_extent();

        if !right_width_fitting.fit {
            width_shift = Some(x - right_gui_pos.2 as i32 - spacing as i32);
        }

        let window_height = self.gui_handler.height();

        if !left_height_fitting.fit {
            height_shift = Some(window_height as i32 - left_gui_pos.3 as i32 - spacing as i32);
        }

        if !right_height_fitting.fit {
            let right = window_height as i32 - right_gui_pos.3 as i32 - spacing as i32;

            match height_shift {
                Some(current) => {
                    if current > right {
                        height_shift = Some(right);
                    }
                }
                None => {
                    height_shift = Some(right);
                }
            }
        }

        if width_shift.is_some() || height_shift.is_some() {
            let width = width_shift.unwrap_or(right_gui_pos.0);
            let height = height_shift.unwrap_or(right_gui_pos.1);

            other.move_to(width, height)?;
            self.move_to(width - spacing as i32 - left_gui_pos.2 as i32, height)?;
        }

        Ok(())
    }
}

impl Into<Arc<GuiBuilder>> for Tooltip {
    fn into(self) -> Arc<GuiBuilder> {
        self.gui
    }
}