/////////////////////////////////////////////////////////////////////////////
// Name:        dbrelation.cc
// Purpose:     Database Objects
// Author:      Daniel Horak
// Modified by:
// RCS-ID:      $Id: dbrelation.cc,v 1.5 2004/01/04 18:32:16 horakdan Exp $
// Copyright:   (c) Daniel Horak
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWindows headers
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

#include <wx/notebook.h>
#include <wx/ogl/ogl.h>
#include "config.h"
#include "xml.h"
#include "dbobject.h"
#include "dbrelation.h"
#include "dbrelattr.h"
#include "dbentity.h"
#include "dbmodelrelation.h"
#include "schema.h"
#include "container.h"

DBRelation::DBRelation(DataDesignerProject *project, DataDesignerContainer *container)
	:DBObject(DBRelationType, "relation", project, container),
	m_type(DBO_RELATION_TYPE_IDENT),
	m_parciality_parent(DBO_RELATION_PARCIALITY_MANDATORY),	m_parciality_child(DBO_RELATION_PARCIALITY_MANDATORY),
	m_refinteg_parent_update(DBO_RELATION_REFINTEG_NONE), m_refinteg_parent_delete(DBO_RELATION_REFINTEG_NONE),
	m_refinteg_child_insert(DBO_RELATION_REFINTEG_NONE), m_refinteg_child_update(DBO_RELATION_REFINTEG_NONE),
	m_refinteg_match(DBO_RELATION_REFINTEG_MATCH_FULL), m_keys(NULL)
{
}

DBRelation::~DBRelation()
{
	if (m_keys) {
		m_project->DeleteChildren(m_keys->GetTreeItemId());
		delete m_keys;
	}

	wxDBObjectListNode      *node;
	
	// must delete all modelrelations derived from this real relation
	node = m_modelrelations.GetFirst();
	while (node) {
		delete node->GetData();
		node = node->GetNext();
	}
}

wxDialog *DBRelation::Editor(bool edit)
{
	return new DBRelationEditor(this, edit);
}

void DBRelation::LoadXmlNode(wxXmlNode *node)
{
	wxXmlNode	*child;
	
	if (node->GetName() == m_typestr) {
		wxString name;
		
		DBObject::LoadXmlNode(node);
		
		m_parent	= node->GetPropVal("parent", wxEmptyString);
		m_child		= node->GetPropVal("child", wxEmptyString);
		
		child = node->GetChildren();
		while (child) {
			name = child->GetName();
			if (name == "parent") {
				LoadTextNode(child, "parent", m_parent);
			} else if (name == "child") {
				LoadTextNode(child, "child", m_child);
			} else if (name == "type") {
				wxString type;
				LoadTextNode(child, "type", type);
				if (type == "ident")
					m_type = DBO_RELATION_TYPE_IDENT;
				else if (type == "nonident")
					m_type = DBO_RELATION_TYPE_NONIDENT;
				else if (type == "inform")
					m_type = DBO_RELATION_TYPE_INFORM;
				else
					wxLogMessage("Unknown relation type specification '%s'", type.c_str());
			} else if (name == "parciality_parent") {
				wxString parc;
				LoadTextNode(child, "parciality_parent", parc);
				if (ParseParciality(parc, m_parciality_parent) == FALSE)
					wxLogMessage("Unknown relation parent parciality specification '%s'", parc.c_str());
			} else if (name == "parciality_child") {
				wxString parc;
				LoadTextNode(child, "parciality_child", parc);
				if (ParseParciality(parc, m_parciality_child) == FALSE)
					wxLogMessage("Unknown relation child parciality specification '%s'", parc.c_str());
			} else if (name == "refinteg_parent_update") {
				wxString refinteg;
				LoadTextNode(child, "refinteg_parent_update", refinteg);
				if (ParseRefIntegParent(refinteg, m_refinteg_parent_update) == FALSE)
					wxLogMessage("Unknown relation parent update refinteg specification '%s'", refinteg.c_str());
			} else if (name == "refinteg_parent_delete") {
				wxString refinteg;
				LoadTextNode(child, "refinteg_parent_delete", refinteg);
				if (ParseRefIntegParent(refinteg, m_refinteg_parent_delete) == FALSE)
					wxLogMessage("Unknown relation parent delete refinteg specification '%s'", refinteg.c_str());
			} else if (name == "refinteg_child_insert") {
				wxString refinteg;
				LoadTextNode(child, "refinteg_child_insert", refinteg);
				if (ParseRefIntegChild(refinteg, m_refinteg_child_insert) == FALSE)
					wxLogMessage("Unknown relation child insert refinteg specification '%s'", refinteg.c_str());
			} else if (name == "refinteg_child_update") {
				wxString refinteg;
				LoadTextNode(child, "refinteg_child_update", refinteg);
				if (ParseRefIntegChild(refinteg, m_refinteg_child_update) == FALSE)
					wxLogMessage("Unknown relation child update refinteg specification '%s'", refinteg.c_str());
			} else if (name == "refinteg_match") {
				wxString match;
				LoadTextNode(child, "refinteg_match", match);
				if (match == "full")
					m_refinteg_match = DBO_RELATION_REFINTEG_MATCH_FULL;
				else if (match == "partial")
					m_refinteg_match = DBO_RELATION_REFINTEG_MATCH_PARTIAL;
				else
					wxLogMessage("Unknown relation match refinteg specification '%s'", match.c_str());
			} else if ((name == "relationattrs") || (name == "keys")) {
				m_keys->LoadXmlNode(child);
			}
			child = child->GetNext();
		}
	} else {
		wxLogMessage("wrong type '%s'", node->GetName().c_str());
	}
}

