/*******************************************************************************
  TPI - flexible but useless plug-in framework.
  Copyright (C) 2002-2009 Silky

  This library is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the Free
  Software Foundation; either version 2.1 of the License, or (at your option)
  any later version.

  This library is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
  for more details.

  You should have received a copy of the GNU Lesser General Public License along
  with this library; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

  $Id: dlg_make.cpp 132 2009-11-21 11:19:22Z sirakaba $
*******************************************************************************/

#include "lychee.h"

#include "dlg_make.h"
#include "frm_main.h"
#include "functions.h"

#include <wx/dirdlg.h>
#include <wx/arrimpl.cpp>

WX_DEFINE_OBJARRAY(ArrayTPI_FORMATINFO);

//******************************************************************************
// MakeDialog
//******************************************************************************

MakeDialog::MakeDialog(): wxDialog()
{
	this->bIsMake = true;
}

//******************************************************************************
// Event Table.
//******************************************************************************

BEGIN_EVENT_TABLE(MakeDialog, wxDialog)
	EVT_INIT_DIALOG(                 MakeDialog::OnInit)
	EVT_BUTTON(XRCID("btnDefault"),  MakeDialog::OnBtnDefault)
	EVT_BUTTON(XRCID("btnDesktop"),  MakeDialog::OnBtnDesktop)
	EVT_BUTTON(XRCID("btnCurrent"),  MakeDialog::OnBtnCurrent)
	EVT_BUTTON(XRCID("btnBrowse"),   MakeDialog::OnBtnBrowse)
	EVT_BUTTON(XRCID("btnBrowseKF"), MakeDialog::OnBtnBrowseKF)
	EVT_BUTTON(XRCID("btnOK"),       MakeDialog::OnBtnOK)
	EVT_BUTTON(XRCID("btnCancel"),   MakeDialog::OnBtnCancel)
	EVT_CHOICE(XRCID("chType"),      MakeDialog::OnChoice)
	EVT_CHECKBOX(XRCID("cbUnmask"),  MakeDialog::OnCbUnmask)
	EVT_NOTEBOOK_PAGE_CHANGED(XRCID("nbTabs"), MakeDialog::OnTabChanged)
END_EVENT_TABLE()

//******************************************************************************
// Event handler.
//******************************************************************************

