use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::{self};
use std::process;

use crate::m_argv;
use crate::m_argv::M_CheckParm;
use crate::m_argv::M_GetOptionalArgumentValueByArgument;
use crate::m_argv::PARM_NOT_FOUND;
use crate::m_misc::M_LoadDefaults;

use crate::w_wad::{W_CheckNumForName, W_InitMultipleFiles};

use crate::doomdef::{
    VERSION,
    // D_DEVSTR,
    GameMode,
    Skill,
    DOOMGLOBALS
};

fn flush_and_wait() {
    let _ = io::stdout().flush();

    // Replaces C's getchar() by waiting for the Enter key
    let mut buffer = String::new();
    let _ = io::stdin().read_line(&mut buffer);
}

#[allow(non_snake_case)]
pub fn D_AddFile(file: &str) {
    DOOMGLOBALS::with_mut(|g| {
        g.wadfiles.push(file.to_string());
    });
}

#[allow(non_snake_case)]
pub fn D_ProcessFileParameters(p_file: i32) {
    let args = m_argv::M_GetAll();
    let pos = p_file as usize;
    // Find the index of "-file"
    // Look at everything after the index
    for &arg in args.iter().skip(pos + 1) {
        if arg.starts_with('-') {
            break; // Stop if we hit another flag like -debug
        }
        D_AddFile(arg);
    }
}

/// Reads a Response file for additional command line arguments
/// A response file is a text file that may store additional command line parameters. 
/// The file may have any name that is valid to the system, optionally with an extension.
/// The parameters are typed as in the command line (-episode 2, for example),
///  but one per line, where up to 100 lines may be used. 
/// The additional parameters may be disabled for later use by placing a vertical bar 
/// (the | character) between the prefixing dash (-) and the rest of the parameter name.
#[allow(non_snake_case)]
fn FindResponseFile(args: &mut Vec<String>) -> Result<(), String> {
    const MAX_ARGVS: usize = 100;

    let mut i = 0;
    while i < args.len() {
        let arg = &args[i];
        if arg.len() > 0 && arg.as_bytes()[0] == b'@' {

            let f_name = &arg[1..]; // remove @
            let f_content: String;

             match fs::read_to_string(f_name) {
                Ok(content) => f_content = content,
                Err(e) => {
                    eprintln!("Found response file {}! (Error reading: {})", f_name, e);
                    std::process::exit(1);
                }
            }
            eprintln!("Found response file {}!", f_name);

            let f_args: Vec<String> = f_content
                .split_whitespace()
                .map(|s| s.to_string())
                .collect();
            
            args.remove(i);

            for (offset, new_arg) in f_args.into_iter().enumerate() {
                args.insert(i+offset, new_arg);
            }

            if args.len() > MAX_ARGVS {
                eprintln!("Warning: Recieved {} total arguments, exceeding original MAX_ARGSV limit {}", args.len(), MAX_ARGVS)
            }

            println!("{} command-line args:", args.len());
            for arg in args {
                println!("\t{}", arg);
            }
            break;
        }
        i +=1;
    }

    Ok(())
}

#[allow(non_snake_case)]
fn IdentifyVersion() -> Result<(), String> {

    if M_CheckParm("-shdev") != PARM_NOT_FOUND {
        eprintln!("-shddev param not implemented in this port");
        eprintln!("Usage: RustyDoom <iwad-file> <Options>");
        process::exit(1);
    }
    if M_CheckParm("-regdev") != PARM_NOT_FOUND {
        eprintln!("-regdev param not implemented in this port");
        eprintln!("Usage: RustyDoom <iwad-file> <Options>");
        process::exit(1);
    }
    if M_CheckParm("-comdev") != PARM_NOT_FOUND {
        eprintln!("-comdev param not implemented in this port");
        eprintln!("Usage: RustyDoom <iwad-file> <Options>");
        process::exit(1);
    }

    let iwad_arg = m_argv::M_GetPositionalArgv()
        .unwrap_or_else(|| {
            eprintln!("Usage: RustyDoom <iwad-file> <Options>");
            process::exit(1);
        });
    
    let Ok(iwad_path) = path::absolute(std::path::PathBuf::from(iwad_arg)) else {
        eprintln!("FATAL: Could not resolve the IWAD path: {}", iwad_arg);
        process::exit(1);
    };
    if !iwad_path.is_file() {
        eprintln!("FATAL: IWAD file not found: {}", iwad_path.display());
        process::exit(1);
    }

    let filename = iwad_path
        .file_name()
        .and_then(|f| f.to_str())
        .unwrap_or_else(|| {
            eprintln!("FATAL: Unable to get WAD filename from path: {:?}", iwad_path);
            std::process::exit(1);
        })
        .to_ascii_lowercase();

    DOOMGLOBALS::with_mut(|g| {
        g.language = 0;

        g.gamemode = match filename.as_str() {

            "doom1.wad" => GameMode::Shareware,

            "doom.wad" => GameMode::Registered,

            "doomu.wad" | "ultimate.wad" => {
                GameMode::Retail
            }

            "doom2.wad" => GameMode::Commercial,

            "doom2f.wad" => {
                g.language = 1;
                GameMode::Commercial
            }

            "tnt.wad" => GameMode::Commercial,

            "plutonia.wad" => GameMode::Commercial,

            _ => GameMode::Indetermined,
        };
    });  
    D_AddFile(&iwad_arg);

    Ok(())
}

