// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
#[cfg(feature = "client")] use std::borrow::Cow;
use std::{io::BufReader, fs::File, path::Path};

#[cfg(feature = "client")] use glium::{Display, Texture2d, texture::{RawImage2d, Texture2dArray, ClientFormat}};
#[cfg(feature = "client")] use glutin::surface::WindowSurface;
use png::{Decoder, ColorType, BitDepth};

pub struct Image {
	pub width: u32,
	pub height: u32,
	pub pixels: Vec<u8>,
	#[cfg(feature = "client")] format: ColorType,
}

#[cfg(feature = "client")]
pub fn load(display: &Display<WindowSurface>, path: &Path, format: ColorType) -> Texture2d {
	Image::try_new(path, format).unwrap().into_texture(display)
}

#[cfg(feature = "client")]
pub fn load_square_array(display: &Display<WindowSurface>, path: &Path, format: ColorType) -> Texture2dArray {
	let image = Image::try_new(path, format).unwrap();
	assert_eq!(image.height % image.width, 0, "Cannot get square texture array size");
	let count = image.height / image.width;
	image.into_texture_array(display, count)
}

impl Image {
	pub fn try_new(path: &Path, format: ColorType) -> Result<Image, String> {
		// Did some copy-pasting from a previous project which was copy-pasted from the documentation, each time some modifications made
		let decoder = Decoder::new(BufReader::new(File::open(path).map_err(|err| format!("cannot load file \"{}\": {err}", path.display()))?));

		let mut reader = decoder.read_info().map_err(|err| format!("failed reading metadata: {err}"))?;
		let mut pixels = vec![0; reader.output_buffer_size().ok_or_else(|| String::from("failed getting image size"))?];
		let info = reader.next_frame(&mut pixels).map_err(|err| format!("failed reading image: {err}"))?;
		pixels.truncate(info.buffer_size());

		if info.bit_depth != BitDepth::Eight {
			return Err(String::from("bit depth isn't eight"));
		}
		if info.color_type != format {
			return Err(format!("expected {format:?}, got {:?}", info.color_type));
		}

		Ok(Image {
			width: info.width,
			height: info.height,
			pixels,
			#[cfg(feature = "client")] format,
		})
	}
}

#[cfg(feature = "client")]
impl Image {
	fn into_texture(self, display: &Display<WindowSurface>) -> Texture2d {
		let format = self.get_glium_format();
		Texture2d::new(display, RawImage2d {
			data: Cow::Owned(self.pixels),
			width: self.width,
			height: self.height,
			format,
		}).unwrap()
	}

	pub fn into_texture_array(self, display: &Display<WindowSurface>, count: u32) -> Texture2dArray {
		let format = self.get_glium_format();
		let size = self.pixels.len() / count as usize;
		Texture2dArray::new(display, (0..count).map(|i| {
			RawImage2d {
				data: Cow::Borrowed(&self.pixels[i as usize * size..(i + 1) as usize * size]),
				width: self.width,
				height: self.height / count,
				format,
			}
		}).collect()).unwrap()
	}

	fn get_glium_format(&self) -> ClientFormat {
		match self.format {
			ColorType::Grayscale => ClientFormat::U8,
			ColorType::GrayscaleAlpha => ClientFormat::U8U8,
			ColorType::Rgb => ClientFormat::U8U8U8,
			ColorType::Rgba => ClientFormat::U8U8U8U8,
			ColorType::Indexed => panic!("Image cannot be indexed"),
		}
	}
}
