// 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 dialog_search_replace.cpp
/// @brief Find and Search/replace dialogue box and logic
/// @ingroup secondary_ui
///


///////////
// Headers
#include "config.h"

#include <wx/wxprec.h>
#include <wx/string.h>
#include <wx/regex.h>
#include "dialog_search_replace.h"
#include "ass_file.h"
#include "ass_dialogue.h"
#include "subs_grid.h"
#include "options.h"
#include "subs_edit_box.h"
#include "video_display.h"
#include "frame_main.h"
#include "main.h"



/// @brief Constructor 
/// @param parent      
/// @param _hasReplace 
/// @param name        
///
DialogSearchReplace::DialogSearchReplace (wxWindow *parent,bool _hasReplace,wxString name)
: wxDialog(parent, -1, name, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE, _T("SearchReplace"))
{
	// Setup
	hasReplace = _hasReplace;

	// Find sizer
	wxSizer *FindSizer = new wxFlexGridSizer(2,2,5,15);
	wxArrayString FindHistory = Options.GetRecentList(_T("Recent find"));
	FindEdit = new wxComboBox(this,-1,_T(""),wxDefaultPosition,wxSize(300,-1),FindHistory,wxCB_DROPDOWN);
	//if (FindHistory.Count()) FindEdit->SetStringSelection(FindHistory[0]);
	FindEdit->SetSelection(0);
	FindSizer->Add(new wxStaticText(this,-1,_("Find what:")),0,wxRIGHT | wxALIGN_CENTER_VERTICAL,0);
	FindSizer->Add(FindEdit,0,wxRIGHT,0);
	if (hasReplace) {
		wxArrayString ReplaceHistory = Options.GetRecentList(_T("Recent replace"));
		ReplaceEdit = new wxComboBox(this,-1,_T(""),wxDefaultPosition,wxSize(300,-1),ReplaceHistory,wxCB_DROPDOWN);
		FindSizer->Add(new wxStaticText(this,-1,_("Replace with:")),0,wxRIGHT | wxALIGN_CENTER_VERTICAL,0);
		FindSizer->Add(ReplaceEdit,0,wxRIGHT,0);
		ReplaceEdit->SetSelection(0);
	}

	// Options sizer
	wxSizer *OptionsSizer = new wxBoxSizer(wxVERTICAL);
	CheckMatchCase = new wxCheckBox(this,CHECK_MATCH_CASE,_("Match case"));
	CheckRegExp = new wxCheckBox(this,CHECK_MATCH_CASE,_("Use regular expressions"));
	CheckUpdateVideo = new wxCheckBox(this,CHECK_UPDATE_VIDEO,_("Update Video (slow)"));
	CheckMatchCase->SetValue(Options.AsBool(_T("Find Match Case")));
	CheckRegExp->SetValue(Options.AsBool(_T("Find RegExp")));
	//CheckRegExp->Enable(false);
	CheckUpdateVideo->SetValue(Options.AsBool(_T("Find Update Video")));
//	CheckUpdateVideo->Enable(Search.grid->video->loaded);
	OptionsSizer->Add(CheckMatchCase,0,wxBOTTOM,5);
	OptionsSizer->Add(CheckRegExp,0,wxBOTTOM,5);
	OptionsSizer->Add(CheckUpdateVideo,0,wxBOTTOM,0);

	// Limits sizer
	wxArrayString field;
	field.Add(_("Text"));
	field.Add(_("Style"));
	field.Add(_("Actor"));
	field.Add(_("Effect"));
	wxArrayString affect;
	affect.Add(_("All rows"));
	affect.Add(_("Selected rows"));
	Field = new wxRadioBox(this,-1,_("In Field"),wxDefaultPosition,wxDefaultSize,field);
	Affect = new wxRadioBox(this,-1,_("Limit to"),wxDefaultPosition,wxDefaultSize,affect);
	wxSizer *LimitSizer = new wxBoxSizer(wxHORIZONTAL);
	LimitSizer->Add(Field,1,wxEXPAND | wxRIGHT,5);
	LimitSizer->Add(Affect,0,wxEXPAND | wxRIGHT,0);
	Field->SetSelection(Options.AsInt(_T("Find Field")));
	Affect->SetSelection(Options.AsInt(_T("Find Affect")));

	// Left sizer
	wxSizer *LeftSizer = new wxBoxSizer(wxVERTICAL);
	LeftSizer->Add(FindSizer,0,wxBOTTOM,10);
	LeftSizer->Add(OptionsSizer,0,wxBOTTOM,5);
	LeftSizer->Add(LimitSizer,0,wxEXPAND | wxBOTTOM,0);

	// Buttons
	wxSizer *ButtonSizer = new wxBoxSizer(wxVERTICAL);
	wxButton *FindNext = new wxButton(this,BUTTON_FIND_NEXT,_("Find next"));
	FindNext->SetDefault();
	ButtonSizer->Add(FindNext,0,wxEXPAND | wxBOTTOM,3);
	if (hasReplace) {
		ButtonSizer->Add(new wxButton(this,BUTTON_REPLACE_NEXT,_("Replace next")),0,wxEXPAND | wxBOTTOM,3);
		ButtonSizer->Add(new wxButton(this,BUTTON_REPLACE_ALL,_("Replace all")),0,wxEXPAND | wxBOTTOM,3);
	}
	ButtonSizer->Add(new wxButton(this,wxID_CANCEL),0,wxEXPAND | wxBOTTOM,20);
	//ButtonSizer->Add(new wxButton(this,wxID_HELP),0,wxEXPAND | wxBOTTOM,0);

	// Main sizer
	wxSizer *MainSizer = new wxBoxSizer(wxHORIZONTAL);
	MainSizer->Add(LeftSizer,0,wxEXPAND | wxALL,5);
	MainSizer->Add(ButtonSizer,0,wxEXPAND | wxALL,5);
	MainSizer->SetSizeHints(this);
	SetSizer(MainSizer);
	CenterOnParent();

	// Open
	Search.OnDialogOpen();
}



