#![no_std]
#![no_main]

use core::{
    arch::asm,
    ops::{Add, Mul},
};

use fpga_lib::{
    interface::{Buttons, Leds},
    panic_handler,
    register::X31,
    vga::{BLACK, BLUE, Color, FONT_WIDTH, GREEN, HEIGHT, RED, VGA, WHITE, WIDTH},
};

type Float = f32;
pub const ITERATIONS: usize = 100;
pub const ASPECT_RATIO: f32 = WIDTH as f32 / HEIGHT as f32;

#[derive(Clone, Copy)]
struct Complex(Float, Float);

impl Complex
{
    pub fn sq_mag(&self) -> Float
    {
        self.0 * self.0 + self.1 * self.1
    }
}

pub fn map(x: Float, xmin: Float, xmax: Float, ymin: Float, ymax: Float) -> Float
{
    ((x - xmin) / (xmax - xmin)) * (ymax - ymin) + ymin
}

impl Add for Complex
{
    type Output = Complex;

    fn add(self, Self(a, b): Self) -> Self::Output
    {
        Complex(self.0 + a, self.1 + b)
    }
}

impl Mul for Complex
{
    type Output = Complex;

    fn mul(self, Self(a, b): Self) -> Self::Output
    {
        Complex(self.0 * a - self.1 * b, self.0 * b + self.1 * a)
    }
}

panic_handler! {}

#[unsafe(no_mangle)]
pub extern "C" fn main() -> !
{
    let color_lut: [Color; ITERATIONS] = {
        let mut tab = [BLACK; _];
        for i in 0..(ITERATIONS - 1)
        {
            let val = ((i as Float / ITERATIONS as Float) * 255.) as u8;
            tab[i] = Color::from_rgb(val, val, val);
        }

        tab
    };

    let width = 2.;
    for x in 0..WIDTH
    {
        for y in 0..HEIGHT
        {
            let pos = Complex(
                map(
                    x as Float,
                    0.,
                    WIDTH as Float,
                    -width * ASPECT_RATIO * 0.5,
                    width * ASPECT_RATIO * 0.5,
                ) - 0.5,
                map(y as Float, 0., HEIGHT as Float, -width * 0.5, width * 0.5),
            );
            let mut acc = pos;
            let mut iter = 0;

            // Optimisations
            let p = ((pos.0 - 0.25) * (pos.0 - 0.25)) + pos.1 * pos.1;
            if (pos.0 < libm::sqrtf(p) - 2. * p + 0.25)
                || (pos.0 + 1.) * (pos.0 + 1.) + pos.1 * pos.1 < 1. / 16.
            {
                iter = ITERATIONS - 1;
            }
            else
            {
                for i in 0..ITERATIONS
                {
                    acc = acc * acc + pos;
                    if acc.sq_mag() > 1.
                    {
                        iter = i;
                        break;
                    }
                }
            }

            unsafe {
                VGA::write_pixel_unsafe(x as u16, y as u16, color_lut[iter]);
            }
        }
    }

    loop
    {
        core::hint::spin_loop();
    }
}
