enum ExtraEffect
{
	AccuracyVirus = 0,
	PeernoidVirus = 1,
	None = 2,
}

class NetworkInkGameController extends inkGameController
{
	private var m_turn : String;
	default m_turn = "Initial";
	private var m_dimension : Int32;
	default m_dimension = 5;
	private var m_steps : Int32;
	default m_steps = 6;
	private var m_symbols : array< String >;
	private var m_symbolProbabilities : array< Int32 >;
	private var m_endGame : Bool;
	default m_endGame = false;
	private var m_initRound : Bool;
	default m_initRound = true;
	private var m_oldPickX : Int32;
	private var m_oldPickY : Int32;
	private var m_pickX : Int32;
	private var m_pickY : Int32;
	private var m_regenGrid : Bool;
	var m_trapsDelayed : array< String >;
	private var m_networkData : NetworkMinigameData;
	var m_visualController : weak< NetworkMinigameVisualController >;
	private var m_miniGameRecord : weak< HackingMiniGame_Record >;
	private var m_officerBreach : Bool;
	private var m_bufferElements : array< ElementData >;
	private var m_enemyBufferElements : array< ElementData >;
	private var m_completedPrograms : array< String >;
	private var m_completedProgramsPD : array< ProgramData >;
	private var m_enemyCompletedPrograms : array< String >;
	private var m_enemyCompletedProgramsPD : array< ProgramData >;
	private var m_playerProgramsCompletion : array< ProgramProgressData >;
	private var m_enemyProgramsCompletion : array< ProgramProgressData >;
	private var m_basicAccessCompletion : ProgramProgressData;
	private var m_appliedViruses : array< ExtraEffect >;
	private var m_onBreachingNetworkListener : CallbackHandle;
	private var m_onDevicesCountChangedListener : CallbackHandle;

	protected event OnInitialize()
	{
		m_visualController = ( ( NetworkMinigameVisualController )( GetRootWidget().GetController() ) );
		RegisterBlackboardCallbacks();
		m_officerBreach = GetBlackboard().GetBool( GetBlackboardDef().OfficerBreach );
		StartBreaching( GetBlackboard().GetString( GetBlackboardDef().NetworkName ) );
		( ( GameObject )( GetPlayerControlledObject() ) ).RegisterInputListener( this, 'UI_Cancel' );
	}

	protected event OnUninitialize()
	{
		( ( GameObject )( GetPlayerControlledObject() ) ).UnregisterInputListener( this );
	}

	private function GetBlackboard() : IBlackboard
	{
		return GetBlackboardSystem().Get( GetBlackboardDef() );
	}

	private function RegisterBlackboardCallbacks()
	{
		m_onBreachingNetworkListener = GetBlackboard().RegisterListenerString( GetBlackboardDef().NetworkName, this, 'OnBreachingNetwork' );
		m_onDevicesCountChangedListener = GetBlackboard().RegisterListenerInt( GetBlackboardDef().DevicesCount, this, 'OnDevicesCountChanged' );
	}

	private function UnregisterBlackboardCallbacks()
	{
		GetBlackboard().UnregisterListenerString( GetBlackboardDef().NetworkName, m_onBreachingNetworkListener );
		GetBlackboard().UnregisterListenerInt( GetBlackboardDef().DevicesCount, m_onDevicesCountChangedListener );
	}

	private function SetPlayerBlackboardInformation( value : Bool )
	{
		var playerStateMachineBlackboard : IBlackboard;
		var ownerEntity : Entity;
		ownerEntity = GetPlayerControlledObject();
		playerStateMachineBlackboard = GetBlackboardSystem().GetLocalInstanced( ownerEntity.GetEntityID(), GetAllBlackboardDefs().PlayerStateMachine );
		playerStateMachineBlackboard.SetBool( GetAllBlackboardDefs().PlayerStateMachine.IsInMinigame, value );
	}

	private function GetBlackboardDef() : NetworkBlackboardDef
	{
		return GetAllBlackboardDefs().NetworkBlackboard;
	}

