// Copyright (c) 2005, Rodrigo Braz Monteiro
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of the Aegisub Group nor the names of its contributors
//     may be used to endorse or promote products derived from this software
//     without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:zeratul@cellosoft.com
//


////////////
// Includes
#include "ass_dialogue.h"
#include "ass_override.h"
#include <wx/tokenzr.h>


////////////////////// AssOverrideParameter //////////////////////
///////////////
// Constructor
AssOverrideParameter::AssOverrideParameter () {
	classification = PARCLASS_NORMAL;
	ommited = false;
}


//////////////
// Destructor
AssOverrideParameter::~AssOverrideParameter () {
}


////////
// Copy
void AssOverrideParameter::CopyFrom (const AssOverrideParameter &param) {
	switch(param.GetType()) {
		case VARDATA_INT: SetInt(param.AsInt()); break;
		case VARDATA_FLOAT: SetFloat(param.AsFloat()); break;
		case VARDATA_TEXT: SetText(param.AsText()); break;
		case VARDATA_BOOL: SetBool(param.AsBool()); break;
		case VARDATA_COLOUR: SetColour(param.AsColour()); break;
		case VARDATA_BLOCK: SetBlock(param.AsBlock()); break;
		default: DeleteValue();
	}
	classification = param.classification;
	ommited = param.ommited;
}

void AssOverrideParameter::operator= (const AssOverrideParameter &param) {
	CopyFrom(param);
}


////////////////////// AssDialogueBlockOverride //////////////////////
///////////////
// Constructor
AssDialogueBlockOverride::AssDialogueBlockOverride () {
	type = BLOCK_OVERRIDE;
}


//////////////
// Destructor
AssDialogueBlockOverride::~AssDialogueBlockOverride () {
	for (std::vector<AssOverrideTag*>::iterator cur=Tags.begin();cur!=Tags.end();cur++) {
		delete *cur;
	}
	Tags.clear();
}


/////////////
// Read tags
void AssDialogueBlockOverride::ParseTags () {
	// Clear current vector
	Tags.clear();

	// Fix parenthesis matching
	while (text.Freq(_T('(')) > text.Freq(_T(')'))) {
		text += _T(")");
	}

	// Initialize tokenizer
	wxStringTokenizer tkn(text,_T("\\"),wxTOKEN_RET_EMPTY_ALL);

	while (tkn.HasMoreTokens()) {
		wxString curTag = _T("\\");
		curTag += tkn.GetNextToken();
		if (curTag == _T("\\")) continue;

		// Check for parenthesis matching
		while (curTag.Freq(_T('(')) > curTag.Freq(_T(')'))) {
			if (!tkn.HasMoreTokens()) {
				wxLogWarning(_T("Unmatched parenthesis! Line contents: ") + parent->Text);
				break;
			}
			curTag << _T("\\") << tkn.GetNextToken();
		}

		AssOverrideTag *newTag = new AssOverrideTag;
		newTag->SetText(curTag);
		Tags.push_back(newTag);
	}
}


///////////////////////////
// Get Text representation
wxString AssDialogueBlockOverride::GetText () {
	text = _T("");
	for (std::vector<AssOverrideTag*>::iterator cur=Tags.begin();cur!=Tags.end();cur++) {
		text += (*cur)->ToString();
	}
	return text;
}


///////////////////////////////////
// Process parameters via callback
void AssDialogueBlockOverride::ProcessParameters(void (*callback)(wxString,int,AssOverrideParameter *,void *),void *userData) {
	AssOverrideTag *curTag;
	AssOverrideParameter *curPar;

	// Find tags
	for (std::vector<AssOverrideTag*>::iterator cur=Tags.begin();cur!=Tags.end();cur++) {
		int n = 0;
		curTag = *cur;

		// Find parameters
		for (std::vector<AssOverrideParameter*>::iterator curParam=curTag->Params.begin();curParam!=curTag->Params.end();curParam++) {
            curPar = *curParam;

			if (curPar->GetType() != VARDATA_NONE && curPar->ommited == false) {
				// Do callback
				(*callback)(curTag->Name,n,curPar,userData);

				// Go recursive if it's a block parameter
				//if (curPar->classification == VARDATA_BLOCK) {
				if (curPar->GetType() == VARDATA_BLOCK) {
					curPar->AsBlock()->ProcessParameters(callback,userData);
				}
			}

			n++;
		}
	}
}