bool DBRelation::ParseParciality(const wxString& parc, int& var)
{
	bool	res = TRUE;
	
	if (parc == "mandatory")
		var = DBO_RELATION_PARCIALITY_MANDATORY;
	else if (parc == "nonmandatory")
		var = DBO_RELATION_PARCIALITY_NONMANDATORY;
	else
		res = FALSE;
	
	return res;
}

bool DBRelation::ParseRefIntegParent(const wxString& refinteg, int& var)
{
	bool	res = TRUE;
	
	if (refinteg == "none")
		var = DBO_RELATION_REFINTEG_NONE;
	else if (refinteg == "restrict")
		var = DBO_RELATION_REFINTEG_RESTRICT;
	else if (refinteg == "cascade")
		var = DBO_RELATION_REFINTEG_CASCADE;
	else if (refinteg == "setnull")
		var = DBO_RELATION_REFINTEG_SETNULL;
	else if (refinteg == "setdefault")
		var = DBO_RELATION_REFINTEG_SETDEFAULT;
	else
		res = FALSE;
	
	return res;
}

bool DBRelation::ParseRefIntegChild(const wxString& refinteg, int& var)
{
	bool	res = TRUE;
	
	if (refinteg == "none")
		var = DBO_RELATION_REFINTEG_NONE;
	else if (refinteg == "restrict")
		var = DBO_RELATION_REFINTEG_RESTRICT;
	else
		res = FALSE;
	
	return res;
}

void DBRelation::ParcialityToString(int parc, wxString& str)
{
	if (parc == DBO_RELATION_PARCIALITY_MANDATORY)
		str = "mandatory";
	else if (parc == DBO_RELATION_PARCIALITY_NONMANDATORY)
		str = "nonmandatory";
	else
		wxLogMessage("DBRelation::ParcialityToString - unknown parciality type '%d'", parc);
}

void DBRelation::RefIntegToString(int refinteg, wxString& str)
{
	if (refinteg == DBO_RELATION_REFINTEG_NONE)
		str = "none";
	else if (refinteg == DBO_RELATION_REFINTEG_RESTRICT)
		str = "restrict";
	else if (refinteg == DBO_RELATION_REFINTEG_CASCADE)
		str = "cascade";
	else if (refinteg == DBO_RELATION_REFINTEG_SETNULL)
		str = "setnull";
	else if (refinteg == DBO_RELATION_REFINTEG_SETDEFAULT)
		str = "setdefault";
	else
		wxLogMessage("DBRelation::RefIntegToString - unknown referential integrity type '%d'", refinteg);
}

