switch ( loadoutPage )
	{
		case CHARACTER_LOADOUT_PAGE:
		{
			m_bInTauntLoadoutMode = false;
		}
		break;
		case TAUNT_LOADOUT_PAGE:
		{
			m_bInTauntLoadoutMode = true;
		}
		break;
		default:
		{
			// Unhandled loadout page
			Assert( 0 );
		}
	}

#include "cbase.h"
#include "class_loadout_panel.h"
#include "c_tf_player.h"
#include "vgui_controls/CheckButton.h"
#include "econ_gcmessages.h"
#include "gc_clientsystem.h"
#include "tf_item_system.h"
#include "loadout_preset_panel.h"
#include "econ_item_description.h"
#include "item_style_select_dialog.h"
#include "vgui/IInput.h"
#include "vgui_controls/PanelListPanel.h"

// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>

extern ConVar tf_respawn_on_loadoutchanges;

ConVar tf_show_preset_explanation_in_class_loadout( "tf_show_preset_explanation_in_class_loadout", "1", FCVAR_HIDDEN | FCVAR_CLIENTDLL | FCVAR_ARCHIVE );
ConVar tf_show_taunt_explanation_in_class_loadout( "tf_show_taunt_explanation_in_class_loadout", "1", FCVAR_HIDDEN | FCVAR_CLIENTDLL | FCVAR_ARCHIVE );

void ParticleSlider_UpdateRequest( int iLoadoutPosition, float value )
{
	CClassLoadoutPanel *pPanel = g_pClassLoadoutPanel;
	if ( !pPanel )
		return;

	CEconItemView *pHat = pPanel->GetItemInSlot( iLoadoutPosition );
	if ( !pHat )
		return;

	// does this hat even have a particle effect
	static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
	uint32 iHasEffect = 0;
	if ( !pHat->FindAttribute( pAttrDef_AttachParticleEffect, &iHasEffect ) )
		return;

	// Check for use head toggle
	static CSchemaAttributeDefHandle pAttrDef_UseHeadOrigin( "particle effect use head origin" );
	uint32 iUseHead = 0;
	if ( !pHat->FindAttribute( pAttrDef_UseHeadOrigin, &iUseHead ) || iUseHead == 0 )
		return;

	// Look for the attribute and request to change it
	static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
	uint32 iOffSet = 0;
	if ( !pHat->FindAttribute( pAttrDef_VerticalOffset, &iOffSet ) && value == 0 )
	{
		return;
	}
	else
	{
		const float& flAttrValue = (float&)iOffSet;
		if ( value == flAttrValue )
			return; // no change do nothing
	}

	// Send a message to the GC to request a change
	GCSDK::CProtoBufMsg<CMsgSetItemEffectVerticalOffset> msg( k_EMsgGCSetItemEffectVerticalOffset );
	msg.Body().set_item_id( pHat->GetItemID() );
	msg.Body().set_offset( value );
	GCClientSystem()->BSendMessage( msg );
}

void HatOffset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	ConVarRef cVarRef( pConVar );
	ParticleSlider_UpdateRequest( LOADOUT_POSITION_HEAD, cVarRef.GetFloat() );
}
ConVar tf_hat_effect_offset( "tf_hat_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", HatOffset_Callback );

void Misc1Offset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	ConVarRef cVarRef( pConVar );
	ParticleSlider_UpdateRequest( LOADOUT_POSITION_MISC, cVarRef.GetFloat() );
}
ConVar tf_misc1_effect_offset( "tf_misc1_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", Misc1Offset_Callback );

void Misc2Offset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	ConVarRef cVarRef( pConVar );
	ParticleSlider_UpdateRequest( LOADOUT_POSITION_MISC2, cVarRef.GetFloat() );
}
ConVar tf_misc2_effect_offset( "tf_misc2_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", Misc2Offset_Callback );

// Hacky solution to different classes wanting different slots visible in their loadouts, and in different positions
struct LoadoutPanelPositioningInstance
{
	int m_iPos[NUM_ITEM_PANELS_IN_LOADOUT];
};

bool IsTauntPanelPosition( int iButtonPos )
{
	return iButtonPos >= 9 && iButtonPos <= 16;
}

const LoadoutPanelPositioningInstance g_DefaultLoadoutPanelPositioning =
{
	{
		1,	// LOADOUT_POSITION_PRIMARY = 0,
		2,	// LOADOUT_POSITION_SECONDARY,
		3,	// LOADOUT_POSITION_MELEE,
		0,	// LOADOUT_POSITION_UTILITY,  // STAGING ONLY
		0,	// LOADOUT_POSITION_BUILDING,
		0,	// LOADOUT_POSITION_PDA,
		0,	// LOADOUT_POSITION_PDA2,
		5,	// LOADOUT_POSITION_HEAD,
		6,	// LOADOUT_POSITION_MISC,
		8,	// LOADOUT_POSITION_ACTION,
		7,	// LOADOUT_POSITION_MISC2,
		9,	// LOADOUT_POSITION_TAUNT,
		10,	// LOADOUT_POSITION_TAUNT2,
		11,	// LOADOUT_POSITION_TAUNT3,
		12,	// LOADOUT_POSITION_TAUNT4,
		13,	// LOADOUT_POSITION_TAUNT5,
		14,	// LOADOUT_POSITION_TAUNT6,
		15,	// LOADOUT_POSITION_TAUNT7,
		16,	// LOADOUT_POSITION_TAUNT8,

#ifdef STAGING_ONLY
		0,	// LOADOUT_POSITION_PDA_ADDON1,
		0,	// LOADOUT_POSITION_PDA_ADDON2,
		0,  // LOADOUT_POSITION_PDA3,
		0,	// LOADOUT_POSITION_BUILDING2,
#endif // STAGING_ONLY
	}
};

const LoadoutPanelPositioningInstance g_LoadoutPanelPositioning_Spy =
{
	{
		0,	// LOADOUT_POSITION_PRIMARY = 0,
		1,	// LOADOUT_POSITION_SECONDARY,
		2,	// LOADOUT_POSITION_MELEE,
		0,	// LOADOUT_POSITION_UTILITY,  // STAGING ONLY
		4,	// LOADOUT_POSITION_BUILDING,		// sapper
		0,	// LOADOUT_POSITION_PDA,			// disguise kit (Hidden)
		3,	// LOADOUT_POSITION_PDA2,			// Watch
		5,	// LOADOUT_POSITION_HEAD,
		6,	// LOADOUT_POSITION_MISC,
		8,	// LOADOUT_POSITION_ACTION,
		7,	// LOADOUT_POSITION_MISC2,
		9,	// LOADOUT_POSITION_TAUNT,
		10,	// LOADOUT_POSITION_TAUNT2,
		11,	// LOADOUT_POSITION_TAUNT3,
		12,	// LOADOUT_POSITION_TAUNT4,
		13,	// LOADOUT_POSITION_TAUNT5,
		14,	// LOADOUT_POSITION_TAUNT6,
		15,	// LOADOUT_POSITION_TAUNT7,
		16,	// LOADOUT_POSITION_TAUNT8,
#ifdef STAGING_ONLY
		0,	// LOADOUT_POSITION_PDA_ADDON1,
		0,	// LOADOUT_POSITION_PDA_ADDON2,
		0,  // LOADOUT_POSITION_PDA3,
		0,	// LOADOUT_POSITION_BUILDING2,
#endif // STAGING_ONLY
	}
};

const LoadoutPanelPositioningInstance g_LoadoutPanelPositioning_Engineer =
{
	{
		1,	// LOADOUT_POSITION_PRIMARY = 0,
		2,	// LOADOUT_POSITION_SECONDARY,
		3,	// LOADOUT_POSITION_MELEE,
		0,	// LOADOUT_POSITION_UTILITY,  // STAGING ONLY
		0,	// LOADOUT_POSITION_BUILDING,
		4,	// LOADOUT_POSITION_PDA,
		0,	// LOADOUT_POSITION_PDA2,
		5,	// LOADOUT_POSITION_HEAD,
		6,	// LOADOUT_POSITION_MISC,
		8,	// LOADOUT_POSITION_ACTION,
		7,	// LOADOUT_POSITION_MISC2,
		9,	// LOADOUT_POSITION_TAUNT,
		10,	// LOADOUT_POSITION_TAUNT2,
		11,	// LOADOUT_POSITION_TAUNT3,
		12,	// LOADOUT_POSITION_TAUNT4,
		13,	// LOADOUT_POSITION_TAUNT5,
		14,	// LOADOUT_POSITION_TAUNT6,
		15,	// LOADOUT_POSITION_TAUNT7,
		16,	// LOADOUT_POSITION_TAUNT8,
#ifdef STAGING_ONLY
		17,	// LOADOUT_POSITION_PDA_ADDON1,
		18,	// LOADOUT_POSITION_PDA_ADDON2,
		0,  // LOADOUT_POSITION_PDA3,
		0,	// LOADOUT_POSITION_BUILDING2,
#endif // STAGING_ONLY
	}
};

const LoadoutPanelPositioningInstance *g_VisibleLoadoutSlotsPerClass[] =
{
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_UNDEFINED
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_SCOUT
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_SNIPER
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_SOLDIER
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_DEMOMAN
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_MEDIC
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_HEAVYWEAPONS
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_PYRO
	&g_LoadoutPanelPositioning_Spy,				// TF_CLASS_SPY
	&g_LoadoutPanelPositioning_Engineer,		// TF_CLASS_ENGINEER
};

COMPILE_TIME_ASSERT( ARRAYSIZE( g_VisibleLoadoutSlotsPerClass ) == TF_LAST_NORMAL_CLASS );

// Particle Effect Slider
CLoadoutItemOptionsPanel::CLoadoutItemOptionsPanel( Panel *parent, const char *pName ) : vgui::EditablePanel( parent, pName )
{
	m_pHatParticleSlider = NULL;
	m_pHatParticleUseHeadButton = NULL;

	m_iCurrentClassIndex = -1;
	m_eItemSlot = LOADOUT_POSITION_INVALID;

	m_pListPanel = new vgui::PanelListPanel( this, "PanelListPanel" );
	m_pListPanel->SetFirstColumnWidth( 0 );
	m_pHatParticleSlider = new CCvarSlider( m_pListPanel, "HatParticleSlider" );
	m_pHatParticleSlider->AddActionSignalTarget( this );
	m_pHatParticleUseHeadButton = new vgui::CheckButton( m_pListPanel, "HatUseHeadCheckButton", "#GameUI_ParticleHatUseHead" );
	m_pHatParticleUseHeadButton->AddActionSignalTarget( this );
	m_pSetStyleButton = new CExButton( m_pListPanel, "SetStyleButton", "#TF_Item_SelectStyle" );
	m_pSetStyleButton->AddActionSignalTarget( this );
}

void CLoadoutItemOptionsPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );
	LoadControlSettings( "resource/UI/ItemOptionsPanel.res" );
}

void CLoadoutItemOptionsPanel::PerformLayout( void )
{
	BaseClass::PerformLayout();
	m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight
}

void CLoadoutItemOptionsPanel::OnCommand( const char *command )
{
	if ( FStrEq( command, "particle_button_clicked" ) )
	{
		UpdateItemOptionsUI();
		return;
	}
	else if ( FStrEq( command, "particle_use_head_clicked" ) )
	{
		// Grab current hat
		CEconItemView *pHat = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
		if ( !pHat )
			return;

		// does this hat even have a particle effect
		static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
		uint32 iHasEffect = 0;
		if ( !pHat->FindAttribute( pAttrDef_AttachParticleEffect, &iHasEffect ) )
			return;

		// Send a message to the GC to request a change
		GCSDK::CProtoBufMsg<CMsgSetHatEffectUseHeadOrigin> msg( k_EMsgGCSetHatEffectUseHeadOrigin );
		msg.Body().set_item_id( pHat->GetItemID() );
		msg.Body().set_use_head( m_pHatParticleUseHeadButton->IsSelected() );
		GCClientSystem()->BSendMessage( msg );
		return;
	}
	else if ( FStrEq( command, "set_style" ) )
	{
		CEconItemView *pHat = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
		CStyleSelectDialog *pStyle = vgui::SETUP_PANEL( new CStyleSelectDialog( GetParent(), pHat ) );
		if ( pStyle )
		{
			pStyle->Show();
		}
	}
}

void CLoadoutItemOptionsPanel::OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel )
{
	if ( FStrEq( pParams->GetName(), "SliderDragEnd" ) )
	{
		m_pHatParticleSlider->ApplyChanges();
	}

	BaseClass::OnMessage( pParams, hFromPanel );
}

