// Copyright (c) 2005-2010, Niels Martin Hansen
// Copyright (c) 2005-2010, Rodrigo Braz Monteiro
// Copyright (c) 2010, Amar Takhar
// 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 subtitle.cpp
/// @brief subtitle/ commands.
/// @ingroup command
///

#include "../config.h"

#ifndef AGI_PRE
#include <wx/filename.h>
#include <wx/filedlg.h>
#include <wx/choicdlg.h>
#endif

#include <libaegisub/charset_conv.h>

#include "command.h"

#include "../ass_dialogue.h"
#include "../ass_file.h"
#include "../compat.h"
#include "../dialog_attachments.h"
#include "../dialog_manager.h"
#include "../dialog_properties.h"
#include "../dialog_search_replace.h"
#include "../dialog_spellchecker.h"
#include "../frame_main.h"
#include "../include/aegisub/context.h"
#include "../main.h"
#include "../subs_grid.h"
#include "../video_context.h"
#include "../utils.h"

namespace {
	using cmd::Command;
/// @defgroup cmd-subtitle Subtitle commands.
/// @{

struct validate_nonempty_selection : public Command {
	CMD_TYPE(COMMAND_VALIDATE)
	bool Validate(const agi::Context *c) {
		return !c->selectionController->GetSelectedSet().empty();
	}
};

struct validate_nonempty_selection_video_loaded : public Command {
	CMD_TYPE(COMMAND_VALIDATE)
	bool Validate(const agi::Context *c) {
		return c->videoController->IsLoaded() && !c->selectionController->GetSelectedSet().empty();
	}
};

/// Open the attachment list.
struct subtitle_attachment : public Command {
	CMD_NAME("subtitle/attachment")
	STR_MENU("A&ttachments...")
	STR_DISP("Attachments")
	STR_HELP("Open the attachment list")

	void operator()(agi::Context *c) {
		c->videoController->Stop();
		DialogAttachments(c->parent, c->ass).ShowModal();
	}
};


/// Find words in subtitles.
struct subtitle_find : public Command {
	CMD_NAME("subtitle/find")
	STR_MENU("&Find...")
	STR_DISP("Find")
	STR_HELP("Find words in subtitles")

	void operator()(agi::Context *c) {
		c->videoController->Stop();
		Search.OpenDialog(false);
	}
};


/// Find next match of last word.
struct subtitle_find_next : public Command {
	CMD_NAME("subtitle/find/next")
	STR_MENU("Find &Next")
	STR_DISP("Find Next")
	STR_HELP("Find next match of last word")

	void operator()(agi::Context *c) {
		c->videoController->Stop();
		Search.FindNext();
	}
};

static void insert_subtitle_at_video(agi::Context *c, bool after) {
	int n = c->subsGrid->GetFirstSelRow();

	// Create line to add
	AssDialogue *def = new AssDialogue;
	int video_ms = c->videoController->TimeAtFrame(c->videoController->GetFrameN(), agi::vfr::START);
	def->Start = video_ms;
	def->End = video_ms + OPT_GET("Timing/Default Duration")->GetInt();
	def->Style = c->subsGrid->GetDialogue(n)->Style;

	// Insert it
	c->subsGrid->BeginBatch();
	c->subsGrid->InsertLine(def, n, after);
	c->subsGrid->SelectRow(n + (int)after);
	c->subsGrid->SetActiveLine(def);
	c->subsGrid->EndBatch();
}

/// Inserts a line after current.
struct subtitle_insert_after : public validate_nonempty_selection {
	CMD_NAME("subtitle/insert/after")
	STR_MENU("&After Current")
	STR_DISP("After Current")
	STR_HELP("Inserts a line after current")

	void operator()(agi::Context *c) {
		AssDialogue *active_line = c->selectionController->GetActiveLine();

		AssDialogue *new_line = new AssDialogue;
		new_line->Style = active_line->Style;
		new_line->Start = active_line->End;
		new_line->End = new_line->Start + OPT_GET("Timing/Default Duration")->GetInt();

		for (entryIter it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) {
			AssDialogue *diag = dynamic_cast<AssDialogue*>(*it);

			// Limit the line to the available time
			if (diag && diag->Start >= new_line->Start)
				new_line->End = std::min(new_line->End, diag->Start);

			// If we just hit the active line, insert the new line after it
			if (diag == active_line) {
				++it;
				c->ass->Line.insert(it, new_line);
				--it;
			}
		}

		c->ass->Commit(_("line insertion"), AssFile::COMMIT_DIAG_ADDREM);
		SelectionController<AssDialogue>::Selection sel;
		sel.insert(new_line);
		c->selectionController->SetSelectionAndActive(sel, new_line);
	}
};

/// Inserts a line after current, starting at video time.
struct subtitle_insert_after_videotime : public validate_nonempty_selection_video_loaded {
	CMD_NAME("subtitle/insert/after/videotime")
	STR_MENU("After Current, at Video Time")
	STR_DISP("After Current, at Video Time")
	STR_HELP("Inserts a line after current, starting at video time")

