use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::mem::size_of;
use std::os::windows::io::AsRawHandle;
use std::path::Path;

#[allow(non_upper_case_globals)]
static mut numlumps: i32 = 0;

#[allow(non_upper_case_globals)]
static mut lumpinfo: Vec<lumpinfo_t> = Vec::new();

#[allow(non_upper_case_globals)]
static mut reloadlump: i32 = 0;

#[allow(non_upper_case_globals)]
static mut reloadname: String = String::new(); 

/// Struct of the header of a WAD file
/// All integers are 4 bytes long in x86-style little-endian order. Their values can never exceed 231-1
#[repr(C)]
pub struct wadinfo_t {
    /// ASCII identifer, either IWAD or PWAD
    pub identification: [u8; 4],

    /// The number of lumps in the WAD
    pub numlumps: i32,

    /// Pointer to the location of the directory
    pub infotableofs: i32,
}

/// Struct of fileumps of a WAD file
#[repr(C)]
#[derive(Clone)]
pub struct filelump_t {
    
    /// Pointer to the start of the lumps data in the WAD file
    pub filepos: i32,

    /// The size of the lump in bytes
    pub size: i32,

    /// ASCII string identifying the lumps name
    /// must be null terminated if less than 8 characters long
    /// For safety, padd till end with null chars
    pub name: [u8; 8],
}

/// TODO: RD-2: fill in if it is even needed for this port??
#[repr(C)]
#[derive(Clone)]
pub struct lumpinfo_t {

    pub name: [u8; 8],

    pub handle: isize,

    pub position: i32,

    pub size: i32
}

#[allow(non_snake_case)]
fn W_AddFile(file: &str) {
    // Get reference to static vvariables to be used locally
    let (lumpinfo_ref, reloadname_ref) = unsafe {
        (
            &mut *std::ptr::addr_of_mut!(lumpinfo),
            &mut *std::ptr::addr_of_mut!(reloadname)
        )
    };    

    if file.as_bytes()[0] == '~' as u8 {
        unsafe {
            reloadname = file.chars().skip(1).collect();
            reloadlump = numlumps;
        }
    }

    let mut fileinfo: Vec<filelump_t> = Vec::new();

    let mut handle: File = match std::fs::File::open(&file) {
        Ok(file) => file,
        Err(_) => {
            println!(" couldn't open {}", file);
            return;
        }
    };
    println!("adding {}",file);

    if !file.to_ascii_lowercase().ends_with(".wad") {
        // process single lump
        let file_meta = handle.metadata().unwrap();
        // Single lump file
        let mut singleinfo = filelump_t {
            filepos: 0,
            size: file_meta.len() as i32,
            name: [0; 8]
        };
        let name = Path::new(file)
            .file_name()
            .and_then(|os_str| os_str.to_str()).unwrap();
        for (i, byte) in name.as_bytes().iter().take(8).enumerate() {
            singleinfo.name[i] = *byte;
        }
        fileinfo.push(singleinfo);
        unsafe {numlumps += 1};
    } else {
        // WAD file
        let mut header: wadinfo_t = wadinfo_t {
            identification: [0; 4],
            numlumps: 0,
            infotableofs: 0 
        };
        handle.read_exact(&mut header.identification).unwrap();
        let mut int_buf:[u8; 4] = [0u8; 4];
        handle.read_exact(&mut int_buf).unwrap();
        header.numlumps = i32::from_le_bytes(int_buf);
        handle.read_exact(&mut int_buf).unwrap();
        header.infotableofs = i32::from_le_bytes(int_buf);

        if !header.identification.eq_ignore_ascii_case(b"IWAD") {
            if !header.identification.eq_ignore_ascii_case(b"PWAD") {
                // TODO: Implement I_Error
                // I_Error ("Wad file %s doesn't have IWAD or PWAD id\n", file);
            }
        }

        let _ = handle.seek(SeekFrom::Start(header.infotableofs as u64));
        fileinfo.resize(header.numlumps as usize, filelump_t {
            filepos: 0,
            size: 0,
            name: [0; 8],
        });
        let dirinfo_bytes = header.numlumps as usize * size_of::<filelump_t>();
        unsafe {
            let fileinfo_slice = std::slice::from_raw_parts_mut(
                fileinfo.as_mut_ptr() as *mut u8,
                dirinfo_bytes,
            );
            handle.read_exact(fileinfo_slice).unwrap();
        }

        // enusre LE-ness of the ints
        for fl in &mut fileinfo {
            fl.filepos = i32::from_le(fl.filepos);
            fl.size = i32::from_le(fl.size);
        }

        unsafe{numlumps += header.numlumps}
    };
    
    let is_reloaded = !reloadname_ref.is_empty();
    let storehandle: isize = if is_reloaded  {
        -1 // Original C code closes the file immediately if it's a reloaded file
    } else {
        // get raw Windows HANDLE as an integer pointer
        let raw_handle = handle.as_raw_handle() as isize;        
        // Tells rust to "trust us" and keeps the handle open after this function scope
        std::mem::forget(handle);         
        raw_handle
    };


    lumpinfo_ref.reserve(fileinfo.len());
    for info in fileinfo {
        lumpinfo_ref.push( lumpinfo_t {
            handle: storehandle,
            position: info.filepos,
            size: info.size,
            name: info.name
        })
    }
}


#[allow(non_snake_case)]
pub fn W_InitMultipleFiles(filenames: Vec<String>) {

    for file in filenames {
        W_AddFile(&file);
    }

    if unsafe{numlumps == 0} {
        // TODO: Implement I_Error
        // I_Error("W_InitFiles: no files found")
    }

    // set up lump cache
}

#[allow(non_snake_case)]
pub fn W_Reload() {

}

#[allow(non_snake_case)]
pub fn W_CheckNumForName(name: &str) -> i32 {
    let lumpinfo_ref = unsafe { &*std::ptr::addr_of!(lumpinfo) };

    let mut search_name = [0u8; 8];
    for (i, byte) in name.as_bytes().iter().take(8).enumerate() {
        search_name[i] = byte.to_ascii_uppercase();
    }

    // 2. Scan backwards so PWAD patches take precedence over IWAD assets
    lumpinfo_ref
        .iter()
        .enumerate()
        .rev() // Iterates backward from the end
        .find(|(_, lump)| lump.name == search_name)
        .map(|(index, _)| index as i32) // If found, return the index
        .unwrap_or(-1) // "TFB. Not found." -> return -1    
}

#[allow(non_snake_case)]
pub fn W_LumpLength() {

}

#[allow(non_snake_case)]
pub fn W_ReadLump() {

}

#[allow(non_snake_case)]
pub fn W_CacheLumpNum() {

}

#[allow(non_snake_case)]
pub fn W_CacheLumpName() {

}