wxTreeItemId DBRelation::AppendItem()
{
	DataDesignerSchema	*schema;

	if (! m_appended) {
		m_treeitemid	= m_project->AppendItem(m_container->GetTreeItemId(), m_name, -1, -1, new DataDesignerItemData(this));
		m_keys	= new DBRelationAttributeContainer(m_project, m_project->AppendItem(m_treeitemid, _("Attribute Pairs")));
		
		if ((schema = m_project->GetSchema()) != NULL) {
			schema->AddObject(this);
			schema->Refresh();
		}
		
		m_appended	= TRUE;
	}

	return m_treeitemid;
}

wxXmlNode *DBRelation::GetXmlNode()
{
	wxString	val;
	
	wxXmlNode *node = DBObject::GetXmlNode();
	
	node->AddChild(GetTextNode("parent", m_parent));
	node->AddChild(GetTextNode("child", m_child));

	if (m_type == DBO_RELATION_TYPE_IDENT)
		val = "ident";
	else if (m_type == DBO_RELATION_TYPE_NONIDENT)
		val = "nonident";
	else if (m_type == DBO_RELATION_TYPE_INFORM)
		val = "inform";
	else
		val = wxEmptyString;
		
	if (! val.IsEmpty())
		node->AddChild(GetTextNode("type", val));
	
	ParcialityToString(m_parciality_parent, val);
	node->AddChild(GetTextNode("parciality_parent", val));
	ParcialityToString(m_parciality_child, val);
	node->AddChild(GetTextNode("parciality_child", val));

	RefIntegToString(m_refinteg_parent_update, val);
	node->AddChild(GetTextNode("refinteg_parent_update", val));
	RefIntegToString(m_refinteg_parent_delete, val);
	node->AddChild(GetTextNode("refinteg_parent_delete", val));
	RefIntegToString(m_refinteg_child_insert, val);
	node->AddChild(GetTextNode("refinteg_child_insert", val));
	RefIntegToString(m_refinteg_child_update, val);
	node->AddChild(GetTextNode("refinteg_child_update", val));

	if (m_refinteg_match == DBO_RELATION_REFINTEG_MATCH_FULL)
		val = "full";
	else if (m_refinteg_match == DBO_RELATION_REFINTEG_MATCH_PARTIAL)
		val = "partial";
	else
		val = wxEmptyString;
		
	if (! val.IsEmpty())
		node->AddChild(GetTextNode("refinteg_match", val));
	

	node->AddChild(m_keys->GetXmlNode());
	
	return node;
}

void DBRelation::CreateShape()
{
	m_shape = new DBRelationShape(this);	
	m_shape->Show(TRUE);
}

void DBRelation::LinkAttributes()
{
	DBEntity	*ent_parent, *ent_child;
	
	ent_parent	= (DBEntity *)(GetProject()->m_top_entities->GetObjectByName(m_parent));
	ent_child	= (DBEntity *)(GetProject()->m_top_entities->GetObjectByName(m_child));
	
	if ((ent_parent == NULL) || (ent_child == NULL)) {
		wxLogMessage("DBRelation::LinkAttribute - cannot find parent or child entity");
		return;
	}
	
	if (m_type == DBO_RELATION_TYPE_IDENT) {
		// copy parent's primary key into child's key + make as foreign key
		ent_child->AddForeignKeyConstraint(this, TRUE);
	} else if (m_type == DBO_RELATION_TYPE_NONIDENT) {
		// copy parent's primary key into child + make as foreign key
		ent_child->AddForeignKeyConstraint(this, FALSE);
	} else if (m_type == DBO_RELATION_TYPE_INFORM ) {
		// copy parent's primary key into child
		ent_child->AddParentsPrimaryKey(this);
	} else {
		wxLogMessage("DBRelation::LinkAttribute - unknown relation type '%d'", m_type);
	}
}

