/* 
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program 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. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "stdafx.h"
#include "SubtitleFile.h"

namespace ssf
{
	SubtitleFile::SubtitleFile()
	{
	}

	SubtitleFile::~SubtitleFile()
	{
	}

	void SubtitleFile::Parse(InputStream& s)
	{
		m_segments.RemoveAll();

		__super::Parse(s, s_predef);

		// TODO: check file.format == "ssf" and file.version == 1

		CAtlList<Definition*> defs;
		GetRootRef()->GetChildDefs(defs, L"subtitle");
		
		StringMapW<float> offset;

		POSITION pos = defs.GetHeadPosition();
		while(pos)
		{
			Definition* pDef = defs.GetNext(pos);

			try
			{
				Definition::Time time;

				if(pDef->GetAsTime(time, offset) && (*pDef)[L"@"].IsValue())
				{
					m_segments.Insert(time.start.value, time.stop.value, pDef);
				}
			}
			catch(Exception&)
			{
			}
		}
	}

	void SubtitleFile::Append(InputStream& s, float start, float stop, bool fSetTime)
	{
		Reference* pRootRef = GetRootRef();

		ParseDefs(s, pRootRef);

		CAtlList<Definition*> defs;
		GetNewDefs(defs);

		POSITION pos = defs.GetHeadPosition();
		while(pos)
		{
			Definition* pDef = defs.GetNext(pos);

			if(pDef->m_parent == pRootRef && pDef->m_type == L"subtitle" && (*pDef)[L"@"].IsValue())
			{
				m_segments.Insert(start, stop, pDef);

				if(fSetTime) 
				{
					try
					{
						Definition::Time time;
						StringMapW<float> offset;
						pDef->GetAsTime(time, offset);
						if(time.start.value == start && time.stop.value == stop)
							continue;
					}
					catch(Exception&)
					{
					}

					CStringW str;
					str.Format(L"%.3f", start);
					pDef->SetChildAsNumber(L"time.start", str, L"s");
					str.Format(L"%.3f", stop);
					pDef->SetChildAsNumber(L"time.stop", str, L"s");
				}
			}
		}

		Commit();
	}

	bool SubtitleFile::Lookup(float at, CAutoPtrList<Subtitle>& subs)
	{
		if(!subs.IsEmpty()) {ASSERT(0); return false;}

		CAtlList<SegmentItem> sis;
		m_segments.Lookup(at, sis);

		POSITION pos = sis.GetHeadPosition();
		while(pos)
		{
			SegmentItem& si = sis.GetNext(pos);

			CAutoPtr<Subtitle> s(new Subtitle(this));

			if(s->Parse(si.pDef, si.start, si.stop, at))
			{
				for(POSITION pos = subs.GetHeadPosition(); pos; subs.GetNext(pos))
				{
					if(s->m_layer < subs.GetAt(pos)->m_layer)
					{
						subs.InsertBefore(pos, s);
						break;
					}
				}

				if(s)
				{
					subs.AddTail(s);
				}
			}
		}

		return !subs.IsEmpty();
	}

	//

	SubtitleFile::Segment::Segment(float start, float stop, const SegmentItem* si)
	{
		m_start = start;
		m_stop = stop;
		if(si) AddTail(*si);
	}

	SubtitleFile::Segment::Segment(const Segment& s)
	{
		*this = s;
	}

	void SubtitleFile::Segment::operator = (const Segment& s)
	{
		m_start = s.m_start; 
		m_stop = s.m_stop; 
		RemoveAll(); 
		AddTailList(&s);
	}

	//

	void SubtitleFile::SegmentList::RemoveAll()
	{
		__super::RemoveAll();
		m_index.RemoveAll();
	}

	void SubtitleFile::SegmentList::Insert(float start, float stop, Definition* pDef)
	{
		if(start >= stop) {ASSERT(0); return;}

		m_index.RemoveAll();

		SegmentItem si = {pDef, start, stop};

		if(IsEmpty())
		{
			AddTail(Segment(start, stop, &si));
			return;
		}
		
		Segment& head = GetHead();
		Segment& tail = GetTail();
		
		if(start >= tail.m_stop && stop > tail.m_stop)
		{
			if(start > tail.m_stop) AddTail(Segment(tail.m_stop, start));
			AddTail(Segment(start, stop, &si));
		}
		else if(start < head.m_start && stop <= head.m_start)
		{
			if(stop < head.m_start) AddHead(Segment(stop, head.m_start));
			AddHead(Segment(start, stop, &si));
		}
		else 
		{
			if(start < head.m_start)
			{
				AddHead(Segment(start, head.m_start, &si));
				start = head.m_start;
			}

			if(stop > tail.m_stop)
			{
				AddTail(Segment(tail.m_stop, stop, &si));
				stop = tail.m_stop;
			}

			for(POSITION pos = GetHeadPosition(); pos; GetNext(pos))
			{
				Segment& s = GetAt(pos);

				if(start >= s.m_stop) continue;
				if(stop <= s.m_start) break;

				if(s.m_start < start && start < s.m_stop)
				{
					Segment s2 = s;
					s2.m_start = start;
					InsertAfter(pos, s2);
					s.m_stop = start;
				}
				else if(s.m_start == start)
				{
					if(stop > s.m_stop)
					{
						start = s.m_stop;
					}
					else if(stop < s.m_stop)
					{
						Segment s2 = s;
						s2.m_start = stop;
						InsertAfter(pos, s2);
						s.m_stop = stop;
					}

					s.AddTail(si);
				}
			}
		}
	}

	size_t SubtitleFile::SegmentList::Index(bool fForce)
	{
		if(m_index.IsEmpty() || fForce)
		{
			m_index.RemoveAll();
			POSITION pos = GetHeadPosition();
			while(pos) m_index.Add(&GetNext(pos));
		}

		return m_index.GetCount();
	}

	void SubtitleFile::SegmentList::Lookup(float at, CAtlList<SegmentItem>& sis)
	{
		sis.RemoveAll();

		size_t k;
		if(Lookup(at, k)) 
		{
			sis.AddTailList(GetSegment(k));
		}
	}

	bool SubtitleFile::SegmentList::Lookup(float at, size_t& k)
	{
		if(!Index()) return false;

		size_t i = 0, j = m_index.GetCount()-1;

		if(m_index[i]->m_start <= at && at < m_index[j]->m_stop)
		do
		{
			k = (i+j)/2;
			if(m_index[k]->m_start <= at && at < m_index[k]->m_stop) {return true;}
			else if(at < m_index[k]->m_start) {if(j == k) k--; j = k;}
			else if(at >= m_index[k]->m_stop) {if(i == k) k++; i = k;}
		}
		while(i <= j);

		return false;
	}

	const SubtitleFile::Segment* SubtitleFile::SegmentList::GetSegment(size_t k)
	{
		return 0 <= k && k < m_index.GetCount() ? m_index[k] : NULL;
	}

	// TODO: this should be overridable from outside

	LPCWSTR SubtitleFile::s_predef = 
		L"color#white {a: 255; r: 255; g: 255; b: 255;}; \n"
		L"color#black {a: 255; r: 0; g: 0; b: 0;}; \n"
		L"color#gray {a: 255; r: 128; g: 128; b: 128;}; \n" 
		L"color#red {a: 255; r: 255; g: 0; b: 0;}; \n"
		L"color#green {a: 255; r: 0; g: 255; b: 0;}; \n"
		L"color#blue {a: 255; r: 0; g: 0; b: 255;}; \n"
		L"color#cyan {a: 255; r: 0; g: 255; b: 255;}; \n"
		L"color#yellow {a: 255; r: 255; g: 255; b: 0;}; \n"
		L"color#magenta {a: 255; r: 255; g: 0; b: 255;}; \n"
		L" \n"
		L"align#topleft {v: \"top\"; h: \"left\";}; \n"
		L"align#topcenter {v: \"top\"; h: \"center\";}; \n"
		L"align#topright {v: \"top\"; h: \"right\";}; \n"
		L"align#middleleft {v: \"middle\"; h: \"left\";}; \n"
		L"align#middlecenter {v: \"middle\"; h: \"center\";}; \n"
		L"align#middleright {v: \"middle\"; h: \"right\";}; \n"
		L"align#bottomleft {v: \"bottom\"; h: \"left\";}; \n"
		L"align#bottomcenter {v: \"bottom\"; h: \"center\";}; \n"
		L"align#bottomright {v: \"bottom\"; h: \"right\";}; \n"
		L" \n"
		L"time#time {scale: 1;}; \n"
		L"time#startstop {start: \"start\"; stop: \"stop\";}; \n"
		L" \n"
		L"#b {font.weight: \"bold\"}; \n"
		L"#i {font.italic: \"true\"}; \n"
		L"#u {font.underline: \"true\"}; \n"
		L"#s {font.strikethrough: \"true\"}; \n"
		L" \n"
		L"#nobr {linebreak: \"none\"}; \n"
		L" \n"
		L"subtitle#subtitle \n"
		L"{ \n"
		L"	frame \n"
		L"	{ \n"
		L"		reference: \"video\"; \n"
		L"		resolution: {cx: 640; cy: 480;}; \n"
		L"	}; \n"
		L" \n"
		L"	direction \n"
		L"	{ \n"
		L"		primary: \"right\"; \n"
		L"		secondary: \"down\"; \n"
		L"	}; \n"
		L" \n"
		L"	wrap: \"normal\"; \n"
		L" \n"
		L"	layer: 0; \n"
		L" \n"
		L"	style \n"
		L"	{ \n"
		L"		linebreak: \"word\"; \n"
		L" \n"		
		L"		placement \n"
		L"		{ \n"
		L"			clip: \"none\"; \n"
		L"			margin: {t: 0; r: 0; b: 0; l: 0;}; \n"
		L"			align: bottomcenter; \n"
		L"			pos: \"auto\" \n"
		L"			offset: {x: 0; y: 0;}; \n"
		L"			angle: {x: 0; y: 0; z: 0;}; \n"
		L"			org: \"auto\" \n"
		L"			path: \"\"; \n"
		L"		}; \n"
		L" \n"
		L"		font \n"
		L"		{ \n"
		L"			face: \"Arial\"; \n"
		L"			size: 20; \n"
		L"			weight: \"bold\"; \n"
		L"			color: white; \n"
		L"			underline: \"false\"; \n"
		L"			strikethrough: \"false\"; \n"
		L"			italic: \"false\"; \n"
		L"			spacing: 0; \n"
		L"			scale: {cx: 1; cy: 1;}; \n"
		L"			kerning: \"true\"; \n"
		L"		}; \n"
		L" \n"
		L"		background \n"
		L"		{ \n"
		L"			color: black; \n"
		L"			size: 2; \n"
		L"			type: \"outline\"; \n"
		L"			blur: 0; \n"
		L"		}; \n"
		L" \n"
		L"		shadow \n"
		L"		{ \n"
		L"			color: black {a: 128;}; \n"
		L"			depth: 2; \n"
		L"			angle: -45; \n"
		L"			blur: 0; \n"
		L"		}; \n"
		L" \n"
		L"		fill \n"
		L"		{ \n"
		L"			color: yellow; \n"
		L"			width: 0; \n"
		L"		}; \n"
		L"	}; \n"
		L"}; \n";
}