	private function StartMinigame()
	{
		var playerPuppet : weak< GameObject >;
		SetActiveMiniGameRecord();
		playerPuppet = ( ( PlayerPuppet )( GetPlayerControlledObject() ) );
		GameInstance.GetTimeSystem( playerPuppet.GetGame() ).SetTimeDilation( 'minigame', 0.00001 );
		switch( m_miniGameRecord.GameType() )
		{
			case 1:
				break;
			case 2:
				PlayGame();
			break;
			case 3:
				break;
			default:
				break;
		}
	}

	private function StartBreaching( const networkName : ref< String > )
	{
		StartMinigame();
	}

	private function SetActiveMiniGameRecord()
	{
		if( m_officerBreach )
		{
			m_miniGameRecord = TweakDBInterface.GetHackingMiniGameRecord( T"MiniGame.DefaultGame" );
		}
		else
		{
			m_miniGameRecord = TweakDBInterface.GetHackingMiniGameRecord( T"MiniGame.DefaultGame" );
		}
	}

	protected event OnDevicesCountChanged( value : Int32 ) {}

	protected event OnStopBreaching( target : weak< inkWidget > )
	{
		StartMinigame();
	}

	private function PlayGame()
	{
		var programList : array< ProgramData >;
		var enemyProgramList : array< ProgramData >;
		var grid : array< CellData >;
		var basicAccess : ProgramData;
		var pType : ProgramType;
		var pEffect : ProgramEffect;
		var gridArr, programArr : array< String >;
		var i, rand : Int32;
		m_dimension = m_miniGameRecord.Dimension();
		ReserveSymbols( m_symbols );
		SetSymbolProbabilities( m_symbolProbabilities, m_miniGameRecord.SymbolProbabilities() );
		pType = ProgramType.BasicAccess;
		pEffect = ProgramEffect.GrantAccess;
		if( !( GetPredefinedBasicAccess( basicAccess, pType, pEffect ) ) )
		{
			basicAccess = MakeProgram( "Basic Access", 2, m_symbols, m_symbolProbabilities, pType, pEffect );
		}
		pType = ProgramType.ExtraPlayerProgram;
		pEffect = ProgramEffect.UnlockQuestFact;
		if( !( GetPredefinedProgram( programList, pType, pEffect, true ) ) )
		{
			programList.PushBack( MakeProgram( "Camera Malfunction", 3, m_symbols, m_symbolProbabilities, pType, pEffect ) );
			programList.PushBack( MakeProgram( "Officer tracing", 3, m_symbols, m_symbolProbabilities, pType, pEffect ) );
		}
		pType = ProgramType.ExtraServerProgram;
		pEffect = ProgramEffect.UnlockQuestFact;
		if( !( GetPredefinedProgram( programList, pType, pEffect, false ) ) )
		{
			programList.PushBack( MakeProgram( "Network Cache", 2, m_symbols, m_symbolProbabilities, pType, pEffect ) );
			programList.PushBack( MakeProgram( "Encrypted Data Package", 4, m_symbols, m_symbolProbabilities, pType, pEffect ) );
		}
		if( m_miniGameRecord.HasEnemyNetrunner() )
		{
			pType = ProgramType.EnemyProgram;
			pEffect = ProgramEffect.BlockAccess;
			if( !( GetPredefinedProgram( enemyProgramList, pType, pEffect, false ) ) )
			{
				enemyProgramList.PushBack( MakeProgram( "Shuffle Buffer", 2, m_symbols, m_symbolProbabilities, pType, pEffect ) );
				enemyProgramList.PushBack( MakeProgram( "Increment Complexity", 4, m_symbols, m_symbolProbabilities, pType, pEffect ) );
				enemyProgramList.PushBack( MakeProgram( "Network Lock", 4, m_symbols, m_symbolProbabilities, pType, pEffect ) );
			}
		}
		SetPlayerBlackboardInformation( true );
		m_visualController.RegisterToCallback( 'OnCellSelected', this, 'OnPressCell' );
		m_visualController.RegisterToCallback( 'OnEndClosed', this, 'OnCloseGame' );
		if( !( GetPredefinedGrid( grid ) ) )
		{
			GenerateGrid( grid );
		}
		gridArr = m_miniGameRecord.PredefinedGrid();
		if( gridArr.Size() == 0 )
		{
			rand = RandRange( 0, programList.Size() );
			for( i = 0; i < programList.Size(); i += 1 )
			{
				InsertProgram( grid, programList[ i ], i == rand );
			}
		}
		GenerateTraps( grid );
		gridArr = m_miniGameRecord.PredefinedGrid();
		programArr = m_miniGameRecord.PredefinedBasicAccess();
		if( ( gridArr.Size() == 0 ) || ( programArr.Size() == 0 ) )
		{
			InsertProgram( grid, basicAccess, false );
		}
		grid[ 0 ].element.id = "--";
		m_oldPickX = 0;
		m_oldPickY = 0;
		m_networkData.gridData = grid;
		m_networkData.basicAccess = basicAccess;
		m_networkData.playerPrograms = programList;
		m_networkData.playerBufferSize = m_steps;
		m_visualController.SetUp( m_networkData );
		ExecuteTurn();
	}

