/* 
* 字幕显示实体 / Subtitle display entity 
* 作者(Author): Ice Box 
* Steam: https://steamcommunity.com/id/ibox666/ 
* 如需使用此脚本请联系作者 / If you need to use this script, please contact the author 
*/

namespace PointSubtitle
{

const int SF_LOOP = 1 << 0;
const int SF_SHOW_ONCE = 1 << 1;
const int SF_REMOVE_AFTER_USE = 1 << 2;

class CPointSubtitle : ScriptBaseEntity
{
	private bool m_bHasShown = false;
	private bool m_bIsShowing = false;
	private float m_flDefaultDisplayTime = 5.0;
	private float m_flDelay = 0.0;
	private float m_flFadeTime = 0.5;
	private float m_flPositionX = -1.0;
	private float m_flPositionY = 0.8;
	private float m_flTextScale = 1.0;
	private int m_iTextColorR = 255;
	private int m_iTextColorG = 255;
	private int m_iTextColorB = 255;
	private string m_szSubtitleFile = "";
	private string m_szSubtitleText = "";
	private string m_szKillTarget = "";
	private array<string> m_aSubtitleLines;
	private array<float> m_aSubtitleStartTimes;
	private array<float> m_aSubtitleEndTimes;
	private CScheduledFunction@ m_pHideSubtitle;
	private CScheduledFunction@ m_pShowNextSubtitle;
	private int m_iCurrentSubtitle = 0;
	private float m_flStartTime = 0.0;

	bool KeyValue(const string& in szKey, const string& in szValue)
	{
		if(szKey == "subtitle_file")
		{
			m_szSubtitleFile = szValue;
			LoadSubtitleFile();
			return true;
		}
		else if(szKey == "subtitle_text")
		{
			m_szSubtitleText = szValue;
			ParseSubtitleText();
			return true;
		}
		else if(szKey == "display_time")
		{
			m_flDefaultDisplayTime = atof(szValue);
			if(m_flDefaultDisplayTime <= 0.0)
				m_flDefaultDisplayTime = 5.0;
			return true;
		}
		else if(szKey == "delay")
		{
			m_flDelay = atof(szValue);
			if(m_flDelay < 0.0)
				m_flDelay = 0.0;
			return true;
		}
		else if(szKey == "fade_time")
		{
			m_flFadeTime = atof(szValue);
			if(m_flFadeTime < 0.0)
				m_flFadeTime = 0.0;
			return true;
		}
		else if(szKey == "position_x")
		{
			m_flPositionX = atof(szValue);
			if(m_flPositionX < -1.0)
				m_flPositionX = -1.0;
			else if(m_flPositionX > 1.0)
				m_flPositionX = 1.0;
			return true;
		}
		else if(szKey == "position_y")
		{
			m_flPositionY = atof(szValue);
			if(m_flPositionY < 0.0)
				m_flPositionY = 0.0;
			else if(m_flPositionY > 1.0)
				m_flPositionY = 1.0;
			return true;
		}
		else if(szKey == "text_color")
		{
			array<string> colors = szValue.Split(" ");
			if(colors.length() >= 3)
			{
				m_iTextColorR = atoi(colors[0]);
				m_iTextColorG = atoi(colors[1]);
				m_iTextColorB = atoi(colors[2]);
				
				if(m_iTextColorR < 0) m_iTextColorR = 0;
				if(m_iTextColorR > 255) m_iTextColorR = 255;
				if(m_iTextColorG < 0) m_iTextColorG = 0;
				if(m_iTextColorG > 255) m_iTextColorG = 255;
				if(m_iTextColorB < 0) m_iTextColorB = 0;
				if(m_iTextColorB > 255) m_iTextColorB = 255;
			}
			return true;
		}
		else if(szKey == "text_scale")
		{
			m_flTextScale = atof(szValue);
			if(m_flTextScale <= 0.0)
				m_flTextScale = 1.0;
			return true;
		}
		else if(szKey == "killtarget")
		{
			m_szKillTarget = szValue;
			return true;
		}

		return BaseClass.KeyValue(szKey, szValue);
	}

	void Spawn()
	{
		self.pev.solid = SOLID_NOT;
		self.pev.movetype = MOVETYPE_NONE;
		self.pev.effects |= EF_NODRAW;
		
		if(m_szSubtitleFile == "" && m_szSubtitleText != "")
		{
			ParseSubtitleText();
		}
	}

	void UpdateOnRemove()
	{
		if(m_pHideSubtitle !is null)
		{
			g_Scheduler.RemoveTimer(m_pHideSubtitle);
			@m_pHideSubtitle = null;
		}
		
		if(m_pShowNextSubtitle !is null)
		{
			g_Scheduler.RemoveTimer(m_pShowNextSubtitle);
			@m_pShowNextSubtitle = null;
		}
		
		BaseClass.UpdateOnRemove();
	}

