//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2013, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// dlgIndex.cpp - PostgreSQL Index Property
//
//////////////////////////////////////////////////////////////////////////

// wxWindows headers
#include <wx/wx.h>

// App headers
#include "pgAdmin3.h"
#include "utils/misc.h"
#include "frm/frmMain.h"
#include "dlg/dlgIndex.h"
#include "schema/pgIndex.h"
#include "schema/pgColumn.h"
#include "schema/pgTable.h"


#define cbTablespace    CTRL_COMBOBOX("cbTablespace")
#define cbType          CTRL_COMBOBOX("cbType")
#define chkUnique       CTRL_CHECKBOX("chkUnique")
#define chkClustered    CTRL_CHECKBOX("chkClustered")
#define chkConcurrent   CTRL_CHECKBOX("chkConcurrent")
#define txtWhere        CTRL_TEXT("txtWhere")
#define txtFillFactor   CTRL_TEXT("txtFillFactor")
#define cbOpClass       CTRL_COMBOBOX("cbOpClass")
#define chkDesc         CTRL_CHECKBOX("chkDesc")
#define rdbNullsFirst   CTRL_RADIOBUTTON("rdbNullsFirst")
#define rdbNullsLast    CTRL_RADIOBUTTON("rdbNullsLast")
#define cbCollation     CTRL_COMBOBOX("cbCollation")


BEGIN_EVENT_TABLE(dlgIndexBase, dlgCollistProperty)
	EVT_TEXT(XRCID("cbTablespace"),                 dlgProperty::OnChange)
	EVT_COMBOBOX(XRCID("cbTablespace"),             dlgProperty::OnChange)
	EVT_TEXT(XRCID("txtFillFactor"),                dlgProperty::OnChange)
	EVT_LIST_ITEM_SELECTED(XRCID("lstColumns"),     dlgIndexBase::OnSelectListCol)
	EVT_COMBOBOX(XRCID("cbColumns"),                dlgIndexBase::OnSelectComboCol)
END_EVENT_TABLE();


dlgProperty *pgIndexFactory::CreateDialog(frmMain *frame, pgObject *node, pgObject *parent)
{
	return new dlgIndex(this, frame, (pgIndex *)node, (pgTable *)parent);
}


dlgIndexBase::dlgIndexBase(pgaFactory *f, frmMain *frame, const wxString &resName, pgIndexBase *node, pgTable *parentNode)
	: dlgCollistProperty(f, frame, resName, parentNode)
{
	index = node;
	wxASSERT(!table || table->GetMetaType() == PGM_TABLE || table->GetMetaType() == GP_PARTITION);
}


dlgIndexBase::dlgIndexBase(pgaFactory *f, frmMain *frame, const wxString &resName, ctlListView *colList)
	: dlgCollistProperty(f, frame, resName, colList)
{
	index = 0;
}


pgObject *dlgIndexBase::GetObject()
{
	return index;
}


int dlgIndexBase::Go(bool modal)
{

	if (index)
	{
		// edit mode
		txtName->Enable(connection->BackendMinimumVersion(9, 2));
		cbColumns->Disable();

		if (txtFillFactor)
		{
			txtFillFactor->SetValue(index->GetFillFactor());
		}
	}
	else
	{
		// create mode
	}

	if (txtFillFactor)
	{
		txtFillFactor->SetValidator(numericValidator);
		if (connection->BackendMinimumVersion(8, 2))
			txtFillFactor->Enable();
		else
			txtFillFactor->Disable();
	}

	btnAddCol->Disable();
	btnRemoveCol->Disable();

	return dlgCollistProperty::Go(modal);
}

void dlgIndexBase::OnSelectListCol(wxListEvent &ev)
{
	OnSelectCol();
}

void dlgIndexBase::OnSelectComboCol(wxCommandEvent &ev)
{
	if (cbType)
	{
		wxString method = wxEmptyString;

		if (cbType->GetValue().Length() == 0)
		{
			method = cbType->GetStringKey(1);
		}
		else
		{
			method = cbType->GetStringKey(cbType->GetCurrentSelection());
		}

		cbOpClass->Clear();

		wxString sql = wxT("SELECT opcname FROM pg_opclass ")
		               wxT("WHERE opcmethod=") + method +
		               wxT(" AND NOT opcdefault")
		               wxT(" ORDER BY 1");
		pgSet *set = connection->ExecuteSet(sql);
		if (set)
		{
			while (!set->Eof())
			{
				cbOpClass->Append(set->GetVal(0));
				set->MoveNext();
			}
			delete set;
		}
	}

	OnSelectCol();
}