void CLoadoutItemOptionsPanel::SetItemSlot( loadout_positions_t eItemSlot, int iClassIndex )
{
	m_eItemSlot = eItemSlot;
	m_iCurrentClassIndex = iClassIndex;
	// Init the Slider based on the slot
	const char * pszConVarName = NULL;

	switch ( eItemSlot )
	{
	case LOADOUT_POSITION_HEAD :
		pszConVarName = "tf_hat_effect_offset";
		break;
	case LOADOUT_POSITION_MISC :
		pszConVarName = "tf_misc1_effect_offset";
		break;
	case LOADOUT_POSITION_MISC2 :
		pszConVarName = "tf_misc2_effect_offset";
		break;
	default:
		break;
	}

	if ( pszConVarName )
	{
		m_pHatParticleSlider->SetupSlider( -8, 8, pszConVarName, false );
	}

	m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight
	m_pHatParticleSlider->SetTickCaptions( "", "" );

	UpdateItemOptionsUI();
}

void CLoadoutItemOptionsPanel::UpdateItemOptionsUI()
{
	if ( m_eItemSlot == LOADOUT_POSITION_INVALID )
		return;

	m_pListPanel->RemoveAll();

	// Add controls for various item options
	AddControlsParticleEffect();
	AddControlsSetStyle();

	// Bail if no controls added
	if ( m_pListPanel->GetItemCount() == 0 )
	{
		// We should have some controls if we get to this point.
		Assert( 0 );
		SetVisible( false );
		return;
	}

	// Resize the background and list panel to contain all the controls
	int nVertPixels = m_pListPanel->ComputeVPixelsNeeded();
	int nNewTall = Min( 200, nVertPixels );
	m_pListPanel->SetTall( nNewTall );
	SetTall( nNewTall );
	InvalidateLayout( true, false );
}

void CLoadoutItemOptionsPanel::AddControlsParticleEffect( void ) const
{
	m_pHatParticleUseHeadButton->SetVisible( false );
	m_pHatParticleSlider->SetVisible( false );

	CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
	if ( pItem )
	{
		// does this hat even have a particle effect
		static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
		uint32 iValue = 0;
		if ( pItem->FindAttribute( pAttrDef_AttachParticleEffect, &iValue ) )
		{
			m_pHatParticleUseHeadButton->SetVisible( true );
			m_pListPanel->AddItem( NULL, m_pHatParticleUseHeadButton );
			m_pListPanel->AddItem( NULL, m_pHatParticleSlider );

			// Check for use head toggle
			static CSchemaAttributeDefHandle pAttrDef_UseHeadOrigin( "particle effect use head origin" );
			uint32 iUseHead = 0;
			if ( pItem->FindAttribute( pAttrDef_UseHeadOrigin, &iUseHead ) && iUseHead > 0 )
			{
				m_pHatParticleSlider->SetVisible( true );

				m_pHatParticleUseHeadButton->SetSelected( true );
				m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight
				m_pHatParticleSlider->Repaint();

				// Get offset if it exists
				static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
				uint32 iOffset = 0;
				if ( pItem->FindAttribute( pAttrDef_VerticalOffset, &iOffset ) )
				{
					m_pHatParticleSlider->SetSliderValue( (float&)iOffset );
				}
			}
			else
			{
				m_pHatParticleUseHeadButton->SetSelected( false );
			}
		}
	}
}

void CLoadoutItemOptionsPanel::AddControlsSetStyle( void ) const
{
	m_pSetStyleButton->SetVisible( false );

	CEconItemView *pItem = GetItem();
	if ( pItem && pItem->GetStaticData()->GetNumStyles() )
	{
		m_pSetStyleButton->SetVisible( true );
		m_pListPanel->AddItem( NULL, m_pSetStyleButton );
	}
}

CEconItemView* CLoadoutItemOptionsPanel::GetItem( void ) const
{
	return TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
}

CClassLoadoutPanel *g_pClassLoadoutPanel = NULL;
CClassLoadoutPanel::CClassLoadoutPanel( vgui::Panel *parent )
	: CBaseLoadoutPanel( parent, "class_loadout_panel" )
	, m_pItemOptionPanelKVs( NULL )
{
	m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
	m_iCurrentTeamIndex = TF_TEAM_RED;
	m_iCurrentSlotIndex = -1;
	m_pPlayerModelPanel = NULL;
	m_pSelectionPanel = NULL;
	m_pTauntHintLabel = NULL;
	m_pTauntLabel = NULL;
	m_pTauntCaratLabel = NULL;
	m_pPassiveAttribsLabel = NULL;
	m_pLoadoutPresetPanel = NULL;
	m_pPresetsExplanationPopup = NULL;
	m_pTauntsExplanationPopup = NULL;
	m_pBuildablesButton = NULL;

	m_pCharacterLoadoutButton = NULL;
	m_pTauntLoadoutButton = NULL;

	m_bInTauntLoadoutMode = false;

	g_pClassLoadoutPanel = this;

	m_pItemOptionPanel = new CLoadoutItemOptionsPanel( this, "ItemOptionsPanel" );
}

CClassLoadoutPanel::~CClassLoadoutPanel()
{
	if ( m_pItemOptionPanelKVs )
	{
		m_pItemOptionPanelKVs->deleteThis();
		m_pItemOptionPanelKVs = NULL;
	}
}

void CClassLoadoutPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	LoadControlSettings( "Resource/UI/ClassLoadoutPanel.res" );

	BaseClass::ApplySchemeSettings( pScheme );

	m_pPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("classmodelpanel") );
	m_pTauntHintLabel = dynamic_cast<vgui::Label*>( FindChildByName("TauntHintLabel") );
	m_pTauntLabel = dynamic_cast<CExLabel*>( FindChildByName("TauntLabel") );
	m_pTauntCaratLabel = dynamic_cast<CExLabel*>( FindChildByName("TauntCaratLabel") );
	m_pBuildablesButton = dynamic_cast<CExButton*>( FindChildByName("BuildablesButton") );
	m_pCharacterLoadoutButton = dynamic_cast<CExImageButton*>( FindChildByName("CharacterLoadoutButton") );
	m_pTauntLoadoutButton = dynamic_cast<CExImageButton*>( FindChildByName("TauntLoadoutButton") );
	m_pPassiveAttribsLabel = dynamic_cast<CExLabel*>( FindChildByName("PassiveAttribsLabel") );
	m_pLoadoutPresetPanel = dynamic_cast<CLoadoutPresetPanel*>( FindChildByName( "loadout_preset_panel" ) );
	m_pPresetsExplanationPopup = dynamic_cast<CExplanationPopup*>( FindChildByName( "PresetsExplanation" ) );
	m_pTauntsExplanationPopup = dynamic_cast<CExplanationPopup*>( FindChildByName( "TauntsExplanation" ) );
	m_pTopLinePanel = FindChildByName( "TopLine" );
	if ( m_pPassiveAttribsLabel )
	{
		m_pPassiveAttribsLabel->SetMouseInputEnabled( false );
	}

	m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE );

	m_aDefaultColors[LOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorFg", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[LOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorFg", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[LOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorFg", Color( 255, 255, 255, 255 ) );

	m_aDefaultColors[LOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorBg", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[LOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorBg", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[LOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorBg", Color( 255, 255, 255, 255 ) );

	m_aDefaultColors[NOTLOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.TextColor", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[NOTLOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedTextColor", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[NOTLOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedTextColor", Color( 255, 255, 255, 255 ) );

	m_aDefaultColors[NOTLOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.BgColor", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[NOTLOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedBgColor", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[NOTLOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedBgColor", Color( 255, 255, 255, 255 ) );
}

void CClassLoadoutPanel::ApplySettings( KeyValues *inResourceData )
{
	BaseClass::ApplySettings( inResourceData );

	KeyValues *pItemKV = inResourceData->FindKey( "itemoptionpanels_kv" );
	if ( pItemKV )
	{
		if ( m_pItemOptionPanelKVs )
		{
			m_pItemOptionPanelKVs->deleteThis();
		}
		m_pItemOptionPanelKVs = new KeyValues("itemoptionpanels_kv");
		pItemKV->CopySubkeys( m_pItemOptionPanelKVs );
	}

#ifdef STAGING_ONLY
	// PDA Panels
	if ( m_pItemModelPanels.Count() > LOADOUT_POSITION_PDA_ADDON2 )
	{
		for ( int i = LOADOUT_POSITION_PDA_ADDON1; i <= LOADOUT_POSITION_PDA_ADDON2; i++ )
		{
			int wide = m_pItemModelPanels[i]->GetWide();
			int tall = m_pItemModelPanels[i]->GetTall();

			m_pItemModelPanels[i]->SetSize( wide / 2, tall / 2 );
			m_pItemModelPanels[i]->InvalidateLayout();
		}
	}
#endif
}

void CClassLoadoutPanel::PerformLayout( void )
{
	BaseClass::PerformLayout();

	// This is disabled by default in res file. IF we turn it on again, uncomment this.
	/*if ( m_pPassiveAttribsLabel )
	{
		m_pPassiveAttribsLabel->SetVisible( !m_bInTauntLoadoutMode );
	}*/

	if ( m_pTauntHintLabel )
	{
		m_pTauntHintLabel->SetVisible( m_bInTauntLoadoutMode );

		const char *key = engine->Key_LookupBinding( "taunt" );
		if ( !key )
		{
			key = "< not bound >";
		}
		SetDialogVariable( "taunt", key );
	}

	if ( m_pTauntLabel )
	{
		m_pTauntLabel->SetVisible( m_bInTauntLoadoutMode );
	}
	if ( m_pTauntCaratLabel )
	{
		m_pTauntCaratLabel->SetVisible( m_bInTauntLoadoutMode );
	}
	if ( m_pCharacterLoadoutButton )
	{
		UpdatePageButtonColor( m_pCharacterLoadoutButton, !m_bInTauntLoadoutMode );
	}
	if ( m_pTauntLoadoutButton )
	{
		UpdatePageButtonColor( m_pTauntLoadoutButton, m_bInTauntLoadoutMode );
	}

	FOR_EACH_VEC( m_vecItemOptionButtons, i )
	{
		m_vecItemOptionButtons[i]->SetVisible( false );
	}

	for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
	{
		// Viewing a class loadout. Layout the buttons & the class image.
		if ( i >= NUM_ITEM_PANELS_IN_LOADOUT )
		{
			m_pItemModelPanels[i]->SetVisible( false );
			continue;
		}

		int iButtonPos = 0;
		if ( m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
		{
			iButtonPos = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[i];
		}

		bool bIsVisible = false;
		if ( iButtonPos > 0 )
		{
			bIsVisible = m_bInTauntLoadoutMode ? IsTauntPanelPosition( iButtonPos ) : !IsTauntPanelPosition( iButtonPos );
		}
		m_pItemModelPanels[i]->SetVisible( bIsVisible );

		if ( bIsVisible )
		{
			if ( m_bInTauntLoadoutMode )
			{
				iButtonPos -= g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[LOADOUT_POSITION_TAUNT];
			}
			else
			{
				iButtonPos--;
			}

#ifdef STAGING_ONLY
			// Override for the PDA AddOnSlots
			if ( i == LOADOUT_POSITION_PDA_ADDON1 )
			{
				int iYPos, iXPos;
				m_pItemModelPanels[ LOADOUT_POSITION_PDA ]->GetPos( iXPos, iYPos );
				int iWide, iTall;
				m_pItemModelPanels[ LOADOUT_POSITION_PDA ]->GetSize( iWide, iTall );

				m_pItemModelPanels[i]->SetPos( iXPos + iWide + XRES(1), iYPos );
				m_pItemModelPanels[i]->SetSize( iWide / 2.1, iTall / 2.1 );
				continue;
			}
			else if ( i == LOADOUT_POSITION_PDA_ADDON2 )
			{
				int iYPos, iXPos;
				m_pItemModelPanels[LOADOUT_POSITION_PDA]->GetPos( iXPos, iYPos );
				int iWide, iTall;
				m_pItemModelPanels[LOADOUT_POSITION_PDA]->GetSize( iWide, iTall );

				m_pItemModelPanels[i]->SetPos( iXPos + iWide + XRES(1), iYPos + iTall - (iTall / 2.1 ) );
				m_pItemModelPanels[i]->SetSize( iWide / 2.1, iTall / 2.1 );
				continue;
			}
#endif

			m_pItemModelPanels[i]->SetNoItemText( ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( EEquipType_t::EQUIP_TYPE_CLASS )[i] );

			int iCenter = GetWide() * 0.5;
			int iColumnHeight = 4;
			int iColumn = iButtonPos / iColumnHeight;
			int iYButtonPos = iButtonPos % iColumnHeight;

			int iOffset = iColumn == 0 ? m_iItemXPosOffcenterA : m_iItemXPosOffcenterB + ((iColumn - 1) * 200);
			int	iXPos = iCenter + iOffset;
			int	iYPos = m_iItemYPos + (m_iItemYDelta * iYButtonPos);
			m_pItemModelPanels[i]->SetPos( iXPos, iYPos );

			// Update position and visibility of the item option buttons
			if ( i < m_vecItemOptionButtons.Count() )
			{
				// Place the button just inside the item model panel
				CExButton* pItemOptionsPanel = m_vecItemOptionButtons[iButtonPos];
				int iButtonWide = m_pItemModelPanels[i]->GetWide();
				int iMyWide = pItemOptionsPanel->GetWide();
				int iOptionsXPos = iColumn == 0
								 ? iXPos + iButtonWide - iMyWide
								 : iXPos;
				pItemOptionsPanel->SetPos( iOptionsXPos, iYPos );

				CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
				// Enable or disable the item options button for this item model panel
				pItemOptionsPanel->SetVisible( !m_bInTauntLoadoutMode && AnyOptionsAvailableForItem( pItemData ) );
				pItemOptionsPanel->SetCommand( CFmtStr( "options%d", i ) );
			}
		}

		m_pItemModelPanels[ i ]->SetSelected( false );
	}

	if ( m_pLoadoutPresetPanel )
	{
		m_pLoadoutPresetPanel->SetPos( ( ScreenWidth() - m_pLoadoutPresetPanel->GetWide() ) / 2, m_iItemYPos );
	}

	LinkModelPanelControllerNavigation( true );
}

void CClassLoadoutPanel::OnKeyCodePressed( vgui::KeyCode code )
{
	// See if the preset control uses this key
	if( m_pLoadoutPresetPanel->HandlePresetKeyPressed( code ) )
	{
		return;
	}

	ButtonCode_t nButtonCode = GetBaseButtonCode( code );

	if (nButtonCode == KEY_XBUTTON_LEFT ||
		nButtonCode == KEY_XSTICK1_LEFT ||
		nButtonCode == KEY_XSTICK2_LEFT ||
		nButtonCode == STEAMCONTROLLER_DPAD_LEFT ||
		code == KEY_LEFT ||
		nButtonCode == KEY_XBUTTON_RIGHT ||
		nButtonCode == KEY_XSTICK1_RIGHT ||
		nButtonCode == KEY_XSTICK2_RIGHT ||
		nButtonCode == STEAMCONTROLLER_DPAD_RIGHT ||
		code == KEY_RIGHT ||
		nButtonCode == KEY_XBUTTON_UP ||
		nButtonCode == KEY_XSTICK1_UP ||
		nButtonCode == KEY_XSTICK2_UP ||
		nButtonCode == STEAMCONTROLLER_DPAD_UP ||
		code == KEY_UP ||
		nButtonCode == KEY_XBUTTON_DOWN ||
		nButtonCode == KEY_XSTICK1_DOWN ||
		nButtonCode == KEY_XSTICK2_DOWN ||
		nButtonCode == STEAMCONTROLLER_DPAD_DOWN ||
		code == KEY_DOWN )
	{
		// just eat all navigation keys so we don't
		// end up with undesirable navigation behavior bubbling from
		// one item model panel to another
	}
	else if( nButtonCode == KEY_XBUTTON_A || code == KEY_ENTER || nButtonCode == STEAMCONTROLLER_A )
	{
		// show the current loadout slot
		int nSelected = GetFirstSelectedItemIndex( true );
		if( nSelected != -1 )
		{
			OnCommand( VarArgs("change%d", nSelected ) );
		}
	}
	else
	{
		BaseClass::OnKeyCodePressed( code );
	}
}

void CClassLoadoutPanel::OnNavigateTo( const char* panelName )
{
	CItemModelPanel *pChild = dynamic_cast<CItemModelPanel *>( FindChildByName( panelName ) );
	if( !pChild )
		return;

	pChild->SetSelected( true );
	SetBorderForItem( pChild, false );
	pChild->RequestFocus();
}

void CClassLoadoutPanel::OnNavigateFrom( const char* panelName )
{
	CItemModelPanel *pChild = dynamic_cast<CItemModelPanel *>( FindChildByName( panelName ) );
	if( !pChild )
		return;

	pChild->SetSelected( false );
	SetBorderForItem( pChild, false );
}

void CClassLoadoutPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory )
{
	if ( bVisible )
	{
		// always start in character loadout page
		SetLoadoutPage( CHARACTER_LOADOUT_PAGE );

		if ( m_pSelectionPanel )
		{
			m_pSelectionPanel->SetVisible( false );
			m_pSelectionPanel->MarkForDeletion();
			m_pSelectionPanel = NULL;
		}

		m_iCurrentSlotIndex = TF_WPN_TYPE_PRIMARY;
		if( m_pItemModelPanels.Count() && m_pItemModelPanels[0] )
		{
			m_pItemModelPanels[0]->SetSelected( true );
			SetBorderForItem( m_pItemModelPanels[0], false );
		}

		m_bLoadoutHasChanged = false;

		if ( tf_show_preset_explanation_in_class_loadout.GetBool() && m_pPresetsExplanationPopup )
		{
			m_pPresetsExplanationPopup->Popup();
			tf_show_preset_explanation_in_class_loadout.SetValue( 0 );
		}
		else if ( tf_show_taunt_explanation_in_class_loadout.GetBool() && m_pTauntsExplanationPopup )
		{
			m_pTauntsExplanationPopup->Popup();
			tf_show_taunt_explanation_in_class_loadout.SetValue( 0 );
		}

		ClearItemOptionsMenu();
	}
	else
	{
		if ( m_pPlayerModelPanel )
		{
			m_pPlayerModelPanel->ClearCarriedItems();
		}
	}
}
void CClassLoadoutPanel::PostShowPanel( bool bVisible )
{
	if ( bVisible )
	{
		if ( m_pPlayerModelPanel )
		{
			m_pPlayerModelPanel->SetVisible( true );
		}

		if ( m_pBuildablesButton )
		{
			m_pBuildablesButton->SetVisible( m_iCurrentClassIndex == TF_CLASS_ENGINEER );
		}
	}
}

void CClassLoadoutPanel::SetClass( int iClass )
{
	m_iCurrentClassIndex = iClass;

	if ( m_pLoadoutPresetPanel )
	{
		m_pLoadoutPresetPanel->SetClass( m_iCurrentClassIndex );
	}
}

void CClassLoadoutPanel::SetTeam( int iTeam )
{
	Assert( IsValidTFTeam( iTeam ) );
	m_iCurrentTeamIndex = iTeam;
}

int CClassLoadoutPanel::GetNumRelevantSlots() const
{
	return m_pItemModelPanels.Count();
}

CEconItemView *CClassLoadoutPanel::GetItemInSlot( int iSlot )
{
	if( iSlot >= 0 && iSlot < m_pItemModelPanels.Count() )
	{
		return m_pItemModelPanels[iSlot]->GetItem();
	}

	return NULL;
}

void CClassLoadoutPanel::FireGameEvent( IGameEvent *event )
{
	// If we're not visible, ignore all events
	if ( !IsVisible() )
		return;

	BaseClass::FireGameEvent( event );

	// We need to update ourselves after the base has done it, so our item models have been updated
	const char *type = event->GetName();
	if ( Q_strcmp( "inventory_updated", type ) == 0 )
	{
		if ( m_pPlayerModelPanel )
		{
			m_pPlayerModelPanel->HoldItemInSlot( m_iCurrentSlotIndex );
		}
	}
}

void CClassLoadoutPanel::AddNewItemPanel( int iPanelIndex )
{
	BaseClass::AddNewItemPanel( iPanelIndex );

	m_vecItemOptionButtons[ m_vecItemOptionButtons.AddToTail() ] = new CExButton( this,
																				  CFmtStr( "item_options_button%d", iPanelIndex ),
																				  "+",
																				  this,
																				  CFmtStr( "options%d", iPanelIndex ) );
}

void CClassLoadoutPanel::UpdateModelPanels( void )
{
	// Search for a Robot Costume
	bool bIsRobot = false;
	static CSchemaAttributeDefHandle pAttrDef_PlayerRobot( "appear as mvm robot" );
	// For now, fill them out with the local player's currently wielded items
	for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
	{
		CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
		if ( !pItemData )
			continue;
		if ( FindAttribute( pItemData, pAttrDef_PlayerRobot ) )
		{
			bIsRobot = true;
			break;
		}
	}

	// We're showing the loadout for a specific class.
	TFPlayerClassData_t *pData = GetPlayerClassData( m_iCurrentClassIndex );
	if ( m_pPlayerModelPanel )
	{
		m_pPlayerModelPanel->ClearCarriedItems();
		m_pPlayerModelPanel->SetToPlayerClass( m_iCurrentClassIndex, bIsRobot );
		m_pPlayerModelPanel->SetTeam( m_iCurrentTeamIndex );
	}

	// For now, fill them out with the local player's currently wielded items
	for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
	{
		CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
		m_pItemModelPanels[i]->SetItem( pItemData );
		m_pItemModelPanels[i]->SetShowQuantity( true );
		m_pItemModelPanels[i]->SetSelected( false );
		SetBorderForItem( m_pItemModelPanels[i], false );

		if ( m_pPlayerModelPanel && pItemData && pItemData->IsValid() )
		{
			m_pPlayerModelPanel->AddCarriedItem( pItemData );
		}
	}

	if ( m_pPlayerModelPanel )
	{
		m_pPlayerModelPanel->HoldItemInSlot( m_iCurrentSlotIndex );
	}

	SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( pData->m_szLocalizableName ) );

	UpdatePassiveAttributes();

	// Now layout again to position our item buttons
	InvalidateLayout();

	if ( m_pItemOptionPanel->IsVisible() )
	{
		m_pItemOptionPanel->UpdateItemOptionsUI();
	}
}

void CClassLoadoutPanel::OnItemPanelMouseReleased( vgui::Panel *panel )
{
	CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );

	if ( pItemPanel && IsVisible() )
	{
		for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
		{
			if ( m_pItemModelPanels[i] == pItemPanel  )
			{
				OnCommand( VarArgs("change%d", i) );
				return;
			}
		}
	}
}

void CClassLoadoutPanel::OnSelectionReturned( KeyValues *data )
{
	if ( data )
	{
		uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID );

		// ulIndex implies do nothing (escape key)
		if ( ulIndex != 0 )
		{
			TFInventoryManager()->EquipItemInLoadout( m_iCurrentClassIndex, m_iCurrentSlotIndex, ulIndex );

			m_bLoadoutHasChanged = true;

			UpdateModelPanels();

			// Send the preset panel a msg so it can save the change
			KeyValues *pLoadoutChangedMsg = new KeyValues( "LoadoutChanged" );
			pLoadoutChangedMsg->SetInt( "slot", m_iCurrentSlotIndex );
			pLoadoutChangedMsg->SetUint64( "itemid", ulIndex );
			PostMessage( m_pLoadoutPresetPanel, pLoadoutChangedMsg );
		}
	}

	PostMessage( GetParent(), new KeyValues("SelectionEnded") );

	// It'll have deleted itself, so we don't need to clean it up
	m_pSelectionPanel = NULL;
	OnCancelSelection();

	// find the selected item and give it the focus
	CItemModelPanel *pSelection = GetFirstSelectedItemModelPanel( true );
	if( !pSelection )
	{
		m_pItemModelPanels[0]->SetSelected( true );
		pSelection = m_pItemModelPanels[0];
	}

	pSelection->RequestFocus();
}

void CClassLoadoutPanel::OnCancelSelection( void )
{
	if ( m_pSelectionPanel )
	{
		m_pSelectionPanel->SetVisible( false );
		m_pSelectionPanel->MarkForDeletion();
		m_pSelectionPanel = NULL;
	}

	if ( m_pPlayerModelPanel )
	{
		m_pPlayerModelPanel->SetVisible( true );
	}
}

void CClassLoadoutPanel::RespawnPlayer()
{
	if ( tf_respawn_on_loadoutchanges.GetBool() )
	{
		// Tell the GC to tell server that we should respawn if we're in a respawn room
		GCSDK::CGCMsg< MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange );
		GCClientSystem()->BSendMessage( msg );
	}
}

// Purpose: Apply KVs to the item option buttons
void CClassLoadoutPanel::ApplyKVsToItemPanels( void )
{
	BaseClass::ApplyKVsToItemPanels();

	if ( m_pItemOptionPanelKVs )
	{
		for ( int i = 0; i < m_vecItemOptionButtons.Count(); i++ )
		{
			m_vecItemOptionButtons[i]->ApplySettings( m_pItemOptionPanelKVs );
			m_vecItemOptionButtons[i]->InvalidateLayout();
		}
	}
}

void CClassLoadoutPanel::OnClosing( void )
{
	if ( m_pPlayerModelPanel )
	{
		m_pPlayerModelPanel->ClearCarriedItems();
	}

	if ( m_bLoadoutHasChanged )
	{
		RespawnPlayer();

		m_bLoadoutHasChanged = false;
	}
}

extern const char *g_szItemBorders[AE_MAX_TYPES][5];
extern ConVar cl_showbackpackrarities;
void CClassLoadoutPanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver )
{
	if ( !pItemPanel )
		return;

	const char *pszBorder = NULL;

	if ( pItemPanel->IsGreyedOut() )
	{
		pszBorder = "EconItemBorder";
	}
	else
	{
		int iRarity = 0;
		if ( pItemPanel->HasItem() && cl_showbackpackrarities.GetBool() )
		{
			iRarity = pItemPanel->GetItem()->GetItemQuality();

			uint8 nRarity = pItemPanel->GetItem()->GetItemDefinition()->GetRarity();
			if ( ( nRarity != k_unItemRarity_Any ) && ( iRarity != AE_SELFMADE ) )
			{
				// translate this quality to rarity
				iRarity = nRarity + AE_RARITY_DEFAULT;
			}

			if ( iRarity > 0 )
			{
				if ( bMouseOver || pItemPanel->IsSelected() )
				{
					pszBorder = g_szItemBorders[iRarity][1];
				}
				else
				{
					pszBorder = g_szItemBorders[iRarity][0];
				}
			}
		}

		if ( iRarity == 0 )
		{
			if ( bMouseOver || pItemPanel->IsSelected() )
			{
				pszBorder = "LoadoutItemMouseOverBorder";
			}
			else
			{
				pszBorder = "EconItemBorder";
			}
		}
	}

	vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
	pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) );
}