	private function ExecuteTurn()
	{
		var endScreen : EndScreenData;
		var end : Bool;
		if( !( m_endGame ) && ( m_steps > 0 ) )
		{
			if( m_initRound )
			{
				m_initRound = false;
			}
			m_steps -= 1;
		}
		else
		{
			end = true;
		}
		if( end )
		{
			endScreen.unlockedPrograms = m_completedProgramsPD;
			if( m_completedPrograms.Contains( "Basic Access" ) )
			{
				endScreen.outcome = OutcomeMessage.Success;
			}
			else
			{
				endScreen.outcome = OutcomeMessage.Failure;
			}
			m_visualController.ShowEndScreen( endScreen );
		}
	}

	public function NewTurn( placementX : Int32, placementY : Int32, optional grid : array< CellData > )
	{
		var hMode : HighlightMode;
		var newTurn : NewTurnMinigameData;
		var tempPrograms : array< ProgramData >;
		var tempEnemyPrograms : array< ProgramData >;
		if( m_turn == "Initial" )
		{
			if( m_oldPickY == placementY )
			{
				m_turn = "Column";
			}
			else
			{
				m_turn = "Row";
			}
		}
		if( m_turn == "Column" )
		{
			hMode = HighlightMode.Row;
		}
		else
		{
			hMode = HighlightMode.Column;
		}
		newTurn.position = Vector2( ( ( Float )( placementX ) ), ( ( Float )( placementY ) ) );
		newTurn.doConsume = true;
		newTurn.nextHighlightMode = hMode;
		if( m_regenGrid )
		{
			newTurn.doRegenerateGrid = true;
			newTurn.regeneratedGridData = grid;
		}
		newTurn.newPlayerBufferContent = m_bufferElements;
		newTurn.newEnemyBufferContent = m_enemyBufferElements;
		tempPrograms = m_networkData.playerPrograms;
		tempPrograms.PushBack( m_networkData.basicAccess );
		KeepTrackPrograms( tempPrograms, false );
		if( m_miniGameRecord.HasEnemyNetrunner() )
		{
			tempEnemyPrograms = m_networkData.enemyPrograms;
			KeepTrackPrograms( tempEnemyPrograms, true );
		}
		newTurn.basicAccessCompletionState = m_basicAccessCompletion;
		newTurn.playerProgramsCompletionState = m_playerProgramsCompletion;
		newTurn.enemyProgramsCompletionState = m_enemyProgramsCompletion;
		m_visualController.SetGridElementPicked( newTurn );
		if( m_turn == "Column" )
		{
			m_turn = "Row";
		}
		else
		{
			m_turn = "Column";
		}
		ExecuteTurn();
	}