void MakeDialog::OnInit(wxInitDialogEvent&)
{
	// XRCと結びつけ。
	// "General"タブ
	this->cbDir             = XRCCTRL(* this, "cbDir",        wxComboBox);
	this->cbFileName        = XRCCTRL(* this, "cbFileName",   wxComboBox);
	this->cbOpenAfter       = XRCCTRL(* this, "cbOpenAfter",  wxCheckBox);
	this->cbIgnorePath      = XRCCTRL(* this, "cbIgnorePath", wxCheckBox);
	this->cbExitAfter       = XRCCTRL(* this, "cbExitAfter",  wxCheckBox);
	this->chType            = XRCCTRL(* this, "chType",       wxChoice);
	this->chDirMake         = XRCCTRL(* this, "chDirMake",    wxChoice);
	// "Config"タブ
	this->scLevel           = XRCCTRL(* this, "scLevel",      wxSpinCtrl);
	this->scRR              = XRCCTRL(* this, "scRR",         wxSpinCtrl);
	this->tcPassword        = XRCCTRL(* this, "tcPassword",   wxTextCtrl);
	this->tcKeyfile         = XRCCTRL(* this, "tcKeyfile",    wxTextCtrl);
	this->cbSplitSize       = XRCCTRL(* this, "cbSplitSize",  wxComboBox);
	this->cbUnmask          = XRCCTRL(* this, "cbUnmask",     wxCheckBox);
	this->cbEncryptHeader   = XRCCTRL(* this, "cbEncryptHeader", wxCheckBox);
	this->cbSolid           = XRCCTRL(* this, "cbSolid",      wxCheckBox);
	this->cbMMOptimize      = XRCCTRL(* this, "cbMMOptimize", wxCheckBox);
	this->cbMakeSFX         = XRCCTRL(* this, "cbMakeSFX",    wxCheckBox);
	// "Comment"タブ
	this->tcComment         = XRCCTRL(* this, "tcComment",    wxTextCtrl);
	// "Files"タブ
	this->lcFiles           = XRCCTRL(* this, "lcFiles",      wxListCtrl);

	if (! this->cbDir->GetValue().IsEmpty())
	{
		return;
	}

	// ListCtrlに列を追加。
	this->lcFiles->InsertColumn(0, wxT("Input"),  wxLIST_FORMAT_LEFT,  150);
	this->lcFiles->InsertColumn(1, wxT("Output"), wxLIST_FORMAT_LEFT,  300);

	::wxXmlResource::Get()->Unload(FE_DIR_S_XRC wxT("dlg_make.xrc"));

	// 展開時は各種コントロールの状態を変更する。
	if (! this->bIsMake)
	{
		this->cbFileName->Disable();
		this->chType->Disable();
		this->chDirMake->Enable();
		this->scLevel->Disable();
		this->scRR->Disable();
		this->cbSplitSize->Disable();
		this->cbEncryptHeader->Disable();
		this->cbSolid->Disable();
		this->cbMMOptimize->Disable();
		this->cbMakeSFX->Disable();
		this->tcComment->SetEditable(false);

		// 初期値を設定。
		MainFrame * frm_main = (MainFrame *) this->GetParent();
		this->cbDir->SetValue(frm_main->fnArchive.GetPath());
		this->cbFileName->SetValue(frm_main->fnArchive.GetFullName());
		this->tcComment->SetValue(frm_main->szComment);

		// 展開先を予測。ただしDTVスキャンに時間がかかる場合はスキップ可能。
		if (this->files.GetCount() < 3000 || ::AskDlg(wxT("This archive contains so many files that it takes long to check Directory Traversal Vulnerability(DTV) problem. If you are sure this archive is safe, you can skip this scanning process. Do you want to scan for DTV problem?"), this) == wxYES)
		{
			wxNotebookEvent e;
			e.SetSelection(-3);
			this->OnTabChanged(e);
		}
		return;
	}

	// ライブラリを検索。
	TPIHandle tpi;
	wxFileSystem fs;
	fs.ChangePathTo(FE_DIR_B_LIB, true);
	wxString szTPIName = fs.FindFirst(wxT("*" TPI_EXT), wxFILE);
	while (! szTPIName.IsEmpty())
	{
		// ロード。
		if (tpi.InitLibrary(szTPIName, wxEmptyString, 0))
		{
			// 対応する形式名を取得。
			TPI_FORMATINFO fiInfo;
			if (tpi.GetFormatInformation(& fiInfo, true))
			{
				do
				{
					if (fiInfo.llSupportedCommand & TPI_COMMAND_ADD && (this->files.GetCount() == 1 || fiInfo.fArchive))
					{
						fiInfo.szTPIName = szTPIName;
						this->afInfo.Add(fiInfo);
						this->chType->Append(fiInfo.szTypeName);
					}
				}
				while (tpi.GetFormatInformation(& fiInfo, false));
			}
			tpi.FreeLibrary();
		}
		szTPIName = fs.FindNext();
	}

	// とりあえず最初の形式にしておく。
	this->chType->SetSelection(0);
	wxCommandEvent e;
	e.SetInt(0);
	this->OnChoice(e);
}

void MakeDialog::OnBtnDefault(wxCommandEvent&)
{
	this->cbDir->SetValue(((MainFrame *) this->GetParent())->conf.Read(wxT("DefaultPath"), wxEmptyString));
}

void MakeDialog::OnBtnDesktop(wxCommandEvent&)
{
	wxFileName fn(wxFileName::GetHomeDir(), wxT("Desktop"));
	this->cbDir->SetValue(fn.GetFullPath());
}

void MakeDialog::OnBtnCurrent(wxCommandEvent&)
{
	this->cbDir->SetValue(((MainFrame *) this->GetParent())->fnArchive.GetPath());
}

void MakeDialog::OnBtnBrowse(wxCommandEvent&)
{
	wxDirDialog dd(this);
	dd.SetPath(this->cbDir->GetValue());
	if (dd.ShowModal() == wxID_OK)
	{
		this->cbDir->SetValue(dd.GetPath());
	}
}

void MakeDialog::OnBtnBrowseKF(wxCommandEvent&)
{
	wxFileDialog fd(this);
	fd.SetWindowStyleFlag(wxFD_OPEN | wxFD_FILE_MUST_EXIST);
	if (fd.ShowModal() == wxID_OK)
	{
		this->tcKeyfile->SetValue(fd.GetPath());
	}
}

void MakeDialog::OnBtnOK(wxCommandEvent&)
{
	this->EndModal(wxID_OK);
}