/// @brief Destructor 
///
DialogSearchReplace::~DialogSearchReplace() {
	// Save options
	UpdateSettings();
}



/// @brief Update search 
///
void DialogSearchReplace::UpdateSettings() {
	Search.isReg = CheckRegExp->IsChecked() && CheckRegExp->IsEnabled();
	Search.matchCase = CheckMatchCase->IsChecked();
	Search.updateVideo = CheckUpdateVideo->IsChecked() && CheckUpdateVideo->IsEnabled();
	Options.SetBool(_T("Find Match Case"),CheckMatchCase->IsChecked());
	Options.SetBool(_T("Find RegExp"),CheckRegExp->IsChecked());
	Options.SetBool(_T("Find Update Video"),CheckUpdateVideo->IsChecked());
	Options.SetInt(_T("Find Field"),Field->GetSelection());
	Options.SetInt(_T("Find Affect"),Affect->GetSelection());
	Options.Save();
}	


///////////////
// Event table
BEGIN_EVENT_TABLE(DialogSearchReplace,wxDialog)
	EVT_BUTTON(wxID_CANCEL,DialogSearchReplace::OnClose)
	EVT_BUTTON(BUTTON_FIND_NEXT,DialogSearchReplace::OnFindNext)
	EVT_BUTTON(BUTTON_REPLACE_NEXT,DialogSearchReplace::OnReplaceNext)
	EVT_BUTTON(BUTTON_REPLACE_ALL,DialogSearchReplace::OnReplaceAll)
	EVT_SET_FOCUS(DialogSearchReplace::OnSetFocus)
	EVT_KILL_FOCUS(DialogSearchReplace::OnKillFocus)
END_EVENT_TABLE()

	