void DBRelation::AddAttributesToKeys(const wxString& attr_parent, const wxString& attr_child)
{
	DBRelationAttribute	*relattr;
	
	relattr = (DBRelationAttribute *)m_keys->CreateObject();
	if (relattr != NULL) {
		relattr->m_parent = attr_parent;
		relattr->m_child = attr_child;
		relattr->AppendItem();
	}
}

void DBRelation::AddModelRelation(DBModelRelation *rel)
{
	m_modelrelations.Append(rel);
}

void DBRelation::DeleteModelRelation(DBModelRelation *rel)
{
	if (m_modelrelations.IndexOf(rel) != wxNOT_FOUND) {
		m_modelrelations.DeleteObject(rel);
	}
}

/*
 * Editor
 */
BEGIN_EVENT_TABLE(DBRelationEditor, DBObjectEditor)
	EVT_BUTTON(wxID_APPLY, DBRelationEditor::OnApply)
END_EVENT_TABLE()

wxString DBRelationEditor::m_type_str[] 	= { _("Identifying"), _("Non-identifying"), _("Informational") };
wxString DBRelationEditor::m_parciality_str[] 	= { _("Mandatory"), _("Non-mandatory") };
wxString DBRelationEditor::m_refinteg_parent_str[] 	= { _("none"), _("restrict"), _("cascade"), _("set NULL"), _("set default") };
wxString DBRelationEditor::m_refinteg_child_str[] 	= { _("none"), _("restrict") };
wxString DBRelationEditor::m_refinteg_match_str[] 	= { _("Full"), _("Partial") };

