namespace ModelReplacement
{
    class Replacement
    {
        string Old;
        string New;

        Replacement( const string&in oldModel, const string&in newModel )
        {
            this.Old = oldModel;
            this.New = newModel;
        }
    }

    array<Replacement@> g_Replacements(0);

    Replacement@ Insert( const string&in oldModel, const string&in newModel )
    {
        auto replacement = Replacement( oldModel, newModel );

        g_Game.PrecacheModel( newModel );

        g_Replacements.insertLast( @replacement );

        return @replacement;
    }

    Replacement@ Find( const string&in model )
    {
        if( model[0] == '*' || model.IsEmpty() )
            return null;

        for( uint ui = 0; ui < g_Replacements.length(); ui++ )
        {
            auto replacement = g_Replacements[ui];

            if( replacement.Old == model )
                return @replacement;
        }
        return null;
    }

    bool ApplyMonster( CBaseEntity@ entity )
    {
        if( entity is null )
            return false;

        auto replacement = Find( string( entity.pev.model ) );

        if( replacement is null )
            return false;

        int sequence = entity.pev.sequence;
		int body = entity.pev.body;
        Vector mins = entity.pev.mins;
        Vector maxs = entity.pev.maxs;
        g_EntityFuncs.SetModel( entity, replacement.New );
        g_EntityFuncs.SetSize( entity.pev, mins, maxs );
        entity.pev.sequence = sequence;
		entity.pev.body = body;

        return true;
    }

// Weapon replacements needs to manually specify wpn_*_model keyvalues to their original models, we have no other way to know the model otherwise
// If you wish to then remove the #if and #endif
//#if FALSE
    bool ApplyWeapon( CBasePlayerWeapon@ entity )
    {
        auto edict = entity.edict();

        bool bAny = false;

        auto v_replacement = Find( entity.GetV_Model() );
        if( v_replacement !is null )
        {
            bAny = true;
            g_EntityFuncs.DispatchKeyValue( edict, "wpn_v_model", v_replacement.New );
        }

        g_Game.AlertMessage( at_console, "Got p model %1\n", entity.GetP_Model() );

        auto p_replacement = Find( entity.GetP_Model() );
        if( p_replacement !is null )
        {
            bAny = true;
            g_EntityFuncs.DispatchKeyValue( edict, "wpn_p_model", p_replacement.New );
        }

        auto w_replacement = Find( entity.GetW_Model() );
        if( w_replacement !is null )
        {
            bAny = true;
            g_EntityFuncs.DispatchKeyValue( edict, "wpn_w_model", w_replacement.New );
            g_EntityFuncs.SetModel( entity, w_replacement.New );
        }
        return bAny;
    }
//#endif

    bool Apply( CBaseEntity@ entity )
    {
        if( entity is null )
            return false;

//#if FALSE
        if( string( entity.pev.classname ).StartsWith( "weapon_" ) )
        {
            return ApplyWeapon( cast<CBasePlayerWeapon@>( entity ) );
        }
//#endif

        if( entity.IsMonster() or entity.IsInWorld() )
            return ApplyMonster( cast<CBaseEntity@>( entity ) );

        auto replacement = Find( string( entity.pev.model ) );

        if( replacement is null )
            return false;

        g_EntityFuncs.SetModel( entity, replacement.Old );

        return true;
    }

    void ApplySquadmaker( CBaseEntity@ squadmaker, CBaseEntity@ entity )
    {
        Apply( entity );
    }

    int ApplyAll( bool AutoApplySquadmakers = false )
    {
        int value = 0;

        for( int i = 1; i <= g_Engine.maxEntities; i++ )
        {
            auto edict = g_EntityFuncs.IndexEnt( i );

            if( edict is null || edict.vars.solid == SOLID_BSP )
                continue;

            auto entity = g_EntityFuncs.Instance( edict );

            if( entity is null )
                continue;

            if( AutoApplySquadmakers && entity.pev.classname == "squadmaker" )
            {
                value++;
                g_EntityFuncs.DispatchKeyValue( edict, "function_name", "ModelReplacement::ApplySquadmaker" );
                continue;
            }

            if( Apply( entity ) )
            {
                value++;
            }
        }
        return value;
    }
}