	void operator()(agi::Context *c) {
		insert_subtitle_at_video(c, true);
	}
};


/// Inserts a line before current.
struct subtitle_insert_before : public validate_nonempty_selection {
	CMD_NAME("subtitle/insert/before")
	STR_MENU("&Before Current")
	STR_DISP("Before Current")
	STR_HELP("Inserts a line before current")

	void operator()(agi::Context *c) {
		AssDialogue *active_line = c->selectionController->GetActiveLine();

		AssDialogue *new_line = new AssDialogue;
		new_line->Style = active_line->Style;
		new_line->End = active_line->Start;
		new_line->Start = new_line->End - OPT_GET("Timing/Default Duration")->GetInt();

		for (entryIter it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) {
			AssDialogue *diag = dynamic_cast<AssDialogue*>(*it);

			// Limit the line to the available time
			if (diag && diag->End <= new_line->End)
				new_line->Start = std::max(new_line->Start, diag->End);

			// If we just hit the active line, insert the new line before it
			if (diag == active_line)
				c->ass->Line.insert(it, new_line);
		}

		c->ass->Commit(_("line insertion"), AssFile::COMMIT_DIAG_ADDREM);
		SelectionController<AssDialogue>::Selection sel;
		sel.insert(new_line);
		c->selectionController->SetSelectionAndActive(sel, new_line);
	}
};


/// Inserts a line before current, starting at video time.
struct subtitle_insert_before_videotime : public validate_nonempty_selection_video_loaded {
	CMD_NAME("subtitle/insert/before/videotime")
	STR_MENU("Before Current, at Video Time")
	STR_DISP("Before Current, at Video Time")
	STR_HELP("Inserts a line before current, starting at video time")

	void operator()(agi::Context *c) {
		insert_subtitle_at_video(c, false);
	}
};


/// New subtitles.
struct subtitle_new : public Command {
	CMD_NAME("subtitle/new")
	STR_MENU("&New Subtitles")
	STR_DISP("New Subtitles")
	STR_HELP("New subtitles")

	void operator()(agi::Context *c) {
		if (wxGetApp().frame->TryToCloseSubs() != wxCANCEL)
			c->ass->LoadDefault();
	}
};


/// Opens a subtitles file.
struct subtitle_open : public Command {
	CMD_NAME("subtitle/open")
	STR_MENU("&Open Subtitles...")
	STR_DISP("Open Subtitles")
	STR_HELP("Opens a subtitles file")

	void operator()(agi::Context *c) {
		wxString path = lagi_wxString(OPT_GET("Path/Last/Subtitles")->GetString());
		wxString filename = wxFileSelector(_("Open subtitles file"),path,"","",AssFile::GetWildcardList(0),wxFD_OPEN | wxFD_FILE_MUST_EXIST);
		if (!filename.empty()) {
			wxGetApp().frame->LoadSubtitles(filename);
		}
	}
};


/// Opens a subtitles file with a specific charset.
struct subtitle_open_charset : public Command {
	CMD_NAME("subtitle/open/charset")
	STR_MENU("Open Subtitles with &Charset...")
	STR_DISP("Open Subtitles with Charset")
	STR_HELP("Opens a subtitles file with a specific charset")

	void operator()(agi::Context *c) {
		// Initialize charsets
		wxString path = lagi_wxString(OPT_GET("Path/Last/Subtitles")->GetString());

		// Get options and load
		wxString filename = wxFileSelector(_("Open subtitles file"),path,"","",AssFile::GetWildcardList(0),wxFD_OPEN | wxFD_FILE_MUST_EXIST);
		if (!filename.empty()) {
			wxString charset = wxGetSingleChoice(_("Choose charset code:"), _("Charset"), agi::charset::GetEncodingsList<wxArrayString>(), c->parent, -1, -1, true, 250, 200);
			if (!charset.empty()) {
				wxGetApp().frame->LoadSubtitles(filename,charset);
			}
		}
	}
};


/// Opens the subtitles from the current video file.
struct subtitle_open_video : public Command {
	CMD_NAME("subtitle/open/video")
	STR_MENU("Open Subtitles from &Video")
	STR_DISP("Open Subtitles from Video")
	STR_HELP("Opens the subtitles from the current video file")
	CMD_TYPE(COMMAND_VALIDATE)

	void operator()(agi::Context *c) {
		wxGetApp().frame->LoadSubtitles(c->videoController->GetVideoName(), "binary");
	}

	bool Validate(const agi::Context *c) {
		return c->videoController->IsLoaded() && c->videoController->HasSubtitles();
	}
};


/// Open script properties window.
struct subtitle_properties : public Command {
	CMD_NAME("subtitle/properties")
	STR_MENU("&Properties...")
	STR_DISP("Properties")
	STR_HELP("Open script properties window")