void MakeDialog::OnBtnCancel(wxCommandEvent&)
{
	this->EndModal(wxID_CANCEL);
}

void MakeDialog::OnChoice(wxCommandEvent& e)
{
	TPI_FORMATINFO * fiInfo = & this->afInfo[e.GetInt()];
	// 形式が各種設定に対応しているか。
	this->scLevel->SetRange(fiInfo->sCompressLevelMin, fiInfo->sCompressLevelMax);
	this->scLevel->SetValue(fiInfo->sCompressLevelMax);
	this->scLevel->Enable(fiInfo->sCompressLevelMin != fiInfo->sCompressLevelMax);
	this->scRR->SetRange(fiInfo->sRecoveryRecordMin, fiInfo->sRecoveryRecordMax);
	this->scRR->SetValue(fiInfo->sRecoveryRecordMin);
	this->scRR->Enable(fiInfo->sRecoveryRecordMin != fiInfo->sRecoveryRecordMax);
	this->cbSplitSize->Enable(fiInfo->fMultiVolume);
	this->tcPassword->Enable(fiInfo->fEncryptPassword);
	this->cbUnmask->Enable(fiInfo->fEncryptPassword);
	this->tcKeyfile->Enable(fiInfo->fEncryptKeyFile);
	this->cbEncryptHeader->Enable(fiInfo->fEncryptHeader);
	this->cbMakeSFX->Enable(fiInfo->fSFX);
	this->cbSolid->Enable(fiInfo->fSolid);
	this->cbMMOptimize->Enable(fiInfo->fMMOptimize);
	this->tcComment->Enable(fiInfo->fComment);
}

void MakeDialog::OnCbUnmask(wxCommandEvent&)
{
	this->tcPassword->SetWindowStyle(this->tcPassword->GetWindowStyle() & (this->cbUnmask->IsChecked() ? ~ wxTE_PASSWORD : wxTE_PASSWORD));
	this->tcPassword->Refresh();
}

void MakeDialog::OnTabChanged(wxNotebookEvent& e)
{
	// "Files"タブのときは処理。
	bool bReallyShow = e.GetSelection() == 3;
	if (abs(e.GetSelection()) != 3)
	{
		return;
	}

	// "Files"タブを表示する初回かどうか。
	if (bReallyShow && this->lcFiles->GetItemCount() == 0)
	{
		// ファイルリストを追加。
		for (size_t i = 0; i < this->files.GetCount(); i++)
		{
			this->lcFiles->InsertItem(i, this->files[i]);
		}
	}

	if (bIsMake)
	{
		// 格納パスを推測。
		for (size_t i = 0; i < this->files.GetCount(); i++)
		{
			if (bReallyShow)
			{
				this->lcFiles->SetItem(i, 1, this->files[i]);
			}
		}
	}
	else
	{
		// ファイルの出力先を推測。
		wxString szOutputRootDir = WillMakeDirByArcName((MainFrame *) this->GetParent(), this) ? MakeDirPath(wxFileName::DirName(this->cbDir->GetValue()), wxFileName(this->cbFileName->GetValue()).GetName(), false).GetPath() : this->cbDir->GetValue();

		// 各ファイルにパスを付加。
		bool fDTVWarning = false;
		for (size_t i = 0; i < this->files.GetCount(); i++)
		{
			wxString szOutputFile = szOutputRootDir + wxFileName::GetPathSeparator();
			wxFileName fnStored(this->files[i]);
			if (! this->cbIgnorePath->IsChecked())
			{
				szOutputFile += fnStored.GetPathWithSep();
			}
			szOutputFile += fnStored.GetFullName();
			wxFileName fnOutput(szOutputFile);
			if (! fnOutput.Normalize() || ! fnOutput.GetFullPath().StartsWith(szOutputRootDir))
			{
				fDTVWarning = true;
				if (bReallyShow)
				{
					this->lcFiles->SetItemTextColour(i, * wxRED);
				}
			}
			if (bReallyShow)
			{
				this->lcFiles->SetItem(i, 1, fnOutput.GetFullPath());
			}
		}

		if (fDTVWarning && ::AskDlg(wxT("This archive may have Directory Traversal Vulnerability(DTV) problem, and some danger files may be extracted to the unexpected system directory! It is strongly recommended to ignore file path. Would you like to do so?"), this) == wxYES)
		{
			this->cbIgnorePath->SetValue(true);
			if (bReallyShow)
			{
				this->OnTabChanged(e);
			}
		}
	}
}