/// @brief Close 
/// @param event 
///
void DialogSearchReplace::OnClose (wxCommandEvent &event) {
	Search.OnDialogClose();
	// Just hide
	Show(false);
}



/// @brief Key 
/// @param event 
///
void DialogSearchReplace::OnKeyDown (wxKeyEvent &event) {
	//if (event.GetKeyCode() == WXK_ESCAPE) {
	//	Search.OnDialogClose();
	//	// Just hide
	//	Show(false);
	//}
	event.Skip();
}



/// @brief Find or replace 
/// @param mode 
/// @return 
///
void DialogSearchReplace::FindReplace(int mode) {
	// Check mode
	if (mode < 0 || mode > 2) return;

	// Variables
	wxString LookFor = FindEdit->GetValue();
	if (LookFor.IsEmpty()) return;

	// Setup
	Search.isReg = CheckRegExp->IsChecked() && CheckRegExp->IsEnabled();
	Search.matchCase = CheckMatchCase->IsChecked();
	Search.updateVideo = CheckUpdateVideo->IsChecked() && CheckUpdateVideo->IsEnabled();
	Search.LookFor = LookFor;
	Search.CanContinue = true;
	Search.affect = Affect->GetSelection();
	Search.field = Field->GetSelection();

	// Find
	if (mode == 0) {
		Search.FindNext();
		if (hasReplace) {
			wxString ReplaceWith = ReplaceEdit->GetValue();
			Search.ReplaceWith = ReplaceWith;
			Options.AddToRecentList(ReplaceWith,_T("Recent replace"));
		}	
	}

	// Replace
	else {
		wxString ReplaceWith = ReplaceEdit->GetValue();
		Search.ReplaceWith = ReplaceWith;
		if (mode == 1) Search.ReplaceNext();
		else Search.ReplaceAll();
		Options.AddToRecentList(ReplaceWith,_T("Recent replace"));
	}
	
	// Add to history
	Options.AddToRecentList(LookFor,_T("Recent find"));
	UpdateDropDowns();
}



/// @brief Find next 
/// @param event 
///
void DialogSearchReplace::OnFindNext (wxCommandEvent &event) {
	FindReplace(0);
}



/// @brief Replace next 
/// @param event 
///
void DialogSearchReplace::OnReplaceNext (wxCommandEvent &event) {
	FindReplace(1);
}



/// @brief Replace all 
/// @param event 
///
void DialogSearchReplace::OnReplaceAll (wxCommandEvent &event) {
	FindReplace(2);
}



/// @brief Update drop down boxes 
///
void DialogSearchReplace::UpdateDropDowns() {
	// Find
	FindEdit->Freeze();
	FindEdit->Clear();
	FindEdit->Append(Options.GetRecentList(_T("Recent find")));
	FindEdit->SetSelection(0);
	FindEdit->Thaw();

	// Replace
	if (hasReplace) {
		ReplaceEdit->Freeze();
		ReplaceEdit->Clear();
		ReplaceEdit->Append(Options.GetRecentList(_T("Recent replace")));
		ReplaceEdit->SetSelection(0);
		ReplaceEdit->Thaw();
	}
}



/// @brief DOCME
/// @param event 
///
void DialogSearchReplace::OnSetFocus (wxFocusEvent &event) {
	Search.hasFocus = true;
}


/// @brief DOCME
/// @param event 
///
void DialogSearchReplace::OnKillFocus (wxFocusEvent &event) {
	Search.hasFocus = false;
}



/// @brief Constructor  SearchReplaceEngine ///////////////////////
///
SearchReplaceEngine::SearchReplaceEngine () {
	CanContinue = false;
}



/// @brief Find next instance 
///
void SearchReplaceEngine::FindNext() {
	ReplaceNext(false);
}



