use std::ops::Add;
use num_integer::Roots;
use crate::system::{GriddedPosition, Position};
use serde::{Serialize, Deserialize};

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct HexPosition {
    pub q: i32,
    pub r: i32,
}

impl Add for HexPosition {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        HexPosition { q: self.q + rhs.q, r: self.r + rhs.r }
    }
}

impl GriddedPosition for HexPosition {
    const NEIGHBOURS: u32 = 6;

    fn neighbour(&self, neighbour_index: u32) -> Self {
        let neighbour_index = neighbour_index as usize;

        const OFFSETS: [(i32, i32); 6] = [
            (1, 0), (1, -1), (0, -1),
            (-1, 0), (-1, 1), (0, 1),
        ];

        self.clone() + HexPosition { q: OFFSETS[neighbour_index].0, r: OFFSETS[neighbour_index].1 }
    }

    fn linear_index(&self, grid_size: u32) -> usize {
        let q = (self.q + (grid_size as i32 / 2)) as usize;
        let r = (self.r + (grid_size as i32 / 2)) as usize;

        r * (grid_size as usize) + q
    }
}

impl Position for HexPosition {
    // const DIM: usize = 2;
    type Cartesian = [f32; 2];

    fn zero() -> Self {
        HexPosition { q: 0, r: 0 }
    }

    fn abs(&self) -> f32 {
        ((self.q.pow(2) + self.r.pow(2) + self.q * self.r) as f32).sqrt()
    }

    fn from_cartesian(cartesian: Self::Cartesian) -> Self {
        let q = (1.0f32 / 3.0f32).sqrt() * cartesian[0] - 1.0 / 3.0 * cartesian[1];
        let r = 2.0 / 3.0 * cartesian[1];

        Self { q: q as i32, r: r as i32 }
    }

    fn to_cartesian(&self) -> Self::Cartesian {
        let q = self.q as f32;
        let r = self.r as f32;
        [
            3f32.sqrt() * q + 3f32.sqrt() / 2f32 * r,
            (3. / 2.) * r
        ]
    }
}