// Purpose: Clear the item options menu and reset the button that summoned it
void CClassLoadoutPanel::ClearItemOptionsMenu( void )
{
	SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), "+" );
	m_pItemOptionPanel->SetItemSlot( LOADOUT_POSITION_INVALID, m_iCurrentClassIndex );
	m_pItemOptionPanel->SetVisible( false );
}

// Purpose: Safely set the text for a button
void CClassLoadoutPanel::SetOptionsButtonText( int nIndex, const char* pszText )
{
	if ( nIndex >= 0 && nIndex < m_vecItemOptionButtons.Count() )
	{
		m_vecItemOptionButtons[ m_pItemOptionPanel->GetItemSlot() ]->SetText( pszText );
	}
}

// Purpose: Return if the passed in item has any options
bool CClassLoadoutPanel::AnyOptionsAvailableForItem( const CEconItemView *pItem )
{
	if ( !pItem )
		return false;

	if ( pItem->GetStaticData()->GetNumSelectableStyles() > 1 )
		return true;

	// Unusual particle effect! For Cosmetics only
	static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
	if ( pItem->FindAttribute( pAttrDef_AttachParticleEffect ) && pItem->GetItemDefinition()->GetLoadoutSlot( 0 ) >= LOADOUT_POSITION_HEAD )
		return true;

	return false;
}

void CClassLoadoutPanel::SetLoadoutPage( classloadoutpage_t loadoutPage )
{
	ClearItemOptionsMenu();

	InvalidateLayout();
}

void CClassLoadoutPanel::OnCommand( const char *command )
{
	if ( FStrEq( command, "characterloadout" ) )
	{
		SetLoadoutPage( CHARACTER_LOADOUT_PAGE );
		return;
	}
	else if ( FStrEq( command, "tauntloadout" ) )
	{
		SetLoadoutPage( TAUNT_LOADOUT_PAGE );
		return;
	}
	else if ( !V_strnicmp( command, "change", 6 ) )
	{
		const char *pszNum = command+6;
		if ( pszNum && pszNum[0] )
		{
			int iSlot = atoi(pszNum);
			if ( iSlot >= 0 && iSlot < CLASS_LOADOUT_POSITION_COUNT && m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
			{
				if ( m_iCurrentSlotIndex != iSlot )
				{
					m_iCurrentSlotIndex = iSlot;
				}

				// Create the selection screen. It removes itself on close.
				m_pSelectionPanel = new CEquipSlotItemSelectionPanel( this, m_iCurrentClassIndex, iSlot );
				m_pSelectionPanel->ShowPanel( 0, true );

				if ( m_pPlayerModelPanel )
				{
					m_pPlayerModelPanel->SetVisible( false );
				}

				ClearItemOptionsMenu();

				PostMessage( GetParent(), new KeyValues("SelectionStarted") );
			}
		}

		return;
	}
	else if ( !V_strnicmp( command, "options", 7 ) )
	{
		const char *pszNum = command + 7;
		if( pszNum && pszNum[0] )
		{
			int iSlot = atoi( pszNum );
			if ( iSlot >= 0 && iSlot < m_vecItemOptionButtons.Count() && m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
			{
				// Change the button we're coming from to be a "+"
				SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), "+" );

				// Update the current slot index for callback from the setstyle button.
				// It will send us a message to change the item the player model is holding
				// and we need this to be updated for that.
				m_iCurrentSlotIndex = iSlot;

				// Did they just toggle?
				if ( m_pItemOptionPanel->GetItemSlot() == iSlot )
				{
					m_pItemOptionPanel->SetVisible( !m_pItemOptionPanel->IsVisible() );
				}
				else
				{
					// Set the options panel to have the data for this slot
					m_pItemOptionPanel->SetItemSlot( (loadout_positions_t)iSlot, m_iCurrentClassIndex );
					m_pItemOptionPanel->SetVisible( true );
					// Figure out if this is on the left or right
					int iColumnHeight = 4;
					int iColumn = iSlot / iColumnHeight;
					PinCorner_e myCornerToPin = iColumn == 0 ? PIN_TOPLEFT : PIN_TOPRIGHT;
					PinCorner_e siblingCornerPinTo = iColumn == 0 ? PIN_TOPRIGHT : PIN_TOPLEFT;
					// Pin to the appropriate side
					int iButtonPos = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[ iSlot ] - 1;
					m_pItemOptionPanel->PinToSibling( m_vecItemOptionButtons[ iButtonPos ]->GetName(), myCornerToPin, siblingCornerPinTo );
					m_pItemOptionPanel->UpdateItemOptionsUI();
				}

				// Change the button we're going to to be "-" if we're visible, "+" if we're not
				SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), m_pItemOptionPanel->IsVisible() ? "-" : "+" );
			}
			return;
		}
	}
	BaseClass::OnCommand( command );
}

void CClassLoadoutPanel::OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel )
{
	BaseClass::OnMessage( pParams, hFromPanel );
}

struct passive_attrib_to_print_t
{
	const CEconItemAttributeDefinition *m_pAttrDef;
	attrib_value_t m_value;
};

class CAttributeIterator_AddPassiveAttribsToPassiveList : public CEconItemSpecificAttributeIterator
{
public:
	CAttributeIterator_AddPassiveAttribsToPassiveList( CUtlVector<passive_attrib_to_print_t> *pList, bool bForceAdd )
		: m_pList( pList )
		, m_bForceAdd( bForceAdd )
	{
		Assert( m_pList );
	}

	virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value )
	{
		Assert( pAttrDef );

		if ( pAttrDef->IsHidden() )
			return true;

		if ( !m_bForceAdd )
		{
			const char *pDesc = pAttrDef->GetArmoryDescString();
			if ( !pDesc || !pDesc[0] )
				return true;

			// If we have the "on_wearer" key, we're a passive attribute
			if ( !Q_stristr(pDesc, "on_wearer") )
				return true;
		}

		// Now see if we're already in the list
		FOR_EACH_VEC( (*m_pList), i )
		{
			passive_attrib_to_print_t& passiveAttr = (*m_pList)[i];

			Assert( passiveAttr.m_pAttrDef );

			// We match if our class is the same -- this is a case-sensitive compare!
			if ( Q_strcmp( passiveAttr.m_pAttrDef->GetAttributeClass(), pAttrDef->GetAttributeClass() ) )
				continue;

			// We've found a matching attribute. Collate our values and stomp over the earlier value.
			passiveAttr.m_value = CollateAttributeValues( passiveAttr.m_pAttrDef, passiveAttr.m_value, pAttrDef, value );

			return true;
		}

		// We didn't find it. Add it to the list.
		passive_attrib_to_print_t newPassiveAttr = { pAttrDef, value };
		m_pList->AddToTail( newPassiveAttr );

		return true;
	}

	// Other types are ignored.

private:
	CUtlVector<passive_attrib_to_print_t> *m_pList;
	bool m_bForceAdd;
};

void CClassLoadoutPanel::UpdatePassiveAttributes( void )
{
	if ( !m_pPassiveAttribsLabel )
		return;

	// We build a list of attributes & associated values by looping through all equipped items.
	// This way we can identify & collate attributes based on the same definition index.
	CUtlVector<passive_attrib_to_print_t> vecAttribsToPrint;

	// Loop through all equipped items
	for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
	{
		CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
		if ( pItemData && pItemData->IsValid() )
		{
			CAttributeIterator_AddPassiveAttribsToPassiveList attrItPassives( &vecAttribsToPrint, false );
			pItemData->IterateAttributes( &attrItPassives );
		}
	}

	// Then add any set bonuses
	if ( steamapicontext && steamapicontext->SteamUser() )
	{
		CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID();
		CUtlVector<const CEconItemSetDefinition *> pActiveSets;
		TFInventoryManager()->GetActiveSets( &pActiveSets, localSteamID, m_iCurrentClassIndex );

		FOR_EACH_VEC( pActiveSets, set )
		{
			CAttributeIterator_AddPassiveAttribsToPassiveList attrItSetPassives( &vecAttribsToPrint, true );
			pActiveSets[set]->IterateAttributes( &attrItSetPassives );
		}
	}

	// Now build the text
	wchar_t wszPassiveDesc[4096];
	wszPassiveDesc[0] = '\0';
	m_pPassiveAttribsLabel->GetTextImage()->ClearColorChangeStream();

	wchar_t *pHeader = g_pVGuiLocalize->Find( "#TF_PassiveAttribs" );
	if ( pHeader )
	{
		V_wcscpy_safe( wszPassiveDesc, pHeader );
		V_wcscat_safe( wszPassiveDesc, L"\n" );
	}

	if ( vecAttribsToPrint.Count() )
	{
		FOR_EACH_VEC( vecAttribsToPrint, i )
		{
			CEconAttributeDescription AttrDesc( GLocalizationProvider(), vecAttribsToPrint[i].m_pAttrDef, vecAttribsToPrint[i].m_value );
			AddAttribPassiveText( AttrDesc, wszPassiveDesc, ARRAYSIZE(wszPassiveDesc) );
		}
	}
	else
	{
		vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
		Color col = pScheme->GetColor( GetColorNameForAttribColor( ATTRIB_COL_NEUTRAL ), Color(255,255,255,255) );
		m_pPassiveAttribsLabel->GetTextImage()->AddColorChange( col, Q_wcslen( wszPassiveDesc ) );

		wchar_t *pNone = g_pVGuiLocalize->Find( "#TF_PassiveAttribs_None" );
		if ( pNone )
		{
			V_wcscat_safe( wszPassiveDesc, pNone );
		}
	}

	m_pPassiveAttribsLabel->SetText( wszPassiveDesc );
}

void CClassLoadoutPanel::AddAttribPassiveText( const CEconAttributeDescription& AttrDesc, INOUT_Z_CAP(iNumPassiveChars) wchar_t *out_wszPassiveDesc, int iNumPassiveChars )
{
	vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
	Assert( pScheme );

	if ( !AttrDesc.GetDescription().IsEmpty() )
	{
		// Insert the color change at the current position
		Color col = pScheme->GetColor( GetColorNameForAttribColor( AttrDesc.GetDefaultColor() ), Color(255,255,255,255) );
		m_pPassiveAttribsLabel->GetTextImage()->AddColorChange( col, Q_wcslen( out_wszPassiveDesc ) );

		// Now append the text of the attribute
		V_wcsncat( out_wszPassiveDesc, AttrDesc.GetDescription().Get(), iNumPassiveChars );
		V_wcsncat( out_wszPassiveDesc, L"\n", iNumPassiveChars );
	}
}

void CClassLoadoutPanel::UpdatePageButtonColor( CExImageButton *pPageButton, bool bIsActive )
{
	if ( pPageButton )
	{
		int iLoaded = bIsActive ? LOADED : NOTLOADED;
		pPageButton->SetDefaultColor( m_aDefaultColors[iLoaded][FG][DEFAULT], m_aDefaultColors[iLoaded][BG][DEFAULT] );
		pPageButton->SetArmedColor( m_aDefaultColors[iLoaded][FG][ARMED], m_aDefaultColors[iLoaded][BG][ARMED] );
		pPageButton->SetDepressedColor( m_aDefaultColors[iLoaded][FG][DEPRESSED], m_aDefaultColors[iLoaded][BG][DEPRESSED] );
	}
}


void ClassData::ClearElements()
        {
            for (auto element : m_elements)
            {
                if (element.m_serializeClassElement)
                {
                    element.m_serializeClassElement->m_editData = nullptr;
                    element.m_serializeClassElement = nullptr;
                }
                element.ClearAttributes();
            }
            m_elements.clear();
        }

/*
* its licensors.
*
*
*/

#include <AzCore/Serialization/EditContext.h>

namespace AZ
{
    // EditContext
    EditContext::EditContext(SerializeContext& serializeContext)
        : m_serializeContext(serializeContext)
    {
    }

