// 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 Project http://www.aegisub.org/
//
// $Id$

/// @file ass_file.cpp
/// @brief Overall storage of subtitle files, undo management and more
/// @ingroup subs_storage
///


////////////
// Includes
#include "config.h"

#include <list>
#include <fstream>
#include <wx/filename.h>
#include <wx/log.h>
#include <wx/msgdlg.h>
#include "ass_file.h"
#include "ass_dialogue.h"
#include "ass_style.h"
#include "ass_attachment.h"
#include "ass_override.h"
#include "ass_exporter.h"
#include "vfr.h"
#include "options.h"
#include "text_file_reader.h"
#include "text_file_writer.h"
#include "version.h"
#include "subtitle_format.h"



/// @brief AssFile constructor  AssFile //////////////////////
///
AssFile::AssFile () {
	AssOverrideTagProto::LoadProtos();
	Clear();
}



/// @brief AssFile destructor 
///
AssFile::~AssFile() {
	Clear();
}



/// @brief Load generic subs 
/// @param file
/// @param charset     
/// @param addToRecent 
///
void AssFile::Load (const wxString _filename,const wxString charset,bool addToRecent) {
	bool ok = true;

	try {
		// Try to open file
		FILE *file;
#ifdef WIN32
		file = _tfopen(_filename.c_str(), _T("r"));
#else
		file = fopen(_filename.mb_str(*wxConvFileName), "r");
#endif
		if (!file) {
			throw _T("Unable to open file \"") + _filename + _T("\". Check if it exists and if you have permissions to read it.");
		}
		fclose(file);

		// Find file encoding
		wxString enc;
		if (charset.IsEmpty()) enc = TextFileReader::GetEncoding(_filename);
		else enc = charset;
		TextFileReader::EnsureValid(enc);

		// Generic preparation
		Clear();

		// Get proper format reader
		SubtitleFormat *reader = SubtitleFormat::GetReader(_filename);

		// Read file
		if (reader) {
			reader->SetTarget(this);
			reader->ReadFile(_filename,enc);
		}

		// Couldn't find a type
		else throw _T("Unknown file type.");
	}

	// String error
	catch (const wchar_t *except) {
		wxMessageBox(except,_T("Error loading file"),wxICON_ERROR | wxOK);
		ok = false;
	}

	catch (wxString except) {
		wxMessageBox(except,_T("Error loading file"),wxICON_ERROR | wxOK);
		ok = false;
	}

	// Other error
	catch (...) {
		wxMessageBox(_T("Unknown error"),_T("Error loading file"),wxICON_ERROR | wxOK);
		ok = false;
	}

	// Verify loading
	if (ok) filename = _filename;
	else LoadDefault();

	// Set general data
	loaded = true;

	// Add comments and set vars
	AddComment(_T("Script generated by Aegisub ") + GetAegisubLongVersionString());
	AddComment(_T("http://www.aegisub.net"));
	SetScriptInfo(_T("ScriptType"),_T("v4.00+"));

	// Add to recent
	if (addToRecent) AddToRecent(_filename);
}



/// @brief Save a file to Hard Disk 
/// @param _filename   
/// @param setfilename 
/// @param addToRecent 
/// @param encoding    
///
void AssFile::Save(wxString _filename,bool setfilename,bool addToRecent,const wxString encoding) {
	// Finds last dot
	int i = 0;
	for (i=(int)_filename.size();--i>=0;) {
		if (_filename[i] == '.') break;
	}
	wxString extension = _filename.substr(i+1);
	extension.Lower();

	// Get writer
	SubtitleFormat *writer = SubtitleFormat::GetWriter(_filename);

	// Write file
	if (writer) {
		writer->SetTarget(this);
		writer->WriteFile(_filename,encoding);
	}

	// Couldn't find a type
	else throw _T("Unknown file type.");

	// Add to recent
	if (addToRecent) AddToRecent(_filename);

	// Done
	if (setfilename) {
		Modified = false;
		filename = _filename;
	}
}