	void operator()(agi::Context *c) {
		c->videoController->Stop();
		DialogProperties(c).ShowModal();
	}
};

static void save_subtitles(agi::Context *c, wxString filename) {
	if (filename.empty()) {
		c->videoController->Stop();
		wxString path = lagi_wxString(OPT_GET("Path/Last/Subtitles")->GetString());
		wxFileName origPath(c->ass->filename);
		filename = wxFileSelector(_("Save subtitles file"), path, origPath.GetName() + ".ass", "ass", AssFile::GetWildcardList(1), wxFD_SAVE | wxFD_OVERWRITE_PROMPT, c->parent);
		if (filename.empty()) return;
	}

	try {
		c->ass->Save(filename, true, true);
	}
	catch (const agi::Exception& err) {
		wxMessageBox(lagi_wxString(err.GetMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, c->parent);
	}
	catch (const char *err) {
		wxMessageBox(err, "Error", wxOK | wxICON_ERROR | wxCENTER, c->parent);
	}
	catch (...) {
		wxMessageBox("Unknown error", "Error", wxOK | wxICON_ERROR | wxCENTER, c->parent);
	}
}

/// Saves subtitles.
struct subtitle_save : public Command {
	CMD_NAME("subtitle/save")
	STR_MENU("&Save Subtitles")
	STR_DISP("Save Subtitles")
	STR_HELP("Saves subtitles")
	CMD_TYPE(COMMAND_VALIDATE)

	void operator()(agi::Context *c) {
		save_subtitles(c, c->ass->CanSave() ? c->ass->filename : "");
	}

	bool Validate(const agi::Context *c) {
		return c->ass->IsModified();
	}
};


/// Saves subtitles with another name.
struct subtitle_save_as : public Command {
	CMD_NAME("subtitle/save/as")
	STR_MENU("Save Subtitles &as...")
	STR_DISP("Save Subtitles as")
	STR_HELP("Saves subtitles with another name")

	void operator()(agi::Context *c) {
		save_subtitles(c, "");
	}
};

/// Selects all dialogue lines
struct subtitle_select_all : public Command {
	CMD_NAME("subtitle/select/all")
	STR_MENU("Select &All")
	STR_DISP("Select All")
	STR_HELP("Selects all dialogue lines")

	void operator()(agi::Context *c) {
		SelectionController<AssDialogue>::Selection sel;
		transform(c->ass->Line.begin(), c->ass->Line.end(),
			inserter(sel, sel.begin()), cast<AssDialogue*>());
		sel.erase(0);
		c->selectionController->SetSelectedSet(sel);
	}
};

/// Selects all lines that are currently visible on video frame.
struct subtitle_select_visible : public Command {
	CMD_NAME("subtitle/select/visible")
	STR_MENU("Select Visible")
	STR_DISP("Select Visible")
	STR_HELP("Selects all lines that are currently visible on video frame")
	CMD_TYPE(COMMAND_VALIDATE)

	void operator()(agi::Context *c) {
		if (!c->videoController->IsLoaded()) return;
		c->videoController->Stop();

		SubtitleSelectionController::Selection new_selection;
		int frame = c->videoController->GetFrameN();

		for (entryIter it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) {
			AssDialogue *diag = dynamic_cast<AssDialogue*>(*it);
			if (diag &&
				c->videoController->FrameAtTime(diag->Start, agi::vfr::START) <= frame &&
				c->videoController->FrameAtTime(diag->End, agi::vfr::END) >= frame)
			{
				if (new_selection.empty())
					c->selectionController->SetActiveLine(diag);
				new_selection.insert(diag);
			}
		}

		c->selectionController->SetSelectedSet(new_selection);
	}

	bool Validate(const agi::Context *c) {
		return c->videoController->IsLoaded();
	}
};


/// Open spell checker.
struct subtitle_spellcheck : public Command {
	CMD_NAME("subtitle/spellcheck")
	STR_MENU("Spell &Checker...")
	STR_DISP("Spell Checker")
	STR_HELP("Open spell checker")

	void operator()(agi::Context *c) {
		c->videoController->Stop();
		c->dialog->Show<DialogSpellChecker>(c);
	}
};
}

/// @}

namespace cmd {
	void init_subtitle() {
		reg(new subtitle_attachment);
		reg(new subtitle_find);
		reg(new subtitle_find_next);
		reg(new subtitle_insert_after);
		reg(new subtitle_insert_after_videotime);
		reg(new subtitle_insert_before);
		reg(new subtitle_insert_before_videotime);
		reg(new subtitle_new);
		reg(new subtitle_open);
		reg(new subtitle_open_charset);
		reg(new subtitle_open_video);
		reg(new subtitle_properties);
		reg(new subtitle_save);
		reg(new subtitle_save_as);
		reg(new subtitle_select_all);
		reg(new subtitle_select_visible);
		reg(new subtitle_spellcheck);
	}
}
