// 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
//


///////////
// Headers
#include "dialog_resample.h"
#include "ass_file.h"
#include "ass_dialogue.h"
#include "ass_style.h"
#include "ass_override.h"
#include "subs_grid.h"
#include "validators.h"
#include "video_context.h"
#include "utils.h"
#include "subs_edit_box.h"
#include "help_button.h"


///////////////
// Constructor
DialogResample::DialogResample(wxWindow *parent, SubtitlesGrid *_grid)
: wxDialog (parent,-1,_("Resample resolution"),wxDefaultPosition)
{
	// Set icon
	SetIcon(BitmapToIcon(wxBITMAP(resample_toolbutton)));

	// Variables
	AssFile *subs = AssFile::top;
	grid = _grid;

	// Margins
	MarginSymmetrical = NULL;	// Do not remove this
	wxSizer *MarginBoxSizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Margin offset"));
	wxSizer *MarginSizer = new wxGridSizer(3,3,5,5);
	MarginTop = new wxTextCtrl(this,TEXT_MARGIN_T,_T("0"),wxDefaultPosition,wxSize(50,-1),0);
	MarginLeft = new wxTextCtrl(this,TEXT_MARGIN_L,_T("0"),wxDefaultPosition,wxSize(50,-1),0);
	MarginSymmetrical = new wxCheckBox(this,CHECK_SYMMETRICAL,_("Symmetrical"));
	MarginRight = new wxTextCtrl(this,TEXT_MARGIN_R,_T("0"),wxDefaultPosition,wxSize(50,-1),0);
	MarginBottom = new wxTextCtrl(this,TEXT_MARGIN_B,_T("0"),wxDefaultPosition,wxSize(50,-1),0);
	MarginSizer->AddSpacer(1);
	MarginSizer->Add(MarginTop,1,wxEXPAND);
	MarginSizer->AddSpacer(1);
	MarginSizer->Add(MarginLeft,1,wxEXPAND);
	MarginSizer->Add(MarginSymmetrical,1,wxEXPAND);
	MarginSizer->Add(MarginRight,1,wxEXPAND);
	MarginSizer->AddSpacer(1);
	MarginSizer->Add(MarginBottom,1,wxEXPAND);
	MarginSizer->AddSpacer(1);
	MarginBoxSizer->Add(MarginSizer,1,wxALIGN_CENTER|wxBOTTOM,5);
	MarginSymmetrical->SetValue(true);
	MarginRight->Enable(false);
	MarginBottom->Enable(false);
	
	// Resolution
	wxSizer *ResBoxSizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Resolution"));
	wxSizer *ResSizer = new wxBoxSizer(wxHORIZONTAL);
	int sw,sh;
	subs->GetResolution(sw,sh);
	ResXValue = wxString::Format(_T("%i"),sw);
	ResYValue = wxString::Format(_T("%i"),sh);
	ResX = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(50,-1),0,NumValidator(&ResXValue));
	ResY = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(50,-1),0,NumValidator(&ResYValue));
	wxStaticText *ResText = new wxStaticText(this,-1,_("x"));
	wxButton *FromVideo = new wxButton(this,BUTTON_DEST_FROM_VIDEO,_("From video"));
	if (!VideoContext::Get()->IsLoaded()) FromVideo->Enable(false);
	ResSizer->Add(ResX,1,wxRIGHT,5);
	ResSizer->Add(ResText,0,wxALIGN_CENTER | wxRIGHT,5);
	ResSizer->Add(ResY,1,wxRIGHT,5);
	ResSizer->Add(FromVideo,1,0,0);
	Anamorphic = new wxCheckBox(this,CHECK_ANAMORPHIC,_("Change aspect ratio"));
	ResBoxSizer->Add(ResSizer,1,wxEXPAND|wxBOTTOM,5);
	ResBoxSizer->Add(Anamorphic,0,0,0);

	// Button sizer
	wxStdDialogButtonSizer *ButtonSizer = new wxStdDialogButtonSizer();
	ButtonSizer->AddButton(new wxButton(this,wxID_OK));
	ButtonSizer->AddButton(new wxButton(this,wxID_CANCEL));
	ButtonSizer->AddButton(new HelpButton(this,_T("Resample")));
	ButtonSizer->Realize();

	// Main sizer
	wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
	MainSizer->Add(MarginBoxSizer,1,wxEXPAND|wxALL,5);
	MainSizer->Add(ResBoxSizer,0,wxEXPAND|wxALL,5);
	MainSizer->Add(ButtonSizer,0,wxEXPAND|wxRIGHT|wxLEFT|wxBOTTOM,5);
	MainSizer->SetSizeHints(this);
	SetSizer(MainSizer);
	CenterOnParent();
	instance = this;
}