/// @brief Saves a file to a ram vector 
/// @param dst      
/// @param encoding 
///
void AssFile::SaveMemory(std::vector<char> &dst,const wxString encoding) {
	// Set encoding
	wxString enc = encoding;
	if (enc.IsEmpty()) enc = _T("UTF-8");
	if (enc != _T("UTF-8")) throw _T("Memory writer only supports UTF-8 for now.");

	// Check if subs contain at least one style
	// Add a default style if they don't for compatibility with libass/asa
	if (GetStyles().Count() == 0)
		InsertStyle(new AssStyle());

	// Prepare vector
	dst.clear();
	dst.reserve(0x4000);

	// Write file
	entryIter cur;
	unsigned int lineSize = 0;
	unsigned int targetSize = 0;
	unsigned int pos = 0;
	wxCharBuffer buffer;
	for (cur=Line.begin();cur!=Line.end();cur++) {
		// Convert
		wxString temp = (*cur)->GetEntryData() + _T("\r\n");
		buffer = temp.mb_str(wxConvUTF8);
		lineSize = strlen(buffer);

		// Raise capacity if needed
		targetSize = dst.size() + lineSize;
		if (dst.capacity() < targetSize) {
			unsigned int newSize = dst.capacity();
			while (newSize < targetSize) newSize *= 2;
			dst.reserve(newSize);
		}

		// Append line
		pos = dst.size();
		dst.resize(targetSize);
		memcpy(&dst[pos],buffer,lineSize);
	}
}



/// @brief Exports file with proper transformations 
/// @param _filename 
///
void AssFile::Export(wxString _filename) {
	AssExporter exporter(this);
	exporter.AddAutoFilters();
	exporter.Export(_filename,_T("UTF-8"));
}



/// @brief Can save file? 
/// @return 
///
bool AssFile::CanSave() {
	// ASS format?
	wxString ext = filename.Lower().Right(4);
	if (ext == _T(".ass")) return true;

	// Never save texts
	if (ext == _T(".txt")) return false;

	// Check if it's a known extension
	SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
	if (!writer) return false;

	// Check if format supports timing
	bool canTime = true;
	//if (filename.Lower().Right(4) == _T(".txt")) canTime = false;

	// Scan through the lines
	AssStyle defstyle;
	AssStyle *curstyle;
	AssDialogue *curdiag;
	AssAttachment *attach;
	for (entryIter cur=Line.begin();cur!=Line.end();cur++) {
		// Check style, if anything non-default is found, return false
		curstyle = AssEntry::GetAsStyle(*cur);
		if (curstyle) {
			if (curstyle->GetEntryData() != defstyle.GetEntryData()) return false;
		}

		// Check for attachments, if any is found, return false
		attach = AssEntry::GetAsAttachment(*cur);
		if (attach) return false;

		// Check dialog
		curdiag = AssEntry::GetAsDialogue(*cur);
		if (curdiag) {
			// Timed?
			if (!canTime && (curdiag->Start.GetMS() != 0 || curdiag->End.GetMS() != 0)) return false;

			// Overrides?
			curdiag->ParseASSTags();
			for (size_t i=0;i<curdiag->Blocks.size();i++) {
				if (curdiag->Blocks[i]->GetType() != BLOCK_PLAIN) return false;
			}
			curdiag->ClearBlocks();
		}
	}

	// Success
	return true;
}


////////////////////////////////////
// Returns script as a single string
//wxString AssFile::GetString() {
//	using std::list;
//	wxString ret;
//	AssEntry *entry;
//	ret += 0xfeff;
//	for (list<AssEntry*>::iterator cur=Line.begin();cur!=Line.end();) {
//		entry = *cur;
//		ret += entry->GetEntryData();
//		ret += L"\n";
//		cur++;
//	}
//	return ret;
//}