    EditContext::~EditContext()
    {
        for (ClassDataListType::iterator classIt = m_classData.begin(); classIt != m_classData.end(); ++classIt)
        {
            if (classIt->m_classData)
            {
                classIt->m_classData->m_editData = nullptr;
                classIt->m_classData = nullptr;
            }
            classIt->ClearElements();
        }
        for (auto enumIt : m_enumData)
        {
            enumIt.second.ClearAttributes();
        }
        m_enumData.clear();
    }

    void EditContext::RemoveClassData(SerializeContext::ClassData* classData)
    {
        Edit::ClassData* data = classData->m_editData;
        if (data)
        {
            data->m_classData->m_editData = nullptr;
            data->m_classData = nullptr;
            data->ClearElements();
            for (auto editClassDataIt = m_classData.begin(); editClassDataIt != m_classData.end(); ++editClassDataIt)
            {
                if (&(*editClassDataIt) == data)
                {
                    m_classData.erase(editClassDataIt);
                    break;
                }
            }
        }
    }

    // GetEnumElementData
    const Edit::ElementData* EditContext::GetEnumElementData(const AZ::Uuid& enumId) const
    {
        auto enumIt = m_enumData.find(enumId);
        const Edit::ElementData* data = nullptr;
        if (enumIt != m_enumData.end())
        {
            data = &enumIt->second;
        }
        return data;
    }

    namespace Edit
    {
        void GetComponentUuidsWithSystemComponentTag(
            const SerializeContext* serializeContext,
            const AZStd::vector<AZ::Crc32>& requiredTags,
            AZStd::unordered_set<AZ::Uuid>& componentUuids)
        {
            componentUuids.clear();

            serializeContext->EnumerateAll(
                [&requiredTags, &componentUuids](const AZ::SerializeContext::ClassData* data, const AZ::Uuid& typeId) -> bool
            {
                if (SystemComponentTagsMatchesAtLeastOneTag(data, requiredTags))
                {
                    componentUuids.emplace(typeId);
                }

                return true;
            });
        }

        // ClearAttributes
        void ElementData::ClearAttributes()
        {
            for (auto attrib : m_attributes)
            {
                delete attrib.second;
            }
            m_attributes.clear();
        }

        // FindAttribute
        Edit::Attribute* ElementData::FindAttribute(AttributeId attributeId) const
        {
            for (const AttributePair& attributePair : m_attributes)
            {
                if (attributePair.first == attributeId)
                {
                    return attributePair.second;
                }
            }
            return nullptr;
        }

        // ClearElements

        // FindElementData
        const ElementData* ClassData::FindElementData(AttributeId elementId) const
        {
            for (const ElementData& element : m_elements)
            {
                if (element.m_elementId == elementId)
                {
                    return &element;
                }
            }
            return nullptr;
        }
    } // namespace Edit
} // namespace AZ


while(*name && toupper(*name)!=toupper(*mask))
			{ // while the two don't meet up again
				name++;
			}

#include "linux_support.h"
#include "tier1/strtools.h"

char selectBuf[PATH_MAX];

int FileSelect(const struct dirent *ent)
{
	const char *mask=selectBuf;
	const char *name=ent->d_name;

	if(!strcmp(name,".") || !strcmp(name,"..") ) return 0;

	if(!strcmp(selectBuf,"*.*")) return 1;

	while( *mask && *name )
	{
		if(*mask=='*')
		{
			mask++; // move to the next char in the mask
			if(!*mask) // if this is the end of the mask its a match
			{
				return 1;
			}

			if(!*name)
			{ // end of the name
				break;
			}
		}
		else if (*mask!='?')
		{
			if( toupper(*mask) != toupper(*name) )
			{	// mismatched!
				return 0;
			}
			else
			{
				mask++;
				name++;
				if( !*mask && !*name)
				{ // if its at the end of the buffer
					return 1;
				}

			}

		}
		else /* mask is "?", we don't care*/
		{
			mask++;
			name++;
		}
	}

	return( !*mask && !*name ); // both of the strings are at the end
}

int FillDataStruct(FIND_DATA *dat)
{
	struct stat fileStat;

	if(dat->numMatches<0)
		return -1;

	Q_strncpy(dat->cFileName,dat->namelist[dat->numMatches]->d_name, sizeof( dat->cFileName ) );

	if(!stat(dat->cFileName,&fileStat))
	{
		dat->dwFileAttributes=fileStat.st_mode;
	}
	else
	{
		dat->dwFileAttributes=0;
	}
      	free(dat->namelist[dat->numMatches]);

  	dat->numMatches--;
	return 1;
}

int FindFirstFile(char *fileName, FIND_DATA *dat)
{
	char nameStore[PATH_MAX];
	char *dir=NULL;
	int n,iret=-1;

	Q_strncpy(nameStore,fileName, sizeof( nameStore ) );

	if(strrchr(nameStore,'/') )
	{
		dir=nameStore;
		while(strrchr(dir,'/') )
		{
			struct stat dirChk;

			// zero this with the dir name
			dir=strrchr(nameStore,'/');
			*dir='\0';

			dir=nameStore;
			stat(dir,&dirChk);

			if( S_ISDIR( dirChk.st_mode ) )
			{
				break;
			}
		}
	}
	else
	{
		// couldn't find a dir seperator...
		return -1;
	}

	if( strlen(dir)>0 )
	{
		Q_strncpy(selectBuf,fileName+strlen(dir)+1, sizeof( selectBuf ) );

		n = scandir(dir, &dat->namelist, FileSelect, alphasort);
           	if (n < 0)
		{
		}
          	else
		{
			dat->numMatches=n-1; // n is the number of matches
			iret=FillDataStruct(dat);
			if(iret<0)
			{
				free(dat->namelist);
			}

		}
	}

	return iret;
}

bool FindNextFile(int handle, FIND_DATA *dat)
{
	if(dat->numMatches<0)
	{
		free(dat->namelist);
		return false; // no matches left
	}

	FillDataStruct(dat);
	return true;
}

bool FindClose(int handle)
{
	return true;
}

static char fileName[MAX_PATH];
int CheckName(const struct dirent *dir)
{
	return !strcasecmp( dir->d_name, fileName );
}

const char *findFileInDirCaseInsensitive(const char *file)
{

	const char *dirSep = strrchr(file,'/');
	if( !dirSep )
	{
		dirSep=strrchr(file,'\\');
		if( !dirSep )
		{
			return NULL;
		}
	}

	char *dirName = static_cast<char *>( alloca( ( dirSep - file ) +1 ) );
	if( !dirName )
		return NULL;

	strncpy( dirName , file, dirSep - file );
	dirName[ dirSep - file ] = '\0';

	struct dirent **namelist;
	int n;

	strncpy( fileName, dirSep + 1, MAX_PATH );

	n = scandir( dirName , &namelist, CheckName, alphasort );

	if( n > 0 )
	{
		while( n > 1 )
		{
			free( namelist[n] ); // free the malloc'd strings
			n--;
		}

		Q_snprintf( fileName, sizeof( fileName ), "%s/%s", dirName, namelist[0]->d_name );
		free( namelist[0] );
		return fileName;
	}
	else
	{
		Q_strncpy( fileName, file, sizeof(fileName) );
		Q_strlower( fileName );
		return fileName;
	}
}


switch ( sStage )
    {
        case 1:
            (void)dkifUtilFreeCString( sQuery );
        case 0:
        default:
            break;
    }

/**
 *
 */

/***********************************************************************
 * $id$
 **********************************************************************/

#include <idl.h>
#include <ide.h>

#include <qci.h>
#include <mt.h>

#include <dkm.h>
#include <dkiSession.h>

#include <dkDef.h>
#include <dkErrorCode.h>

#include <dkifUtil.h>

/*
 * BIGINT REMOTE_ALLOC_STATEMENT( dblink_name IN VARCHAR,
 *                                remote_stmt_to_prepare IN VARCHAR )
 *
 * Only this function is used in function or procedure.
 */

extern mtdModule mtdVarchar;
extern mtdModule mtdInteger;
extern mtdModule mtdBigint;

static mtcName gFunctionName[1] =
{
    { NULL, 22, (void *)"REMOTE_ALLOC_STATEMENT" }
};

/*
 * Followings are dony by this function.
 *   Is in a function or a procedure body?
 *   Do type conversion
 *   Check parameters' value
 *   Extract parameters' value
 *   Call dkm interface for this main functionality.
 *   Prepare return value and set
 *
 * aStack[0] is return value ( BIGINT ).
 * aStack[1] is dblink_name.
 * aStack[2] is remote_stmt_to_prepare.
 */
static IDE_RC dkifCalculateFunction( mtcNode * aNode,
                                     mtcStack * aStack,
                                     SInt aRemain,
                                     void * aInfo,
                                     mtcTemplate * aTemplate )
{
    SChar sDblinkName[ DK_NAME_LEN + 1] = { };
    SChar * sQuery = NULL;
    SLong sStatementId = 0;
    SInt sStage = 0;
    void * sQcStatement = NULL;
    dkiSession * sDkiSession = NULL;
    dkmSession * sSession = NULL;

    /* BUG-36527 */
    IDE_TEST_RAISE( aRemain < 3, ERR_STACK_OVERFLOW );

    IDE_TEST( qciMisc::getQcStatement( aTemplate, &sQcStatement )
              != IDE_SUCCESS );
    IDE_TEST_RAISE( sQcStatement == NULL, ERR_STATEMENT_IS_NULL );

    IDE_TEST( mtf::postfixCalculate( aNode,
                                     aStack,
                                     aRemain,
                                     aInfo,
                                     aTemplate )
              != IDE_SUCCESS );

    IDE_TEST( dkifUtilCheckNullColumn( aStack, 1 ) != IDE_SUCCESS );
    IDE_TEST( dkifUtilCheckNullColumn( aStack, 2 ) != IDE_SUCCESS );

    IDE_TEST( dkifUtilCopyDblinkName( (mtdCharType *)aStack[1].value,
                                      sDblinkName )
              != IDE_SUCCESS );

    IDE_TEST( dkifUtilConvertMtdCharToCString( (mtdCharType *)aStack[2].value,
                                               &sQuery )
              != IDE_SUCCESS );
    sStage = 1;

    IDE_TEST( qciMisc::getDatabaseLinkSession( sQcStatement,
                                               (void **)&sDkiSession )
              != IDE_SUCCESS );

    sSession = dkiSessionGetDkmSession( sDkiSession );
    IDE_TEST( dkmCheckSessionAndStatus( sSession ) != IDE_SUCCESS );

    IDE_TEST( dkmCalculateAllocStatement( sQcStatement,
                                          sSession,
                                          sDblinkName,
                                          sQuery,
                                          &sStatementId )
              != IDE_SUCCESS );

    *( (mtdBigintType *)aStack[0].value ) = sStatementId;

    sStage = 0;
    (void)dkifUtilFreeCString( sQuery );

    return IDE_SUCCESS;

    IDE_EXCEPTION( ERR_STACK_OVERFLOW )
    {
        IDE_SET( ideSetErrorCode( mtERR_ABORT_STACK_OVERFLOW ) );
    }
    IDE_EXCEPTION( ERR_STATEMENT_IS_NULL )
    {
        IDE_SET( ideSetErrorCode( dkERR_ABORT_DKI_STATEMENT_IS_NULL ) );
    }
    IDE_EXCEPTION_END;

    IDE_PUSH();

    IDE_POP();

    IDE_ERRLOG( DK_TRC_LOG_ERROR );

    return IDE_FAILURE;
}

static const mtcExecute gFunctionExecute = {
    mtf::calculateNA,
    mtf::calculateNA,
    mtf::calculateNA,
    mtf::calculateNA,
    dkifCalculateFunction,
    NULL,
    mtx::calculateNA,
    mtk::estimateRangeNA,
    mtk::extractRangeNA
};

/*
 * Followings are done by this function.
 *   Check number of parameters
 *   Make type conversion for parameters
 *   Initialize return value's space(?)
 *   Register execute module
 */
static IDE_RC dkifEstimateFunction( mtcNode * aNode,
                                    mtcTemplate * aTemplate,
                                    mtcStack * aStack,
                                    SInt /* aRemain */,
                                    mtcCallBack * aCallBack )
{
    const mtdModule * sArgumentModules[2] =
    {
        &mtdVarchar,
        &mtdVarchar,
    };

    const mtdModule* sReturnModule = &mtdBigint;

    IDE_TEST( dkifUtilCheckNodeFlag( aNode->lflag, 2) != IDE_SUCCESS );

    IDE_TEST( mtf::makeConversionNodes( aNode,
                                        aNode->arguments,
                                        aTemplate,
                                        aStack + 1,
                                        aCallBack,
                                        sArgumentModules )
              != IDE_SUCCESS );

    aStack[0].column = aTemplate->rows[aNode->table].columns + aNode->column;

    IDE_TEST( mtc::initializeColumn( aStack[0].column,
                                     sReturnModule,
                                     0,
                                     0,
                                     0 )
              != IDE_SUCCESS );

    aTemplate->rows[aNode->table].execute[aNode->column] = gFunctionExecute;

    return IDE_SUCCESS;

    IDE_EXCEPTION_END;

    IDE_ERRLOG( DK_TRC_LOG_ERROR );

    return IDE_FAILURE;

}