namespace ReplaceClassicModels
{
void Register()
{
	//// supply model replacements

	ModelReplacement::Insert( "models/w_desert_eagle.mdl", "models/hlclassic/w_desert_eagle.mdl" );
	ModelReplacement::Insert( "models/w_saw.mdl", "models/hlclassic/w_saw.mdl" );
	ModelReplacement::Insert( "models/w_m40a1.mdl", "models/hlclassic/w_m40a1.mdl" );
	ModelReplacement::Insert( "models/w_uzi.mdl", "models/hlclassic/w_uzi.mdl" );
	ModelReplacement::Insert( "models/w_2uzis.mdl", "models/hlclassic/w_2uzis.mdl" );
	ModelReplacement::Insert( "models/w_minigun.mdl", "models/hlclassic/w_minigun.mdl" );

    ModelReplacement::Insert( "models/w_saw_clip.mdl", "models/hlclassic/w_saw_clip.mdl" );
	ModelReplacement::Insert( "models/w_m40a1clip.mdl", "models/hlclassic/w_m40a1clip.mdl" );
	ModelReplacement::Insert( "models/w_uzi_clip.mdl", "models/hlclassic/w_uzi_clip.mdl" );
	
	ModelReplacement::Insert( "models/p_desert_eagle.mdl", "models/hlclassic/p_desert_eagle.mdl" );
	ModelReplacement::Insert( "models/p_saw.mdl", "models/hlclassic/p_saw.mdl" );
	ModelReplacement::Insert( "models/p_m40a1.mdl", "models/hlclassic/p_m40a1.mdl" );
	ModelReplacement::Insert( "models/p_uzi.mdl", "models/hlclassic/p_uzi.mdl" );
	ModelReplacement::Insert( "models/p_2uzis.mdl", "models/hlclassic/p_2uzis.mdl" );
	ModelReplacement::Insert( "models/p_minigunidle.mdl", "models/hlclassic/p_minigunidle.mdl" );
	ModelReplacement::Insert( "models/p_minigunspin.mdl", "models/hlclassic/p_minigunspin.mdl" );
	
	ModelReplacement::Insert( "models/w_uzi_gold.mdl", "models/hlclassic/w_uzi_gold.mdl" );
	ModelReplacement::Insert( "models/w_2uzis_gold.mdl", "models/hlclassic/w_2uzis_gold.mdl" );
	ModelReplacement::Insert( "models/p_uzi_gold.mdl", "models/hlclassic/p_uzi_gold.mdl" );
	ModelReplacement::Insert( "models/p_2uzis_gold.mdl", "models/hlclassic/p_2uzis_gold.mdl" );
	
	ModelReplacement::Insert( "models/sc_insecure/v_uzi.mdl", "models/sc_insecure/hlclassic/v_uzi.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/v_minigun.mdl", "models/sc_insecure/hlclassic/v_minigun.mdl" );
	
	ModelReplacement::Insert( "models/saw_shell.mdl", "models/hlclassic/saw_shell.mdl" );
	ModelReplacement::Insert( "models/barney_vest.mdl", "models/sc_insecure/hlclassic/barney_vest.mdl" );
	ModelReplacement::Insert( "models/barney_helmet.mdl", "models/sc_insecure/hlclassic/barney_helmet.mdl" );


	//// npc model replacements
	
	ModelReplacement::Insert( "models/sc_insecure/zombie_barney.mdl", "models/sc_insecure/hlclassic/zombie_barney.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/zombie_soldier.mdl", "models/sc_insecure/hlclassic/zombie_soldier.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/zombie_worker.mdl", "models/sc_insecure/hlclassic/zombie_worker.mdl" );
	ModelReplacement::Insert( "models/gonome.mdl", "models/opfor/gonome.mdl" );
	
	ModelReplacement::Insert( "models/sc_insecure/hgrunt_saw.mdl", "models/sc_insecure/hlclassic/hgrunt_saw.mdl" );
	ModelReplacement::Insert( "models/hwgrunt.mdl", "models/sc_insecure/hlclassic/hwgrunt.mdl" );
	ModelReplacement::Insert( "models/massn.mdl", "models/sc_insecure/hlclassic/massn.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/hassassin_akimbo.mdl", "models/sc_insecure/hlclassic/hassassin_akimbo.mdl" );
	
    ModelReplacement::Insert( "models/sc_insecure/islave.mdl", "models/sc_insecure/hlclassic/islave.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/agrunt_premature.mdl", "models/sc_insecure/hlclassic/agrunt_premature.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/bgarg.mdl", "models/sc_insecure/hlclassic/bgarg.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/controller.mdl", "models/controller.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/controller_ext.mdl", "models/sc_insecure/hlclassic/controller_ext.mdl" );
	
	
	//// environment model replacements
	
	ModelReplacement::Insert( "models/sc_insecure/otis.mdl", "models/sc_insecure/hlclassic/otis.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/bailey.mdl", "models/sc_insecure/hlclassic/bailey.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/adrian.mdl", "models/sc_insecure/hlclassic/adrian.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/cross.mdl", "models/sc_insecure/hlclassic/cross.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/green.mdl", "models/sc_insecure/hlclassic/green.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/hev.mdl", "models/sc_insecure/hlclassic/hev.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/gordon_suit.mdl", "models/bshift/gordon_suit.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/construction.mdl", "models/sc_insecure/hlclassic/construction.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/wrangler.mdl", "models/sc_insecure/hlclassic/wrangler.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/dead_barney.mdl", "models/sc_insecure/hlclassic/dead_barney.mdl" );
    ModelReplacement::Insert( "models/sc_insecure/dead_islave.mdl", "models/sc_insecure/hlclassic/dead_islave.mdl" );
	
	ModelReplacement::Insert( "models/sc_insecure/islave_emp.mdl", "models/sc_insecure/hlclassic/islave_emp.mdl" );
	ModelReplacement::Insert( "models/strooper.mdl", "models/opfor/strooper.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/hgrunt.mdl", "models/sc_insecure/hlclassic/hgrunt.mdl" );
    ModelReplacement::Insert( "models/sc_insecure/commander.mdl", "models/sc_insecure/hlclassic/commander.mdl" );
	
	ModelReplacement::Insert( "models/sc_insecure/cine_apache.mdl", "models/sc_insecure/hlclassic/cine_apache.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/cine_osprey.mdl", "models/sc_insecure/hlclassic/cine_osprey.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/cine_gman.mdl", "models/hlclassic/gman.mdl" );
	
	ModelReplacement::Insert( "models/sc_insecure/headless_zombie.mdl", "models/sc_insecure/hlclassic/headless_zombie.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/hgrunt_leg.mdl", "models/sc_insecure/hlclassic/hgrunt_leg.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/barney_boots.mdl", "models/sc_insecure/hlclassic/barney_boots.mdl" );
	
    ModelReplacement::Insert( "models/sc_insecure/scientist.mdl", "models/sc_insecure/hlclassic/scientist.mdl" );
	ModelReplacement::Insert( "models/bshift/civ_coat_scientist.mdl", "models/sc_insecure/hlclassic/civ_coat_scientist.mdl" );
	ModelReplacement::Insert( "models/bshift/civ_paper_scientist.mdl", "models/sc_insecure/hlclassic/civ_paper_scientist.mdl" );
	ModelReplacement::Insert( "models/bshift/civ_scientist.mdl", "models/sc_insecure/hlclassic/civ_scientist.mdl" );
	ModelReplacement::Insert( "models/bshift/console_civ_scientist.mdl", "models/sc_insecure/hlclassic/console_civ_scientist.mdl" );
	
	ModelReplacement::Insert( "models/sc_insecure/scigun.mdl", "models/sc_insecure/hlclassic/scigun.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/glock.mdl", "models/hlclassic/w_9mmhandgun.mdl" );
	ModelReplacement::Insert( "models/w_9mmar.mdl", "models/hlclassic/w_9mmar.mdl" );
	ModelReplacement::Insert( "models/w_shotgun.mdl", "models/hlclassic/w_shotgun.mdl" );
	ModelReplacement::Insert( "models/w_medkit.mdl" , "models/hlclassic/w_medkit.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/raketia.mdl", "models/hlclassic/w_rpg.mdl" );
	ModelReplacement::Insert( "models/sc_insecure/rpg_can.mdl" , "models/sc_insecure/hlclassic/rpg_can.mdl" );
	ModelReplacement::Insert( "models/w_pipe_wrench.mdl" , "models/sc_insecure/pipe_wrench.mdl" );
	ModelReplacement::Insert( "models/w_tripmine.mdl" , "models/hlclassic/w_tripmine.mdl" );
	ModelReplacement::Insert( "models/shell.mdl" , "models/hlclassic/shell.mdl" );
	ModelReplacement::Insert( "models/shotgunshell.mdl" , "models/hlclassic/shotgunshell.mdl" );
	ModelReplacement::Insert( "models/w_sqknest.mdl" , "models/hlclassic/w_sqknest.mdl" );
	ModelReplacement::Insert( "models/w_gaussammo.mdl" , "models/hlclassic/w_gaussammo.mdl" );
}
}

/*void MapActivate()
{
    auto count = ModelReplacement::ApplyAll( true );
	g_Game.AlertMessage( at_console, "Applied %1 changes of models\n", count );
}*/