/// @brief  even moving things out of order might break ASS parsing - AMZ. I strongly advice you against touching this function unless you know what you're doing; ------------------- Appends line to Ass 
/// @param data     
/// @param group    
/// @param lasttime 
/// @param version  
/// @param outGroup 
/// @return 
///
int AssFile::AddLine (wxString data,wxString group,int lasttime,int &version,wxString *outGroup) {
	// Group
	AssEntry *entry = NULL;
	wxString origGroup = group;
	static wxString keepGroup;
	if (!keepGroup.IsEmpty()) group = keepGroup;
	if (outGroup) *outGroup = group;
	wxString lowGroup = group.Lower();

	// Attachment
	if (lowGroup == _T("[fonts]") || lowGroup == _T("[graphics]")) {
		// Check if it's valid data
		size_t dataLen = data.Length();
		bool validData = (dataLen > 0) && (dataLen <= 80);
		for (size_t i=0;i<dataLen;i++) {
			if (data[i] < 33 || data[i] >= 97) validData = false;
		}

		// Is the filename line?
		bool isFilename = (data.Left(10) == _T("fontname: ") || data.Left(10) == _T("filename: "));

		// The attachment file is static, since it is built through several calls to this
		// After it's done building, it's reset to NULL
		static AssAttachment *attach = NULL;

		// Attachment exists, and data is over
		if (attach && (!validData || isFilename)) {
			attach->Finish();
			keepGroup.Clear();
			group = origGroup;
			lowGroup = group.Lower();
			Line.push_back(attach);
			attach = NULL;
		}

		// Create attachment if needed
		if (isFilename) {
			attach = new AssAttachment(data.Mid(10));
			attach->SetStartMS(lasttime);
			attach->group = group;
			keepGroup = group;
			return lasttime;
		}

		// Valid data?
		if (attach && validData) {
			// Insert data
			attach->AddData(data);

			// Done building
			if (data.Length() < 80) {
				attach->Finish();
				keepGroup.Clear();
				group = origGroup;
				lowGroup = group.Lower();
				entry = attach;
				attach = NULL;
			}

			// Not done
			else {
				return lasttime;
			}
		}
	}

	// Dialogue
	if (lowGroup == _T("[events]")) {
		if ((data.Left(9) == _T("Dialogue:") || data.Left(8) == _T("Comment:"))) {
			AssDialogue *diag = new AssDialogue(data,version);
			lasttime = diag->GetStartMS();
			//diag->ParseASSTags();
			entry = diag;
			entry->SetStartMS(lasttime);
			entry->group = group;
		}
		if (data.Left(7) == _T("Format:")) {
			entry = new AssEntry(_T("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"));
			entry->SetStartMS(lasttime);
			entry->group = group;
		}
	}

	// Style
	else if (lowGroup == _T("[v4+ styles]")) {
		if (data.Left(6) == _T("Style:")) {
			AssStyle *style = new AssStyle(data,version);
			entry = style;
			entry->SetStartMS(lasttime);
			entry->group = group;
		}
		if (data.Left(7) == _T("Format:")) {
			entry = new AssEntry(_T("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"));
			entry->SetStartMS(lasttime);
			entry->group = group;
		}
	}

	// Script info
	else if (lowGroup == _T("[script info]")) {
		// Comment
		if (data.Left(1) == _T(";")) {
			// Skip stupid comments added by other programs
			// Of course, we'll add our own in place later... ;)
			return lasttime;
		}

		// Version
		if (data.Left(11) == _T("ScriptType:")) {
			wxString versionString = data.Mid(11);
			versionString.Trim(true);
			versionString.Trim(false);
			versionString.MakeLower();
			int trueVersion;
			if (versionString == _T("v4.00")) trueVersion = 0;
			else if (versionString == _T("v4.00+")) trueVersion = 1;
			else if (versionString == _T("v4.00++")) trueVersion = 2;
			else throw _T("Unknown SSA file format version");
			if (trueVersion != version) {
				if (!(trueVersion == 2 && version == 1)) wxLogMessage(_T("Warning: File has the wrong extension."));
				version = trueVersion;
			}
		}

		// Everything
		entry = new AssEntry(data);
		entry->SetStartMS(lasttime);
		entry->group = group;
	}

	// Common entry
	if (entry == NULL) {
		entry = new AssEntry(data);
		entry->SetStartMS(lasttime);
		entry->group = group;
	}

	// Insert the line
	Line.push_back(entry);
	return lasttime;
}



/// @brief Clears contents of assfile 
///
void AssFile::Clear () {
	for (std::list<AssEntry*>::iterator cur=Line.begin();cur != Line.end();cur++) {
		if (*cur) delete *cur;
	}
	Line.clear();

	loaded = false;
	filename = _T("");
	Modified = false;
}