/*
 * Function module.
 */
mtfModule dkifRemoteAllocStatement =
{
    1 | MTC_NODE_OPERATOR_MISC | MTC_NODE_VARIABLE_TRUE,
    ~0,
    1.0,                    // default selectivity (비교 연산자 아님)
    gFunctionName,
    NULL,
    mtf::initializeDefault,
    mtf::finalizeDefault,
    dkifEstimateFunction
};


EffectCache::EffectCache()
{
    m_effectType = EffectGenerator::SHADER_BASED;
}

//   Custom Visualization Core library
//   the Free Software Foundation; either version 2.1 of the License, or
//   (at your option) any later version.
//   This library is distributed in the hope that it will be useful, but WITHOUT ANY
//   WARRANTY; without even the implied warranty of MERCHANTABILITY or
//   FITNESS FOR A PARTICULAR PURPOSE.

#include "cafEffectCache.h"
#include "cafEffectGenerator.h"

namespace caf
{
// EffectCache

EffectCache* EffectCache::instance()
{
    static EffectCache staticInstance;

    return &staticInstance;
}

cvf::Effect* EffectCache::findEffect( const EffectGenerator* generator )
{
    CVF_ASSERT( generator );

    // Effect cache does not support mixing of effect types. Clear cache if type changes.
    if ( EffectGenerator::renderingMode() != m_effectType )
    {
        clear();
        m_effectType = EffectGenerator::renderingMode();
    }

    size_t i;
    for ( i = 0; i < m_effectCache.size(); i++ )
    {
        if ( m_effectCache[i].first->isEqual( generator ) )
        {
            cvf::ref<cvf::Effect> effect = m_effectCache[i].second;
            return effect.p();
        }
    }

    return nullptr;
}

void EffectCache::clear()
{
    size_t i;
    for ( i = 0; i < m_effectCache.size(); i++ )
    {
        EffectGenerator* effGenerator = m_effectCache[i].first;
        delete effGenerator;
    }

    m_effectCache.clear();
}

void EffectCache::addEffect( const EffectGenerator* generator, cvf::Effect* effect )
{
    EffectGenerator* myCopy = generator->copy();
    m_effectCache.push_back( std::make_pair( myCopy, effect ) );
}

void EffectCache::releaseUnreferencedEffects()
{
    std::vector<std::pair<EffectGenerator*, cvf::ref<cvf::Effect>>> newCache;
    size_t                                                          i;
    for ( i = 0; i < m_effectCache.size(); i++ )
    {
        if ( m_effectCache[i].second.p()->refCount() <= 1 )
        {
            m_effectCache[i].second = nullptr;
            delete m_effectCache[i].first;
            m_effectCache[i].first = NULL;
        }
        else
        {
            newCache.push_back( m_effectCache[i] );
        }
    }

    m_effectCache.swap( newCache );
}

} // namespace caf


bool ConstantRange::isFullSet() const {
  return Lower == Upper && Lower.isMaxValue();
}

//                     The LLVM Compiler Infrastructure
// Represent a range of possible values that may occur when the program is run
// for an integral value.  This keeps track of a lower and upper bound for the
// constant, which MAY wrap around the end of the numeric range.  To do this, it
// keeps track of a [lower, upper) bound, which specifies an interval just like
// STL iterators.  When used with boolean values, the following are important
// ranges (other integral ranges use min/max values for special range values):

#include "llvm/IR/Instruction.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Operator.h"
#include "llvm/IR/ConstantRange.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;

/// Initialize a full (the default) or empty set for the specified type.
ConstantRange::ConstantRange(uint32_t BitWidth, bool Full) {
  if (Full)
    Lower = Upper = APInt::getMaxValue(BitWidth);
  else
    Lower = Upper = APInt::getMinValue(BitWidth);
}

/// Initialize a range to hold the single specified value.
ConstantRange::ConstantRange(APIntMoveTy V)
    : Lower(std::move(V)), Upper(Lower + 1) {}

ConstantRange::ConstantRange(APIntMoveTy L, APIntMoveTy U)
    : Lower(std::move(L)), Upper(std::move(U)) {
  assert(Lower.getBitWidth() == Upper.getBitWidth() &&
         "ConstantRange with unequal bit widths");
  assert((Lower != Upper || (Lower.isMaxValue() || Lower.isMinValue())) &&
         "Lower == Upper, but they aren't min or max value!");
}

ConstantRange ConstantRange::makeAllowedICmpRegion(CmpInst::Predicate Pred,
                                                   const ConstantRange &CR) {
  if (CR.isEmptySet())
    return CR;

  uint32_t W = CR.getBitWidth();
  switch (Pred) {
  default:
    llvm_unreachable("Invalid ICmp predicate to makeAllowedICmpRegion()");
  case CmpInst::ICMP_EQ:
    return CR;
  case CmpInst::ICMP_NE:
    if (CR.isSingleElement())
      return ConstantRange(CR.getUpper(), CR.getLower());
    return ConstantRange(W);
  case CmpInst::ICMP_ULT: {
    APInt UMax(CR.getUnsignedMax());
    if (UMax.isMinValue())
      return ConstantRange(W, /* empty */ false);
    return ConstantRange(APInt::getMinValue(W), UMax);
  }
  case CmpInst::ICMP_SLT: {
    APInt SMax(CR.getSignedMax());
    if (SMax.isMinSignedValue())
      return ConstantRange(W, /* empty */ false);
    return ConstantRange(APInt::getSignedMinValue(W), SMax);
  }
  case CmpInst::ICMP_ULE: {
    APInt UMax(CR.getUnsignedMax());
    if (UMax.isMaxValue())
      return ConstantRange(W);
    return ConstantRange(APInt::getMinValue(W), UMax + 1);
  }
  case CmpInst::ICMP_SLE: {
    APInt SMax(CR.getSignedMax());
    if (SMax.isMaxSignedValue())
      return ConstantRange(W);
    return ConstantRange(APInt::getSignedMinValue(W), SMax + 1);
  }
  case CmpInst::ICMP_UGT: {
    APInt UMin(CR.getUnsignedMin());
    if (UMin.isMaxValue())
      return ConstantRange(W, /* empty */ false);
    return ConstantRange(UMin + 1, APInt::getNullValue(W));
  }
  case CmpInst::ICMP_SGT: {
    APInt SMin(CR.getSignedMin());
    if (SMin.isMaxSignedValue())
      return ConstantRange(W, /* empty */ false);
    return ConstantRange(SMin + 1, APInt::getSignedMinValue(W));
  }
  case CmpInst::ICMP_UGE: {
    APInt UMin(CR.getUnsignedMin());
    if (UMin.isMinValue())
      return ConstantRange(W);
    return ConstantRange(UMin, APInt::getNullValue(W));
  }
  case CmpInst::ICMP_SGE: {
    APInt SMin(CR.getSignedMin());
    if (SMin.isMinSignedValue())
      return ConstantRange(W);
    return ConstantRange(SMin, APInt::getSignedMinValue(W));
  }
  }
}

ConstantRange ConstantRange::makeSatisfyingICmpRegion(CmpInst::Predicate Pred,
                                                      const ConstantRange &CR) {
  // Follows from De-Morgan's laws:
  return makeAllowedICmpRegion(CmpInst::getInversePredicate(Pred), CR)
      .inverse();
}

ConstantRange ConstantRange::makeExactICmpRegion(CmpInst::Predicate Pred,
                                                 const APInt &C) {
  // Computes the exact range that is equal to both the constant ranges returned
  // by makeAllowedICmpRegion and makeSatisfyingICmpRegion. This is always true
  // when RHS is a singleton such as an APInt and so the assert is valid.
  assert(makeAllowedICmpRegion(Pred, C) == makeSatisfyingICmpRegion(Pred, C));
  return makeAllowedICmpRegion(Pred, C);
}

bool ConstantRange::getEquivalentICmp(CmpInst::Predicate &Pred,
                                      APInt &RHS) const {
  bool Success = false;

  if (isFullSet() || isEmptySet()) {
    Pred = isEmptySet() ? CmpInst::ICMP_ULT : CmpInst::ICMP_UGE;
    RHS = APInt(getBitWidth(), 0);
    Success = true;
  } else if (auto *OnlyElt = getSingleElement()) {
    Pred = CmpInst::ICMP_EQ;
    RHS = *OnlyElt;
    Success = true;
  } else if (auto *OnlyMissingElt = getSingleMissingElement()) {
    Pred = CmpInst::ICMP_NE;
    RHS = *OnlyMissingElt;
    Success = true;
  } else if (getLower().isMinSignedValue() || getLower().isMinValue()) {
    Pred =
        getLower().isMinSignedValue() ? CmpInst::ICMP_SLT : CmpInst::ICMP_ULT;
    RHS = getUpper();
    Success = true;
  } else if (getUpper().isMinSignedValue() || getUpper().isMinValue()) {
    Pred =
        getUpper().isMinSignedValue() ? CmpInst::ICMP_SGE : CmpInst::ICMP_UGE;
    RHS = getLower();
    Success = true;
  }

  assert((!Success || ConstantRange::makeExactICmpRegion(Pred, RHS) == *this) &&
         "Bad result!");

  return Success;
}

ConstantRange
ConstantRange::makeGuaranteedNoWrapRegion(Instruction::BinaryOps BinOp,
                                          const ConstantRange &Other,
                                          unsigned NoWrapKind) {
  typedef OverflowingBinaryOperator OBO;

  // Computes the intersection of CR0 and CR1.  It is different from
  // intersectWith in that the ConstantRange returned will only contain elements
  // in both CR0 and CR1 (i.e. SubsetIntersect(X, Y) is a *subset*, proper or
  // not, of both X and Y).
  auto SubsetIntersect =
      [](const ConstantRange &CR0, const ConstantRange &CR1) {
    return CR0.inverse().unionWith(CR1.inverse()).inverse();
  };

  assert(BinOp >= Instruction::BinaryOpsBegin &&
         BinOp < Instruction::BinaryOpsEnd && "Binary operators only!");

  assert((NoWrapKind == OBO::NoSignedWrap ||
          NoWrapKind == OBO::NoUnsignedWrap ||
          NoWrapKind == (OBO::NoUnsignedWrap | OBO::NoSignedWrap)) &&
         "NoWrapKind invalid!");

  unsigned BitWidth = Other.getBitWidth();
  if (BinOp != Instruction::Add)
    // Conservative answer: empty set
    return ConstantRange(BitWidth, false);

  if (auto *C = Other.getSingleElement())
    if (C->isMinValue())
      // Full set: nothing signed / unsigned wraps when added to 0.
      return ConstantRange(BitWidth);

  ConstantRange Result(BitWidth);

  if (NoWrapKind & OBO::NoUnsignedWrap)
    Result =
        SubsetIntersect(Result, ConstantRange(APInt::getNullValue(BitWidth),
                                              -Other.getUnsignedMax()));

  if (NoWrapKind & OBO::NoSignedWrap) {
    APInt SignedMin = Other.getSignedMin();
    APInt SignedMax = Other.getSignedMax();

    if (SignedMax.isStrictlyPositive())
      Result = SubsetIntersect(
          Result,
          ConstantRange(APInt::getSignedMinValue(BitWidth),
                        APInt::getSignedMinValue(BitWidth) - SignedMax));

    if (SignedMin.isNegative())
      Result = SubsetIntersect(
          Result, ConstantRange(APInt::getSignedMinValue(BitWidth) - SignedMin,
                                APInt::getSignedMinValue(BitWidth)));
  }

  return Result;
}

/// isFullSet - Return true if this set contains all of the elements possible

/// isEmptySet - Return true if this set contains no members.
bool ConstantRange::isEmptySet() const {
  return Lower == Upper && Lower.isMinValue();
}

/// isWrappedSet - Return true if this set wraps around the top of the range,
bool ConstantRange::isWrappedSet() const {
  return Lower.ugt(Upper);
}

/// isSignWrappedSet - Return true if this set wraps around the INT_MIN of
bool ConstantRange::isSignWrappedSet() const {
  return contains(APInt::getSignedMaxValue(getBitWidth())) &&
         contains(APInt::getSignedMinValue(getBitWidth()));
}

/// getSetSize - Return the number of elements in this set.
APInt ConstantRange::getSetSize() const {
  if (isFullSet()) {
    APInt Size(getBitWidth()+1, 0);
    Size.setBit(getBitWidth());
    return Size;
  }

  // This is also correct for wrapped sets.
  return (Upper - Lower).zext(getBitWidth()+1);
}

/// getUnsignedMax - Return the largest unsigned value contained in the
APInt ConstantRange::getUnsignedMax() const {
  if (isFullSet() || isWrappedSet())
    return APInt::getMaxValue(getBitWidth());
  return getUpper() - 1;
}

/// getUnsignedMin - Return the smallest unsigned value contained in the
APInt ConstantRange::getUnsignedMin() const {
  if (isFullSet() || (isWrappedSet() && getUpper() != 0))
    return APInt::getMinValue(getBitWidth());
  return getLower();
}