void dlgIndexBase::OnSelectCol()
{
	// Can't change the columns on an existing index.
	if (index)
		return;

	if (lstColumns->GetSelection() != wxNOT_FOUND)
		btnRemoveCol->Enable(true);
	else
		btnRemoveCol->Enable(false);

	if (cbColumns->GetSelection() != wxNOT_FOUND && !cbColumns->GetValue().IsEmpty())
		btnAddCol->Enable(true);
	else
		btnAddCol->Enable(false);
}


void dlgIndexBase::CheckChange()
{
	if (index)
	{
		EnableOK(txtName->GetValue() != index->GetName() ||
		         txtComment->GetValue() != index->GetComment() ||
		         cbTablespace->GetOIDKey() != index->GetTablespaceOid() ||
		         txtFillFactor->GetValue() != index->GetFillFactor());
	}
	else
	{
		bool enable = true;
		txtComment->Enable(!GetName().IsEmpty());
		CheckValid(enable, lstColumns->GetItemCount() > 0, _("Please specify columns."));
		EnableOK(enable);
	}
}


BEGIN_EVENT_TABLE(dlgIndex, dlgIndexBase)
	EVT_BUTTON(XRCID("btnAddCol"),                  dlgIndex::OnAddCol)
	EVT_BUTTON(XRCID("btnRemoveCol"),               dlgIndex::OnRemoveCol)
	EVT_CHECKBOX(XRCID("chkClustered"),             dlgProperty::OnChange)
	EVT_CHECKBOX(XRCID("chkDesc"),                  dlgIndex::OnDescChange)
#ifdef __WXMAC__
	EVT_SIZE(                                       dlgIndex::OnChangeSize)
#endif
	EVT_COMBOBOX(XRCID("cbType"),                   dlgIndex::OnSelectType)
END_EVENT_TABLE();


dlgIndex::dlgIndex(pgaFactory *f, frmMain *frame, pgIndex *index, pgTable *parentNode)
	: dlgIndexBase(f, frame, wxT("dlgIndex"), index, parentNode)
{
	lstColumns->AddColumn(_("Column name"), 90);
	lstColumns->AddColumn(_("Order"), 40);
	lstColumns->AddColumn(_("NULLs order"), 50);
	lstColumns->AddColumn(_("Op. class"), 40);
	lstColumns->AddColumn(_("Collation"), 40);
}


void dlgIndex::CheckChange()
{
	bool fill = false;

	txtComment->Enable(!txtName->GetValue().IsEmpty());
	chkClustered->Enable(!txtName->GetValue().IsEmpty());

	if (index)
	{
		if (txtFillFactor)
		{
			fill = txtFillFactor->GetValue() != index->GetFillFactor() && !txtFillFactor->GetValue().IsEmpty();
		}

		EnableOK(fill ||
		         txtComment->GetValue() != index->GetComment() ||
		         chkClustered->GetValue() != index->GetIsClustered() ||
		         cbTablespace->GetOIDKey() != index->GetTablespaceOid() ||
		         (txtName->GetValue() != index->GetName() &&
		          txtName->GetValue().Length() != 0));
	}
	else
	{
		wxString name = GetName();

		bool enable = true;
		CheckValid(enable, !name.IsEmpty() || (name.IsEmpty() && this->database->BackendMinimumVersion(9, 0)), _("Please specify name."));
		CheckValid(enable, lstColumns->GetItemCount() > 0, _("Please specify columns."));
		EnableOK(enable);
	}
}

void dlgIndex::OnSelectType(wxCommandEvent &ev)
{
	// The column options available change depending on the
	// index type. We need to clear the column list, and
	// setup some of the other controls accordingly.

	wxString newType = cbType->GetValue();
	bool changingDefault = false;

	// Detect if we're changing between default and btree (which are the same) to
	// avoid annoying the user needlessly.
	if ((m_previousType == wxEmptyString && cbType->GetValue() == wxT("btree")) ||
	        (m_previousType == wxT("btree") && cbType->GetValue() == wxEmptyString))
		changingDefault = true;

	if (lstColumns->GetItemCount() > 0 && !changingDefault)
	{
		if (wxMessageBox(_("Changing the index type will cause the column list to be cleared. Do you wish to continue?"), _("Change index type?"), wxYES_NO) != wxYES)
		{
			cbType->SetValue(m_previousType);
			return;
		}

		// Move all the columns back to the combo
		for (int pos = lstColumns->GetItemCount(); pos > 0; pos--)
		{
			wxString colName = lstColumns->GetItemText(pos - 1);

			lstColumns->DeleteItem(pos - 1);
			cbColumns->Append(colName);
		}
	}

	if (newType == wxT("btree") || newType == wxEmptyString)
	{
		cbOpClass->Enable(true);
		chkDesc->Enable(true);
		rdbNullsFirst->Enable(true);
		rdbNullsLast->Enable(true);
	}
	else
	{
		cbOpClass->Enable(false);
		chkDesc->Enable(false);
		rdbNullsFirst->Enable(false);
		rdbNullsLast->Enable(false);
	}

	// Make a note of the type so we can compare if it changes again.
	m_previousType = cbType->GetValue();
}