/// @brief Loads default subs 
/// @param defline 
///
void AssFile::LoadDefault (bool defline) {
	// Clear first
	Clear();

	// Write headers
	AssStyle defstyle;
	int version = 1;
	AddLine(_T("[Script Info]"),_T("[Script Info]"),-1,version);
	AddLine(_T("Title: Default Aegisub file"),_T("[Script Info]"),-1,version);
	AddLine(_T("ScriptType: v4.00+"),_T("[Script Info]"),-1,version);
	AddLine(_T("WrapStyle: 0"), _T("[Script Info]"),-1,version);
	AddLine(_T("PlayResX: 640"),_T("[Script Info]"),-1,version);
	AddLine(_T("PlayResY: 480"),_T("[Script Info]"),-1,version);
	AddLine(_T("ScaledBorderAndShadow: yes"),_T("[Script Info]"),-1,version);
	AddLine(_T(""),_T("[Script Info]"),-1,version);
	AddLine(_T("[V4+ Styles]"),_T("[V4+ Styles]"),-1,version);
	AddLine(_T("Format:  Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"),_T("[V4+ Styles]"),-1,version);
	AddLine(defstyle.GetEntryData(),_T("[V4+ Styles]"),-1,version);
	AddLine(_T(""),_T("[V4+ Styles]"),-1,version);
	AddLine(_T("[Events]"),_T("[Events]"),-1,version);
	AddLine(_T("Format:  Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"),_T("[Events]"),-1,version);

	if (defline) {
		AssDialogue def;
		AddLine(def.GetEntryData(),_T("[Events]"),0,version);
	}

	loaded = true;
}



/// @brief Copy constructor 
/// @param from 
///
AssFile::AssFile (AssFile &from) {
	using std::list;

	// Copy standard variables
	filename = from.filename;
	loaded = from.loaded;
	Modified = from.Modified;

	// Copy lines
	for (list<AssEntry*>::iterator cur=from.Line.begin();cur!=from.Line.end();cur++) {
		Line.push_back((*cur)->Clone());
	}
}



/// @brief Insert a new style 
/// @param style 
///
void AssFile::InsertStyle (AssStyle *style) {
	// Variables
	using std::list;
	AssEntry *curEntry;
	list<AssEntry*>::iterator lastStyle = Line.end();
	list<AssEntry*>::iterator cur;
	int lasttime = -1;
	wxString lastGroup;

	// Look for insert position
	for (cur=Line.begin();cur!=Line.end();cur++) {
		curEntry = *cur;
		if (curEntry->GetType() == ENTRY_STYLE || (lastGroup == _T("[V4+ Styles]") && curEntry->GetEntryData().substr(0,7) == _T("Format:"))) {
			lastStyle = cur;
		}
		lasttime = curEntry->GetStartMS();
		lastGroup = curEntry->group;
	}

	// No styles found, add them
	if (lastStyle == Line.end()) {
		// Add space
		curEntry = new AssEntry(_T(""));
		curEntry->group = lastGroup;
		curEntry->SetStartMS(lasttime);
		Line.push_back(curEntry);

		// Add header
		curEntry = new AssEntry(_T("[V4+ Styles]"));
		curEntry->group = _T("[V4+ Styles]");
		curEntry->SetStartMS(lasttime);
		Line.push_back(curEntry);

		// Add format line
		curEntry = new AssEntry(_T("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"));
		curEntry->group = _T("[V4+ Styles]");
		curEntry->SetStartMS(lasttime);
		Line.push_back(curEntry);

		// Add style
		style->group = _T("[V4+ Styles]");
		style->SetStartMS(lasttime);
		Line.push_back(style);
	}

	// Add to end of list
	else {
		lastStyle++;
		style->group = (*lastStyle)->group;
		style->SetStartMS(lasttime);
		Line.insert(lastStyle,style);
	}
}



/// @brief Insert attachment 
/// @param attach 
///
void AssFile::InsertAttachment (AssAttachment *attach) {
	// Search for insertion point
	std::list<AssEntry*>::iterator insPoint=Line.end(),cur;
	for (cur=Line.begin();cur!=Line.end();cur++) {
		// Check if it's another attachment
		AssAttachment *att = AssEntry::GetAsAttachment(*cur);
		if (att) {
			if (attach->group == att->group) insPoint = cur;
		}

		// See if it's the start of group
		else if ((*cur)->GetType() == ENTRY_BASE) {
			AssEntry *entry = (AssEntry*) (*cur);
			if (entry->GetEntryData() == attach->group) insPoint = cur;
		}
	}

	// Found point, insert there
	if (insPoint != Line.end()) {
		insPoint++;
		attach->SetStartMS((*insPoint)->GetStartMS());
		Line.insert(insPoint,attach);
	}

	// Otherwise, create the [Fonts] group and insert
	else {
		int version=1;
		int StartMS = Line.back()->GetStartMS();
		AddLine(_T(""),Line.back()->group,StartMS,version);
		AddLine(attach->group,attach->group,StartMS,version);
		attach->SetStartMS(StartMS);
		Line.push_back(attach);
		AddLine(_T(""),attach->group,StartMS,version);
	}
}