/// getSignedMax - Return the largest signed value contained in the
APInt ConstantRange::getSignedMax() const {
  APInt SignedMax(APInt::getSignedMaxValue(getBitWidth()));
  if (!isWrappedSet()) {
    if (getLower().sle(getUpper() - 1))
      return getUpper() - 1;
    return SignedMax;
  }
  if (getLower().isNegative() == getUpper().isNegative())
    return SignedMax;
  return getUpper() - 1;
}

/// getSignedMin - Return the smallest signed value contained in the
APInt ConstantRange::getSignedMin() const {
  APInt SignedMin(APInt::getSignedMinValue(getBitWidth()));
  if (!isWrappedSet()) {
    if (getLower().sle(getUpper() - 1))
      return getLower();
    return SignedMin;
  }
  if ((getUpper() - 1).slt(getLower())) {
    if (getUpper() != SignedMin)
      return SignedMin;
  }
  return getLower();
}

/// contains - Return true if the specified value is in the set.
bool ConstantRange::contains(const APInt &V) const {
  if (Lower == Upper)
    return isFullSet();

  if (!isWrappedSet())
    return Lower.ule(V) && V.ult(Upper);
  return Lower.ule(V) || V.ult(Upper);
}

/// contains - Return true if the argument is a subset of this range.
/// Two equal sets contain each other. The empty set contained by all other
bool ConstantRange::contains(const ConstantRange &Other) const {
  if (isFullSet() || Other.isEmptySet()) return true;
  if (isEmptySet() || Other.isFullSet()) return false;

  if (!isWrappedSet()) {
    if (Other.isWrappedSet())
      return false;

    return Lower.ule(Other.getLower()) && Other.getUpper().ule(Upper);
  }

  if (!Other.isWrappedSet())
    return Other.getUpper().ule(Upper) ||
           Lower.ule(Other.getLower());

  return Other.getUpper().ule(Upper) && Lower.ule(Other.getLower());
}

/// subtract - Subtract the specified constant from the endpoints of this
ConstantRange ConstantRange::subtract(const APInt &Val) const {
  assert(Val.getBitWidth() == getBitWidth() && "Wrong bit width");
  // If the set is empty or full, don't modify the endpoints.
  if (Lower == Upper)
    return *this;
  return ConstantRange(Lower - Val, Upper - Val);
}

/// \brief Subtract the specified range from this range (aka relative complement
/// of the sets).
ConstantRange ConstantRange::difference(const ConstantRange &CR) const {
  return intersectWith(CR.inverse());
}

/// intersectWith - Return the range that results from the intersection of this
/// range with another range.  The resultant range is guaranteed to include all
/// elements contained in both input ranges, and to have the smallest possible
/// set size that does so.  Because there may be two intersections with the
/// same set size, A.intersectWith(B) might not be equal to B.intersectWith(A).
ConstantRange ConstantRange::intersectWith(const ConstantRange &CR) const {
  assert(getBitWidth() == CR.getBitWidth() &&
         "ConstantRange types don't agree!");

  // Handle common cases.
  if (   isEmptySet() || CR.isFullSet()) return *this;
  if (CR.isEmptySet() ||    isFullSet()) return CR;

  if (!isWrappedSet() && CR.isWrappedSet())
    return CR.intersectWith(*this);

  if (!isWrappedSet() && !CR.isWrappedSet()) {
    if (Lower.ult(CR.Lower)) {
      if (Upper.ule(CR.Lower))
        return ConstantRange(getBitWidth(), false);

      if (Upper.ult(CR.Upper))
        return ConstantRange(CR.Lower, Upper);

      return CR;
    }
    if (Upper.ult(CR.Upper))
      return *this;

    if (Lower.ult(CR.Upper))
      return ConstantRange(Lower, CR.Upper);

    return ConstantRange(getBitWidth(), false);
  }

  if (isWrappedSet() && !CR.isWrappedSet()) {
    if (CR.Lower.ult(Upper)) {
      if (CR.Upper.ult(Upper))
        return CR;

      if (CR.Upper.ule(Lower))
        return ConstantRange(CR.Lower, Upper);

      if (getSetSize().ult(CR.getSetSize()))
        return *this;
      return CR;
    }
    if (CR.Lower.ult(Lower)) {
      if (CR.Upper.ule(Lower))
        return ConstantRange(getBitWidth(), false);

      return ConstantRange(Lower, CR.Upper);
    }
    return CR;
  }

  if (CR.Upper.ult(Upper)) {
    if (CR.Lower.ult(Upper)) {
      if (getSetSize().ult(CR.getSetSize()))
        return *this;
      return CR;
    }

    if (CR.Lower.ult(Lower))
      return ConstantRange(Lower, CR.Upper);

    return CR;
  }
  if (CR.Upper.ule(Lower)) {
    if (CR.Lower.ult(Lower))
      return *this;

    return ConstantRange(CR.Lower, Upper);
  }
  if (getSetSize().ult(CR.getSetSize()))
    return *this;
  return CR;
}

/// unionWith - Return the range that results from the union of this range with
/// another range.  The resultant range is guaranteed to include the elements of
/// both sets, but may contain more.  For example, [3, 9) union [12,15) is
/// [3, 15), which includes 9, 10, and 11, which were not included in either
ConstantRange ConstantRange::unionWith(const ConstantRange &CR) const {
  assert(getBitWidth() == CR.getBitWidth() &&
         "ConstantRange types don't agree!");

  if (   isFullSet() || CR.isEmptySet()) return *this;
  if (CR.isFullSet() ||    isEmptySet()) return CR;

  if (!isWrappedSet() && CR.isWrappedSet()) return CR.unionWith(*this);

  if (!isWrappedSet() && !CR.isWrappedSet()) {
    if (CR.Upper.ult(Lower) || Upper.ult(CR.Lower)) {
      // If the two ranges are disjoint, find the smaller gap and bridge it.
      APInt d1 = CR.Lower - Upper, d2 = Lower - CR.Upper;
      if (d1.ult(d2))
        return ConstantRange(Lower, CR.Upper);
      return ConstantRange(CR.Lower, Upper);
    }

    APInt L = Lower, U = Upper;
    if (CR.Lower.ult(L))
      L = CR.Lower;
    if ((CR.Upper - 1).ugt(U - 1))
      U = CR.Upper;

    if (L == 0 && U == 0)
      return ConstantRange(getBitWidth());

    return ConstantRange(L, U);
  }

  if (!CR.isWrappedSet()) {
    //   L--U                            L--U  : CR
    if (CR.Upper.ule(Upper) || CR.Lower.uge(Lower))
      return *this;

    if (CR.Lower.ule(Upper) && Lower.ule(CR.Upper))
      return ConstantRange(getBitWidth());

    if (Upper.ule(CR.Lower) && CR.Upper.ule(Lower)) {
      APInt d1 = CR.Lower - Upper, d2 = Lower - CR.Upper;
      if (d1.ult(d2))
        return ConstantRange(Lower, CR.Upper);
      return ConstantRange(CR.Lower, Upper);
    }

    if (Upper.ult(CR.Lower) && Lower.ult(CR.Upper))
      return ConstantRange(CR.Lower, Upper);

    assert(CR.Lower.ult(Upper) && CR.Upper.ult(Lower) &&
           "ConstantRange::unionWith missed a case with one range wrapped");
    return ConstantRange(Lower, CR.Upper);
  }

  if (CR.Lower.ule(Upper) || Lower.ule(CR.Upper))
    return ConstantRange(getBitWidth());

  APInt L = Lower, U = Upper;
  if (CR.Upper.ugt(U))
    U = CR.Upper;
  if (CR.Lower.ult(L))
    L = CR.Lower;

  return ConstantRange(L, U);
}

ConstantRange ConstantRange::castOp(Instruction::CastOps CastOp,
                                    uint32_t ResultBitWidth) const {
  switch (CastOp) {
  default:
    llvm_unreachable("unsupported cast type");
  case Instruction::Trunc:
    return truncate(ResultBitWidth);
  case Instruction::SExt:
    return signExtend(ResultBitWidth);
  case Instruction::ZExt:
    return zeroExtend(ResultBitWidth);
  case Instruction::BitCast:
    return *this;
  case Instruction::FPToUI:
  case Instruction::FPToSI:
    if (getBitWidth() == ResultBitWidth)
      return *this;
    else
      return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  case Instruction::UIToFP: {
    // TODO: use input range if available
    auto BW = getBitWidth();
    APInt Min = APInt::getMinValue(BW).zextOrSelf(ResultBitWidth);
    APInt Max = APInt::getMaxValue(BW).zextOrSelf(ResultBitWidth);
    return ConstantRange(Min, Max);
  }
  case Instruction::SIToFP: {
    // TODO: use input range if available
    auto BW = getBitWidth();
    APInt SMin = APInt::getSignedMinValue(BW).sextOrSelf(ResultBitWidth);
    APInt SMax = APInt::getSignedMaxValue(BW).sextOrSelf(ResultBitWidth);
    return ConstantRange(SMin, SMax);
  }
  case Instruction::FPTrunc:
  case Instruction::FPExt:
  case Instruction::IntToPtr:
  case Instruction::PtrToInt:
  case Instruction::AddrSpaceCast:
    // Conservatively return full set.
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  };
}

/// zeroExtend - Return a new range in the specified integer type, which must
/// be strictly larger than the current type.  The returned range will
/// correspond to the possible range of values as if the source range had been
ConstantRange ConstantRange::zeroExtend(uint32_t DstTySize) const {
  if (isEmptySet()) return ConstantRange(DstTySize, /*isFullSet=*/false);

  unsigned SrcTySize = getBitWidth();
  assert(SrcTySize < DstTySize && "Not a value extension");
  if (isFullSet() || isWrappedSet()) {
    // Change into [0, 1 << src bit width)
    APInt LowerExt(DstTySize, 0);
    if (!Upper) // special case: [X, 0) -- not really wrapping around
      LowerExt = Lower.zext(DstTySize);
    return ConstantRange(LowerExt, APInt::getOneBitSet(DstTySize, SrcTySize));
  }

  return ConstantRange(Lower.zext(DstTySize), Upper.zext(DstTySize));
}

/// signExtend - Return a new range in the specified integer type, which must
/// be strictly larger than the current type.  The returned range will
/// correspond to the possible range of values as if the source range had been
ConstantRange ConstantRange::signExtend(uint32_t DstTySize) const {
  if (isEmptySet()) return ConstantRange(DstTySize, /*isFullSet=*/false);

  unsigned SrcTySize = getBitWidth();
  assert(SrcTySize < DstTySize && "Not a value extension");

  // special case: [X, INT_MIN) -- not really wrapping around
  if (Upper.isMinSignedValue())
    return ConstantRange(Lower.sext(DstTySize), Upper.zext(DstTySize));

  if (isFullSet() || isSignWrappedSet()) {
    return ConstantRange(APInt::getHighBitsSet(DstTySize,DstTySize-SrcTySize+1),
                         APInt::getLowBitsSet(DstTySize, SrcTySize-1) + 1);
  }

  return ConstantRange(Lower.sext(DstTySize), Upper.sext(DstTySize));
}

/// truncate - Return a new range in the specified integer type, which must be
/// strictly smaller than the current type.  The returned range will
/// correspond to the possible range of values as if the source range had been
/// truncated to the specified type.
ConstantRange ConstantRange::truncate(uint32_t DstTySize) const {
  assert(getBitWidth() > DstTySize && "Not a value truncation");
  if (isEmptySet())
    return ConstantRange(DstTySize, /*isFullSet=*/false);
  if (isFullSet())
    return ConstantRange(DstTySize, /*isFullSet=*/true);

  APInt MaxValue = APInt::getMaxValue(DstTySize).zext(getBitWidth());
  APInt MaxBitValue(getBitWidth(), 0);
  MaxBitValue.setBit(DstTySize);

  APInt LowerDiv(Lower), UpperDiv(Upper);
  ConstantRange Union(DstTySize, /*isFullSet=*/false);

  // Analyze wrapped sets in their two parts: [0, Upper) \/ [Lower, MaxValue]
  // We use the non-wrapped set code to analyze the [Lower, MaxValue) part, and
  // then we do the union with [MaxValue, Upper)
  if (isWrappedSet()) {
    // If Upper is greater than Max Value, it covers the whole truncated range.
    if (Upper.uge(MaxValue))
      return ConstantRange(DstTySize, /*isFullSet=*/true);

    Union = ConstantRange(APInt::getMaxValue(DstTySize),Upper.trunc(DstTySize));
    UpperDiv = APInt::getMaxValue(getBitWidth());

    // Union covers the MaxValue case, so return if the remaining range is just
    if (LowerDiv == UpperDiv)
      return Union;
  }

  // Chop off the most significant bits that are past the destination bitwidth.
  if (LowerDiv.uge(MaxValue)) {
    APInt Div(getBitWidth(), 0);
    APInt::udivrem(LowerDiv, MaxBitValue, Div, LowerDiv);
    UpperDiv = UpperDiv - MaxBitValue * Div;
  }

  if (UpperDiv.ule(MaxValue))
    return ConstantRange(LowerDiv.trunc(DstTySize),
                         UpperDiv.trunc(DstTySize)).unionWith(Union);

  // The truncated value wraps around. Check if we can do better than fullset.
  APInt UpperModulo = UpperDiv - MaxBitValue;
  if (UpperModulo.ult(LowerDiv))
    return ConstantRange(LowerDiv.trunc(DstTySize),
                         UpperModulo.trunc(DstTySize)).unionWith(Union);

  return ConstantRange(DstTySize, /*isFullSet=*/true);
}

