foreign fn putchar(c: i32) -> i32;
foreign fn print_number(n: i32);
foreign fn sqrtf(num: f32) -> f32;

struct Vec3 {
    x: f32,
    y: f32,
    z: f32
}

struct Ray {
    origin: Vec3,
    dir: Vec3
}

struct Sphere {
    center: Vec3,
    radius: f32,
    color: Vec3
}

struct Hit {
    hit: bool,
    t: f32,
    normal: Vec3,
    color: Vec3
}

fn vec3_add(a: Vec3, b: Vec3) -> Vec3 {
    return Vec3 { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
}

fn vec3_sub(a: Vec3, b: Vec3) -> Vec3 {
    return Vec3 { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
}

fn vec3_mul(a: Vec3, t: f32) -> Vec3 {
    return Vec3 { x: a.x * t, y: a.y * t, z: a.z * t };
}

fn vec3_dot(a: Vec3, b: Vec3) -> f32 {
    return a.x * b.x + a.y * b.y + a.z * b.z;
}

fn vec3_normalize(v: Vec3) -> Vec3 {
    let len = sqrtf(vec3_dot(v, v));
    if len == 0.0 {
        return Vec3 { x: 0.0, y: 0.0, z: 0.0 };
    }
    return vec3_mul(v, 1.0 / len);
}

fn intersect_sphere(ray: *Ray, sphere: *Sphere, t_min: f32, t_max: f32) -> Hit {
    let oc = vec3_sub((*ray).origin, (*sphere).center);
    let a = vec3_dot((*ray).dir, (*ray).dir);
    let half_b = vec3_dot(oc, (*ray).dir);
    let c = vec3_dot(oc, oc) - (*sphere).radius * (*sphere).radius;
    let discriminant = half_b * half_b - a * c;

    let miss = Hit {
        hit: false,
        t: 0.0,
        normal: Vec3 { x: 0.0, y: 0.0, z: 0.0 },
        color: Vec3 { x: 0.0, y: 0.0, z: 0.0 }
    };

    if discriminant < 0.0 {
        return miss;
    }

    let sqrtd = sqrtf(discriminant);
    let root = (-half_b - sqrtd) / a;

    if root < t_min {
        root = (-half_b + sqrtd) / a;
        if root < t_min {
            return miss;
        }
    }
    
    if root > t_max {
        return miss;
    }

    let hit_point = vec3_add((*ray).origin, vec3_mul((*ray).dir, root));
    let normal = vec3_mul(vec3_sub(hit_point, (*sphere).center), 1.0 / (*sphere).radius);

    return Hit {
        hit: true,
        t: root,
        normal: normal,
        color: (*sphere).color
    };
}

fn ray_color(ray: *Ray, spheres: *[Sphere; 2]) -> Vec3 {
    let closest_so_far = 1000.0;
    let hit_anything = false;
    let hit_color = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
    let hit_normal = Vec3 { x: 0.0, y: 0.0, z: 0.0 };

    let i = 0;
    while i < 2 {
        // Pointer arithmetic to array structs via the &[] index syntax
        let hit = intersect_sphere(ray, &(*spheres)[i], 0.001, closest_so_far);
        
        if hit.hit {
            hit_anything = true;
            closest_so_far = hit.t;
            hit_color = hit.color;
            hit_normal = hit.normal;
        }
        i = i + 1;
    }

    if hit_anything {
        // We remap the surface normal from [-1..1] to the [0..1] color space
        let mapped_normal = vec3_mul(vec3_add(hit_normal, Vec3 { x: 1.0, y: 1.0, z: 1.0 }), 0.5);
        
        // Multiply the resulting surface map shading by the intrinsic color of the object
        return Vec3 {
            x: hit_color.x * mapped_normal.x,
            y: hit_color.y * mapped_normal.y,
            z: hit_color.z * mapped_normal.z
        };
    }

    // Beautiful skybox blue to white linear gradient
    let unit_dir = vec3_normalize((*ray).dir);
    let t = 0.5 * (unit_dir.y + 1.0);
    return vec3_add(
        vec3_mul(Vec3 { x: 1.0, y: 1.0, z: 1.0 }, 1.0 - t),
        vec3_mul(Vec3 { x: 0.5, y: 0.7, z: 1.0 }, t)
    );
}

fn main() -> i32 {
    let image_width = 1024;
    let image_height = 1024;

    let spheres: [Sphere; 2] = [
        Sphere {
            center: Vec3 { x: 0.0, y: 0.0, z: -1.0 },
            radius: 0.5,
            color: Vec3 { x: 1.0, y: 0.2, z: 0.2 } // Red sphere
        },
        Sphere {
            center: Vec3 { x: 0.0, y: -100.5, z: -1.0 },
            radius: 100.0,
            color: Vec3 { x: 0.2, y: 0.8, z: 0.2 } // Green ground
        }
    ];

    // Print standard PPM Image File Header
    putchar(80); putchar(51); putchar(10); // "P3\n"
    
    print_number(image_width);
    putchar(32); // " "
    print_number(image_height);
    putchar(10); // "\n"
    
    print_number(255);
    putchar(10); // "255\n"

    let j = image_height - 1;
    while j >= 0 {
        let i = 0;
        while i < image_width {
            let u = (i as f32) / (image_width as f32 - 1.0);
            let v = (j as f32) / (image_height as f32 - 1.0);

            // Set up a classic look-at viewport layout
            let lower_left_corner = Vec3 { x: -2.0, y: -2.0, z: -1.0 };
            let horizontal = Vec3 { x: 4.0, y: 0.0, z: 0.0 };
            let vertical = Vec3 { x: 0.0, y: 4.0, z: 0.0 };
            let origin = Vec3 { x: 0.0, y: 0.0, z: 0.0 };

            let dir = vec3_sub(
                vec3_add(lower_left_corner, vec3_add(vec3_mul(horizontal, u), vec3_mul(vertical, v))),
                origin
            );

            let ray = Ray { origin: origin, dir: dir };
            let col = ray_color(&ray, &spheres);

            // Precedence parser respects parenthesis grouping over multiplication
            let ir = (255.999 * col.x) as i32;
            let ig = (255.999 * col.y) as i32;
            let ib = (255.999 * col.z) as i32;

            print_number(ir);
            putchar(32);
            print_number(ig);
            putchar(32);
            print_number(ib);
            putchar(10);

            i = i + 1;
        }
        j = j - 1;
    }

    return 0;
}