	private function CloseGame()
	{
		var playerPuppet : weak< GameObject >;
		var deviceID : EntityID;
		var evt : AccessPointMiniGameStatus;
		playerPuppet = ( ( PlayerPuppet )( GetPlayerControlledObject() ) );
		GameInstance.GetTimeSystem( playerPuppet.GetGame() ).UnsetTimeDilation( 'minigame' );
		if( m_completedPrograms.Contains( "Encrypted Data (?)" ) )
		{
			RPGManager.GiveReward( playerPuppet.GetGame(), T"RPGActionRewards.ProgramPartsAccessPoint" );
		}
		SetPlayerBlackboardInformation( false );
		deviceID = GetBlackboard().GetEntityID( GetBlackboardDef().DeviceID );
		if( EntityID.IsDefined( deviceID ) )
		{
			evt = new AccessPointMiniGameStatus;
			evt.minigameState = HackingMinigameState.Succeeded;
			GetPlayerControlledObject().QueueEventForEntityID( deviceID, evt );
		}
	}

	private function GenerateGrid( grid : ref< array< CellData > > )
	{
		var i : Int32;
		var j : Int32;
		var cell : CellData;
		grid.Clear();
		for( i = 0; i < m_dimension; i += 1 )
		{
			for( j = 0; j < m_dimension; j += 1 )
			{
				cell.position = Vector2( ( ( Float )( i ) ), ( ( Float )( j ) ) );
				cell.element.id = GenerateHexNumber2();
				grid.PushBack( cell );
			}
		}
	}

	private function RegenerateGrid( grid : ref< array< CellData > >, const symbols : ref< array< String > >, const symbolProbabilities : ref< array< Int32 > > )
	{
		grid.Clear();
		GenerateGrid( grid );
		GenerateTraps( grid );
	}

	private function SetSymbolProbabilities( prob : ref< array< Int32 > >, const arr : ref< array< String > > )
	{
		var i : Int32;
		for( i = 0; i < arr.Size(); i += 1 )
		{
			prob.PushBack( StringToInt( arr[ i ] ) );
		}
	}

	public function ReserveSymbols( symbols : ref< array< String > > )
	{
		var arr : array< String >;
		var i : Int32;
		arr = m_miniGameRecord.Symbols();
		for( i = 0; i < arr.Size(); i += 1 )
		{
			symbols.PushBack( arr[ i ] );
		}
	}

	private function GetPredefinedGrid( listCells : ref< array< CellData > > ) : Bool
	{
		var i : Int32;
		var j : Int32;
		var cell : CellData;
		var preGrid : array< String >;
		preGrid = m_miniGameRecord.PredefinedGrid();
		if( preGrid.Size() == 0 )
		{
			return false;
		}
		listCells.Clear();
		for( i = 0; i < m_dimension; i += 1 )
		{
			for( j = 0; j < m_dimension; j += 1 )
			{
				cell.position = Vector2( ( ( Float )( i ) ), ( ( Float )( j ) ) );
				if( preGrid[ GridPositionToList( i, j, m_dimension ) ] == "??" )
				{
					cell.element.id = m_symbols[ ChooseRandomOption( m_symbolProbabilities ) ];
				}
				else
				{
					cell.element.id = preGrid[ GridPositionToList( i, j, m_dimension ) ];
				}
				listCells.PushBack( cell );
			}
		}
		return true;
	}

	private function GetPredefinedBasicAccess( program : ref< ProgramData >, programType : ProgramType, programEffect : ProgramEffect ) : Bool
	{
		var i : Int32;
		var element : ElementData;
		var command : array< ElementData >;
		var arr : array< String >;
		arr = m_miniGameRecord.PredefinedBasicAccess();
		if( arr.Size() == 0 )
		{
			return false;
		}
		program.id = "Basic Access";
		for( i = 0; i < arr.Size(); i += 1 )
		{
			if( arr[ i ] == "??" )
			{
				element.id = m_symbols[ ChooseRandomOption( m_symbolProbabilities ) ];
			}
			else
			{
				element.id = arr[ i ];
			}
			command.PushBack( element );
		}
		program.commandLists.PushBack( command );
		program.type = programType;
		program.effects.PushBack( programEffect );
		program.startAsUnknown = false;
		return true;
	}

