// Copyright 2026 Gabriel Bjørnager Jensen.
//
// This file is part of LOOKLOOK.
//
// LOOKLOOK is free software: you can redistribute
// it and/or modify it under the terms of the GNU
// Affero General Public License as published by
// the Free Software Foundation, either version 3
// of the License, or (at your option) any later
// version.
//
// LOOKLOOK is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Af-
// fero General Public License along with LOOKLOOK.
// If not, see <https://www.gnu.org/licenses/>.

//! The [`Render`] type.

use crate::looklook::Looklook;
use crate::mode::{
	Mode,
	ModeRaw,
	ModeVideo,
	PixelLayout,
	VideoFormat,
};
use crate::render::Render;

use oct::IntoOctets;
use png::{BitDepth, ColorType};
use std::fs::File;
use std::io::{self, Write};
use std::path::Path;

impl Render {
	/// Dumps the encoded render.
	///
	/// # Errors
	///
	/// Any I/O errors are forwarded.
	pub fn dump<W: ?Sized + Write>(&self, mode: &Mode, w: &mut W) -> io::Result<()> {
		eprintln!("note: dumping as {}/{}", mode.as_str(), mode.format_as_str());

		match *mode {
			Mode::Raw(ref raw) => {
				self.dump_raw(raw, w)?;
			}

			Mode::Video(ref video) => {
				self.dump_video(video, w)?;
			}

			_ => {
				unimplemented!();
			}
		}

		Ok(())
	}

	/// Dumps the encoded render at a specific path.
	///
	/// # Errors
	///
	/// Any I/O errors are forwarded.
	pub fn dump_at<P: AsRef<Path>>(&self, mode: &Mode, path: P) -> io::Result<()> {
		let path = path.as_ref();

		eprintln!("note: dumping at \"{}\"", path.display());

		let mut file = File::create(path)?;

		self.dump(mode, &mut file)?;
		Ok(())
	}

	/// Dumps the encoded render to the provided path.
	///
	/// # Errors
	///
	/// Any I/O errors are forwarded.
	fn dump_video<W: ?Sized + Write>(&self, video: &ModeVideo, w: &mut W) -> io::Result<()> {
		match video.format {
			VideoFormat::Png => {
				let mut encoder = png::Encoder::new(w, video.width.as_u16().into(), video.height.as_u16().into());

				let colour_type = match video.pixel_layout {
					PixelLayout::Grey      => ColorType::Grayscale,
					PixelLayout::GreyAlpha => ColorType::GrayscaleAlpha,
					PixelLayout::Rgb       => ColorType::Rgb,
					PixelLayout::Rgba      => ColorType::Rgba,
				};

				let bit_depth = match video.colour_depth.as_u8() {
					1 => BitDepth::Eight,
					2 => BitDepth::Sixteen,

					_ => {
						unreachable!();
					}
				};

				encoder.set_color(colour_type);
				encoder.set_depth(bit_depth);

				let mut w = encoder.write_header()?;
				w.write_image_data(self.encoded())?;
			}

			format => {
				unimplemented!("unsupported format `{format}`");
			}
		}

		Ok(())
	}

	/// Dumps the raw render.
	///
	/// # Errors
	///
	/// Any I/O errors are forwarded.
	pub fn dump_raw<W: ?Sized + Write>(&self, raw: &ModeRaw, w: &mut W) -> io::Result<()> {
		const MAGIC: u32 = u32::from_le_bytes(*b"l00k");

		w.write_all(&MAGIC.to_le_bytes())?;

		w.write_all(&Looklook::VERSION_MAJOR.to_le_bytes())?;
		w.write_all(&Looklook::VERSION_MINOR.to_le_bytes())?;

		#[expect(clippy::cast_possible_truncation)]
		let words = self.raw.get(..raw.length as usize)
			.ok_or(io::ErrorKind::UnexpectedEof)?;

		// NOTE: We always store the render as little-
		// endian.
		w.write_all(words.as_octets())?;

		Ok(())
	}
}