/// @brief Insert attachment from file 
/// @param filename 
///
void AssFile::InsertAttachment (wxString filename) {
	// Get name
	wxFileName fname(filename);

	// Create
	AssAttachment *newAttach = new AssAttachment(fname.GetFullName());

	// Load
	try {
		newAttach->Import(filename);
	}
	catch (...) {
		delete newAttach;
		throw;
	}

	// Insert
	wxString ext = filename.Right(4).Lower();
	if (ext == _T(".ttf") || ext == _T(".ttc") || ext == _T(".pfb")) newAttach->group = _T("[Fonts]");
	else newAttach->group = _T("[Graphics]");
	InsertAttachment(newAttach);
}



/// @brief Gets script info 
/// @param _key 
/// @return 
///
wxString AssFile::GetScriptInfo(const wxString _key) {
	// Prepare
	wxString key = _key;;
	key.Lower();
	key += _T(":");
	std::list<AssEntry*>::iterator cur;
	bool GotIn = false;

	// Look for it
	for (cur=Line.begin();cur!=Line.end();cur++) {
		if ((*cur)->group == _T("[Script Info]")) {
			GotIn = true;
			wxString curText = (*cur)->GetEntryData();
			curText.Lower();

			// Found
			if (curText.Left(key.length()) == key) {
				wxString result = curText.Mid(key.length());
				result.Trim(false);
				result.Trim(true);
				return result;
			}
		}
		else if (GotIn) break;
	}

	// Couldn't find
	return _T("");
}



/// @brief Get script info as int 
/// @param key 
/// @return 
///
int AssFile::GetScriptInfoAsInt(const wxString key) {
	long temp = 0;
	try {
		GetScriptInfo(key).ToLong(&temp);
	}
	catch (...) {
		temp = 0;
	}
	return temp;
}



/// @brief Set a script info line 
/// @param _key  
/// @param value 
/// @return 
///
void AssFile::SetScriptInfo(const wxString _key,const wxString value) {
	// Prepare
	wxString key = _key;;
	key.Lower();
	key += _T(":");
	std::list<AssEntry*>::iterator cur;
	std::list<AssEntry*>::iterator prev;
	bool GotIn = false;

	// Look for it
	for (cur=Line.begin();cur!=Line.end();cur++) {
		if ((*cur)->group == _T("[Script Info]")) {
			GotIn = true;
			wxString curText = (*cur)->GetEntryData();
			curText.Lower();

			// Found
			if (curText.Left(key.length()) == key) {
				// Set value
				if (value != _T("")) {
					wxString result = _key;
					result += _T(": ");
					result += value;
					(*cur)->SetEntryData(result);
				}

				// Remove key
				else {
					Line.erase(cur);
					return;
				}
				return;
			}

			if (!(*cur)->GetEntryData().empty()) prev = cur;
		}

		// Add
		else if (GotIn) {
			if (value != _T("")) {
				wxString result = _key;
				result += _T(": ");
				result += value;
				AssEntry *entry = new AssEntry(result);
				entry->group = (*prev)->group;
				entry->SetStartMS((*prev)->GetStartMS());
				Line.insert(++prev,entry);
			}
			return;
		}
	}
}