	private function GetPredefinedProgram( programList : ref< array< ProgramData > >, programType : ProgramType, programEffect : ProgramEffect, cyberdeck : Bool ) : Bool
	{
		var i : Int32;
		var element : ElementData;
		var program : ProgramData;
		var command : array< ElementData >;
		var arr : array< String >;
		if( programType == ProgramType.EnemyProgram )
		{
			arr = m_miniGameRecord.PredefinedEnemyPrograms();
		}
		else if( cyberdeck )
		{
			arr = m_miniGameRecord.PredefinedCyberdeckPrograms();
		}
		else
		{
			arr = m_miniGameRecord.PredefinedNetworkPrograms();
		}
		if( arr.Size() == 0 )
		{
			return false;
		}
		for( i = 0; i < arr.Size(); i += 1 )
		{
			if( !( m_symbols.Contains( arr[ i ] ) ) && arr[ i ] != "??" )
			{
				if( program.id != "" )
				{
					program.commandLists.PushBack( command );
					program.type = programType;
					program.effects.PushBack( programEffect );
					program.startAsUnknown = false;
					programList.PushBack( program );
					command.Clear();
					program.commandLists.Clear();
				}
				program.id = arr[ i ];
			}
			else
			{
				if( arr[ i ] == "??" )
				{
					element.id = m_symbols[ ChooseRandomOption( m_symbolProbabilities ) ];
				}
				else
				{
					element.id = arr[ i ];
				}
				command.PushBack( element );
			}
		}
		program.commandLists.PushBack( command );
		program.type = programType;
		program.effects.PushBack( programEffect );
		program.startAsUnknown = false;
		programList.PushBack( program );
		return true;
	}

	public function MakeProgram( const programName : ref< String >, num : Int32, const symbols : ref< array< String > >, const probabilities : ref< array< Int32 > >, programType : ProgramType, programEffect : ProgramEffect ) : ProgramData
	{
		var element : ElementData;
		var program : ProgramData;
		var command : array< ElementData >;
		var i : Int32;
		var instruction : String;
		program.id = programName;
		for( i = 0; i < num; i += 1 )
		{
			instruction = symbols[ ChooseRandomOption( probabilities ) ];
			element.id = instruction;
			command.PushBack( element );
		}
		program.commandLists.PushBack( command );
		program.type = programType;
		program.effects.PushBack( programEffect );
		program.startAsUnknown = false;
		return program;
	}

	private function InsertProgram( grid : ref< array< CellData > >, const program : ref< ProgramData >, forceFirstRow : Bool )
	{
		var i, x, y, oldX, oldY : Int32;
		var initial, columnTurn : Bool;
		var tempCell : CellData;
		initial = true;
		x = RandRange( 0, 2 );
		if( ( x == 1 ) || forceFirstRow )
		{
			columnTurn = true;
		}
		else
		{
			columnTurn = false;
		}
		for( i = 0; i < program.commandLists[ 0 ].Size(); i += 1 )
		{
			if( initial )
			{
				x = RandRange( 0, m_dimension );
				y = RandRange( 0, m_dimension );
				if( forceFirstRow )
				{
					x = 0;
				}
				while( ( x == 0 ) && ( y == 0 ) )
				{
					x = RandRange( 0, m_dimension );
					y = RandRange( 0, m_dimension );
					if( forceFirstRow )
					{
						x = 0;
					}
				}
				oldX = x;
				oldY = y;
				tempCell = GetCellFromPosition( grid, x, y );
				tempCell.element.id = program.commandLists[ 0 ][ i ].id;
				grid[ GridPositionToList( x, y, m_dimension ) ] = tempCell;
				initial = false;
			}
			else
			{
				if( columnTurn )
				{
					while( oldX == x )
					{
						x = RandRange( 0, m_dimension );
					}
					tempCell = GetCellFromPosition( grid, x, oldY );
					tempCell.element.id = program.commandLists[ 0 ][ i ].id;
					grid[ GridPositionToList( x, oldY, m_dimension ) ] = tempCell;
					oldX = x;
					columnTurn = false;
				}
				else
				{
					while( oldY == y )
					{
						y = RandRange( 0, m_dimension );
					}
					tempCell = GetCellFromPosition( grid, oldX, y );
					tempCell.element.id = program.commandLists[ 0 ][ i ].id;
					grid[ GridPositionToList( oldX, y, m_dimension ) ] = tempCell;
					oldY = y;
					columnTurn = false;
				}
			}
		}
	}