wxString dlgIndex::GetColumns()
{
	wxString sql;

	int pos;
	// iterate cols
	for (pos = 0 ; pos < lstColumns->GetItemCount() ; pos++)
	{
		if (pos)
			sql += wxT(", ");

		sql += qtIdent(lstColumns->GetItemText(pos));

		if (this->database->BackendMinimumVersion(9, 1))
		{
			wxString collation = lstColumns->GetText(pos, 4);
			if (!collation.IsEmpty() && collation != wxT("pg_catalog.\"default\""))
				sql += wxT(" COLLATE ") + collation;
		}

		wxString opclass = lstColumns->GetText(pos, 3);
		if (!opclass.IsEmpty())
			sql += wxT(" ") + opclass;

		if (this->database->BackendMinimumVersion(8, 3))
		{
			wxString order = lstColumns->GetText(pos, 1);
			if (!order.IsEmpty())
				sql += wxT(" ") + order;

			wxString nullsOrder = lstColumns->GetText(pos, 2);
			if (!nullsOrder.IsEmpty())
				sql += wxT(" NULLS ") + nullsOrder;
		}
	}
	return sql;
}


int dlgIndex::Go(bool modal)
{
	if (!connection->BackendMinimumVersion(7, 4))
		chkClustered->Disable();

	if (index)
	{
		// edit mode: view only

		// We only display the column options (ASC/DESC, NULLS FIRST/LAST)
		// on PostgreSQL 8.3+, for btree indexes.
		wxArrayString colsArr = index->GetColumnList();
		wxArrayString collationsArray = index->GetCollationsArray();
		wxString colDef, colRest, colName, descDef, nullsDef, opclassDef, collation;
		const wxString firstOrder = wxT(" NULLS FIRST"), lastOrder = wxT(" NULLS LAST"), descOrder = wxT(" DESC");
		if (this->database->BackendMinimumVersion(8, 3) && index->GetIndexType() == wxT("btree"))
		{
			for (unsigned int colIdx = 0, colsCount = colsArr.Count(); colIdx < colsCount; colIdx++)
			{
				colDef = colsArr.Item(colIdx);
				descDef = index->GetOrdersArray().Item(colIdx);
				nullsDef = index->GetNullsArray().Item(colIdx);
				opclassDef = index->GetOpClassesArray().Item(colIdx);
				nullsDef = index->GetNullsArray().Item(colIdx);

				lstColumns->InsertItem(colIdx, colDef, columnFactory.GetIconId());
				lstColumns->SetItem(colIdx, 1, descDef);
				lstColumns->SetItem(colIdx, 2, nullsDef);
				lstColumns->SetItem(colIdx, 3, opclassDef);
				if (colIdx < collationsArray.Count())
					lstColumns->SetItem(colIdx, 4, collationsArray.Item(colIdx));
			}
		}
		else
		{
			for (unsigned int colIdx = 0, colsCount = colsArr.Count(); colIdx < colsCount; colIdx++)
			{
				int pos = colDef.First(wxT(" "));
				if (pos > 0)
				{
					opclassDef = colDef.Mid(pos + 1);
					colDef = colDef.Mid(0, pos);
				}
				else
					opclassDef = wxEmptyString;

				lstColumns->InsertItem(colIdx, colsArr.Item(colIdx), columnFactory.GetIconId());
				lstColumns->SetItem(colIdx, 3, cbOpClass->GetValue());
				if (colIdx < collationsArray.Count())
					lstColumns->SetItem(colIdx, 4, collationsArray.Item(colIdx));
			}
		}

		cbType->Append(index->GetIndexType());
		chkUnique->SetValue(index->GetIsUnique());
		chkClustered->SetValue(index->GetIsClustered());
		txtWhere->SetValue(index->GetConstraint());
		cbType->SetSelection(0);
		cbType->Disable();
		txtWhere->Disable();
		chkUnique->Disable();
		chkConcurrent->Disable();
		PrepareTablespace(cbTablespace, index->GetTablespaceOid());
		cbOpClass->Disable();
		chkDesc->Disable();
		rdbNullsFirst->Disable();
		rdbNullsLast->Disable();
		cbCollation->Disable();
		lstColumns->Disable();
	}
	else
	{
		// create mode
		PrepareTablespace(cbTablespace);
		cbType->Append(wxT(""));
		pgSet *set = connection->ExecuteSet(wxT(
		                                        "SELECT oid, amname FROM pg_am"));
		if (set)
		{
			while (!set->Eof())
			{
				cbType->Append(set->GetVal(1), set->GetVal(0));
				set->MoveNext();
			}
			delete set;
		}

		if (connection->BackendMinimumVersion(9, 1))
		{
			// fill collation combobox
			cbCollation->Append(wxEmptyString);
			set = connection->ExecuteSet(
			          wxT("SELECT nspname, collname\n")
			          wxT("  FROM pg_collation c, pg_namespace n\n")
			          wxT("  WHERE c.collnamespace=n.oid\n")
			          wxT("  ORDER BY nspname, collname"));
			if (set)
			{
				while (!set->Eof())
				{
					wxString name = qtIdent(set->GetVal(wxT("nspname"))) + wxT(".") + qtIdent(set->GetVal(wxT("collname")));
					cbCollation->Append(name);
					set->MoveNext();
				}
				delete set;
			}
			cbCollation->SetSelection(0);
		}
		else
			cbCollation->Disable();

		if (!this->database->BackendMinimumVersion(8, 2))
			chkConcurrent->Disable();

		if (!this->database->BackendMinimumVersion(8, 3))
		{
			chkDesc->Disable();
			rdbNullsFirst->Disable();
			rdbNullsLast->Disable();
		}

		// Add the default tablespace
		cbTablespace->Insert(_("<default tablespace>"), 0, (void *)0);
		cbTablespace->SetSelection(0);
	}

	// Reset the labels as the XRC defined values will have been localised :-(
	rdbNullsFirst->SetLabel(wxT("FIRST"));
	rdbNullsLast->SetLabel(wxT("LAST"));

	int returnCode = dlgIndexBase::Go(modal);

	if (index && connection->BackendMinimumVersion(8, 0))
		txtName->Enable(true);

	// This fixes a UI glitch on MacOS X
	// Because of the new layout code, the Columns pane doesn't size itself properly
	SetSize(GetSize().GetWidth() + 1, GetSize().GetHeight());
	SetSize(GetSize().GetWidth() - 1, GetSize().GetHeight());

	return returnCode;
}


