/*
    AdjustingMapcycle version 1.0
    Copyright (C) 2011 n0nexe
    
    This program is free software; you can redistribute and/or modify
    it under the terms of the Open Unreal Mod License version 1.1.
*/
class AMC extends Mutator
	config(AdjustingMapcycle);

struct MapConfig
{
	var string mapname;
	var int minAvgPlayers;
	var int maxAvgPlayers;
	var int skipCount;
};	

var config MapConfig mapcycle[50];
var config int currentIndex;
var bool bPreInit;
var bool bMsgShown;
//////////////////////
var Pawn skipVotes[32];
var config float skipPercentage;
//////////////////////
var config float avgPopulation;
//////////////////////
var bool bNotEnded;
var int emptyCount;
var name tag;

var string Reason;

function PreBeginPlay() 
{
	local int i;
	local string currMap;
	
	if(bPreInit)
		return;

	bPreInit = True;
	Level.Game.RegisterMessageMutator(Self);
	tag = 'AdjustingMapcycle';
	
	SetTimer(FMax(0.25, TeamGamePlus(Level.Game).NetWait - Level.TimeSeconds - 2), False);
	
	for(i = 0;i<50;i++)//@debug
	{
		if(mapcycle[i].mapname != "")
			Log(mapcycle[i].mapname $
				" ||| " $ mapcycle[i].minAvgPlayers $
				" - " $ mapcycle[i].maxAvgPlayers, tag);
	}

	currMap = GetLevelName();
	Log("map is " $ currMap $ " should be " $ mapcycle[currentIndex].mapname, tag);
	
	if(InStr(mapcycle[currentIndex].mapname, currMap) == -1)
		FindMapIndex(currMap);
	
}

function FindMapIndex(string currMap)
{
	local int i;
	
	for(i = 0;i<50;i++)
	{
		if(InStr(mapcycle[i].mapname, currMap) != -1)//found
		{
			currentIndex = i;
			Log("map is matching with " $ i, tag);
			SaveConfig();
			return;
		}
	}
	//map is not on the list -> move on
	bMsgShown = True;
	
	NextMap(False);
}

function Timer()
{
	if(!bMsgShown)
	{
		Self.BroadcastMessage("If you want to see this map skipped say !skip.");
		Self.BroadcastMessage(int(100 * skipPercentage) $ "% !skip-votes needed.");
		Self.BroadcastMessage(Left(mapcycle[currentIndex].mapName, Len(mapcycle[currentIndex].mapname) - 4) $ " used for " $ 
			mapcycle[currentIndex].minAvgPlayers $ " - " $ mapcycle[currentIndex].maxAvgPlayers $
			" players.");
		
		//next calc avg population every minute
		bMsgShown = True;
		
		bNotEnded = True;
		SetTimer(60 * Level.TimeDilation, True);
		return;
	}

	if(bNotEnded)
	{
		avgPopulation += (Level.Game.NumPlayers - avgPopulation) / 2;
		Log(Level.Game.NumPlayers $ " -> " $ avgPopulation, tag);
		
		//suggest
		if(avgPopulation + 0.5 < mapcycle[currentIndex].minAvgPlayers)
		{
			Self.BroadcastMessage("If you think the map is too big now - all say !skip.");
		}
		
		if(Level.Game.NumPlayers == 0)
			emptyCount ++;
		else
			emptyCount = 0;
		
		//4 times in a row no player, but map is not made for few players -> try to change
		if(emptyCount == 4 && mapcycle[currentIndex].minAvgPlayers > 0)
		{
			if(NextMap(True))//can find a better map?
			{
				emptyCount = 0;
				Self.BroadcastMessage("Auto-Skipping this map", True, 'CriticalEvent');
				EndThisGameForSkipping();
			}
		}
			
	}
	else //delay after we ended game
	{
		Level.ServerTravel(mapcycle[currentIndex].mapname $ "?game=Botpack.TeamGamePlus", False);
		//Level.Game.ProcessServerTravel(mapcycle[currentIndex].mapname, False);
	}
}