	private function GenerateTraps( grid : ref< array< CellData > > )
	{
		var i : Int32;
		var j : Int32;
		var traps : array< ETrap >;
		var property : SpecialProperties;
		for( i = 0; i < m_dimension; i += 1 )
		{
			for( j = 0; j < m_dimension; j += 1 )
			{
				GetRandomTraps( traps );
				property.traps = traps;
				grid[ GridPositionToList( i, j, m_dimension ) ].properties = property;
			}
		}
	}

	private function GetRandomTraps( traps : ref< array< ETrap > > ) : Bool
	{
		var i : Int32;
		var rand : Float;
		var trapRecords : array< weak< Trap_Record > >;
		var trap : ETrap;
		if( !( m_miniGameRecord ) )
		{
			return false;
		}
		m_miniGameRecord.AllowedTraps( trapRecords );
		for( i = 0; i < trapRecords.Size(); i += 1 )
		{
			rand = RandRangeF( 1.0, 100.0 );
			if( rand < trapRecords[ i ].Probability() )
			{
				trap = ( ( ETrap )( ( ( Int32 )( EnumValueFromString( "ETrap", trapRecords[ i ].Name() ) ) ) ) );
				if( trap != ETrap.Invalid )
				{
					traps.PushBack( trap );
				}
			}
		}
		return traps.Size() > 0;
	}

	public function ApplyRandomVirus() : ExtraEffect
	{
		switch( m_miniGameRecord.NetworkLevel() )
		{
			case 0:
				return ExtraEffect.None;
			case 1:
				return ExtraEffect.AccuracyVirus;
			case 2:
				return ExtraEffect.PeernoidVirus;
			default:
				return ExtraEffect.None;
		}
	}

	private function KeepTrackPrograms( programs : array< ProgramData >, enemy : Bool )
	{
		var i : Int32;
		var playerProgramsCompletion : array< ProgramProgressData >;
		var enemyProgramsCompletion : array< ProgramProgressData >;
		var basicAccessCompletion, tempProgramCompletion : ProgramProgressData;
		for( i = 0; i < programs.Size(); i += 1 )
		{
			if( !( programs[ i ].wasCompleted ) )
			{
				programs[ i ].wasCompleted = CheckUploaded( programs[ i ] );
				if( programs[ i ].wasCompleted )
				{
					if( !( enemy ) )
					{
						if( !( m_completedPrograms.Contains( programs[ i ].id ) ) )
						{
							m_completedPrograms.PushBack( programs[ i ].id );
							m_completedProgramsPD.PushBack( programs[ i ] );
							m_visualController.SetProgramCompleted( programs[ i ].id, false );
						}
					}
					else
					{
						if( !( m_enemyCompletedPrograms.Contains( programs[ i ].id ) ) )
						{
							m_enemyCompletedPrograms.PushBack( programs[ i ].id );
							m_enemyCompletedProgramsPD.PushBack( programs[ i ] );
							m_visualController.SetProgramCompleted( programs[ i ].id, false );
						}
					}
				}
				if( programs[ i ].wasCompleted )
				{
					if( programs[ i ].type == ProgramType.BasicAccess )
					{
						basicAccessCompletion.isComplete = true;
					}
					tempProgramCompletion.isComplete = true;
				}
				else
				{
					tempProgramCompletion.isComplete = false;
				}
				tempProgramCompletion.id = programs[ i ].id;
				tempProgramCompletion.completionProgress.Clear();
				if( programs[ i ].type == ProgramType.BasicAccess )
				{
					basicAccessCompletion.completionProgress.PushBack( FeedbackProgramCompletion( programs[ i ].commandLists[ 0 ], m_bufferElements ) );
				}
				else if( enemy )
				{
					tempProgramCompletion.completionProgress.PushBack( FeedbackProgramCompletion( programs[ i ].commandLists[ 0 ], m_bufferElements ) );
					enemyProgramsCompletion.PushBack( tempProgramCompletion );
				}
				else
				{
					tempProgramCompletion.completionProgress.PushBack( FeedbackProgramCompletion( programs[ i ].commandLists[ 0 ], m_bufferElements ) );
					playerProgramsCompletion.PushBack( tempProgramCompletion );
				}
			}
		}
		if( enemy )
		{
			m_enemyProgramsCompletion = enemyProgramsCompletion;
		}
		else
		{
			m_playerProgramsCompletion = playerProgramsCompletion;
			m_basicAccessCompletion = basicAccessCompletion;
		}
	}

