use std::path::Path;

use crate::core::image::{Image, ImageIO};
use crate::core::light::lookup_spectrum;
use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture;
use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::resolve_filename;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i;
use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::Shape;
use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType;
use shared::lights::GoniometricLight;
use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform};
use shared::{Float, PI};

pub fn create(
    render_from_light: Transform,
    medium: Option<Medium>,
    params: &ParameterDictionary,
    loc: &FileLoc,
    _shape: &Shape,
    _alpha_text: &FloatTexture,
    colorspace: Option<&RGBColorSpace>,
    arena: &Arena,
) -> Result<Light> {

    let default_cs = crate::spectra::default_colorspace();
    let cs = colorspace.unwrap_or(&default_cs);
    let i = params
        .get_one_spectrum(
            "I",
            Some(Spectrum::Dense(cs.illuminant)),
            SpectrumType::Illuminant,
        )
        .expect("Could not retrieve spectrum");
    let mut scale = params.get_one_float("scale", 1.)?;
    let filename = resolve_filename(&params.get_one_string("filename", "")?);
    let image: Ptr<Image> = if filename.is_empty() {
        Ptr::null()
    } else {
        let im = Image::read(Path::new(&filename), None)
            .map_err(|e| anyhow!("could not load image '{}': {}", filename, e))?;

        let loaded = im.image;
        let res = loaded.resolution();

        if loaded.has_any_infinite_pixels() {
            return Err(anyhow!(
                "image '{}' has infinite pixels, not suitable for light",
                filename
            ));
        }

        if res.x() != res.y() {
            return Err(anyhow!(
                "image resolution ({}, {}) is non-square; unlikely to be an equal-area map",
                res.x(),
                res.y()
            ));
        }

        Ptr::from(&convert_to_luminance_image(&loaded, &filename, loc)?)
    };

    scale /= spectrum_to_photometric(i);
    let phi_v = params.get_one_float("power", -1.0)?;

    if phi_v > 0.0 {
        let k_e = compute_emissive_power(&image);
        scale *= phi_v / k_e;
    }

    let swap_yz: [Float; 16] = [
        1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1.,
    ];
    let t =
        Transform::from_flat(&swap_yz).expect("Could not create transform for GoniometricLight");
    let _final_render_from_light = render_from_light * t;

    let mi = match medium {
        Some(m) => {
            let ptr = arena.alloc(m);
            MediumInterface {
                inside: ptr,
                outside: ptr,
            }
        }
        None => MediumInterface::default(),
    };
    let base = LightBase::new(LightType::DeltaPosition, render_from_light, mi);

    let iemit = lookup_spectrum(&i);

    let image_ptr = if !image.is_null() {
        let distrib = PiecewiseConstant2D::from_image(&image);
        let distrib_ptr = arena.alloc(distrib);
        let img_ptr = arena.alloc(image);
        (img_ptr, distrib_ptr)
    } else {
        (Ptr::null(), Ptr::null())
    };

    let specific = GoniometricLight {
        base,
        iemit: arena.alloc(*iemit),
        scale,
        image: image_ptr.0,
        distrib: image_ptr.1,
    };

    Ok(Light::Goniometric(specific))
}

fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result<Image> {
    let res = image.resolution();
    let rgb_desc = image.get_channel_desc(&["R", "G", "B"]);
    let y_desc = image.get_channel_desc(&["Y"]);

    match (rgb_desc, y_desc) {
        (Ok(_), Ok(_)) => Err(anyhow!(
            "{}: Image '{}' has both RGB and Y channels; ambiguous",
            loc,
            filename
        )),

        (Ok(_), Err(_)) => {
            // Convert RGB to Y (luminance)
            let mut y_pixels = Vec::with_capacity((res.x() * res.y()) as usize);

            for y in 0..res.y() {
                for x in 0..res.x() {
                    let r = image.get_channel(Point2i::new(x, y), 0);
                    let g = image.get_channel(Point2i::new(x, y), 1);
                    let b = image.get_channel(Point2i::new(x, y), 2);
                    y_pixels.push((r + g + b) / 3.0);
                }
            }

            Ok(Image::from_f32(y_pixels, res, &["Y"].to_vec()))
        }

        (Err(_), Ok(_)) => {
            // Already has Y channel
            Ok(image.clone())
        }

        (Err(_), Err(_)) => Err(anyhow!(
            "{}: Image '{}' has neither RGB nor Y channels",
            loc,
            filename
        )),
    }
}

fn compute_emissive_power(image: &Image) -> Float {
    let res = image.resolution();
    let mut sum_y = 0.0;

    for y in 0..res.y() {
        for x in 0..res.x() {
            sum_y += image.get_channel(Point2i::new(x, y), 0);
        }
    }

    4.0 * PI * sum_y / (res.x() * res.y()) as Float
}