///////////////////////// AssOverrideParamProto //////////////////////////
///////////////
// Constructor
AssOverrideParamProto::AssOverrideParamProto (VariableDataType _type,int opt,ASS_ParameterClass classi) {
	type = _type;
	optional = opt;
	classification = classi;
}


//////////////
// Destructor
AssOverrideParamProto::~AssOverrideParamProto() {
}


///////////////////////// AssOverrideTagProto //////////////////////////
///////////////
// Static vars
std::list<AssOverrideTagProto> AssOverrideTagProto::proto;
bool AssOverrideTagProto::loaded = false;


///////////////
// Constructor
AssOverrideTagProto::AssOverrideTagProto() {
}


//////////////
// Destructor
AssOverrideTagProto::~AssOverrideTagProto() {
}


///////////////////
// Load prototypes
void AssOverrideTagProto::LoadProtos () {
	if (loaded) return;
	loaded = true;

	// \alpha
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\alpha");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT));

	// \bord<depth>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\bord");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_SIZE));

	// \shad<depth>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\shad");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_SIZE));

	// \fade(<a1>,<a2>,<a3>,<t1>,<t2>,<t3>,<t4>)
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fade");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START));

	// \move(<x1>,<y1>,<x2>,<y2>[,<t1>,<t2>])
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\move");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_6,PARCLASS_RELATIVE_TIME_START));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_6,PARCLASS_RELATIVE_TIME_START));

	// \clip(<x1>,<y1>,<x2>,<y2>)
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\clip");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y));

	// \clip([<scale>,]<some drawings>)
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\clip");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_2,PARCLASS_NORMAL));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_DRAWING));

	// \fscx<percent>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fscx");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_RELATIVE_SIZE_X));

	// \fscy<percent>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fscy");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_RELATIVE_SIZE_Y));

	// \pos(<x>,<y>)
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\pos");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y));

	// \org(<x>,<y>)
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\org");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y));

	// \pbo<y>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\pbo");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y));

	// \fad(<t1>,<t2>)
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fad");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_END));

	// \fsp<pixels>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fsp");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_SIZE));

	// \frx<degrees>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\frx");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \fry<degrees>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fry");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_NORMAL));
	
	// \frz<degrees>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\frz");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \fr<degrees>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fr");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \1c&H<bbggrr>&
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\1c");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \2c&H<bbggrr>&
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\2c");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \3c&H<bbggrr>&
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\3c");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \4c&H<bbggrr>&
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\4c");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \1a&H<aa>&
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\1a");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \2a&H<aa>&
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\2a");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \3a&H<aa>&
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\3a");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \4a&H<aa>&
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\4a");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \fe<charset>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fe");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \ko<duration>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\ko");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_KARAOKE));

	// \kf<duration>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\kf");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_KARAOKE));

	// \be<0/1>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\be");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_BOOL,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \fn<name>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fn");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \fs<size>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\fs");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_SIZE));

	// \an<alignment>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\an");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \c&H<bbggrr>&
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\c");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \b<0/1/weight>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\b");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_1,PARCLASS_NORMAL));
	proto.back().params.back().defaultValue.SetBool(false);

	// \i<0/1>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\i");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_BOOL,OPTIONAL_1,PARCLASS_NORMAL));
	proto.back().params.back().defaultValue.SetBool(false);

	// \u<0/1>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\u");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_BOOL,OPTIONAL_1,PARCLASS_NORMAL));
	proto.back().params.back().defaultValue.SetBool(false);

	// \s<0/1>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\s");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_BOOL,OPTIONAL_1,PARCLASS_NORMAL));
	proto.back().params.back().defaultValue.SetBool(false);

	// \a<alignment>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\a");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \k<duration>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\k");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_KARAOKE));

	// \K<duration>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\K");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_KARAOKE));

	// \q<0-3>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\q");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \p<n>
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\p");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_NORMAL));

	// \r[<name>]
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\r");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,OPTIONAL_1,PARCLASS_NORMAL));

	// \t([<t1>,<t2>,][<accel>,]<style modifiers>)
	proto.push_back(AssOverrideTagProto());
	proto.back().name = _T("\\t");
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_3 | OPTIONAL_4,PARCLASS_RELATIVE_TIME_START));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_3 | OPTIONAL_4,PARCLASS_RELATIVE_TIME_START));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,OPTIONAL_2 | OPTIONAL_4,PARCLASS_NORMAL));
	proto.back().params.push_back(AssOverrideParamProto(VARDATA_BLOCK,NOT_OPTIONAL,PARCLASS_NORMAL));
}