	void Use(CBaseEntity@ pActivator, CBaseEntity@ pCaller, USE_TYPE useType, float flValue)
	{
		switch(useType)
		{
			case USE_TOGGLE:
			{
				if(m_flDelay > 0.0)
				{
					g_Scheduler.SetTimeout(this, "ShowSubtitle", m_flDelay, self);
				}
				else
				{
					ShowSubtitle(pActivator);
				}
				return;
			}
		}
	}

	float ParseTime(const string& in timeStr)
	{
		string time = timeStr;
		time.Trim();

		int commaPos = time.Find(",");
		float milliseconds = 0.0;

		if(commaPos >= 0)
		{
			string msStr = time.SubString(commaPos + 1);
			msStr.Trim();
			milliseconds = atof(msStr) / 1000.0;

			time = time.SubString(0, commaPos);
			time.Trim();
		}

		int colonPos = time.Find(":");
		if(colonPos >= 0)
		{
			array<string> parts = time.Split(":");
			float totalSeconds = 0.0;

			if(parts.length() == 2)
			{
				float minutes = atof(parts[0]);
				float seconds = atof(parts[1]);
				totalSeconds = minutes * 60.0 + seconds;
			}
			else if(parts.length() == 3)
			{
				float hours = atof(parts[0]);
				float minutes = atof(parts[1]);
				float seconds = atof(parts[2]);
				totalSeconds = hours * 3600.0 + minutes * 60.0 + seconds;
			}
			else
			{
				return 0.0;
			}

			return totalSeconds + milliseconds;
		}
		else
		{
			return atof(time) + milliseconds;
		}
	}

	void LoadSubtitleFile()
	{
		if(m_szSubtitleFile == "")
			return;

		m_aSubtitleLines.resize(0);
		m_aSubtitleStartTimes.resize(0);
		m_aSubtitleEndTimes.resize(0);

		string fullPath = m_szSubtitleFile;

		if(!fullPath.StartsWith("scripts/"))
			fullPath = "scripts/maps/" + fullPath;

		File@ file = g_FileSystem.OpenFile(fullPath, OpenFile::READ);
		if(file is null || !file.IsOpen())
		{
			return;
		}
		
		string line;
		while(!file.EOFReached())
		{
			file.ReadLine(line);
			line.Trim();

			if(line.Length() == 0 || line[0] == '#')
				continue;

			int separatorPos = line.Find("|");
			if(separatorPos < 0)
				continue;

			string timeRange = line.SubString(0, separatorPos);
			timeRange.Trim();
			string text = line.SubString(separatorPos + 1);
			text.Trim();

			array<string> times = timeRange.Split("-");
			if(times.length() >= 2)
			{
				float startTime = ParseTime(times[0]);
				float endTime = ParseTime(times[1]);

				if(endTime <= startTime)
					endTime = startTime + m_flDefaultDisplayTime;

				m_aSubtitleStartTimes.insertLast(startTime);
				m_aSubtitleEndTimes.insertLast(endTime);
				m_aSubtitleLines.insertLast(text);
			}
		}
		
		file.Close();
	}

	void ParseSubtitleText()
	{
		if(m_szSubtitleText == "")
			return;
			
		m_aSubtitleLines.resize(0);
		m_aSubtitleStartTimes.resize(0);
		m_aSubtitleEndTimes.resize(0);
		
		array<string> lines = m_szSubtitleText.Split("|");
		
		for(uint i = 0; i < lines.length(); i++)
		{
			string line = lines[i];
			line.Trim();
			
			if(line.Length() > 0)
			{
				array<string> parts = line.Split(",");
				if(parts.length() >= 2)
				{
					string timeRange = parts[0];
					string text = parts[1];
					
					for(uint j = 2; j < parts.length(); j++)
					{
						text += "," + parts[j];
					}
					
					array<string> times = timeRange.Split("-");
					if(times.length() >= 2)
					{
						float startTime = ParseTime(times[0]);
						float endTime = ParseTime(times[1]);

						if(endTime <= startTime)
							endTime = startTime + m_flDefaultDisplayTime;

						m_aSubtitleStartTimes.insertLast(startTime);
						m_aSubtitleEndTimes.insertLast(endTime);
						m_aSubtitleLines.insertLast(text);
					}
				}
				else
				{
					m_aSubtitleStartTimes.insertLast(0.0);
					m_aSubtitleEndTimes.insertLast(m_flDefaultDisplayTime);
					m_aSubtitleLines.insertLast(line);
				}
			}
		}
	}