function bool MutatorTeamMessage(Actor Sender, Pawn Receiver, PlayerReplicationInfo PRI, coerce string Msg, name Type, optional bool bBeep) 
{
	if(Sender == Receiver) 
	{ 
		if(Left(Msg, 5) ~= "!skip") 
		{ 
			if(Level.TimeSeconds < 35)
			{
				Receiver.ClientMessage("Wait for " $ int(1 + (35 - Level.TimeSeconds)/Level.TimeDilation) $ " seconds then say it again.");
			}
			else
				ProcessSkipVote(Receiver);
		}
	}
  
	if(NextMessageMutator != None)
		return NextMessageMutator.MutatorTeamMessage( Sender, Receiver, PRI, Msg, Type, bBeep );
	else
		return true;
}

function ProcessSkipVote(Pawn p)
{
	local int i, k;
	local bool bDone;

	for(i = 0; i < 32; i++)
	{
		if(p == skipVotes[i])
			return;//already voted
	}
	
	//clean
	for(i = 0; i < 32; i++)
	{
		if(skipVotes[i] == None)
		{
			if(!bDone)
			{
				skipVotes[i] = p;
				k++;
				bDone = True;
			}
		}
		else //count
			k++;
	}
	
	Self.BroadcastMessage(k $ " valid skip-votes atm.");
	
	if(k >= skipPercentage * Level.Game.NumPlayers)
	{
		if(!NextMap(True))
		{
			BroadcastMessage("No other map matching current population", True, 'CriticalEvent');
		}
		else
		{
			Self.BroadcastMessage("Skipping this map", True, 'CriticalEvent');
			EndThisGameForSkipping();
		}
	}
}

function bool Nextmap(bool skipMap)
{
	local int oldIndex;
	
	oldIndex = currentIndex;

	Log(currentIndex $ " old", tag);
	//find a matching map -> maybe little offset due to slow convergence in the end
	while(mapcycle[++currentIndex].mapname == "" || 
		mapcycle[currentIndex].minAvgPlayers > avgPopulation + 0.2 ||
		mapcycle[currentIndex].maxAvgPlayers < avgPopulation - 0.2)
		{
			if(currentIndex == oldIndex)//no els
				break;
				
			if(currentIndex == 49)//wrap
				currentIndex = -1;
		}
	Log(currentIndex $ " new", tag);
	
	if(currentIndex == oldIndex)//ending on the same map!
		return False;


	
	//prepare
	DeathMatchPlus(Level.Game).bDontRestart = True;
	
	//last calculation
	avgPopulation += (Level.Game.NumPlayers - avgPopulation) / 2;
	SaveConfig();//save now
	Log(Level.Game.NumPlayers $ " ->NM " $ avgPopulation, tag);
	
	//delay
	bNotEnded = False;//delay mode in timer()
	if(skipMap)
		SetTimer(10, False);
	else
		SetTimer(15, False);
		
	return True;
}


function bool HandleEndGame()
{
	Super.HandleEndGame();
	
	
	if(True)//tgp.teams[0].Score != tgp.teams[1].Score)@todo -> determine ?TDM only
	{
		//find next suitable map - and start end-game timer
		NextMap(False);
		//show winner
		Level.Game.SetEndCams("teamscorelimit");
		
		return True;
	}
	else
		return False;
}

function EndThisGameForSkipping()
{
	local Actor A;
	
	mapCycle[currentIndex].skipCount ++;
				
	Level.Game.bGameEnded = true;
	foreach AllActors(class'Actor', A, 'EndGame')
		A.trigger(Level.Game, none);

	if (Level.Game.LocalLog != None)
	{
		Level.Game.LocalLog.LogGameEnd(Reason);
		Level.Game.LocalLog.StopLog();
		if (Level.Game.bBatchLocal)
			Level.Game.LocalLog.ExecuteSilentLogBatcher();
		Level.Game.LocalLog.Destroy();
		Level.Game.LocalLog = None;
	}
	/*if (Level.Game.WorldLog != None)
	{
		Level.Game.WorldLog.LogGameEnd(Reason);
		Level.Game.WorldLog.StopLog();
		Level.Game.WorldLog.ExecuteWorldLogBatcher();
		Level.Game.WorldLog.Destroy();
		Level.Game.WorldLog = None;
	}*/
}