//////////////////////// AssOverrideTag //////////////////////////
///////////////
// Constructor
AssOverrideTag::AssOverrideTag () {
	valid = false;
}


//////////////
// Destructor
AssOverrideTag::~AssOverrideTag () {
	for (std::vector<AssOverrideParameter*>::iterator cur=Params.begin();cur!=Params.end();cur++) {
		delete *cur;
	}
	Params.clear();
	valid = false;
}


////////////////////////////
// Parses text and sets tag
void AssOverrideTag::SetText (wxString text) {
	// Determine name
	Name = _T("");
	AssOverrideTagProto *proto;
	for (std::list<AssOverrideTagProto>::iterator cur=AssOverrideTagProto::proto.begin();cur!=AssOverrideTagProto::proto.end();cur++) {
		proto = &(*cur);
		if (text.Left(proto->name.length()) == proto->name) {
			Name = proto->name;
			break;
		}
	}

	// Set tag name
	if (!Name.empty()) {
		wxLogDebug(_T("Parsing tag: %s"), Name.c_str());
		ParseParameters(text.Mid(Name.length()));
		valid = true;
	}

	// Junk tag
	else {
		Name = text;
		valid = false;
	}
}


/////////////////////////
// Checks if it is valid
bool AssOverrideTag::IsValid() {
	return valid;
}