	void ShowSubtitle(CBaseEntity@ pActivator)
	{
		if(m_bIsShowing)
		{
			return;
		}
		
		if((self.pev.spawnflags & SF_SHOW_ONCE) != 0 && m_bHasShown)
		{
			return;
		}
		
		if(m_aSubtitleLines.length() == 0)
		{
			return;
		}
		
		m_bIsShowing = true;
		m_bHasShown = true;
		m_iCurrentSubtitle = 0;
		m_flStartTime = g_Engine.time;
		
		ShowSubtitleLine(0);
	}

	void ShowSubtitleLine(int index)
	{
		if(index < 0 || index >= int(m_aSubtitleLines.length()))
		{
			SubtitleSequenceComplete();
			return;
		}
		
		HUDTextParams textParams;
		textParams.x = m_flPositionX;
		textParams.y = m_flPositionY;
		textParams.effect = 0;
		textParams.r1 = m_iTextColorR;
		textParams.g1 = m_iTextColorG;
		textParams.b1 = m_iTextColorB;
		textParams.a1 = 255;
		textParams.r2 = m_iTextColorR;
		textParams.g2 = m_iTextColorG;
		textParams.b2 = m_iTextColorB;
		textParams.a2 = 255;
		textParams.fadeinTime = m_flFadeTime;
		textParams.fadeoutTime = m_flFadeTime;
		textParams.holdTime = m_aSubtitleEndTimes[index] - m_aSubtitleStartTimes[index];
		textParams.fxTime = 0.0;
		textParams.channel = 4;

		g_PlayerFuncs.HudMessageAll(textParams, m_aSubtitleLines[index]);
		
		if(index + 1 < int(m_aSubtitleLines.length()))
		{
			float nextTime = m_aSubtitleStartTimes[index + 1] - m_aSubtitleStartTimes[index];
			if(nextTime <= 0.0)
				nextTime = m_flDefaultDisplayTime;
				
			@m_pShowNextSubtitle = g_Scheduler.SetTimeout(this, "ShowNextSubtitle", nextTime, index + 1);
		}
		else
		{
			if(m_pHideSubtitle !is null)
			{
				g_Scheduler.RemoveTimer(m_pHideSubtitle);
			}
			@m_pHideSubtitle = g_Scheduler.SetTimeout(this, "SubtitleSequenceComplete", 
				m_aSubtitleEndTimes[index] - m_aSubtitleStartTimes[index] + m_flFadeTime);
		}
	}

	void ShowNextSubtitle(int index)
	{
		if(index < 0 || index >= int(m_aSubtitleLines.length()))
		{
			SubtitleSequenceComplete();
			return;
		}
		
		ShowSubtitleLine(index);
	}

	void SubtitleSequenceComplete()
	{
		HideSubtitle();
		
		m_bIsShowing = false;
		
		TriggerTargets();
	}

	void TriggerTargets()
	{
		if(self.pev.target != "")
		{
			g_EntityFuncs.FireTargets(string(self.pev.target), self, self, USE_TOGGLE, 0.0);
		}
		
		if(m_szKillTarget != "")
		{
			CBaseEntity@ pKillTarget = null;
			while((@pKillTarget = g_EntityFuncs.FindEntityByTargetname(pKillTarget, m_szKillTarget)) !is null)
			{
				g_EntityFuncs.Remove(pKillTarget);
			}
		}
		
		if((self.pev.spawnflags & SF_REMOVE_AFTER_USE) != 0)
		{
			g_EntityFuncs.Remove(self);
			return;
		}
		
		if((self.pev.spawnflags & SF_LOOP) == 0)
		{
			m_bHasShown = true;
		}
		else
		{
			m_bHasShown = false;
		}
	}

	void HideSubtitle()
	{
		HUDTextParams textParams;
		textParams.x = m_flPositionX;
		textParams.y = m_flPositionY;
		textParams.effect = 0;
		textParams.r1 = 0;
		textParams.g1 = 0;
		textParams.b1 = 0;
		textParams.a1 = 0;
		textParams.r2 = 0;
		textParams.g2 = 0;
		textParams.b2 = 0;
		textParams.a2 = 0;
		textParams.fadeinTime = 0.0;
		textParams.fadeoutTime = 0.0;
		textParams.holdTime = 0.0;
		textParams.fxTime = 0.0;
		textParams.channel = 4;

		g_PlayerFuncs.HudMessageAll(textParams, "");
	}
}

void Register()
{
	g_CustomEntityFuncs.RegisterCustomEntity("PointSubtitle::CPointSubtitle", "point_subtitle");
}

}