function Mutate(string MutateString, PlayerPawn Sender)
{
	local int i, empty;
	local string temp;
	
	if(MutateString ~= "maplist")
	{
		for(i = 0;i < 50;i++)
		{
			if(mapcycle[i].mapname != "")
				Sender.ClientMessage(Left(mapcycle[i].mapname, Len(mapcycle[i].mapname) - 4) $ " for " $ 
				mapcycle[i].minAvgPlayers $ " - " $ mapcycle[i].maxAvgPlayers $ " / received " $ mapcycle[i].skipcount $ " skips.");
		}
	}
	else if(Sender.bAdmin)
	{
		if(MutateString ~= "resetskipcounts")
		{
			for(i = 0;i < 50;i++)
				mapcycle[i].skipCount = 0;
				
			Sender.ClientMessage("Done.");
			SaveConfig();
		}
		else if(Left(MutateString, 10) ~= "removemap ")
		{
			MutateString = Mid(MutateString, 10);//get mapname
			
			if(InStr(MutateString, GetLevelName()) != -1)
			{
				Sender.ClientMessage("Removing map just played is not allowed.");//@test
				return;
			}
			
			for(i = 0;i < 50;i++)
			{
				if(MutateString ~= mapcycle[i].mapname)//found
				{
					mapcycle[i].mapname = "";
					mapcycle[i].minAvgPlayers = 0;
					mapcycle[i].maxAvgPlayers = 0;
					mapcycle[i].skipCount = 0;
					SaveConfig();
					
					Sender.ClientMessage(MutateString $ " has been removed.");
					return;
				}
			}
		}
		else if(Left(MutateString, 7) ~= "addmap ")
		{
			//know the mapname first
			MutateString = Mid(MutateString, 7);//get command
			i = InStr(MutateString, "/");
			if(i == -1)
				return;
			//Mapname
			temp = Left(MutateString, i);
			MutateString = Mid(MutateString, i+1);
		
			i = -1;	
			//find an empty entry@todo refind for edit
			for(empty = 0; empty < 50; empty ++)
			{
				if(i == -1 && mapcycle[empty].mapname == "")//found first empty one
					i = empty;
				
				if(mapcycle[empty].mapname ~= temp)
				{
					SetMapEntry(empty, temp, MutateString, Sender);
					return;
				}
			}
			
			if(i != -1)//found an empty line
				SetMapEntry(i, temp, MutateString, Sender);
		}
	}

	if ( NextMutator != None )
		NextMutator.Mutate(MutateString, Sender);
}

function SetMapEntry(int index, string mapname, string rest, Pawn Sender)
{
	local int i;
	local string temp;

	if(!(Right(mapname, 4) ~= ".unr"))
	{
		Sender.ClientMessage("MAPNAME.UNR/MIN/MAX plz!");
		return;
	}
	
	mapcycle[index].mapname = mapname;
	/////////////////////
	//min
	i = InStr(rest, "/");
	temp = Left(rest, i);
	rest = Mid(rest, i+1);
	
	mapcycle[index].minAvgPlayers = int(temp);
	//max
	mapcycle[index].maxAvgPlayers = int(rest);
	mapcycle[index].skipCount = 0;
	
	Sender.ClientMessage("new/edited entry: " $ mapcycle[index].mapname $ " for " $ 
		mapcycle[index].minAvgPlayers $ " - " $ mapcycle[index].maxAvgPlayers);
}

function string GetLevelName()
{
	local string Str;
	local int Pos;

	Str = string(Level);
	Pos = InStr(Str, ".");
	if(Pos != -1)
		return Left(Str, Pos);
	else
		return Str;
}



defaultproperties
{
	skipPercentage=0.6
	Reason="Players want to skip this map."
}