/////////////////////
// Parses parameters
void AssOverrideTag::ParseParameters(wxString text) {
	// text is all text following the name until the next \ or the end of the override block

	// Tokenize text, attempting to find all parameters
	wxArrayString paramList;
	wxString work;

	{
		if (text.IsEmpty() || text[0] != _T('(')) {
			// There's just one (or none at all) parameter (because there's no parantheses)
			// This means text is all our parameters
			paramList.Add(text.Trim(true).Trim(false));
			// Only using goto here to avoid yet another nested block (keeps the code cleaner!)
			goto end_tokenizing;
		}

		// Ok, so there are parantheses used here, so there may be more than one parameter
		// Enter fullscale parsing!
		size_t i = 0, textlen = text.Length();
		size_t start = 0;
		int parDepth = 1;
		while (i < textlen && parDepth > 0) {
			// Just skip until next ',' or ')', whichever comes first
			// (Next ')' is achieved when parDepth == 0)
			start = ++i;
			while (i < textlen && parDepth > 0) {
				if (text[i] == _T('(')) parDepth++;
				if (text[i] == _T(')')) parDepth--;
				// parDepth 1 is where we start, and the tag-level we're interested in parsing on
				if (text[i] == _T(',') && parDepth == 1) break;
				if (parDepth < 0) {
					wxLogWarning(_T("Unmatched parenthesis near '%s'!\nTag-parsing incomplete."), text.SubString(i, 10).c_str());
					goto end_tokenizing;
				}
				if (parDepth == 0) {
					// We just ate the paranthesis ending this parameter block
					// Make sure it doesn't get included in the parameter text
					break;
				}
				i++;
			}
			// i now points to the first character not member of this parameter
			work = text.SubString(start, i-1);
			work.Trim(true).Trim(false);
			paramList.Add(work);
			wxLogDebug(_T("Got parameter: %s"), work.c_str());
		}

		if (i+1 < textlen) {
			// There's some additional garbage after the parantheses
			// Just add it in for completeness
			paramList.Add(text.Mid(i+1));
		}
	}
	// This label is only gone to from inside the previous block, if the tokenizing needs to end early
end_tokenizing:

	int curPar = 0;
	size_t totalPars = paramList.GetCount();

	// Get optional parameters flag
	ASS_ParameterOptional parsFlag = OPTIONAL_0;
	switch (totalPars) {
		case 1: parsFlag = OPTIONAL_1; break;
		case 2: parsFlag = OPTIONAL_2; break;
		case 3: parsFlag = OPTIONAL_3; break;
		case 4: parsFlag = OPTIONAL_4; break;
		case 5: parsFlag = OPTIONAL_5; break;
		case 6: parsFlag = OPTIONAL_6; break;
		case 7: parsFlag = OPTIONAL_7; break;
	}

	// Find prototype
	bool clipOnce = true;
	AssOverrideTagProto *proto = NULL;
	for (std::list<AssOverrideTagProto>::iterator cur=AssOverrideTagProto::proto.begin();cur!=AssOverrideTagProto::proto.end();cur++) {
		if (Name == (*cur).name) {
			if (Name == _T("\\clip") && totalPars != 4 && clipOnce) {
				clipOnce = false;
				continue;
			}
			proto = &(*cur);
			break;
		}
	}
	if (proto == NULL) {
		throw _T("Couldn't find tag prototype while parsing.");
	}

	// Get parameters
	size_t n=0;
	wxString curtok = _T("");
	if (curPar < (signed)totalPars) {
		curtok = paramList[curPar];
		curPar++;
	}

	// For each parameter
	while (n < proto->params.size()) {
		AssOverrideParamProto *curproto = &proto->params[n];
		bool isDefault = false;
		n++;

		// Create parameter
		AssOverrideParameter *newparam = new AssOverrideParameter;

		// Check if it's optional and not set (set to default)
		if (!(curproto->optional & parsFlag)) {
			if (curproto->defaultValue.GetType() != VARDATA_NONE) {
				isDefault = true;
				newparam->CopyFrom(curproto->defaultValue);
			}
			newparam->ommited = true;
			// This parameter doesn't really count against the number of parsed parameters,
			// since it's left out. Don't count it.
			curPar--;
		}

		if (isDefault == false) {
			// Determine parameter type and set value
			switch (curproto->type) {
				case VARDATA_INT: {
					long temp = 0;
					curtok.ToLong(&temp);
					newparam->SetInt(temp);
					break;
				}
				case VARDATA_FLOAT: {
					double temp = 0.0;
					curtok.ToDouble(&temp);
					newparam->SetFloat(temp);
					break;
				}
				case VARDATA_TEXT: {
					newparam->SetText(curtok);
					break;
				}
				case VARDATA_BOOL: {
					long temp = false;
					curtok.ToLong(&temp);
					newparam->SetBool(temp != 0);
					break;
				}
				case VARDATA_BLOCK: {
					AssDialogueBlockOverride *temp = new AssDialogueBlockOverride;
					temp->text = curtok;
					temp->ParseTags();
					newparam->SetBlock(temp);
					break;
				}
			}

			// Get next actual parameter
			if (curPar < (signed)totalPars) {
				// Unless this parameter was omitted (in which case the token shouldn't be eaten)
				if (!newparam->ommited) {
					curtok = paramList[curPar];
				}
				curPar++;
			}
			else curtok = _T("");
		}

		// Add to list
		newparam->classification = curproto->classification;
		Params.push_back(newparam);
	}
}


//////////////
// Get string
wxString AssOverrideTag::ToString() {
	// Start with name
	wxString result = Name;

	// Determine if it needs parentheses
	bool parenthesis = false;
	if (Name == _T("\\t") || Name == _T("\\pos") || Name == _T("\\fad") || Name == _T("\\org") || Name == _T("\\clip") || Name == _T("\\move") || Name == _T("\\fade")) parenthesis = true;
	if (parenthesis) result += _T("(");

	// Add parameters
	int n = 0;
	for (std::vector<AssOverrideParameter*>::iterator cur=Params.begin();cur!=Params.end();cur++) {
		if ((*cur)->GetType() != VARDATA_NONE && (*cur)->ommited == false) {
			result += (*cur)->AsText();
			result += _T(",");
			n++;
		}
	}
	if (n > 0) result = result.Left(result.Length()-1);

	// Finish
	if (parenthesis) result += _T(")");
	return result;
}