/// zextOrTrunc - make this range have the bit width given by \p DstTySize. The
/// value is zero extended, truncated, or left alone to make it that width.
ConstantRange ConstantRange::zextOrTrunc(uint32_t DstTySize) const {
  unsigned SrcTySize = getBitWidth();
  if (SrcTySize > DstTySize)
    return truncate(DstTySize);
  if (SrcTySize < DstTySize)
    return zeroExtend(DstTySize);
  return *this;
}

/// sextOrTrunc - make this range have the bit width given by \p DstTySize. The
/// value is sign extended, truncated, or left alone to make it that width.
ConstantRange ConstantRange::sextOrTrunc(uint32_t DstTySize) const {
  unsigned SrcTySize = getBitWidth();
  if (SrcTySize > DstTySize)
    return truncate(DstTySize);
  if (SrcTySize < DstTySize)
    return signExtend(DstTySize);
  return *this;
}

ConstantRange ConstantRange::binaryOp(Instruction::BinaryOps BinOp,
                                      const ConstantRange &Other) const {
  assert(BinOp >= Instruction::BinaryOpsBegin &&
         BinOp < Instruction::BinaryOpsEnd && "Binary operators only!");

  switch (BinOp) {
  case Instruction::Add:
    return add(Other);
  case Instruction::Sub:
    return sub(Other);
  case Instruction::Mul:
    return multiply(Other);
  case Instruction::UDiv:
    return udiv(Other);
  case Instruction::Shl:
    return shl(Other);
  case Instruction::LShr:
    return lshr(Other);
  case Instruction::And:
    return binaryAnd(Other);
  case Instruction::Or:
    return binaryOr(Other);
  // Note: floating point operations applied to abstract ranges are just
  // ideal integer operations with a lossy representation
  case Instruction::FAdd:
    return add(Other);
  case Instruction::FSub:
    return sub(Other);
  case Instruction::FMul:
    return multiply(Other);
  default:
    // Conservatively return full set.
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  }
}

ConstantRange
ConstantRange::add(const ConstantRange &Other) const {
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);
  if (isFullSet() || Other.isFullSet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);

  APInt Spread_X = getSetSize(), Spread_Y = Other.getSetSize();
  APInt NewLower = getLower() + Other.getLower();
  APInt NewUpper = getUpper() + Other.getUpper() - 1;
  if (NewLower == NewUpper)
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);

  ConstantRange X = ConstantRange(NewLower, NewUpper);
  if (X.getSetSize().ult(Spread_X) || X.getSetSize().ult(Spread_Y))
    // We've wrapped, therefore, full set.
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);

  return X;
}

ConstantRange ConstantRange::addWithNoSignedWrap(const APInt &Other) const {
  // Calculate the subset of this range such that "X + Other" is
  // guaranteed not to wrap (overflow) for all X in this subset.
  // makeGuaranteedNoWrapRegion will produce an exact NSW range since we are
  // passing a single element range.
  auto NSWRange = ConstantRange::makeGuaranteedNoWrapRegion(BinaryOperator::Add,
                                      ConstantRange(Other),
                                      OverflowingBinaryOperator::NoSignedWrap);
  auto NSWConstrainedRange = intersectWith(NSWRange);

  return NSWConstrainedRange.add(ConstantRange(Other));
}

ConstantRange
ConstantRange::sub(const ConstantRange &Other) const {
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);
  if (isFullSet() || Other.isFullSet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);

  APInt Spread_X = getSetSize(), Spread_Y = Other.getSetSize();
  APInt NewLower = getLower() - Other.getUpper() + 1;
  APInt NewUpper = getUpper() - Other.getLower();
  if (NewLower == NewUpper)
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);

  ConstantRange X = ConstantRange(NewLower, NewUpper);
  if (X.getSetSize().ult(Spread_X) || X.getSetSize().ult(Spread_Y))
    // We've wrapped, therefore, full set.
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);

  return X;
}

ConstantRange
ConstantRange::multiply(const ConstantRange &Other) const {
  // TODO: If either operand is a single element and the multiply is known to
  // be non-wrapping, round the result min and max value to the appropriate
  // multiple of that element. If wrapping is possible, at least adjust the
  // range according to the greatest power-of-two factor of the single element.

  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);

  // Multiplication is signedness-independent. However different ranges can be
  // obtained depending on how the input ranges are treated. These different
  // ranges are all conservatively correct, but one might be better than the
  // other. We calculate two ranges; one treating the inputs as unsigned
  // and the other signed, then return the smallest of these ranges.

  // Unsigned range first.
  APInt this_min = getUnsignedMin().zext(getBitWidth() * 2);
  APInt this_max = getUnsignedMax().zext(getBitWidth() * 2);
  APInt Other_min = Other.getUnsignedMin().zext(getBitWidth() * 2);
  APInt Other_max = Other.getUnsignedMax().zext(getBitWidth() * 2);

  ConstantRange Result_zext = ConstantRange(this_min * Other_min,
                                            this_max * Other_max + 1);
  ConstantRange UR = Result_zext.truncate(getBitWidth());

  // If the unsigned range doesn't wrap, and isn't negative then it's a range
  // from one positive number to another which is as good as we can generate.
  // In this case, skip the extra work of generating signed ranges which aren't
  // going to be better than this range.
  if (!UR.isWrappedSet() && UR.getLower().isNonNegative())
    return UR;

  // Now the signed range. Because we could be dealing with negative numbers
  // here, the lower bound is the smallest of the cartesian product of the
  // lower and upper ranges; for example:
  // Similarly for the upper bound, swapping min for max.

  this_min = getSignedMin().sext(getBitWidth() * 2);
  this_max = getSignedMax().sext(getBitWidth() * 2);
  Other_min = Other.getSignedMin().sext(getBitWidth() * 2);
  Other_max = Other.getSignedMax().sext(getBitWidth() * 2);

  auto L = {this_min * Other_min, this_min * Other_max,
            this_max * Other_min, this_max * Other_max};
  auto Compare = [](const APInt &A, const APInt &B) { return A.slt(B); };
  ConstantRange Result_sext(std::min(L, Compare), std::max(L, Compare) + 1);
  ConstantRange SR = Result_sext.truncate(getBitWidth());

  return UR.getSetSize().ult(SR.getSetSize()) ? UR : SR;
}

ConstantRange
ConstantRange::smax(const ConstantRange &Other) const {
  // X smax Y is: range(smax(X_smin, Y_smin),
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);
  APInt NewL = APIntOps::smax(getSignedMin(), Other.getSignedMin());
  APInt NewU = APIntOps::smax(getSignedMax(), Other.getSignedMax()) + 1;
  if (NewU == NewL)
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  return ConstantRange(NewL, NewU);
}

ConstantRange
ConstantRange::umax(const ConstantRange &Other) const {
  // X umax Y is: range(umax(X_umin, Y_umin),
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);
  APInt NewL = APIntOps::umax(getUnsignedMin(), Other.getUnsignedMin());
  APInt NewU = APIntOps::umax(getUnsignedMax(), Other.getUnsignedMax()) + 1;
  if (NewU == NewL)
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  return ConstantRange(NewL, NewU);
}

ConstantRange
ConstantRange::smin(const ConstantRange &Other) const {
  // X smin Y is: range(smin(X_smin, Y_smin),
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);
  APInt NewL = APIntOps::smin(getSignedMin(), Other.getSignedMin());
  APInt NewU = APIntOps::smin(getSignedMax(), Other.getSignedMax()) + 1;
  if (NewU == NewL)
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  return ConstantRange(NewL, NewU);
}

ConstantRange
ConstantRange::umin(const ConstantRange &Other) const {
  // X umin Y is: range(umin(X_umin, Y_umin),
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);
  APInt NewL = APIntOps::umin(getUnsignedMin(), Other.getUnsignedMin());
  APInt NewU = APIntOps::umin(getUnsignedMax(), Other.getUnsignedMax()) + 1;
  if (NewU == NewL)
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  return ConstantRange(NewL, NewU);
}

ConstantRange
ConstantRange::udiv(const ConstantRange &RHS) const {
  if (isEmptySet() || RHS.isEmptySet() || RHS.getUnsignedMax() == 0)
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);
  if (RHS.isFullSet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);

  APInt Lower = getUnsignedMin().udiv(RHS.getUnsignedMax());

  APInt RHS_umin = RHS.getUnsignedMin();
  if (RHS_umin == 0) {
    // We want the lowest value in RHS excluding zero. Usually that would be 1
    // except for a range in the form of [X, 1) in which case it would be X.
    if (RHS.getUpper() == 1)
      RHS_umin = RHS.getLower();
    else
      RHS_umin = APInt(getBitWidth(), 1);
  }

  APInt Upper = getUnsignedMax().udiv(RHS_umin) + 1;

  // If the LHS is Full and the RHS is a wrapped interval containing 1 then
  if (Lower == Upper)
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);

  return ConstantRange(Lower, Upper);
}

ConstantRange
ConstantRange::binaryAnd(const ConstantRange &Other) const {
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);

  // TODO: replace this with something less conservative

  APInt umin = APIntOps::umin(Other.getUnsignedMax(), getUnsignedMax());
  if (umin.isAllOnesValue())
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  return ConstantRange(APInt::getNullValue(getBitWidth()), umin + 1);
}

ConstantRange
ConstantRange::binaryOr(const ConstantRange &Other) const {
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);

  // TODO: replace this with something less conservative

  APInt umax = APIntOps::umax(getUnsignedMin(), Other.getUnsignedMin());
  if (umax.isMinValue())
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  return ConstantRange(umax, APInt::getNullValue(getBitWidth()));
}

ConstantRange
ConstantRange::shl(const ConstantRange &Other) const {
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);

  APInt min = getUnsignedMin().shl(Other.getUnsignedMin());
  APInt max = getUnsignedMax().shl(Other.getUnsignedMax());

  // there's no overflow!
  APInt Zeros(getBitWidth(), getUnsignedMax().countLeadingZeros());
  if (Zeros.ugt(Other.getUnsignedMax()))
    return ConstantRange(min, max + 1);

  // FIXME: implement the other tricky cases
  return ConstantRange(getBitWidth(), /*isFullSet=*/true);
}

ConstantRange
ConstantRange::lshr(const ConstantRange &Other) const {
  if (isEmptySet() || Other.isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);

  APInt max = getUnsignedMax().lshr(Other.getUnsignedMin());
  APInt min = getUnsignedMin().lshr(Other.getUnsignedMax());
  if (min == max + 1)
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);

  return ConstantRange(min, max + 1);
}

ConstantRange ConstantRange::inverse() const {
  if (isFullSet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/false);
  if (isEmptySet())
    return ConstantRange(getBitWidth(), /*isFullSet=*/true);
  return ConstantRange(Upper, Lower);
}

/// print - Print out the bounds to a stream...
void ConstantRange::print(raw_ostream &OS) const {
  if (isFullSet())
    OS << "full-set";
  else if (isEmptySet())
    OS << "empty-set";
  else
    OS << "[" << Lower << "," << Upper << ")";
}

/// dump - Allow printing from a debugger easily...
LLVM_DUMP_METHOD void ConstantRange::dump() const {
  print(dbgs());
}

ConstantRange llvm::getConstantRangeFromMetadata(const MDNode &Ranges) {
  const unsigned NumRanges = Ranges.getNumOperands() / 2;
  assert(NumRanges >= 1 && "Must have at least one range!");
  assert(Ranges.getNumOperands() % 2 == 0 && "Must be a sequence of pairs");

  auto *FirstLow = mdconst::extract<ConstantInt>(Ranges.getOperand(0));
  auto *FirstHigh = mdconst::extract<ConstantInt>(Ranges.getOperand(1));

  ConstantRange CR(FirstLow->getValue(), FirstHigh->getValue());

  for (unsigned i = 1; i < NumRanges; ++i) {
    auto *Low = mdconst::extract<ConstantInt>(Ranges.getOperand(2 * i + 0));
    auto *High = mdconst::extract<ConstantInt>(Ranges.getOperand(2 * i + 1));

    // Note: unionWith will potentially create a range that contains values not
    // contained in any of the original N ranges.
    CR = CR.unionWith(ConstantRange(Low->getValue(), High->getValue()));
  }

  return CR;
}