use std::ops;

use crate::p256_field::a_256::A256;

#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct AffinePoint {
    pub x: A256,
    pub y: A256,
}

#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Point {
    pub x: A256,
    pub y: A256,
    pub z: A256,
}

#[derive(Debug, Copy, Clone)]
pub struct ShortWeierstrass {
    pub a: A256,
    pub b: A256,
    pub p: A256,
    pub n: A256,
    pub g: AffinePoint,
}

impl Point {
    pub fn to_affine(self: Self) -> AffinePoint {
        let inv = A256::inv(self.z);
        return AffinePoint { x: self.x * inv, y: self.y * inv };
    }
}

impl AffinePoint {
    pub(crate) fn to_projective(self: Self) -> Point {
        return Point { x: self.x, y: self.y, z: A256 { r: [1, 0, 0, 0, 0, 0, 0, 0] } };
    }

    pub fn new(a: [u32; 16]) -> AffinePoint {
        return AffinePoint { x: A256 { r: [a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]] }, y: A256 { r: [a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]] } };
    }

    pub fn to_readable(self: Self) -> String {
        format!(" x: {:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}\n y: {:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}", self.x.r[0], self.x.r[1], self.x.r[2], self.x.r[3], self.x.r[4], self.x.r[5], self.x.r[6], self.x.r[7], self.y.r[0], self.y.r[1], self.y.r[2], self.y.r[3], self.y.r[4], self.y.r[5], self.y.r[6], self.y.r[7])
    }
}

/// Implementacja krzywej eliptycznej w skróconej postaci Weierstrass'a
impl ShortWeierstrass {
    pub(crate) const P256: ShortWeierstrass = ShortWeierstrass {
        a: A256 { r: [3, 0, 0, 0, 0, 0, 0, 0] },
        b: A256 { r: [0x27d2604b, 0x3bce3c3e, 0xcc53b0f6, 0x651d06b0, 0x769886bc, 0xb3ebbd55, 0xaa3a93e7, 0x5ac635d8] },
        p: A256 { r: [0xffffffff, 0xffffffff, 0xffffffff, 0x0, 0x0, 0x0, 0x1, 0xffffffff] },
        n: A256 { r: [0xfc632551, 0xf3b9cac2, 0xa7179e84, 0xbce6faad, 0xffffffff, 0xffffffff, 0x0, 0xffffffff] },
        g: AffinePoint {
            x: A256 { r: [0xd898c296, 0xf4a13945, 0x2deb33a0, 0x77037d81, 0x63a440f2, 0xf8bce6e5, 0xe12c4247, 0x6b17d1f2] },
            y: A256 { r: [0x37bf51f5, 0xcbb64068, 0x6b315ece, 0x2bce3357, 0x7c0f9e16, 0x8ee7eb4a, 0xfe1a7f9b, 0x4fe342e2] },
        },
    };

    const INF: Point = Point { x: A256 { r: [0, 0, 0, 0, 0, 0, 0, 0] }, y: A256 { r: [0, 0, 0, 0, 0, 0, 0, 0] }, z: A256 { r: [0, 0, 0, 0, 0, 0, 0, 0] } };

    /// Obliczanie wielokrotności punktu na krzywej.
    pub fn mul(b: A256, mut a: Point) -> Point {
        if a == ShortWeierstrass::INF { return ShortWeierstrass::INF; };
        let mut res = ShortWeierstrass::INF;

        for i in 0..8 {
            for j in 0..32 {
                if (b.r[i] >> j) & 0x00000001 == 1 {
                    res = res + a
                }
                a = a + a;
            }
        }
        return res;
    }
}

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

    /// Dodawanie punktów punktów na krzywej.
    fn add(self, b: Point) -> Point {
        if self == ShortWeierstrass::INF { return b; };
        if b == ShortWeierstrass::INF { return self; };

        if self == b {
            let w = self.x - self.z;
            let w = w + w + w;
            let w = w * (self.x + self.z);

            let s = self.y * self.z;
            let s = s + s;
            let ss = s * s;
            let sss = s * ss;

            let r = self.y * s;
            let rr = r * r;

            let k = self.x * r;
            let k = k + k;
            let h = w * w - k - k;
            return Point { x: h * s, y: w * (k - h) - rr - rr, z: sss };
        }
        let y1z2 = self.y * b.z;
        let x1z2 = self.x * b.z;
        let z1z2 = self.z * b.z;

        let u = b.y * self.z - y1z2;
        let uu = u * u;

        let v = b.x * self.z - x1z2;
        let vv = v * v;
        let vvv = vv * v;

        let r = vv * x1z2;
        let a = uu * z1z2 - vvv - r - r;
        return Point { x: v * a, y: u * (r - a) - vvv * y1z2, z: vvv * z1z2 };
    }
}