///////////////
// Event table
BEGIN_EVENT_TABLE(DialogResample,wxDialog)
	EVT_BUTTON(wxID_OK,DialogResample::OnResample)
	EVT_BUTTON(BUTTON_DEST_FROM_VIDEO,DialogResample::OnGetDestRes)
	EVT_CHECKBOX(CHECK_SYMMETRICAL,DialogResample::OnSymmetrical)
	EVT_TEXT(TEXT_MARGIN_T,DialogResample::OnMarginChange)
	EVT_TEXT(TEXT_MARGIN_L,DialogResample::OnMarginChange)
	EVT_TEXT(TEXT_MARGIN_R,DialogResample::OnMarginChange)
	EVT_TEXT(TEXT_MARGIN_B,DialogResample::OnMarginChange)
END_EVENT_TABLE()


/////////////////
// Resample tags
void DialogResample::ResampleTags (wxString name,int n,AssOverrideParameter *curParam,void *_curDiag) {
	instance->DoResampleTags(name,n,curParam,_curDiag);
}
void DialogResample::DoResampleTags (wxString name,int n,AssOverrideParameter *curParam,void *_curDiag) {
	double resizer = 1.0;
	bool isX = false;
	bool isY = false;

	switch (curParam->classification) {
		case PARCLASS_ABSOLUTE_SIZE:
			resizer = r;
			break;
			
		case PARCLASS_ABSOLUTE_POS_X:
			resizer = rx;
			isX = true;
			break;

		case PARCLASS_ABSOLUTE_POS_Y:
			resizer = ry;
			isY = true;
			break;

		case PARCLASS_RELATIVE_SIZE_X:
			resizer = ar;
			break;

		case PARCLASS_RELATIVE_SIZE_Y:
			//resizer = ry;
			break;

		case PARCLASS_DRAWING:
			{
				AssDialogueBlockDrawing block;
				block.text = curParam->AsText();
				block.TransformCoords(m[0],m[2],rx,ry);
				curParam->SetText(block.GetText());
			}
			return;

		default:
			return;
	}

	VariableDataType curType = curParam->GetType();
	if (curType == VARDATA_FLOAT) {
		float par = curParam->AsFloat();
		if (isX) par += m[0];
		if (isY) par += m[2];
		curParam->SetFloat(par * resizer);
	}
	if (curType == VARDATA_INT) {
		int par = curParam->AsInt();
		if (isX) par += m[0];
		if (isY) par += m[2];
		curParam->SetInt(int(double(par) * resizer + 0.5));
	}
}