DBRelationEditor::DBRelationEditor(DBObject *object, bool edit)
	: DBObjectEditor(_("Relation"), wxSize(500,350), object, edit)
{
	wxString	**strings;
	int		idx = 0;
	
	m_page_refinteg = new wxPanel(m_notebook);
	
	new wxStaticBox(m_page_refinteg, -1, _("Parent"), wxPoint(5,5), wxSize(210,80));
	new wxStaticText(m_page_refinteg, -1, _("Update"), wxPoint(10,25), wxSize(80,-1), wxALIGN_RIGHT);
	c6 = new wxComboBox(m_page_refinteg, -1, wxEmptyString, wxPoint(100,25), wxSize(100,-1), 5, m_refinteg_parent_str, wxCB_READONLY);
	new wxStaticText(m_page_refinteg, -1, _("Delete"), wxPoint(10,50), wxSize(80,-1), wxALIGN_RIGHT);
	c7 = new wxComboBox(m_page_refinteg, -1, wxEmptyString, wxPoint(100,50), wxSize(100,-1), 5, m_refinteg_parent_str, wxCB_READONLY);

	new wxStaticBox(m_page_refinteg, -1, _("Child"), wxPoint(225,5), wxSize(210,80));
	new wxStaticText(m_page_refinteg, -1, _("Insert"), wxPoint(230,25), wxSize(80,-1), wxALIGN_RIGHT);
	c8 = new wxComboBox(m_page_refinteg, -1, wxEmptyString, wxPoint(320,25), wxSize(100,-1), 2, m_refinteg_child_str, wxCB_READONLY);
	new wxStaticText(m_page_refinteg, -1, _("Update"), wxPoint(230,50), wxSize(80,-1), wxALIGN_RIGHT);
	c9 = new wxComboBox(m_page_refinteg, -1, wxEmptyString, wxPoint(320,50), wxSize(100,-1), 2, m_refinteg_child_str, wxCB_READONLY);

	r10 = new wxRadioBox(m_page_refinteg, -1, _("Match"), wxPoint(10,100), wxSize(-1,-1), 2, m_refinteg_match_str, 1, wxRA_HORIZONTAL);

	m_page_keys = new wxPanel(m_notebook);

        m_list_keys = new DBRelationAttributeListCtrl(m_page_keys,((DBRelation *)GetObject())->m_keys);
	
	if (m_edit) {
		((DBRelation *)GetObject())->m_keys->SetList(m_list_keys);
		((DBRelation *)GetObject())->m_keys->AddObjectsToList();
	}

	wxLayoutConstraints *c = new wxLayoutConstraints;
	c->top.SameAs   (m_page_keys, wxTop);
	c->left.SameAs  (m_page_keys, wxLeft);
	c->right.SameAs (m_page_keys, wxRight);
	c->bottom.SameAs(m_page_keys, wxBottom);
	m_list_keys->SetConstraints(c);

	m_page_keys->SetAutoLayout(TRUE);
	
	m_notebook->InsertPage(m_notebook->GetPageCount() - 1, m_page_refinteg, _("Referential Integrity"));
	m_notebook->InsertPage(m_notebook->GetPageCount() - 1, m_page_keys, _("Keys"));

	strings = GetObject()->GetProject()->m_top_entities->ListNames();
	
	new wxStaticText(m_page_general, -1, _("Parent entity"), wxPoint(10,60), wxSize(80,-1), wxALIGN_RIGHT);
	c1 = new wxComboBox(m_page_general, -1, wxEmptyString, wxPoint(100,60), wxSize(200,-1), 0, NULL, wxCB_READONLY | wxCB_SORT);

	new wxStaticText(m_page_general, -1, _("Child entity"), wxPoint(10,85), wxSize(80,-1), wxALIGN_RIGHT);
	c2 = new wxComboBox(m_page_general, -1, wxEmptyString, wxPoint(100,85), wxSize(200,-1), 0, NULL, wxCB_READONLY | wxCB_SORT);

	while (strings[idx]) {
		c1->Append(*strings[idx]);
		c2->Append(*strings[idx]);
		idx++;
	}
	
	delete [] strings;
	
	r3 = new wxRadioBox(m_page_general, -1, _("Type"), wxPoint(10,110), wxSize(-1,-1), 3, m_type_str, 2, wxRA_HORIZONTAL);
	
	r4 = new wxRadioBox(m_page_general, -1, _("Parent Parciality"), wxPoint(10,190), wxSize(-1,-1), 2, m_parciality_str, 1, wxRA_HORIZONTAL);
	r5 = new wxRadioBox(m_page_general, -1, _("Child Parciality"), wxPoint(200,190), wxSize(-1,-1), 2, m_parciality_str, 1, wxRA_HORIZONTAL);
	
	m_button_apply = AddButton(wxID_APPLY, _("Apply"), wxSize(60,-1));

	if (m_edit) {
		m_button_ok->SetDefault();
	} else {
		m_button_apply->SetDefault();
		
		m_page_keys->Disable();
	}
	
	if (m_edit) {
		c1->Disable();
		c2->Disable();
	}
}


DBRelationEditor::~DBRelationEditor()
{
}

bool DBRelationEditor::TransferDataFromWindow()
{
	DBRelation	*object = (DBRelation *)GetObject();

	DBObjectEditor::TransferDataFromWindow();
	
	if (! m_edit) {
		object->m_parent	= c1->GetValue();
		object->m_child		= c2->GetValue();
	}

	object->m_type				= r3->GetSelection();
		
	object->m_parciality_parent 		= r4->GetSelection();
	object->m_parciality_child 		= r5->GetSelection();
		
	object->m_refinteg_parent_update	= c6->FindString(c6->GetValue());
	object->m_refinteg_parent_delete	= c7->FindString(c7->GetValue());
	object->m_refinteg_child_insert		= c8->FindString(c8->GetValue());
	object->m_refinteg_child_update		= c9->FindString(c9->GetValue());
	object->m_refinteg_match 		= r10->GetSelection();
	
	return TRUE;
}