void dlgIndex::OnAddCol(wxCommandEvent &ev)
{
	wxString colName = cbColumns->GetValue();

	if (!colName.IsEmpty())
	{
		long colIndex = lstColumns->InsertItem(lstColumns->GetItemCount(), colName, columnFactory.GetIconId());


		if (this->database->BackendMinimumVersion(8, 3))
		{
			if (chkDesc->GetValue())
			{
				if (chkDesc->IsEnabled())
					lstColumns->SetItem(colIndex, 1, wxT("DESC"));


				if (rdbNullsLast->GetValue())
				{
					if (rdbNullsLast->IsEnabled())
						lstColumns->SetItem(colIndex, 2, wxT("LAST"));
				}
				else
				{
					if (rdbNullsLast->IsEnabled())
						lstColumns->SetItem(colIndex, 2, wxT("FIRST"));
				}
			}
			else
			{
				if (chkDesc->IsEnabled())
					lstColumns->SetItem(colIndex, 1, wxT("ASC"));

				if (rdbNullsFirst->GetValue())
				{
					if (rdbNullsFirst->IsEnabled())
						lstColumns->SetItem(colIndex, 2, wxT("FIRST"));
				}
				else
				{
					if (rdbNullsLast->IsEnabled())
						lstColumns->SetItem(colIndex, 2, wxT("LAST"));
				}
			}

			lstColumns->SetItem(colIndex, 3, cbOpClass->GetValue());
			lstColumns->SetItem(colIndex, 4, cbCollation->GetValue());
		}

		cbColumns->Delete(cbColumns->GetCurrentSelection());
		if (cbColumns->GetCount())
			cbColumns->SetSelection(0);

		CheckChange();
		if (!cbColumns->GetCount())
			btnAddCol->Disable();
	}
}


