/******* command.h *******/

class CommandCallParameters {
public:
	// The actual type of this doesn't matter but of course needs to be
	// decided on at some point. It's probably worth looking towards
	// Avisynth's filter parameter design.
	typename CommandCallParameterValue;
	
	CommandCallParameterValue GetValue(const wxString &name) const;
	
	void SetValue(const wxString &name, const CommandCallParameterValue &value);
};


// Should probably derive from another more specific exception base class
class CommandInvalidParametersException : public Aegisub::Exception;


class Command {
public:
	/// @brief Invoke the command in interactive mode
	/// @param project    The subtitle project to perform the command on
	/// @param main_frame The main window the command was invoked in, usable
	///                   for parenting dialogue boxes and obtaining various
	///                   other GUI state.
	virtual void InvokeInteractive(
		SubtitleProject *project,
		FrameMain *main_frame
		) const = 0;
	
	/// @brief Invoke the command in scripted mode
	/// @param project    The subtitle project to perform the command on
	/// @param parameters The parameters to the command
	///
	/// This method should throw CommandInvalidParametersException if the
	/// passed parameters are insufficient or invalid for the command.
	virtual void InvokeScripted(
		SubtitleProject *project,
		const CommandCallParameters &parameters
		) const = 0;
	
	/// @brief Determine whether the command can meaningfully be invoked in
	///        interactive mode
	/// @param project    The subtitle project to test whether the command can
	///                   be invoked on
	/// @param main_frame The main window to test whether the command can be
	///                   invoked on
	/// @return False if the command can't successfully complete in the given
	///         state, true if all conditions for the command to successfully
	///         complete are satisfied
	///
	/// Takes the same parameters as InvokeInteractive() and should check
	/// whether the user should be allowed to run the command in the current
	/// combination of project and UI state. In practice this will control
	/// whether the command's menu item and toolbar button will apppear grayed
	/// out or clickable.
	///
	/// @todo Possibly commands should react to certain UI events like changing
	///       selection so this can be updated in a sensible way without
	///       polling or other ugly hacks.
	virtual bool CanInvokeInteractive(
		const SubtitleProject *project,
		const FrameMain *main_frame
		) const = 0;
	
	
	/// @brief Get the internal name for the command
	/// @return A static store null-terminated character string without spaces
	///         uniquely identifying the command
	virtual const char * GetName() const = 0;
	
	/// @brief Get the translated menu name for the command
	/// @return The name for the command, suitable for being displayed on
	///         a menu item. The name should be translated into the user's
	///         UI language and have an appropriate character prepended by
	//          an ampersand to denote the access key.
	virtual wxString GetMenuName() const = 0;
	
	/// @brief Get the translated display name for the command
	/// @return The name for the command, suitable for display to the user in
	///         all other cases than being on a menu. The name must not
	///         contain an ampersand denoting access key or any formatting
	///         relating to access keys, but should be translated into the
	///         user's UI language.
	virtual wxString GetDisplayName() const = 0;
	
	/// @brief Get the translated help string for the command
	/// @return The help string concisely describing the purpose of the command
	///         to the user, translated into the UI language.
	virtual wxString GetHelpString() const = 0;
	
	
	/// @brief Get the default hotkeys assigned to the command
	/// @return A vector of hotkey keystrokes assigned to the command by
	///         default. The first hotkey will be displayed in menu items and
	///         on toolbar tooltips.
	///
	/// The default hotkeys are only used if the command hasn't been seen
	/// before and there is no known hotkey list associated with it. It may
	/// also be used if the user requests resetting the hotkeys for the
	/// command. If a default hotkey keystroke has already been registered for
	/// another command, the second command to claim the keystroke will not
	/// get it assigned.
	virtual std::vector<HotkeyKeystroke> GetDefaultHotkeys() const = 0;
	
	/// @todo Some way of specifying default menu and toolbar layouts and
	///       the behaviour if a new command appears after the menu and toolbar
	///       layout has been initially built, how the command can specify
	///       where it wants to sit.
};


class CommandRepository {
public:
	/// @brief Register a new command
	/// @param cmd The command object to register
	///
	/// The registration will be ignored if there is already a command with
	/// the same internal name as the new command.
	///
	/// If the command hasn't been seen before its default hotkeys and default
	/// menu and toolbar positions are registered and the UI is updated.
	///
	/// The command object must have been allocated with operator new and will
	/// be owned by the command repository after having been registered. One
	/// command object can only belong to one command repository at a time.
	void RegisterCommand(Command *cmd);
	
	/// @brief Find a command by internal name
	/// @param name Internal name of the command to locate
	/// @return A const pointer to the command requested if it exists,
	///         otherwise 0.
	const Command * FindCommand(const char *name) const;
};


/******* timing_commands.h *******/

class CommandRepository;

void RegisterTimingCommands(CommandRepository *repository);


/******* timing_commands.cpp *******/

#include "timing_commands.h"
#include "command.h"


namespace {

	class MakeContinuous : public Command {
		void DoWork(std::vector<AssDialogue*> &lines) const;
	
