// Get Jakob tables into a native rust format.

use std::{
    env,
    fs::File,
    io::{self, Read, Write},
    path::Path,
};

/// How many polynomial coefficients?
const RGB2SPEC_N_COEFFS: usize = 3;

/// Table resolution.
const TABLE_RES: usize = 64;

// For the small table, what is the middle value used?
const MID_VALUE: f32 = 0.5;

fn main() {
    // Write tables to Rust file
    let out_dir = env::var("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("jakob_table_inc.rs");
    let mut f = File::create(&dest_path).unwrap();

    // Rec.709
    let rec709_table = rgb2spec_load_small("jakob_tables/srgb.coeff");
    f.write_all(format!("\nconst REC709_TABLE_RES: usize = {};", TABLE_RES).as_bytes())
        .unwrap();
    f.write_all(format!("\nconst REC709_TABLE_MID_VALUE: f32 = {};", MID_VALUE).as_bytes())
        .unwrap();
    f.write_all("\n#[allow(clippy::unreadable_literal, clippy::approx_constant)]".as_bytes())
        .unwrap();
    f.write_all("\npub static REC709_TABLE: &[[(f32, f32, f32); 2]; 64 * 64 * 3] = &[".as_bytes())
        .unwrap();
    for item in &rec709_table {
        f.write_all(
            format!(
                "\n    [({}, {}, {}), ({}, {}, {})],",
                item[0].0, item[0].1, item[0].2, item[1].0, item[1].1, item[1].2
            )
            .as_bytes(),
        )
        .unwrap();
    }
    f.write_all("\n];".as_bytes()).unwrap();

    // Rec.2020
    let rec2020_table = rgb2spec_load_small("jakob_tables/rec2020.coeff");
    f.write_all(format!("\nconst REC2020_TABLE_RES: usize = {};", TABLE_RES).as_bytes())
        .unwrap();
    f.write_all(format!("\nconst REC2020_TABLE_MID_VALUE: f32 = {};", MID_VALUE).as_bytes())
        .unwrap();
    f.write_all("\n#[allow(clippy::unreadable_literal, clippy::approx_constant)]".as_bytes())
        .unwrap();
    f.write_all("\npub static REC2020_TABLE: &[[(f32, f32, f32); 2]; 64 * 64 * 3] = &[".as_bytes())
        .unwrap();
    for item in &rec2020_table {
        f.write_all(
            format!(
                "\n    [({}, {}, {}), ({}, {}, {})],",
                item[0].0, item[0].1, item[0].2, item[1].0, item[1].1, item[1].2
            )
            .as_bytes(),
        )
        .unwrap();
    }
    f.write_all("\n];".as_bytes()).unwrap();

    // sRGB / ACES
    let aces_table = rgb2spec_load_small("jakob_tables/aces2065_1.coeff");
    f.write_all(format!("\nconst ACES_TABLE_RES: usize = {};", TABLE_RES).as_bytes())
        .unwrap();
    f.write_all(format!("\nconst ACES_TABLE_MID_VALUE: f32 = {};", MID_VALUE).as_bytes())
        .unwrap();
    f.write_all("\n#[allow(clippy::unreadable_literal, clippy::approx_constant)]".as_bytes())
        .unwrap();
    f.write_all("\npub static ACES_TABLE: &[[(f32, f32, f32); 2]; 64 * 64 * 3] = &[".as_bytes())
        .unwrap();
    for item in &aces_table {
        f.write_all(
            format!(
                "\n    [({}, {}, {}), ({}, {}, {})],",
                item[0].0, item[0].1, item[0].2, item[1].0, item[1].1, item[1].2
            )
            .as_bytes(),
        )
        .unwrap();
    }
    f.write_all("\n];".as_bytes()).unwrap();
}

/// Underlying representation
pub struct RGB2Spec {
    res: usize,
    scale: Vec<f32>,
    data: Vec<[f32; RGB2SPEC_N_COEFFS]>,
}

pub fn rgb2spec_load(filepath: &str) -> RGB2Spec {
    let file_contents = {
        let mut file_contents = Vec::new();
        let mut f = io::BufReader::new(File::open(filepath).unwrap());
        f.read_to_end(&mut file_contents).unwrap();
        file_contents
    };

    // Check the header
    let header = &file_contents[0..4];
    if header != "SPEC".as_bytes() {
        panic!("Not a spectral table.");
    }

    // Get resolution of the table
    let res = u32::from_le_bytes([
        file_contents[4],
        file_contents[5],
        file_contents[6],
        file_contents[7],
    ]) as usize;

    // Calculate sizes
    let size_scale = res;
    let size_data = res * res * res * RGB2SPEC_N_COEFFS;

    // Load the table scale data
    let mut scale = Vec::with_capacity(size_scale);
    for i in 0..size_scale {
        let ii = i * 4 + 8;
        let n = f32::from_bits(u32::from_le_bytes([
            file_contents[ii],
            file_contents[ii + 1],
            file_contents[ii + 2],
            file_contents[ii + 3],
        ]));
        scale.push(n);
    }

    // Load the table coefficient data
    let mut data = Vec::with_capacity(size_data);
    for i in 0..size_data {
        let ii = i * 4 * RGB2SPEC_N_COEFFS + 8 + (size_scale * 4);
        let n1 = f32::from_bits(u32::from_le_bytes([
            file_contents[ii],
            file_contents[ii + 1],
            file_contents[ii + 2],
            file_contents[ii + 3],
        ]));
        let n2 = f32::from_bits(u32::from_le_bytes([
            file_contents[ii + 4],
            file_contents[ii + 5],
            file_contents[ii + 6],
            file_contents[ii + 7],
        ]));
        let n3 = f32::from_bits(u32::from_le_bytes([
            file_contents[ii + 8],
            file_contents[ii + 9],
            file_contents[ii + 10],
            file_contents[ii + 11],
        ]));
        data.push([n1, n2, n3]);
    }

    RGB2Spec {
        res: res,
        scale: scale,
        data: data,
    }
}

pub fn rgb2spec_load_small(filepath: &str) -> Vec<[(f32, f32, f32); 2]> {
    let big_table = rgb2spec_load(filepath);
    assert!(big_table.res == TABLE_RES);

    // Calculate z offsets and such for the mid value.
    let dz: usize = 1 * big_table.res * big_table.res;
    let z05_i = rgb2spec_find_interval(&big_table.scale, MID_VALUE);
    let z05_1: f32 = (MID_VALUE - big_table.scale[z05_i])
        / (big_table.scale[z05_i + 1] - big_table.scale[z05_i]);
    let z05_0: f32 = 1.0 - z05_1;

    // Fill in table.
    let mut table = vec![[(0.0, 0.0, 0.0); 2]; TABLE_RES * TABLE_RES * 3];
    for i in 0..3 {
        let offset = i * big_table.res * big_table.res * big_table.res;
        for j in 0..(big_table.res * big_table.res) {
            let one_coef = big_table.data[offset + ((TABLE_RES - 1) * dz) + j];

            let mid_coef_0 = big_table.data[offset + (z05_i * dz) + j];
            let mid_coef_1 = big_table.data[offset + ((z05_i + 1) * dz) + j];
            let mid_coef = [
                (mid_coef_0[0] * z05_0) + (mid_coef_1[0] * z05_1),
                (mid_coef_0[1] * z05_0) + (mid_coef_1[1] * z05_1),
                (mid_coef_0[2] * z05_0) + (mid_coef_1[2] * z05_1),
            ];

            table[(i * big_table.res * big_table.res) + j] = [
                (mid_coef[0], mid_coef[1], mid_coef[2]),
                (one_coef[0], one_coef[1], one_coef[2]),
            ];
        }
    }

    table
}

fn rgb2spec_find_interval(values: &[f32], x: f32) -> usize {
    let last_interval = values.len() - 2;
    let mut left = 0;
    let mut size = last_interval;

    while size > 0 {
        let half = size >> 1;
        let middle = left + half + 1;

        if values[middle] < x {
            left = middle;
            size -= half + 1;
        } else {
            size = half;
        }
    }

    if left < last_interval {
        left
    } else {
        last_interval
    }
}
