mod directory;

use directory::Directory;
use std::{
    fs::File,
    io::{Read, Seek, SeekFrom},
    path::Path,
};

/// Representation of a WAD file in memory.
///
/// Struct is created by
/// ```
/// let wad_file = WADFile::from_path("WADs/doom1.wad").unwrap();
/// ```
pub struct WADFile {
    /// Holds the path to the WAD file
    pub wad_path: String,
    /// Type of WAD file (either 'IWAD' or 'PWAD')
    pub identifier: String,
    /// Number of lumps in the WAD
    pub num_lumps: u32,
    /// Offset to the lump directory
    pub init_offset: u32,
    /// List of each directory entry in WAD directory
    lump_directory: Vec<Directory>,
}

impl WADFile {
    /// Constructs a WADFile from a given path.
    ///
    /// Takes in a path as a string slice, then opens the file at this path.
    /// after this it will read and parse the header file, and load the lump
    /// directory into memory.
    ///
    /// TODO: Add example code here
    pub fn from_path(path: &str) -> Result<Self, &str> {
        let wad_file = Path::new(path);

        if !(wad_file.exists()) {
            return Err("Could not open file!");
        }

        let header_data = get_header_data(path);

        let mut lump_dir: Vec<Directory> = Vec::with_capacity(header_data.1 as usize);

        get_lump_dir(path, &header_data.2, &header_data.1, &mut lump_dir);

        Ok(Self {
            wad_path: path.to_string(),
            identifier: header_data.0,
            num_lumps: header_data.1,
            init_offset: header_data.2,
            lump_directory: lump_dir,
        })
    }

    /// Get a lumps name
    ///
    /// Gets the name of a lump given an index. Will return a
    /// string with the lumps name.
    ///
    /// TODO: Add example code here
    pub fn get_lump_name(&self, index: u32) -> String {
        self.lump_directory[index as usize].lump_name.to_owned()
    }

    /// Get a lumps offset
    ///
    /// Returns the offset where lump begins that is
    /// identified by index.
    ///
    /// TODO: Add example code here
    pub fn get_lump_offset(&self, index: u32) -> u32 {
        self.lump_directory[index as usize].lump_pos.to_owned()
    }

    /// Get size of a lump
    ///
    /// Returns the size of lump at position index.
    ///
    /// TODO: Add example code here
    pub fn get_lump_size(&self, index: u32) -> u32 {
        self.lump_directory[index as usize].lump_size.to_owned()
    }

    /// Get length of directory list
    ///
    /// Returns the length of the directory list.
    pub fn get_directory_len(&self) -> usize {
        self.lump_directory.len()
    }

    // Get index of a lump with given name.
    //
    // Will return the index of a lump that has name specified
    // in lump_name. If not found will return None.
    //
    // TODO: Add example code here
    pub fn get_index_from_name(&self, lump_name: &str) -> Option<u32> {
        let mut search_lump = lump_name.to_ascii_uppercase();

        for _ in 0..8 - search_lump.len() {
            search_lump.push(0 as char);
        }

        for i in 0..self.num_lumps {
            if self.lump_directory[i as usize].lump_name == search_lump {
                return Some(i);
            }
        }

        None
    }
}

/// Loads lump directory into memory
///
/// Reads the directory in the WAD file and loads into memory.
fn get_lump_dir(path: &str, dir_offset: &u32, num_lumps: &u32, lump_buffer: &mut Vec<Directory>) {
    let mut wad_data = File::open(&path).unwrap();
    wad_data
        .seek(SeekFrom::Start(dir_offset.to_owned() as u64))
        .unwrap();

    for _ in 0..num_lumps.to_owned() {
        let mut lump_pos = [0; 4];
        let mut lump_size = [0; 4];
        let mut lump_name = [0; 8];

        wad_data.read(&mut lump_pos).unwrap();
        wad_data.read(&mut lump_size).unwrap();
        wad_data.read(&mut lump_name).unwrap();

        lump_buffer.push(Directory {
            lump_pos: u32::from_le_bytes(lump_pos),
            lump_size: u32::from_le_bytes(lump_size),
            lump_name: String::from_utf8(lump_name.to_vec()).unwrap(),
        })
    }
}

/// Reads header data
///
/// Reads and returns the data from the WAD header.
fn get_header_data(path: &str) -> (String, u32, u32) {
    let mut wad_data = File::open(&path).unwrap();

    let mut wad_id = [0; 4];
    let mut dir_size = [0; 4];
    let mut dir_offset = [0; 4];

    wad_data.read(&mut wad_id).unwrap();
    wad_data.read(&mut dir_size).unwrap();
    wad_data.read(&mut dir_offset).unwrap();

    (
        String::from_utf8(wad_id.to_vec()).unwrap(),
        u32::from_le_bytes(dir_size),
        u32::from_le_bytes(dir_offset),
    )
}