/// @brief Get resolution 
/// @param sw 
/// @param sh 
///
void AssFile::GetResolution(int &sw,int &sh) {
	// Height
	wxString temp = GetScriptInfo(_T("PlayResY"));
	if (temp.IsEmpty() || !temp.IsNumber()) {
		sh = 0;
	}
	else {
		long templ;
		temp.ToLong(&templ);
		sh = templ;
	}

	// Width
	temp = GetScriptInfo(_T("PlayResX"));
	if (temp.IsEmpty() || !temp.IsNumber()) {
		sw = 0;
	}
	else {
		long templ;
		temp.ToLong(&templ);
		sw = templ;
	}

	// Gabest logic?
	if (sw == 0 && sh == 0) {
		sw = 384;
		sh = 288;
	} else if (sw == 0) {
		if (sh == 1024)
			sw = 1280;
		else
			sw = sh * 4 / 3;
	} else if (sh == 0) {
		if (sw == 1280)
			sh = 1024;
		else
			sh = sw * 3 / 4;
	}
}



/// @brief Adds a comment to [Script Info] 
/// @param _comment 
///
void AssFile::AddComment(const wxString _comment) {
	wxString comment = _T("; ");
	comment += _comment;
	std::list<AssEntry*>::iterator cur;
	int step = 0;

	// Find insert position
	for (cur=Line.begin();cur!=Line.end();cur++) {
		// Start of group
		if (step == 0 && (*cur)->group == _T("[Script Info]")) step = 1;

		// First line after a ;
		else if (step == 1 && (*cur)->GetEntryData().Left(1) != _T(";")) {
			AssEntry *prev = *cur;
			AssEntry *comm = new AssEntry(comment);
			comm->group = prev->group;
			comm->SetStartMS(prev->GetStartMS());
			Line.insert(cur,comm);
			break;
		}
	}
}



/// @brief Get list of styles 
/// @return 
///
wxArrayString AssFile::GetStyles() {
	wxArrayString styles;
	AssStyle *curstyle;
	for (entryIter cur=Line.begin();cur!=Line.end();cur++) {
		curstyle = AssEntry::GetAsStyle(*cur);
		if (curstyle) {
			styles.Add(curstyle->name);
		}
	}
	return styles;
}



/// @brief Gets style of specific name 
/// @param name 
/// @return 
///
AssStyle *AssFile::GetStyle(wxString name) {
	AssStyle *curstyle;
	for (entryIter cur=Line.begin();cur!=Line.end();cur++) {
		curstyle = AssEntry::GetAsStyle(*cur);
		if (curstyle) {
			if (curstyle->name == name) return curstyle;
		}
	}
	return NULL;
}



/// @brief Adds file name to list of recent 
/// @param file 
///
void AssFile::AddToRecent(wxString file) {
	Options.AddToRecentList(file,_T("Recent sub"));
}



/// @brief List of supported wildcards 
/// @param mode 
/// @return 
///
wxString AssFile::GetWildcardList(int mode) {
	//if (mode == 0) return _T("All Supported Types (*.ass,*.ssa,*.srt,*.txt,*.mkv,*.mks,*.mka)|*.ass;*.ssa;*.srt;*.txt;*.mkv;*.mks;*.mka|Advanced Substation Alpha (*.ass)|*.ass|Substation Alpha (*.ssa)|*.ssa|SubRip (*.srt)|*.srt|Plain-text (*.txt)|*.txt|Matroska (*.mkv,*.mks,*.mka)|*.mkv;*.mks;*.mka");
	//else if (mode == 1) return _T("Advanced Substation Alpha (*.ass)|*.ass");
	//else if (mode == 2) return _T("All Supported Types (*.ass,*.ssa,*.srt,*.txt,*.mkv,*.mks,*.mka)|*.ass;*.ssa;*.srt;*.txt|Advanced Substation Alpha (*.ass)|*.ass|Substation Alpha (*.ssa)|*.ssa|SubRip (*.srt)|*.srt|Plain-text (*.txt)|*.txt");
	//else return _T("");

	if (mode == 0) return SubtitleFormat::GetWildcards(0);
	else if (mode == 1) return _T("Advanced Substation Alpha (*.ass)|*.ass");
	else if (mode == 2) return SubtitleFormat::GetWildcards(1);
	else return _T("");
}



/// @brief Compress/decompress for storage on stack 
/// @param compress 
///
void AssFile::CompressForStack(bool compress) {
	AssDialogue *diag;
	for (entryIter cur=Line.begin();cur!=Line.end();cur++) {
		diag = AssEntry::GetAsDialogue(*cur);
		if (diag) {
			if (compress) {
				diag->SetEntryData(_T(""));
				diag->ClearBlocks();
			}
			else diag->UpdateData();
		}
	}
}