	public:
		void InvokeInteractive(SubtitleProject *project, FrameMain *main_frame) const;
		void InvokeScripted(SubtitleProject *project, const CommandCallParameters &parameters) const;
		bool CanInvokeInteractive(const SubtitleProject *project, const FrameMain *main_frame) const;
		const char * GetName() const { return "timing_make_continuous"; }
		wxString GetMenuName() const { return _("Make &continuous"); }
		wxString GetDisplayName() const { return _("Make continuous"); }
		wxString GetHelpString() const { return _("Adjust the timings of the selected lines so there are no gaps between them"); }
		std::vector<HotkeyKeystroke> GetDefaultHotkeys() const { return std::vector<HotkeyKeystroke>(); }
	};
	
	
	class ShiftTimes : public Command {
		void ShiftByTime(std::vector<AssDialogue*> &lines, int amount_ms, bool adjust_start, bool adjust_end) const;
		void ShiftByFrames(std::vector<AssDialogue*> &lines, int amount_frames, const FramerateData &vfr, bool adjust_start, bool adjust_end) const;
		
	public:
		void InvokeInteractive(SubtitleProject *project, FrameMain *main_frame) const;
		void InvokeScripted(SubtitleProject *project, const CommandCallParameters &parameters) const;
		bool CanInvokeInteractive(const SubtitleProject *project, const FrameMain *main_frame) const;
		const char * GetName() const { return "timing_shift"; }
		wxString GetMenuName() const { return _("&Shift times"); }
		wxString GetDisplayName() const { return _("Shift times"); }
		wxString GetHelpString() const { return _("Change the timings of many lines by a fixed amount at once"); }
		std::vector<HotkeyKeystroke> GetDefaultHotkeys() const { return std::vector<HotkeyKeystroke>(); }
	};
	
};


void MakeContinuous::DoWork(std::vector<AssDialogue*> &lines) const
{
	// Implement the algorithm here
}

void MakeContinuous::InvokeInteractive(SubtitleProject *project, FrameMain *main_frame) const
{
	// Should probably do some sanity checks here first
	std::vector<AssDialogue*> lines;
	main_frame->subs_grid->GetSelected(lines);
	DoWork(lines);
}

void MakeContinuous::InvokeScripted(SubtitleProject *project, const CommandCallParameters &parameters) const
{
	std::vector<AssDialogue*> lines;
	int first_line = parameters.GetValue(_T("from"));
	int last_line = parameters.GetValue(_T("to"));
	for (int i = first_line; i <= last_line; ++i)
		lines.push_back(project->subtitles->GetDialogueByIndex(i));
	DoWork(lines);
}

bool MakeContinuous::CanInvokeInteractive(const SubtitleProject *project, const FrameMain *main_frame) const
{
	bool multiple_lines_selected_and_adjacent;
	// Do some checks here
	return multiple_lines_selected_and_adjacent;
}


void ShiftTimes::ShiftByTime(std::vector<AssDialogue*> &lines, int amount_ms, bool adjust_start, bool adjust_end) const
{
	// Do work
}

void ShiftTimes::ShiftByFrames(std::vector<AssDialogue*> &lines, int amount_frames, const FramerateData &vfr, bool adjust_start, bool adjust_end) const
{
	// Do work
}

void ShiftTimes::InvokeInteractive(SubtitleProject *project, FrameMain *main_frame) const
{
	bool by_frames, adjust_start, adjust_end;
	int amount_ms, amount_frames;
	std::vector<AssDialogue*> lines;
	
	ShiftTimesDialogue dlg(main_frame, project);
	if (dlg.ShowModal() == ID_OK)
	{
		// fill variables from dialogue
		// invoke command (also see below for scripted version)
	}
}

void ShiftTimes::InvokeScripted(SubtitleProject *project, const CommandCallParameters &parameters) const
{
	wxString mode = parameters.GetValue(_T("mode"));
	bool start = parameters.GetValue(_T("start"));
	bool end = parameters.GetValue(_T("end"));
	std::vector<AssDialogue*> lines;
	
	// Do some more stuff to fill in lines, probably involving more parameters
	
	if (!mode || mode == _T("time"))
	{
		int amount_ms = parameters.GetValue(_T("ms"));
		ShiftByTime(lines, ms, start, end);
	}
	else if (mode == _T("frames"))
	{
		int amount_frames = parameters.GetValue(_T("frames"));
		ShiftByFrames(lines, frames, project->vfr, start, end);
	}
	else
	{
		throw CommandInvalidParametersException(GetName(), _T("mode"), _T("Value must be \"time\" or \"frames\", or empty to default to \"time\""));
	}
}

bool ShiftTimes::CanInvokeInteractive(const SubtitleProject *project, const FrameMain *main_frame) const
{
	return true;
}


void RegisterTimingCommands(CommandRepository *repository)
{
	repository.RegisterCommand(new MakeContinuous);
	repository.RegisterCommand(new ShiftTimes);
}