	private function FeedbackProgramCompletion( const program : ref< array< ElementData > >, const buffer : ref< array< ElementData > > ) : Int32
	{
		var i, num : Int32;
		var done : Bool;
		var appendedBuffer, appendedProgram : String;
		num = 0;
		done = false;
		for( i = program.Size(); i > 0; i -= 1 )
		{
			appendedBuffer = ArrayCellsToString( buffer, i, true );
			appendedProgram = ArrayCellsToString( program, i, false );
			if( appendedBuffer == appendedProgram && !( done ) )
			{
				num = i;
				done = true;
			}
		}
		return num;
	}

	private function CheckUploaded( const program : ref< ProgramData > ) : Bool
	{
		var uploaded : Bool;
		var appendedBuffer, appendedProgram : String;
		uploaded = false;
		appendedBuffer = ArrayCellsToString( m_bufferElements );
		appendedProgram = ArrayCellsToString( program.commandLists[ 0 ] );
		if( StrContains( appendedBuffer, appendedProgram ) )
		{
			uploaded = true;
		}
		return uploaded;
	}

	protected event OnPressCell( e : weak< inkWidget > )
	{
		var placementX, placementY : Int32;
		var grid : array< CellData >;
		var cell : CellData;
		cell = m_visualController.GetLastCellSelected();
		placementX = ( ( Int32 )( cell.position.X ) );
		placementY = ( ( Int32 )( cell.position.Y ) );
		if( cell.properties.traps.Contains( ETrap.AppendStart ) )
		{
			m_bufferElements.Insert( 0, cell.element );
		}
		else
		{
			m_bufferElements.PushBack( cell.element );
		}
		if( cell.properties.enemyMarker )
		{
			m_enemyBufferElements.PushBack( cell.element );
		}
		if( cell.properties.traps.Contains( ETrap.Virus ) )
		{
			m_appliedViruses.PushBack( ApplyRandomVirus() );
		}
		if( cell.properties.traps.Contains( ETrap.GridRegen ) )
		{
			RegenerateGrid( grid, m_symbols, m_symbolProbabilities );
			m_regenGrid = true;
		}
		else
		{
			m_regenGrid = false;
		}
		m_oldPickX = m_pickX;
		m_oldPickY = m_pickY;
		m_pickX = placementX;
		m_pickY = placementY;
		NewTurn( placementX, placementY, grid );
	}

	protected event OnCloseGame( e : weak< inkWidget > )
	{
		CloseGame();
	}

	private function GenerateHexNumber2() : String
	{
		var i : Int32;
		i = ChooseRandomOption( m_symbolProbabilities );
		switch( i )
		{
			case 0:
				return "1C";
			case 1:
				return "55";
			case 2:
				return "BD";
			case 3:
				return "E9";
			default:
				return "--";
		}
	}