// Converts the given string into a valid  [u8; 128] array
fn to_fixed_bytes(input: &str) -> [u8; 128] {
    let mut b = [0u8; 128];
    let src = input.as_bytes();
    let len = src.len().min(128);
    b[..len].copy_from_slice(&src[..len]);
    b
}

#[allow(non_snake_case)]
pub fn D_DoomMain() {
    // get cli args from env
    // Original doom uses a static module argc, argv
    // Collects arguments into a Vector of Strings
    // Process args
    let mut args: Vec<String> = env::args().collect();
    let _ = FindResponseFile(&mut args);
    
    m_argv::init(args);
    let _ = IdentifyVersion();

    // setbuf (stdout, NULL); // TODO: Investigate this
    DOOMGLOBALS::with_mut(|g| {
        g.modifiedgame = false;
        g.nomonsters = M_CheckParm("-nomonsters") != 0;
        g.respawnparm = M_CheckParm("-respawn") != 0;
        g.fastparm = M_CheckParm("-fastparm") != 0;
        g.devparm = M_CheckParm("-devparam") != 0;
        g.deathmatch = if M_CheckParm("-altdeath") != 0 {
            2
        } else if M_CheckParm("-deathmatch") != 0 {
            1
        } else {
            0
        };
        // TODO: fix the original issue to handle TnT + Plutonia
        g.title = match  g.gamemode {
            GameMode::Retail       => to_fixed_bytes(&format!("                         The Ultimate DOOM Startup v{}.{}                           ", VERSION / 100, VERSION % 100)),
            GameMode::Shareware    => to_fixed_bytes(&format!("                         DOOM Shareware Startup v{}.{}                           ", VERSION / 100, VERSION % 100)),
            GameMode::Registered   => to_fixed_bytes(&format!("                         DOOM Registered Startup v{}.{}                           ", VERSION / 100, VERSION % 100)),
            GameMode::Commercial   => to_fixed_bytes(&format!("                         DOOM 2: Hell on Earth v{}.{}                           ", VERSION / 100, VERSION % 100)),
            GameMode::Indetermined => to_fixed_bytes(&format!("                         Public DOOM - v{}.{}                           ", VERSION / 100, VERSION % 100)),
        };
        
        println!("{}", String::from_utf8_lossy(&g.title));

        if g.devparm {
            eprintln!("devparm not implemented in this port");
            eprintln!("Usage: RustyDoom <iwad-file> <Options>");
            process::exit(1);
        }
    });

    // Check and parse turbo options if applicable
    if  M_CheckParm("-turbo") != PARM_NOT_FOUND {
        let mut scale :i32 = 200;
        // TODO: Resolve externs (int should be fixed_t) or FixedPoint in our case
	    // extern int forwardmove[2];
	    // extern int sidemove[2];
        if let Some(arg_str) = M_GetOptionalArgumentValueByArgument("-turbo") {
            if let Ok(parsed_val) = arg_str.parse() {
                scale = parsed_val;
            }
            else {

            }
        }
        scale = scale.clamp(10, 400);
        println!("turbo scale: {}%", scale);
        // TODO: Resolve externs
	    //forwardmove[0] = forwardmove[0]*scale/100;
	    //forwardmove[1] = forwardmove[1]*scale/100;
	    //sidemove[0] = sidemove[0]*scale/100;
	    //sidemove[1] = sidemove[1]*scale/100;        
    }

    if M_CheckParm ("-wart") != PARM_NOT_FOUND {
        eprintln!("-wart is not implemented in this port");
        eprintln!("Usage: RustyDoom <iwad-file> <Options>");
        process::exit(1);
    }

    let mut p = M_CheckParm("-file");{
    if p != PARM_NOT_FOUND {
        // Params after -file are wadfile/lump names
        // Continue collecting until end of params or another `-` preceded param
        DOOMGLOBALS::with_mut(|g|{
            g.modifiedgame = true;
        });
        D_ProcessFileParameters(p);
        }
    }

    // Playing a dmeo?
    p = M_CheckParm("-playdemo");
    if p != PARM_NOT_FOUND {
        p = M_CheckParm("-timedemo");
    }

    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        if let Some(demo_file) = m_argv::M_GetOptionalArgumentValueByIndex(p+1) {
             D_AddFile(demo_file);
             println!("Playing demo {}.lmp.\n", demo_file);
        }       
    }

    // get the skill, episode and map from the parms if given

    DOOMGLOBALS::with_mut(|g| {
        g.startskill = Skill::sk_medium as i32;
        g.startepisode = 1;
        g.startmap = 1;
        g.autostart = false;
    });

    p = M_CheckParm("-skill");
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        if let Some(skill_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) {
            if let Some(first_char) = skill_val.chars().next() {
                let start_skill = (first_char as i32).wrapping_sub('1' as i32);

                DOOMGLOBALS::with_mut(|g| {
                    g.startskill = start_skill;
                })
            }
        }
    }

    p = M_CheckParm("-episode");
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        if let Some(episode_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) {
            if let Some(first_char) = episode_val.chars().next() {
                let start_episode = (first_char as i32).wrapping_sub('0' as i32);

                DOOMGLOBALS::with_mut(|g| {
                    g.startepisode = start_episode;
                    g.startmap = 1;
                })
            }
        }
    }

    p = M_CheckParm("-timer");
    let is_deathmath = DOOMGLOBALS::with_ref(|g| g.deathmatch);
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 && is_deathmath > 0 {
        if let Some(timer_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) {
            match timer_val.parse::<u32>() {
                Ok(time) => {
                    if time > 1 {
                        println!("Levels will end after {} minutes", time)
                    } else {
                        
                        println!("Levels will end after {} minute", time)
                    }                    
                },
                Err(_) => {
                    eprintln!("value for -timer is not a valid number of minutes");
                    eprintln!("Usage: RustyDoom <iwad-file> <Options>");
                    std::process::exit(1);
                }
            };
        }
    }

    p = M_CheckParm("-avg");
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 && is_deathmath > 0 {
        println!("Austin Virtual Gaming: Levels will end after 20 minutes\n");    
    }

    p = M_CheckParm("-warp");
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        let g_mode = DOOMGLOBALS::with_ref(|g| g.gamemode);
        if g_mode == GameMode::Commercial {
            if let Some(gamemode_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) {
                match gamemode_val.parse::<i32>() {
                    Ok(mapstart) => {
                        DOOMGLOBALS::with_mut(|g| {
                            g.startmap = mapstart;
                            g.autostart = true;
                        })
                    },
                    Err(_) => {
                        eprintln!("value for -warp is not a valid map number");
                        eprintln!("Usage: RustyDoom <iwad-file> <Options>");
                        std::process::exit(1);
                    }
                };
            }
        } else {
            if let Some(episode_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) {
                if let Some(map_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 2) {
                    match (episode_val.parse::<i32>(), map_val.parse::<i32>()) {
                        (Ok(startepisode), Ok(startmap)) => {
                            DOOMGLOBALS::with_mut(|g| {
                                g.startepisode = startepisode;
                                g.startmap = startmap;
                                g.autostart = true;
                            }
                        );
                        }
                        (Ok(_), Err(_)) => {
                            eprintln!("value for starting map in -warp is not a valid number");
                            eprintln!("Usage: RustyDoom <iwad-file> <Options>");
                            std::process::exit(1);
                        }
                        (Err(_), Ok(_)) => {
                            eprintln!("value for starting episode in -warp is not a valid number");
                            eprintln!("Usage: RustyDoom <iwad-file> <Options>");
                            std::process::exit(1);
                        }
                        (Err(_), Err(_)) => {
                            eprintln!("Values for starting episode and map in -warp are not a valid numbers");
                            eprintln!("Usage: RustyDoom <iwad-file> <Options>");
                            std::process::exit(1)
                        }
                    }
                };
            }
        }
    }

    // TODO: Implement subsytem inits
    /*
    println!("V_Init: allocate screens.");
    V_Init();
    */
    println!("M_LoadDefaults: Load system defaults.");
    M_LoadDefaults();

    /*
    println!("Z_init: Init zone memory allocation daemon.");
    Z_Init();
    
     */
    println!("W_Init: Init WADfiles");
    W_InitMultipleFiles(DOOMGLOBALS::with_ref(|g| g.wadfiles.clone()));  // we are loading so how cares about a borrow

    let is_modified = DOOMGLOBALS::with_ref(|g| g.modifiedgame);

    if is_modified {
        let iwads_lumps_to_check: Vec<&str> = vec!["e2m1","e2m2","e2m3","e2m4","e2m5","e2m6","e2m7","e2m8","e2m9",
			"e3m1","e3m3","e3m3","e3m4","e3m5","e3m6","e3m7","e3m8","e3m9",
			"dphoof","bfgga0","heada1","cybra1","spida1d1"];
        
        let g_mode = DOOMGLOBALS::with_ref(|g| g.gamemode);

        if g_mode == GameMode::Shareware {
            // I_ERROR?
        }

        if g_mode == GameMode::Registered {
            let missing_registered_lump = iwads_lumps_to_check
                .iter()
                .any(|lump_name| W_CheckNumForName(lump_name) == -1);
			    if missing_registered_lump {
				    // TODO: Implement I_Error
                    // I_Error("\nThis is not the registered version.");
                }
        }
    }

    // Display the modifed game header

    if is_modified {
        print!("===========================================================================\n\
	    ATTENTION:  This version of DOOM has been modified.  If you would like to\n\
	    get a copy of the original game, call 1-800-IDGAMES or see the readme file.\n\
	            You will not receive technical support for modified games.\n\
	                          press enter to continue\n\
	    ===========================================================================\n"
	    );
        flush_and_wait();
    }

    match DOOMGLOBALS::with_ref(|g| g.gamemode) {
        GameMode::Shareware | GameMode::Indetermined => {
            print!("===========================================================================\n\
	                                    Shareware!\n\
	    ===========================================================================\n")
        }
        GameMode::Registered | GameMode::Retail | GameMode::Commercial => {
            print!("===========================================================================\n\
	                     Commercial product - do not distribute!\n\
	             Please report software piracy to the SPA: 1-800-388-PIR8\n\
	    ===========================================================================\n")
        }
    }

    // TODO: Implement other inits
    /*
    println!("M_Init: Init miscellaneous info.");
    M_Init();

    println!("R_Init: Init DOOM refresh daemon - ")
    R_Init();

    println!("P_Init: Init Playloop state.");
    P_Init();

    println!("I_Init: Setting up machine state.");
    I_Init()
    
    println!("D_CheckNetGame: Checking network game status.");
    D_CheckNetGame()
    
    println!("S_Init: Setting up sound.");
    S_Init (snd_SfxVolume /* *8 */, snd_MusicVolume /* *8*/ );

    printf ("HU_Init: Setting up heads up display.\n");
    HU_Init ();

    printf ("ST_Init: Init status bar.\n");
    ST_Init ();
    */

    // Check for a driver that wants intermission stats

    
    p = M_CheckParm("-statcopy");
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        // TODO: implement for statistics driver
        /*extern  void*	statcopy;                            

        statcopy = (void*)atoi(myargv[p+1]);
        printf ("External statistics registered.\n");*/
    }

    // Start the approiate gaem based on parms
    p = M_CheckParm("-record");
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        // TODO: Implement G_RecordDemo
        //G_RecordDemo(m_argv::M_GetOptionalArgumentValueByIndex(p+1));
        DOOMGLOBALS::with_mut(|g| g.autostart = true);
    }

    p = M_CheckParm("-playdemo");    
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        // TODO: Implement G_DeferedPlayDemo
        // G_DeferedPlayDemo (myargv[p+1]);
	    // D_DoomLoop ();  // never returns
    }

    p = M_CheckParm("-timedemo");    
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        // TODO: Implement G_TimeDemo
        // G_TimeDemo (myargv[p+1]);
	    // D_DoomLoop ();  // never returns        
    }

    p = M_CheckParm("-loadgame");    
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        // TODO: Implement loadgame param G_LoadGame
        // G_TimeDemo (myargv[p+1]);
	    // D_DoomLoop ();  // never returns
        if M_CheckParm("-cdrom") != PARM_NOT_FOUND {
            // sprintf(file, "c:\\doomdata\\"SAVEGAMENAME"%c.dsg",myargv[p+1][0]);
        } else {
            // sprintf(file, SAVEGAMENAME"%c.dsg",myargv[p+1][0]);
        }
        // G_LoagGame(file)        
    }

    p = M_CheckParm("-ga_loadgame");    
    if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 {
        
    }

    // let g_action = DOOMGLOBALS::with_ref(|g| g.gameaction);

    // TODO: Impement Game actions
    // if g_action != ga_loadgame {
    //     let autostart = DOOMGLOBALS::with_ref(|g| g.autostart);
    //     let netgame = DOOMGLOBALS::with_ref(|G| G.)
    // }

    // D_DoomLoop()

}