/// @brief Find & Replace next instance 
/// @param DoReplace 
/// @return 
///
void SearchReplaceEngine::ReplaceNext(bool DoReplace) {
	// Check if it's OK to go on
	if (!CanContinue) {
		OpenDialog(DoReplace);
		return;
	}
	
	wxArrayInt sels = grid->GetSelection();
	int firstLine = 0;
	if (sels.Count() > 0) firstLine = sels[0];
	// if selection has changed reset values
	if (firstLine < curLine) {
		curLine = firstLine;
		Modified = false;
		LastWasFind = true;
		pos = 0;
		matchLen = 0;
		replaceLen = 0;
	}

	// Setup
	int start = curLine;
	int nrows = grid->GetRows();
	bool found = false;
	wxString *Text = NULL;
	size_t tempPos;
	int regFlags = wxRE_ADVANCED;
	if (!matchCase) {
		if (isReg) regFlags |= wxRE_ICASE;
		else LookFor.MakeLower();
	}

	// Search for it
	while (!found) {
		Text = GetText(curLine,field);
		if (DoReplace && LastWasFind) tempPos = pos;
		else tempPos = pos+replaceLen;

		// RegExp
		if (isReg) {
			wxRegEx regex (LookFor,regFlags);
			if (regex.IsValid()) {
				if (regex.Matches(Text->Mid(tempPos))) {
					size_t match_start;
					regex.GetMatch(&match_start,&matchLen,0);
					pos = match_start + tempPos;
					found = true;
				}
			}
		}

		// Normal
		else {
			int textPos = tempPos;
			wxString src = Text->Mid(tempPos);
			if (!matchCase) src.MakeLower();
			int tempPos = src.Find(LookFor);
			if (tempPos != -1) {
				pos = textPos+tempPos;
				found = true;
				matchLen = LookFor.Length();
			}
		}

		// Didn't find, go to next line
		if (!found) {
			curLine++;
			pos = 0;
			matchLen = 0;
			replaceLen = 0;
			if (curLine == nrows) curLine = 0;
			if (curLine == start) break;
		}
	}

	// Found
	if (found) {
		grid->BeginBatch();

		// If replacing
		if (DoReplace) {
			// Replace with regular expressions
			if (isReg) {
				wxString toReplace = Text->Mid(pos,matchLen);
				wxRegEx regex(LookFor,regFlags);
				regex.ReplaceFirst(&toReplace,ReplaceWith);
				*Text = Text->Left(pos) + toReplace + Text->Mid(pos+matchLen);
				replaceLen = toReplace.Length();
			}

			// Normal replace
			else {
				*Text = Text->Left(pos) + ReplaceWith + Text->Mid(pos+matchLen);
				replaceLen = ReplaceWith.Length();
			}

			// Update
			AssDialogue *cur = grid->GetDialogue(curLine);
			//cur->ParseASSTags();
			cur->UpdateData();

			// Commit
			grid->ass->FlagAsModified(_("replace"));
			grid->CommitChanges();
		}

		else {
			replaceLen = matchLen;
		}

		// Select
		grid->SelectRow(curLine,false);
		grid->MakeCellVisible(curLine,0);
		if (field == 0) {
			grid->editBox->SetToLine(curLine);
			grid->editBox->TextEdit->SetSelectionU(pos,pos+replaceLen);
		}
		grid->EndBatch();

		// Update video
		if (updateVideo) {
			grid->CommitChanges();
			grid->SetVideoToSubs(true);
		}
		else if (DoReplace) Modified = true;

		// hAx to prevent double match on style/actor
		if (field != 0) replaceLen = 99999;
	}
	LastWasFind = !DoReplace;
}