////////////
// Resample
void DialogResample::OnResample (wxCommandEvent &event) {
	// Resolutions
	AssFile *subs = AssFile::top;
	int x1,y1;
	subs->GetResolution(x1,y1);
	long x2 = 0;
	long y2 = 0;
	ResX->GetValue().ToLong(&x2);
	ResY->GetValue().ToLong(&y2);

	// Sanity check
	if (x1 == 0 || y1 == 0) {
		wxMessageBox(_T("Invalid source resolution. This should not happen. Please contact the developers."),_("Error"),wxCENTRE|wxICON_ERROR);
		return;
	}
	if (x2 == 0 || y2 == 0) {
		wxMessageBox(_("Invalid resolution: destination resolution cannot be 0 on either dimension."),_("Error"),wxCENTRE|wxICON_ERROR);
		return;
	}

	// Get margins
	MarginLeft->GetValue().ToLong(&m[0]);
	MarginRight->GetValue().ToLong(&m[1]);
	MarginTop->GetValue().ToLong(&m[2]);
	MarginBottom->GetValue().ToLong(&m[3]);

	// Add margins to original resolution
	x1 += m[0] + m[1];
	x2 += m[2] + m[3];

	// Calculate resamples
	rx = double(x2)/double(x1);
	ry = double(y2)/double(y1);
	r = ry;
	if (Anamorphic->IsChecked()) ar = rx/ry;
	else ar = 1.0;

	// Iterate through subs
	AssStyle *curStyle;
	AssDialogue *curDiag;
	for (entryIter cur=subs->Line.begin();cur!=subs->Line.end();cur++) {
		// Apply to dialogues
		curDiag = AssEntry::GetAsDialogue(*cur);
		if (curDiag && !(curDiag->Comment && (curDiag->Effect.StartsWith(_T("template")) || curDiag->Effect.StartsWith(_T("code"))))) {
			try {
				// Override tags
				curDiag->ParseASSTags();
				curDiag->ProcessParameters(&DialogResample::ResampleTags,curDiag);

				// Drawing tags
				size_t nblocks = curDiag->Blocks.size();
				AssDialogueBlockDrawing *curBlock;
				for (size_t i=0;i<nblocks;i++) {
					curBlock = AssDialogueBlock::GetAsDrawing(curDiag->Blocks.at(i));
					if (curBlock) {
						curBlock->TransformCoords(m[0],m[2],rx,ry);
					}
				}

				// Margins
				for (int i=0;i<2;i++) {
					curDiag->Margin[i] = int((curDiag->Margin[i]+m[i]) * rx + 0.5);
					curDiag->Margin[i+2] = int((curDiag->Margin[i+2]+m[i+2]) * ry + 0.5);
				}

				// Update
				curDiag->UpdateText();
				curDiag->UpdateData();
				curDiag->ClearBlocks();
				continue;
			}
			catch (const wchar_t *err) {
				wxLogMessage(err);
			}
			catch (wxString err) {
				wxLogMessage(err);
			}
		}

		// Apply to styles
		curStyle = AssEntry::GetAsStyle(*cur);
		if (curStyle) {
			curStyle->fontsize = int(curStyle->fontsize * r + 0.5);
			curStyle->outline_w *= r;
			curStyle->shadow_w *= r;
			curStyle->spacing *= rx;
			curStyle->scalex *= ar;
			for (int i=0;i<2;i++) {
				curStyle->Margin[i] = int((curStyle->Margin[i]+m[i]) * rx + 0.5);
				curStyle->Margin[i+2] = int((curStyle->Margin[i+2]+m[i+2]) * ry + 0.5);
			}
			curStyle->UpdateData();
		}
	}

	// Change script resolution
	subs->SetScriptInfo(_T("PlayResX"),wxString::Format(_T("%i"),x2));
	subs->SetScriptInfo(_T("PlayResY"),wxString::Format(_T("%i"),y2));

	// Flag as modified
	subs->FlagAsModified(_("resolution resampling"));
	grid->CommitChanges();
	grid->editBox->Update();
	EndModal(0);
}


/////////////////////////////////////////
// Get destination resolution from video
void DialogResample::OnGetDestRes (wxCommandEvent &event) {
	ResX->SetValue(wxString::Format(_T("%i"),VideoContext::Get()->GetWidth()));
	ResY->SetValue(wxString::Format(_T("%i"),VideoContext::Get()->GetHeight()));
}


////////////////////////////////
// Symmetrical checkbox clicked
void DialogResample::OnSymmetrical (wxCommandEvent &event) {
	bool state = !MarginSymmetrical->IsChecked();
	MarginRight->Enable(state);
	MarginBottom->Enable(state);
	if (!state) {
		MarginRight->SetValue(MarginLeft->GetValue());
		MarginBottom->SetValue(MarginTop->GetValue());
	}
}


////////////////////////
// Margin value changed
void DialogResample::OnMarginChange (wxCommandEvent &event) {
	if (!MarginSymmetrical) return;
	bool state = !MarginSymmetrical->IsChecked();
	if (!state && (event.GetEventObject() == MarginLeft || event.GetEventObject() == MarginTop)) {
		MarginRight->SetValue(MarginLeft->GetValue());
		MarginBottom->SetValue(MarginTop->GetValue());
	}
}


////////////////////
// Static variables
DialogResample *DialogResample::instance = NULL;