//! Map file header.

use crate::{err::*, text::mac_roman_cstr};
use std::io::prelude::*;

/// Reads a Map file.
pub fn read(rd: &mut impl Read) -> ResultS<Map>
{
	let mut data = Vec::new();
	rd.read_to_end(&mut data)?;

	read_data! {
		endian: BIG, buf: &data, size: 128, start: 0, data {
			let ver_wad   = u16[0] enum Ver;
			let ver_data  = u16[2];
			let name      = mac_roman_cstr[4; 64] no_try;
			let dir_ofs   = u32[72] usize;
			let num_ent   = u16[76] usize;
			let size_appl = u16[78] usize;
			let size_wcnk = u16[80] usize;
			let size_went = u16[82] usize;
		}
	}

	let head = Header::new(size_appl, ver_data, ver_wad, Some(name));

	if !head.old_wad() && head.size_entry_base() != size_went {
		bail!("invalid entry size");
	}

	if !head.old_wad() && head.size_chunk() != size_wcnk {
		bail!("invalid chunk size");
	}

	let map = Map{head, data, dir_ofs, num_ent};

	if map.dir_end() > map.data.len() {
		bail!("not enough data in map file");
	}

	Ok(map)
}

impl Header
{
	/// Creates a new `Header`.
	pub fn new(size_appl: usize,
	           ver_data:  u16,
	           ver_wad:   Ver,
	           name:      Option<String>) -> Self
	{
		let name = name.unwrap_or_default();
		Self{name, size_appl, ver_data, ver_wad}
	}

	/// Returns `true` if the data is in Marathon 1 format.
	#[inline]
	pub const fn old_data(&self) -> bool {self.ver_data() == 0}

	/// Returns `true` if the Map file is in Marathon 1 format.
	#[inline]
	pub fn old_wad(&self) -> bool {self.ver_wad() == Ver::Base}

	/// The data version of this file.
	#[inline]
	pub const fn ver_data(&self) -> u16 {self.ver_data}

	/// The format version of this file.
	#[inline]
	pub const fn ver_wad(&self) -> Ver {self.ver_wad}

	/// The size of each `Entry`'s `appdata` field.
	#[inline]
	pub const fn size_appl(&self) -> usize {self.size_appl}

	/// The size of each `Entry`'s data.
	#[inline]
	pub fn size_entry_base(&self) -> usize {if self.old_wad() {8} else {10}}

	/// The size of each `Entry`.
	#[inline]
	pub fn size_entry(&self) -> usize {self.size_entry_base() + self.size_appl()}

	/// The size of each chunk's header.
	#[inline]
	pub fn size_chunk(&self) -> usize {if self.old_wad() {12} else {16}}
}

impl Map
{
	/// The header for this map.
	#[inline]
	pub const fn head(&self) -> &Header {&self.head}

	/// The data section of this map.
	#[inline]
	pub fn data(&self) -> &[u8] {&self.data[..]}

	/// The directory section of this map.
	#[inline]
	pub fn dir(&self) -> &[u8] {&self.data[self.dir_beg()..self.dir_end()]}

	/// The number of entries in the directory.
	#[inline]
	pub const fn num_ent(&self) -> usize {self.num_ent}

	#[inline]
	const fn dir_beg(&self) -> usize {self.dir_ofs}

	#[inline]
	fn dir_end(&self) -> usize
	{
		self.dir_ofs + self.head.size_entry() * self.num_ent
	}
}

/// A Map header.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Header {
	/// The original name of this file.
	pub name: String,

	size_appl: usize,
	ver_data:  u16,
	ver_wad:   Ver,
}

/// A Map file.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Map {
	head:    Header,
	data:    Vec<u8>,
	dir_ofs: usize,
	num_ent: usize,
}

c_enum! {
	/// The version of a Map file.
	#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
	pub enum Ver: u16 {
		Base = 0,
		Dir  = 1,
		Over = 2,
		Inf  = 4,
	}
}

// EOF