void dlgIndex::OnRemoveCol(wxCommandEvent &ev)
{
	long pos = lstColumns->GetSelection();
	if (pos >= 0)
	{
		wxString colName = lstColumns->GetItemText(pos);

		lstColumns->DeleteItem(pos);
		cbColumns->Append(colName);

		CheckChange();
		btnRemoveCol->Disable();
	}
}

#ifdef __WXMAC__
void dlgIndex::OnChangeSize(wxSizeEvent &ev)
{
	lstColumns->SetSize(wxDefaultCoord, wxDefaultCoord,
	                    ev.GetSize().GetWidth(), ev.GetSize().GetHeight() - 700);
	if (GetAutoLayout())
	{
		Layout();
	}
}
#endif

wxString dlgIndex::GetSql()
{
	wxString sql;

	if (table)
	{
		wxString name = GetName();
		if (!index)
		{
			sql = wxT("CREATE ");
			if (chkUnique->GetValue())
				sql += wxT("UNIQUE ");

			sql += wxT("INDEX ");

			if (chkConcurrent->GetValue())
				sql += wxT("CONCURRENTLY ");

			sql += qtIdent(name);

			sql += wxT("\n   ON ") + table->GetQuotedFullIdentifier();

			if (cbType->GetCurrentSelection() > 0)
				AppendIfFilled(sql, wxT(" USING "), cbType->GetValue());

			sql += wxT(" (") + GetColumns()
			       + wxT(")");

			if (txtFillFactor)
			{
				if (connection->BackendMinimumVersion(8, 2) && txtFillFactor->GetValue().Length() > 0)
					sql += wxT("\n  WITH (FILLFACTOR=") + txtFillFactor->GetValue() + wxT(")");
			}

			if (cbTablespace->GetOIDKey() > 0)
				AppendIfFilled(sql, wxT("\n  TABLESPACE "), qtIdent(cbTablespace->GetValue()));

			AppendIfFilled(sql, wxT(" WHERE "), txtWhere->GetValue());
			sql +=  wxT(";\n");
		}
		else
		{
			if (connection->BackendMinimumVersion(8, 2) && txtFillFactor->GetValue().Length() > 0)
				sql += wxT("ALTER INDEX ") + qtIdent(index->GetSchema()->GetName()) + wxT(".")
				       + qtIdent(index->GetName()) +  wxT("\n  SET (FILLFACTOR=")
				       + txtFillFactor->GetValue() + wxT(");\n");

			if(connection->BackendMinimumVersion(8, 0))
			{
				if (index->GetName() != txtName->GetValue() &&
				        !txtName->GetValue().IsEmpty())
					sql += wxT("ALTER INDEX ") + qtIdent(index->GetSchema()->GetName()) + wxT(".")
					       + qtIdent(index->GetName()) +  wxT("\n  RENAME TO ")
					       + qtIdent(txtName->GetValue()) + wxT(";\n");

				if (cbTablespace->GetOIDKey() != index->GetTablespaceOid())
					sql += wxT("ALTER INDEX ") + qtIdent(index->GetSchema()->GetName()) + wxT(".") + qtIdent(name)
					       +  wxT("\n  SET TABLESPACE ") + qtIdent(cbTablespace->GetValue())
					       +  wxT(";\n");
			}
		}
		if (connection->BackendMinimumVersion(7, 4) && chkClustered->IsEnabled())
		{
			if (index && index->GetIsClustered() && !chkClustered->GetValue())
				sql += wxT("ALTER TABLE ") + table->GetQuotedFullIdentifier()
				       +  wxT("\n  SET WITHOUT CLUSTER;\n");
			else if (chkClustered->GetValue() && (!index || !index->GetIsClustered()))
				sql += wxT("ALTER TABLE ") + table->GetQuotedFullIdentifier()
				       +  wxT("\n  CLUSTER ON ") + qtIdent(name) + wxT(";\n");
		}

		if (txtComment->IsEnabled())
		{
			AppendComment(sql, wxT("INDEX"), table->GetSchema(), index);
		}
	}
	return sql;
}


pgObject *dlgIndex::CreateObject(pgCollection *collection)
{
	wxString name = GetName();

	pgObject *obj = indexFactory.CreateObjects(collection, 0, wxT(
	                    "\n   AND cls.relname=") + qtDbString(name) + wxT(
	                    "\n   AND cls.relnamespace=") + table->GetSchema()->GetOidStr());
	return obj;
}

void dlgIndex::OnDescChange(wxCommandEvent &ev)
{
	if (chkDesc->GetValue())
	{
		rdbNullsFirst->SetValue(true);
	}
	else
	{
		rdbNullsLast->SetValue(true);
	}
}
