use crate::fix_vec::FixVec;
use core::cmp::Ordering;
use core::{fmt, ops};

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct IntPoint {
    pub x: i32,
    pub y: i32,
}

impl IntPoint {
    pub const ZERO: Self = Self { x: 0, y: 0 };
    pub const EMPTY: Self = Self {
        x: i32::MAX,
        y: i32::MAX,
    };

    #[inline(always)]
    pub fn new(x: i32, y: i32) -> Self {
        Self { x, y }
    }

    #[inline(always)]
    pub fn cross_product(self, v: Self) -> i64 {
        let a = (self.x as i64) * (v.y as i64);
        let b = (self.y as i64) * (v.x as i64);

        a - b
    }

    #[inline(always)]
    pub fn dot_product(self, v: Self) -> i64 {
        let xx = (self.x as i64) * (v.x as i64);
        let yy = (self.y as i64) * (v.y as i64);
        xx + yy
    }

    #[inline(always)]
    pub fn subtract(self, other: IntPoint) -> FixVec {
        let x = (self.x as i64) - (other.x as i64);
        let y = (self.y as i64) - (other.y as i64);
        FixVec::new(x, y)
    }

    #[inline(always)]
    pub fn sqr_length(self) -> i64 {
        let x = self.x as i64;
        let y = self.y as i64;
        x * x + y * y
    }

    #[inline(always)]
    pub fn sqr_distance(self, other: IntPoint) -> i64 {
        (self - other).sqr_length()
    }
}

impl fmt::Display for IntPoint {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}, {}]", self.x, self.y)
    }
}

impl From<[i32; 2]> for IntPoint {
    #[inline(always)]
    fn from(value: [i32; 2]) -> Self {
        IntPoint::new(value[0], value[1])
    }
}

impl From<(i32, i32)> for IntPoint {
    #[inline(always)]
    fn from(value: (i32, i32)) -> Self {
        IntPoint::new(value.0, value.1)
    }
}

impl PartialOrd for IntPoint {
    #[inline(always)]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for IntPoint {
    #[inline(always)]
    fn cmp(&self, other: &Self) -> Ordering {
        let x = self.x == other.x;
        if x && self.y == other.y {
            Ordering::Equal
        } else if self.x < other.x || x && self.y < other.y {
            Ordering::Less
        } else {
            Ordering::Greater
        }
    }
}

impl ops::Add for IntPoint {
    type Output = IntPoint;

    #[inline(always)]
    fn add(self, other: IntPoint) -> IntPoint {
        IntPoint {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

impl ops::Sub for IntPoint {
    type Output = IntPoint;

    #[inline(always)]
    fn sub(self, other: IntPoint) -> IntPoint {
        IntPoint {
            x: self.x - other.x,
            y: self.y - other.y,
        }
    }
}

#[macro_export]
macro_rules! int_pnt {
    ($x:expr, $y:expr) => {
        IntPoint::new($x, $y)
    };
}

#[cfg(test)]
mod tests {
    use crate::int::point::IntPoint;

    #[test]
    fn test_0() {
        let p: IntPoint = (1, 2).into();
        assert_eq!(p.x, 1);
        assert_eq!(p.y, 2);
    }

    #[test]
    fn test_1() {
        let p: IntPoint = [1, 2].into();
        assert_eq!(p.x, 1);
        assert_eq!(p.y, 2);
    }
    #[test]
    fn test_2() {
        assert_eq!(int_pnt![0, 0], int_pnt![0, 0]);
        assert!(int_pnt![0, 0] < int_pnt![0, 4]);
        assert!(int_pnt![1, 0] > int_pnt![0, 4]);
        assert!(int_pnt![0, 4] > int_pnt![0, 0]);
        assert!(int_pnt![0, 4] < int_pnt![1, 0]);
    }
}