/// @brief Replace all instances 
///
void SearchReplaceEngine::ReplaceAll() {
	// Setup
	wxString *Text;
	int nrows = grid->GetRows();
	size_t count = 0;
	int regFlags = wxRE_ADVANCED;
	if (!matchCase) {
		if (isReg) regFlags |= wxRE_ICASE;
		//else LookFor.MakeLower();
	}
	bool replaced;
	grid->BeginBatch();

	// Selection
	bool hasSelection = false;
	wxArrayInt sels = grid->GetSelection();
	if (sels.Count() > 0) hasSelection = true;
	bool inSel = false;
	if (affect == 1) inSel = true;

	// Scan
	for (int i=0;i<nrows;i++) {
		// Check if row is selected
		if (inSel && hasSelection && sels.Index(i) == wxNOT_FOUND) {
			continue;
		}

		// Prepare
		replaced = false;
		Text = GetText(i,field);

		// Regular expressions
		if (isReg) {
			wxRegEx reg(LookFor,regFlags);
			if (reg.IsValid()) {
				size_t reps = reg.Replace(Text,ReplaceWith,1000);
				if (reps > 0) replaced = true;
				count += reps;
			}
		}
		// Normal replace
		else {
			if (!Search.matchCase) {
				wxString Left = _T(""), Right = *Text;
				int pos = 0;
				Left.Alloc(Right.Len());
				while (pos <= (int)(Right.Len() - LookFor.Len())) {
					if (Right.Mid(pos, LookFor.Len()).CmpNoCase(LookFor) == 0) {
						Left.Append(Right.Mid(0,pos)).Append(ReplaceWith);
						Right = Right.Mid(pos+LookFor.Len());
						count++;
						replaced = true;
						pos = 0;
					}
					else {
						pos++;
					}
				}
				if (replaced) {
					*Text = Left + Right;
				}
			}
			else {
				if(Text->Contains(LookFor)) {
					count += Text->Replace(LookFor, ReplaceWith);
					replaced = true;
				}
			}
		}

		// Replaced?
		if (replaced) {
			AssDialogue *cur = grid->GetDialogue(i);
			cur->UpdateData();
		}
	}

	// Commit
	if (count > 0) {
		grid->ass->FlagAsModified(_("replace"));
		grid->CommitChanges();
		grid->editBox->Update();
		wxMessageBox(wxString::Format(_("%i matches were replaced."),count));
	}

	// None found
	else {
		wxMessageBox(_("No matches found."));
	}
	grid->EndBatch();
	LastWasFind = false;
}



/// @brief Search dialog opened 
///
void SearchReplaceEngine::OnDialogOpen() {
	// Set curline
	wxArrayInt sels = grid->GetSelection();
	curLine = 0;
	if (sels.Count() > 0) curLine = sels[0];

	// Reset values
	Modified = false;
	LastWasFind = true;
	pos = 0;
	matchLen = 0;
	replaceLen = 0;
}



/// @brief Search dialog closed 
///
void SearchReplaceEngine::OnDialogClose() {
	if (Modified) grid->CommitChanges();
}



/// @brief Open dialog 
/// @param replace 
/// @return 
///
void SearchReplaceEngine::OpenDialog (bool replace) {
	static DialogSearchReplace *diag = NULL;
	wxString title = replace? _("Replace") : _("Find");

	// already opened
	if (diag) {
		// it's the right type so give focus
		if(replace == hasReplace) {
			diag->FindEdit->SetFocus();
			diag->Show();
			OnDialogOpen();
			return;
		}
		// wrong type - destroy and create the right one
		diag->Destroy();
	}
	// create new one
	diag = new DialogSearchReplace(((AegisubApp*)wxTheApp)->frame,replace,title);
	diag->FindEdit->SetFocus();
	diag->Show();
	hasReplace = replace;
}



/// @brief Get text pointer 
/// @param n     
/// @param field 
/// @return 
///
wxString *SearchReplaceEngine::GetText(int n,int field) {
	AssDialogue *cur = grid->GetDialogue(n);
	if (field == 0) return &cur->Text;
	else if (field == 1) return &cur->Style;
	else if (field == 2) return &cur->Actor;
	else if (field == 3) return &cur->Effect;
	else throw wxString(_T("Invalid field"));
}



/// DOCME
SearchReplaceEngine Search;


