/*
 *    Copyright 2025 Jared Davis
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */


use crate::lab::Lab;
use crate::rgb::RGB;

#[derive(Clone, Copy)]
pub struct XYZ {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

impl XYZ {
    pub fn lab(self, illumination: Illumination) -> Lab {
        let fx = forward(self.x * illumination.reference.x);
        let fy = forward(self.y * illumination.reference.y);
        let fz = forward(self.z * illumination.reference.z);

        let l = (116.0 * fy) - 16.0;
        let a = 500.0 * (fx - fy);
        let b = 200.0 * (fy - fz);

        Lab{ l, a, b }
    }

    pub fn rgb(self, illumination: Illumination) -> RGB {
        let r = illumination.r[0] * self.x + illumination.r[1] * self.y + illumination.r[2] * self.z;
        let g = illumination.g[0] * self.x + illumination.g[1] * self.y + illumination.g[2] * self.z;
        let b = illumination.b[0] * self.x + illumination.b[1] * self.y + illumination.b[2] * self.z;

        RGB {
            r: (gamma(r) * 255.0).max(0.0).min(255.0),
            g: (gamma(g) * 255.0).max(0.0).min(255.0),
            b: (gamma(b) * 255.0).max(0.0).min(255.0),
        }
    }
}

fn gamma(v: f32) -> f32 {
    if v < 0.0031308 {
        v * 12.92
    } else {
        1.055 * v.powf(1.0 / 2.4) - 0.055
    }
}

fn forward(v: f32) -> f32 {
    if v > 0.008856451679035631 {
        v.powf(1.0/3.0)
    } else {
        v * 7.787037037037037 + 0.13793103448275862
    }
}

#[derive(Clone, Copy)]
pub struct Illumination {
    pub(crate) x: [f32; 3],
    pub(crate) y: [f32; 3],
    pub(crate) z: [f32; 3],

    pub(crate) r: [f32; 3],
    pub(crate) g: [f32; 3],
    pub(crate) b: [f32; 3],

    pub(crate) reference: XYZ,
}