bool DBRelationEditor::TransferDataToWindow()
{
	DBRelation	*object = (DBRelation *)GetObject();
	
	DBObjectEditor::TransferDataToWindow();

	c1->SetValue(object->m_parent);
	c2->SetValue(object->m_child);
	
	r3->SetSelection(object->m_type);
	r4->SetSelection(object->m_parciality_parent);
	r5->SetSelection(object->m_parciality_child);
	
	c6->SetValue(c6->GetString(object->m_refinteg_parent_update));
	c7->SetValue(c7->GetString(object->m_refinteg_parent_delete));
	c8->SetValue(c8->GetString(object->m_refinteg_child_insert));
	c9->SetValue(c9->GetString(object->m_refinteg_child_update));
	r10->SetSelection(object->m_refinteg_match);
	
	return TRUE;
}

bool DBRelationEditor::Validate()
{
	bool res = DBObjectEditor::Validate();
	
	if (res == FALSE)
		return res;
	
	if (c1->GetValue().IsEmpty() || c2->GetValue().IsEmpty()) {
		wxMessageBox(_("Missing name(s) of linked table(s)"), _("Error"), wxOK | wxICON_ERROR);
		return FALSE;
	}
	
	return TRUE;
}

void DBRelationEditor::OnApply(wxCommandEvent& event)
{
	DBRelation	*object = (DBRelation *)GetObject();

	if (Validate() == FALSE)
		return;
		
	TransferDataFromWindow();
	
	object->AppendItem();
	m_list_keys->SetContainer(object->m_keys);

	m_page_keys->Enable();
	
	m_button_apply->Disable();
	m_button_ok->SetDefault();
	
	m_edit = TRUE;
}

/*
 * Container
 */
DBRelationContainer::DBRelationContainer(DataDesignerProject *project, const wxTreeItemId& parent)
	: DataDesignerContainer(project, parent, "relations")
{
}

DBObject *DBRelationContainer::CreateObject()
{
	return new DBRelation(GetProject(), this);
}

void DBRelationContainer::ShowList()
{
	SetList(new DBRelationListCtrl(GetProject()->GetSplitter(), this));
	
	DataDesignerContainer::AddObjectsToListAndShow();
}

/*
 * ObjectList
 */
DBRelationListCtrl::DBRelationListCtrl(wxWindow *parent, DataDesignerContainer *container)
	: DBObjectListCtrl(parent, container)
{
	SetColumnWidth(0, 200);
	InsertColumn(1, _("Parent entity"));
	SetColumnWidth(1, 150);
	InsertColumn(2, _("Child entity"));
	SetColumnWidth(2, 150);
}

DBRelationListCtrl::~DBRelationListCtrl()
{
}

void DBRelationListCtrl::SetObject(long item, DBObject *object)
{
	DBRelation *relation = (DBRelation *)object;
	
	SetItem(item, 1, relation->m_parent);
	SetItem(item, 2, relation->m_child);
}

/*
 * Shape
 */
DBRelationShape::DBRelationShape(DBRelation *relation)
	: wxLineShape()
{
	DBObject	*entity;

	SetClientData(relation);

	entity = relation->GetProject()->m_top_entities->GetObjectByName(relation->m_parent);
	if (entity)
		m_shape_from = entity->GetShape();
	else {
		wxLogMessage("no parent");
		return;
	}

	entity = relation->GetProject()->m_top_entities->GetObjectByName(relation->m_child);
	if (entity)
		m_shape_to = entity->GetShape();
	else {
		wxLogMessage("no child");
		return;
	}
		
	SetPen(wxBLACK_PEN);
	SetBrush(wxRED_BRUSH);
	AddArrow(ARROW_ARROW, ARROW_POSITION_END, 20);
	MakeLineControlPoints(2);
	SetDisableLabel(TRUE);
	
	m_shape_from->AddLine(this, m_shape_to);
	
	wxClientDC dc(m_shape_from->GetCanvas());
	
	m_shape_from->Move(dc, m_shape_from->GetX(), m_shape_from->GetY());
	m_shape_to->Move(dc, m_shape_to->GetX(), m_shape_to->GetY());
}


syntax highlighted by Code2HTML, v. 0.9.1