use crate::{Position, Rectangle, Size};
use uuid::Uuid;

#[derive(Debug)]
/// Camera data.
pub struct Camera {
    position: Position,
    boundary: Option<Rectangle>,
    activated: bool,
    /// Tether target value.
    pub tether_target: Option<Uuid>,
    tether_size: Option<Size>,
    smoothing_strength: f32,
}

impl Camera {
    /// Sets the boundary.
    pub fn set_boundary(&mut self, boundary: Rectangle) {
        self.boundary = Some(boundary);
    }

    /// Clear boundary.
    pub fn clear_boundary(&mut self) {
        self.boundary = None;
    }

    /// Activate.
    pub fn activate(&mut self) {
        self.activated = true;
    }

    /// Deactivate.
    pub fn deactivate(&mut self) {
        self.activated = false;
    }

    /// Returns the pos.
    pub fn get_pos(&self, scale_factor: f32) -> Position {
        if self.activated {
            Position {
                x: self.position.x * scale_factor,
                y: self.position.y * scale_factor,
            }
        } else {
            Position { x: 0.0, y: 0.0 }
        }
    }

    /// Logical pos.
    pub fn logical_pos(&self) -> Position {
        self.position
    }
    /// Sets the pos.
    pub fn set_pos(&mut self, new_pos: Position) {
        self.position = self.desired_position(new_pos);
    }

    /// Sets the pos with dt.
    pub fn set_pos_with_dt(&mut self, new_pos: Position, delta_time: f32) {
        let desired = self.desired_position(new_pos);
        if self.smoothing_strength > 0.0 && delta_time > 0.0 {
            let alpha = 1.0 - (-self.smoothing_strength * delta_time).exp();
            self.position.x += (desired.x - self.position.x) * alpha;
            self.position.y += (desired.y - self.position.y) * alpha;
        } else {
            self.position = desired;
        }
    }

    fn desired_position(&self, new_pos: Position) -> Position {
        if let Some(boundary) = &self.boundary {
            // Calculate the logical boundary taking into account both camera position and tether size
            let logical_boundary = if let Some(tether_size) = self.tether_size {
                Rectangle::new(
                    boundary.x + self.position.x,
                    boundary.y + self.position.y,
                    boundary.width - tether_size.width,
                    boundary.height - tether_size.height,
                )
            } else {
                Rectangle::new(
                    boundary.x + self.position.x,
                    boundary.y + self.position.y,
                    boundary.width,
                    boundary.height,
                )
            };

            // Handle the x-direction
            let dx = {
                let right_overflow = new_pos.x - (logical_boundary.x + logical_boundary.width);
                let left_overflow = new_pos.x - logical_boundary.x;
                if right_overflow > 0.0 {
                    right_overflow
                } else if left_overflow < 0.0 {
                    left_overflow
                } else {
                    0.0
                }
            };

            // Handle the y-direction
            let dy = {
                let bottom_overflow = new_pos.y - (logical_boundary.y + logical_boundary.height);
                let top_overflow = new_pos.y - logical_boundary.y;
                if bottom_overflow > 0.0 {
                    bottom_overflow
                } else if top_overflow < 0.0 {
                    top_overflow
                } else {
                    0.0
                }
            };
            Position {
                x: self.position.x + dx,
                y: self.position.y + dy,
            }
        } else {
            // If no boundary is set, simply update the position
            new_pos
        }
    }

    /// Creates a new value.
    pub fn new(position: Position) -> Self {
        Self {
            position,
            tether_target: None,
            activated: false,
            boundary: None,
            tether_size: None,
            smoothing_strength: 0.0,
        }
    }

    /// Sets the tether target.
    pub fn set_tether_target(&mut self, target: Option<Uuid>) {
        self.tether_target = target;
    }

    /// Sets the tether size.
    pub fn set_tether_size(&mut self, size: Option<Size>) {
        self.tether_size = size;
    }

    /// Sets the smoothing strength.
    pub fn set_smoothing_strength(&mut self, smoothing_strength: f32) {
        self.smoothing_strength = smoothing_strength.max(0.0);
    }

    /// Smoothing strength.
    pub fn smoothing_strength(&self) -> f32 {
        self.smoothing_strength
    }
}