	public function GridPositionToList( x : Int32, y : Int32, dimension : Int32 ) : Int32
	{
		return ( dimension * x ) + y;
	}

	public function CheckDirection( placementX : Int32, placementY : Int32 ) : String
	{
		if( ( placementX == m_oldPickX ) && ( placementY > m_oldPickY ) )
		{
			return "right";
		}
		if( ( placementX == m_oldPickX ) && ( placementY < m_oldPickY ) )
		{
			return "left";
		}
		if( ( placementY == m_oldPickY ) && ( placementX > m_oldPickX ) )
		{
			return "down";
		}
		if( ( placementY == m_oldPickY ) && ( placementX < m_oldPickX ) )
		{
			return "up";
		}
		return "";
	}

	public function AppendListPrograms( arr1 : array< ProgramData >, const arr2 : ref< array< ProgramData > > ) : array< ProgramData >
	{
		var i : Int32;
		for( i = 0; i < arr2.Size(); i += 1 )
		{
			arr1.PushBack( arr2[ i ] );
		}
		return arr1;
	}

	private function ChooseRandomOption( const probabilities : ref< array< Int32 > > ) : Int32
	{
		var i : Int32;
		var num : Int32;
		var sum : Int32;
		sum = 0;
		for( i = 0; i < probabilities.Size(); i += 1 )
		{
			sum += probabilities[ i ];
		}
		num = RandRange( 0, sum + 1 );
		for( i = 0; i < probabilities.Size(); i += 1 )
		{
			if( num <= probabilities[ i ] )
			{
				return i;
			}
			else
			{
				num -= probabilities[ i ];
			}
		}
		return -1;
	}

	private function ArrayCellsToString( const arr : ref< array< ElementData > > ) : String
	{
		var str : String;
		var i : Int32;
		str = "";
		for( i = 0; i < arr.Size(); i += 1 )
		{
			str = str + arr[ i ].id;
		}
		return str;
	}

	private function ArrayCellsToString( const arr : ref< array< ElementData > >, num : Int32, fromNumber : Bool ) : String
	{
		var str : String;
		var i : Int32;
		str + "";
		if( !( fromNumber ) )
		{
			for( i = 0; i < num; i += 1 )
			{
				str = str + arr[ i ].id;
			}
		}
		else
		{
			for( i = arr.Size() - num; i <= arr.Size(); i += 1 )
			{
				if( i >= 0 )
				{
					str = str + arr[ i ].id;
				}
			}
		}
		return str;
	}

	private function ArrayCellsToString( const arr : ref< array< String > >, num : Int32, fromNumber : Bool ) : String
	{
		var str : String;
		var i : Int32;
		str + "";
		if( !( fromNumber ) )
		{
			for( i = 0; i < num; i += 1 )
			{
				str = str + arr[ i ];
			}
		}
		else
		{
			for( i = arr.Size() - num; i <= arr.Size(); i += 1 )
			{
				if( i >= 0 )
				{
					str = str + arr[ i ];
				}
			}
		}
		return str;
	}

	private function GetCellFromPosition( const arr : ref< array< CellData > >, x : Int32, y : Int32 ) : CellData
	{
		var cell : CellData;
		var i : Int32;
		for( i = 0; i < arr.Size(); i += 1 )
		{
			cell = arr[ i ];
			if( cell.position == Vector2( ( ( Float )( x ) ), ( ( Float )( y ) ) ) )
			{
				return cell;
			}
		}
		return cell;
	}

	protected event OnAction( action : ListenerAction, consumer : ListenerActionConsumer )
	{
		if( ListenerAction.GetName( action ) == 'UI_Cancel' && ListenerAction.GetType( action ) == gameinputActionType.BUTTON_PRESSED )
		{
			CloseGame();
		}
	}

	protected event OnPressSkip( e : inkPointerEvent )
	{
		CloseGame();
	}

}