/// @brief Checks if file is modified 
/// @return 
///
bool AssFile::IsModified() {
	return Modified;
}



/// @brief Flag file as modified 
/// @param desc 
///
void AssFile::FlagAsModified(wxString desc) {
	// Clear redo
	if (!RedoStack.empty()) {
		//StackPush();
		//UndoStack.push_back(new AssFile(*UndoStack.back()));
		for (std::list<AssFile*>::iterator cur=RedoStack.begin();cur!=RedoStack.end();cur++) {
			delete *cur;
		}
		RedoStack.clear();
	}

	Modified = true;
	StackPush(desc);
}



/// @brief Stack push 
/// @param desc 
///
void AssFile::StackPush(wxString desc) {
	// Places copy on stack
	AssFile *curcopy = new AssFile(*top);
	curcopy->CompressForStack(true);
	curcopy->undodescription = desc;
	UndoStack.push_back(curcopy);
	StackModified = true;

	// Cap depth
	int n = 0;
	for (std::list<AssFile*>::iterator cur=UndoStack.begin();cur!=UndoStack.end();cur++) {
		n++;
	}
	int depth = Options.AsInt(_T("Undo levels"));
	while (n > depth) {
		delete UndoStack.front();
		UndoStack.pop_front();
		n--;
	}
}



/// @brief Stack pop 
///
void AssFile::StackPop() {
	bool addcopy = false;
	wxString undodesc=_T("");
	

	if (StackModified) {
		undodesc=UndoStack.back()->undodescription;
		delete UndoStack.back();
		UndoStack.pop_back();
		StackModified = false;
		addcopy = true;
	}

	if (!UndoStack.empty()) {
		//delete top;
		AssFile *undo = UndoStack.back();
		top->CompressForStack(true);
		top->undodescription = undodesc;
		RedoStack.push_back(top);
		top = undo;
		top->CompressForStack(false);
		UndoStack.pop_back();
		Popping = true;
	}

	if (addcopy) {
		StackPush(top->undodescription);
	}
}



/// @brief Stack redo 
///
void AssFile::StackRedo() {

	bool addcopy = false;
	if (StackModified) {
		delete UndoStack.back();
		UndoStack.pop_back();
		StackModified = false;
		addcopy = true;
	}

	if (!RedoStack.empty()) {
		top->CompressForStack(true);
		UndoStack.push_back(top);
		top = RedoStack.back();
		top->CompressForStack(false);
		RedoStack.pop_back();
		Popping = true;
		//StackModified = false;
	}

	if (addcopy) {
		StackPush(top->undodescription);
	}
}



/// @brief Stack clear 
///
void AssFile::StackClear() {
	// Clear undo
	for (std::list<AssFile*>::iterator cur=UndoStack.begin();cur!=UndoStack.end();cur++) {
		delete *cur;
	}
	UndoStack.clear();

	// Clear redo
	for (std::list<AssFile*>::iterator cur=RedoStack.begin();cur!=RedoStack.end();cur++) {
		delete *cur;
	}
	RedoStack.clear();

	Popping = false;
}



/// @brief Stack reset 
/// @return 
///
void AssFile::StackReset() {
	StackClear();
	delete top;
	top = new AssFile;
	StackModified = false;
}



/// @brief Returns if undo stack is empty 
/// @return 
///
bool AssFile::IsUndoStackEmpty() {
	if (StackModified) return (UndoStack.size() <= 1);
	else return UndoStack.empty();
}



/// @brief Returns if redo stack is empty 
/// @return 
///
bool AssFile::IsRedoStackEmpty() {
	return RedoStack.empty();
}


/// @brief DOCME
/// @return 
///
wxString AssFile::GetUndoDescription() {
	return (IsUndoStackEmpty())?_T(""):(UndoStack.back())->undodescription;
}


/// @brief DOCME
/// @return 
///
wxString AssFile::GetRedoDescription() {
	return (IsRedoStackEmpty())?_T(""):(RedoStack.back())->undodescription;

}


/// DOCME
AssFile *AssFile::top;

/// DOCME
std::list<AssFile*> AssFile::UndoStack;

/// DOCME
std::list<AssFile*> AssFile::RedoStack;

/// DOCME
bool AssFile::Popping;

/// DOCME
bool AssFile::StackModified;






