/*
 * MiningMart Version 1.0
 * 
 * Copyright (C) 2006 Martin Scholz, Timm Euler, 
 *                    Daniel Hakenjos, Katharina Morik
 *
 * Contact: miningmart@ls8.cs.uni-dortmund.de
 *
 * A list of contributing developers (other than the copyright 
 * holders) can be found at
 * http://mmart.cs.uni-dortmund.de/downloads/download.html
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program, see the file MM_HOME/LICENSE; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 */
package edu.udo.cs.miningmart.m4.core;

import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import edu.udo.cs.miningmart.db.DB;
import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.m4.utils.InterM4ObjectToObject;
import edu.udo.cs.miningmart.m4.utils.InterM4RelationToColumnset;
import edu.udo.cs.miningmart.m4.utils.InterM4RelationToFromKey;
import edu.udo.cs.miningmart.m4.utils.InterM4RelationToToKey;
import edu.udo.cs.miningmart.m4.utils.M4Info;
import edu.udo.cs.miningmart.m4.utils.M4InfoEntry;
import edu.udo.cs.miningmart.m4.utils.Print;
import edu.udo.cs.miningmart.m4.utils.XmlInfo;

/**
 * This class represents an M4 Relation object. It bundles 
 * the pointers to the ToConcept and FromConcept as well as
 * the FromKey and ToKey.
 * 
 * @see edu.udo.cs.miningmart.m4.core.Concept 
 * @see edu.udo.cs.miningmart.m4.core.Key
 * 
 * @author Timm Euler
 * @version $Id: Relation.java,v 1.7 2006/04/11 14:10:14 euler Exp $
 */
public class Relation extends ParameterObject implements XmlInfo, edu.udo.cs.miningmart.m4.Relation {

	// ***** Database constants *****	
	public static final String M4_TABLE_NAME       = "relation_t";
	public static final String ATTRIB_REL_ID       = "rel_id";        // NOT NULL NUMBER
	public static final String ATTRIB_REL_NAME     = "rel_name";      // NOT NULL VARCHAR2(100)
	public static final String ATTRIB_FROM_CONCEPT = "rel_fromconid"; // NUMBER
	public static final String ATTRIB_TO_CONCEPT   = "rel_toconid";   // NUMBER
	public static final String ATTRIB_FROM_KEY     = "rel_fromkid";   // NUMBER
	public static final String ATTRIB_TO_KEY       = "rel_tokid";     // NUMBER
	public static final String ATTRIB_COLUMNSET    = "rel_csid";      // NUMBER

	// ***** Inter M4 communication classes *****
	static InterM4ObjectToObject rel2cs = new InterM4RelationToColumnset();
	static InterM4ObjectToObject rel2fromKey = new InterM4RelationToFromKey();
	static InterM4ObjectToObject rel2toKey = new InterM4RelationToToKey();


    // ***** Relation Variables from M4-DB *****
    private Concept    fromConcept, toConcept;
    private ForeignKey fromKey, toKey;
    private Columnset  myColumnSet;
    private boolean myCsLoaded = false;
    private boolean myFkeyLoaded = false;
    private boolean myTkeyLoaded = false;
    private boolean myFconLoaded = false;
    private boolean myTconLoaded = false;

	/** Cache for getM4Info() */
	public static M4Info m4Info = null;

	/** @see M4Table.getM4TableName() */
	public String getM4TableName() {
		return M4_TABLE_NAME;	
	}

	/** @see M4Table.getIdAttributeName() */
	public String getIdAttributeName() {
		return ATTRIB_REL_ID;
	}

	/** @see M4Table.getM4Info() */
	public M4Info getM4Info() 
	{
		if (m4Info == null) 
		{
		  M4InfoEntry[] m4i = {
			new M4InfoEntry(ATTRIB_REL_ID,             "getId",                 "setId",                 long.class,   NOT_NULL),
			new M4InfoEntry(ATTRIB_REL_NAME,           "getName",               "setName",               String.class, NOT_NULL),
			new M4InfoEntry(ATTRIB_COLUMNSET,          "getCrossLinkColumnSet", "primitiveSetColumnset", edu.udo.cs.miningmart.m4.Columnset.class),
			new M4InfoEntry(ATTRIB_FROM_CONCEPT,       "getTheFromConcept",     "primitiveSetFromConcept", edu.udo.cs.miningmart.m4.Concept.class),
			new M4InfoEntry(ATTRIB_FROM_KEY,           "getFromKey",            "primitiveSetFromKey",   edu.udo.cs.miningmart.m4.ForeignKey.class),
			new M4InfoEntry(ATTRIB_TO_CONCEPT,         "getTheToConcept",       "primitiveSetToConcept", edu.udo.cs.miningmart.m4.Concept.class),
			new M4InfoEntry(ATTRIB_TO_KEY,             "getToKey",              "primitiveSetToKey",     edu.udo.cs.miningmart.m4.ForeignKey.class)
		  };
		  m4Info = new M4Info(m4i);
		}
		return m4Info;
	}
	
	/** Cache for getXmlInfo() */
	private static M4Info xmlInfo = null;

	/** @see XmlInfo.getXmlInfo() */
	public M4Info getXmlInfo() {
		if (xmlInfo == null) {
			M4InfoEntry[] m4i = {				
				new M4InfoEntry("Name",           "getName",               "setName",               String.class),
				new M4InfoEntry("CrossColumnSet", "getCrossLinkColumnSet", "setCrossLinkColumnSet", edu.udo.cs.miningmart.m4.Columnset.class),
				new M4InfoEntry("FromConcept",    "getTheFromConcept",     "setTheFromConcept",     edu.udo.cs.miningmart.m4.Concept.class),
				new M4InfoEntry("ToConcept",      "getTheToConcept",       "setTheToConcept",       edu.udo.cs.miningmart.m4.Concept.class),
				new M4InfoEntry("FromKey",        "getFromKey",            "setFromKey",            edu.udo.cs.miningmart.m4.ForeignKey.class),
				new M4InfoEntry("ToKey",          "getToKey",              "setToKey",              edu.udo.cs.miningmart.m4.ForeignKey.class),
				new M4InfoEntry("Docu",           "getDocumentation",      "setDocumentation",      String.class)
			};
			xmlInfo = new M4Info(m4i);
		}
		return xmlInfo;
	}

	/**
	 * Constructor for Relation.
	 * 
	 * @param m4Db
	 * 
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#Constructor
	 */
	public Relation(DB m4Db) {
		super(m4Db);
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.core.Parameter#print
	 */
	public void print()
	{
		this.doPrint(
						Print.M4_OBJECT,
						"Relation "
						+ myName
						+ "(Id = "
						+ myId
						+ ")"
						+ " between "
						+ fromConcept.getName()
						+ " and "
						+ toConcept.getName());
		super.print();  // prints graphical info
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#getObjectsInNamespace(Class)
	 */
	protected Collection getObjectsInNamespace(Class typeOfObjects) throws M4Exception {
		if (typeOfObjects.isAssignableFrom(ForeignKey.class)) {
			Collection c = new Vector();
			ForeignKey fk = (ForeignKey) this.getFromKey();
			if (fk != null) {
				c.add(fk);
			}
			fk = (ForeignKey) this.getToKey();
			if (fk != null) {
				c.add(fk);
			}
			return c;
		}
		else throw new M4Exception("Relation.getObjectsInNamespace: unknown type of objects given: " + typeOfObjects.getName());
	}

	/**
	 * Setter method.
	 * 
	 * @param c the new value
	 */
    public void setTheToConcept(edu.udo.cs.miningmart.m4.Concept c) throws M4Exception
    {   
		Concept.con2ToRel.checkNameExists(this, c);
		Concept.con2ToRel.updateReferenceTo(this, c); 
    }

	/**
	 * Getter method.
	 * 
	 * @return the value
	 */
    public edu.udo.cs.miningmart.m4.Concept getTheToConcept()
    {  return toConcept;  }

	/**
	 * Setter method.
	 * 
	 * @param c the new value
	 */
    public void setTheFromConcept(edu.udo.cs.miningmart.m4.Concept c) throws M4Exception
    {   
		Concept.con2FromRel.checkNameExists(this, c);
		Concept.con2FromRel.updateReferenceTo(this, c); 
    }

	/**
	 * Getter method.
	 * 
	 * @return the value
	 */
    public edu.udo.cs.miningmart.m4.Concept getTheFromConcept()
    {  return fromConcept;  }

	/**
	 * Setter method.
	 * 
	 * @param newToKey the new toKey
	 */
    public void setToKey(edu.udo.cs.miningmart.m4.ForeignKey newToKey) throws M4Exception
    {   
    	rel2toKey.removeReferences(this, this.toKey);
    	rel2toKey.setReciprocalReferences(this, (ForeignKey) newToKey);
    }

	/**
	 * Primitive setter. Do not use!
	 */
	public void primitiveSetToKey(edu.udo.cs.miningmart.m4.ForeignKey fk)
	{   
		this.setDirty();
		this.toKey = (ForeignKey) fk;
	}
	
	/**
	 * Getter method.
	 * 
	 * @return the value
	 */
    public edu.udo.cs.miningmart.m4.ForeignKey getToKey() {
    	return this.toKey;
    }

	/**
	 * Setter method.
	 * 
	 * @param newFromKey the new FromKey
	 */
    public void setFromKey(edu.udo.cs.miningmart.m4.ForeignKey newFromKey) throws M4Exception
    {   
    	rel2fromKey.removeReferences(this, this.fromKey);
    	rel2fromKey.setReciprocalReferences(this, (ForeignKey) newFromKey);
    }
    
    /**
     * Primitive setter. Do not use!
     */
    public void primitiveSetFromKey(edu.udo.cs.miningmart.m4.ForeignKey fk)
    {   
		this.setDirty();
    	this.fromKey = (ForeignKey) fk;
    }

	/**
	 * Getter method.
	 * 
	 * @return the value
	 */
    public edu.udo.cs.miningmart.m4.ForeignKey getFromKey()
    {  return fromKey;  }

    /**
     * @return The Columnset that realizes this relation if it is an n:m
     *          relation. For 1:n relations, null is returned.
     */
    public edu.udo.cs.miningmart.m4.Columnset getCrossLinkColumnSet()
    {  return this.myColumnSet;  }

	/**
	 * Setter method.
	 * 
	 * @param cs The new cross columnset for the n:m relation.
	 */
    public void setCrossLinkColumnSet(edu.udo.cs.miningmart.m4.Columnset cs) throws M4Exception
    {   
    	rel2cs.removeReferences(this, this.myColumnSet);
    	rel2cs.setReciprocalReferences(this, (Columnset) cs);   
    }

	/**
	 * @see miningmart.m4.Relationship#removeColumnSet()
	 */
	public void removeCrossLinkColumnset() throws M4Exception
	{   this.setCrossLinkColumnSet(null);  }

	/**
	 * @see miningmart.m4.Relationship#createColumnSet(String, String, String)
	 */
	public edu.udo.cs.miningmart.m4.Columnset createCrossLinkColumnset(String name, String schema, String type)
		throws M4Exception
	{
		if (name == null || schema == null || type == null)
		{   throw new M4Exception("ConceptImpl.createColumnSet: got <null> as a parameter!");  }
			
		Columnset newCS = new Columnset(this.getM4Db());
		
		try {
			newCS.setType(type);
			newCS.setName(name);
			newCS.setSchema(schema);
			this.setCrossLinkColumnSet(newCS); // takes care of back-reference from newCS to <this>
		}
		catch (M4Exception se)
		{   throw new M4Exception("Could not create/connect new Columnset '" + name +
			                      " to Concept " + this.getId() + ": " + se.getMessage());
		}	
		// create the columns, too:
		newCS.createColumnsFromDbObject(name);
		return newCS;
	}		

	public boolean isOneToManyRelation() {
		return (this.getToKey() == null); // do not use the cross table as it is relational-level
	}	

	public boolean isManyToManyRelation() {
		return (this.getToKey() != null); // do not use the cross table as it is relational-level
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Relation#getResultOfJoin()
	 */
    public edu.udo.cs.miningmart.m4.Columnset getResultOfJoin() throws M4Exception {
    	Columnset cross = (Columnset) this.getCrossLinkColumnSet();
    	String selectPart = "SELECT ";
    	String fromPart = "FROM "; 
    	String wherePart = "WHERE ";
    	Columnset fromCs = null, toCs = null;
    	Columnset joinedCs = (Columnset) this.getM4Db().createNewInstance(Columnset.class);
    	if (cross == null) {
    		// 1:n
    		ForeignKey link = (ForeignKey) this.getFromKey();
    		fromCs = (Columnset) link.getForeignKeyColumnset();
    		toCs = (Columnset) link.getPrimaryKeyColumnset();    		
    		fromPart += fromCs.getSQLDefinition() + " T1, " + 
			            toCs.getSQLDefinition() + " T2 ";
    		// go through all columns of both columnsets:
    		Iterator colIt = toCs.getColumns().iterator();
    		while (colIt.hasNext()) {
				Column myCol = (Column) colIt.next();
				selectPart += myCol.getName() + ", ";
				Column foreignKeyCol = (Column) link.getForeignForPrimaryColumn(myCol);
				if (foreignKeyCol != null) {
					wherePart += " T1." + myCol.getName() + 
					             " = T2." + foreignKeyCol.getName() +
								 " AND";
				}				
				myCol.copyColToCS(joinedCs);
			}
    		colIt = fromCs.getColumns().iterator();
    		while (colIt.hasNext()) {
				Column myCol = (Column) colIt.next();
				if ( ! link.isColumnContainedInForeignColumns(myCol)) {
					selectPart += myCol.getName() + ", ";
					myCol.copyColToCS(joinedCs);
				}				
			}
    		selectPart = selectPart.substring(0, selectPart.length() - 2);
    		wherePart = wherePart.substring(0, wherePart.length() - 4);
    	}
    	else {
    		// n:m
    		ForeignKey firstLink = (ForeignKey) this.getFromKey();
    		ForeignKey secondLink = (ForeignKey) this.getToKey();
    		fromCs = (Columnset) firstLink.getPrimaryKeyColumnset();
    		toCs = (Columnset) secondLink.getPrimaryKeyColumnset();
    		// debug:
    		if ( ! (
    				cross.equals(firstLink.getForeignKeyColumnset()) &&
					cross.equals(secondLink.getForeignKeyColumnset())
					)) {
    			throw new M4Exception("Relation '" + this.getName() + "', getJoinedColumnset(): invalid key meta data!");
    		}
    		fromPart += fromCs.getSQLDefinition() + " T1, " + 
			            toCs.getSQLDefinition() + " T2, " +
						cross.getSQLDefinition() + " K ";

    		// go through all columns of both columnsets:
    		Iterator colIt = fromCs.getColumns().iterator();
    		while (colIt.hasNext()) {
				Column myCol = (Column) colIt.next();
				selectPart += myCol.getName() + ", ";
				Column foreignKeyCol = (Column) firstLink.getForeignForPrimaryColumn(myCol);
				if (foreignKeyCol != null) {
					wherePart += " T1." + myCol.getName() + 
					             " = K." + foreignKeyCol.getName() +
								 " AND";
				}				
				myCol.copyColToCS(joinedCs);
			}
    		colIt = toCs.getColumns().iterator();
    		while (colIt.hasNext()) {
				Column myCol = (Column) colIt.next();
				selectPart += myCol.getName() + ", ";
				Column foreignKeyCol = (Column) secondLink.getForeignForPrimaryColumn(myCol);
				if (foreignKeyCol != null) {
					wherePart += " T1." + myCol.getName() + 
					             " = K." + foreignKeyCol.getName() +
								 " AND";
				}				
				myCol.copyColToCS(joinedCs);
			}
    		selectPart = selectPart.substring(0, selectPart.length() - 2);
    		wherePart = wherePart.substring(0, wherePart.length() - 4);
    	}
    	
    	String sqlDef = "(" + selectPart + fromPart + wherePart + ")";
    	joinedCs.setName("Joined" + fromCs.getName() + toCs.getName());
    	joinedCs.setType(Columnset.CS_TYPE_VIEW);
    	joinedCs.setSchema(fromCs.getSchema());
    	joinedCs.setSQLDefinition(sqlDef);
    	
    	return joinedCs;
    }
    
	/**
	 * This method is used for n:m relationships. 
	 * 
	 * @see edu.udo.cs.miningmart.m4.Relation#setM2mKeys(ForeignKey, ForeignKey)
	 */
	public void setM2mKeys(edu.udo.cs.miningmart.m4.ForeignKey fromFK, edu.udo.cs.miningmart.m4.ForeignKey toFK)
		throws M4Exception 
		{
		if (fromFK == null || toFK == null)
		{   return;   }
	
		this.setFromKey(fromFK);
		this.setToKey(toFK);
	}
	
    /**
     * Primitive setter method. Do not use!
     */
    public void primitiveSetColumnset(edu.udo.cs.miningmart.m4.Columnset cs)
    {   
		this.setDirty(); 
    	this.myColumnSet = (Columnset) cs;
    }
	    
    /**
	 * Primitive setter, do not use.
	 * @param c the <code>Concept</code> to be set
	 * */
    public void primitiveSetFromConcept(edu.udo.cs.miningmart.m4.Concept c) {
  		this.setDirty();
    	this.fromConcept = (Concept) c;
    }
    
    /**
	 * Primitive setter, do not use.
	 * @param c the <code>Concept</code> to be set
	 * */
    public void primitiveSetToConcept(edu.udo.cs.miningmart.m4.Concept c) {
 		this.setDirty();
    	this.toConcept = (Concept) c;
    }

   	/** @see M4Data#removeAllM4References() */
	protected void removeAllM4References() throws M4Exception {
		super.removeAllM4References();
		this.setTheFromConcept(null);
		this.setTheToConcept(null);
		this.setCrossLinkColumnSet(null);
		this.setFromKey(null);
		this.setToKey(null);
		this.removeDocObject();
	}

	/**
	 * Overwrites the superclass method because the cross link
	 * columnset must be deleted, too, if it is used only for this
	 * relation.
	 * 
	 * @throws M4Exception
	 */
	public void deleteSoon() throws M4Exception {
		Columnset cross = (Columnset) this.getCrossLinkColumnSet();
		if (cross != null && (cross.getTheConcept() == null)) {
			cross.deleteSoon();
		}
		super.deleteSoon();
	}
    
 	/** <code>Relation</code>s have coordinates. */
	protected boolean hasCoordinates() {
		return true;	
	}

	/**
	 * A Relationship is relationally valid, if the fromConcept,
	 * the toConcept and the fromKey exist. For an n:m Relationship, 
	 * the cross table columnset and the toKey must exist in addition.
	 * FromConcept and ToConcept must be of type DB.
	 * 
	 * @see miningmart.m4.Relationship#isRelationallyValid()
	 */
	public boolean isRelationallyValid() {
		if (this.getTheFromConcept() == null || this.getTheToConcept() == null)
		{   return false;  }
		
		if ( ! ((this.getTheFromConcept().getType().equals(Concept.TYPE_DB)) &&
		        (this.getTheToConcept().getType().equals(Concept.TYPE_DB))))
		{   return false;  }
		
		if (this.getFromKey() == null)
		{   return false;  }
		
		return true;
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Relation#copy(Concept, Concept)
	 */
	public edu.udo.cs.miningmart.m4.Relation copy( edu.udo.cs.miningmart.m4.Concept fromConcept,
			                                    edu.udo.cs.miningmart.m4.Concept toConcept)
		throws M4Exception {
		
		Relation theCopy = (Relation) fromConcept.createFromConceptRelation(this.getName(), toConcept);
		
		try {
			theCopy.setCrossLinkColumnSet(this.getCrossLinkColumnSet());
			theCopy.setFromKey(this.getFromKey());
			theCopy.setToKey(this.getToKey());
			// theCopy.setSubRelRestriction(this.getSubRelRestriction());
			theCopy.setSuperRelation(this.getSuperRelation());
			theCopy.setDocumentation(this.getDocumentation());
		}
		catch (M4Exception m4e) {   
			throw new M4Exception("Could not create copy of Relationship " + this.getId() + ": " + m4e.getMessage()); 
		}
		
		return theCopy;	
	}	

    /**
     * Returns all Steps in the Case that use this Relation as
     * an input.
     * 
     * @return a Collection of Steps
     */
    public Collection getStepsWhereThisIsInputRelation() throws M4Exception {
		Collection params = this.getParameterReferences();
		Vector steps = new Vector();
		if (params != null) {
			Iterator it = params.iterator();
			while (it.hasNext()) {
				Parameter p = (Parameter) it.next();
				if (p != null && p.getParameterType().equals(Parameter.TYPE_INPUT)) {
					steps.add(p.getTheStep());
				}					
			}
		}
		return steps;
    }    
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Relation#removeFromKey()
	 */
	public void removeFromKey() throws M4Exception {
		this.setFromKey(null);
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Relation#removeToKey()
	 */
	public void removeToKey() throws M4Exception {
		this.setToKey(null);
	}
	
	// Methods handling Sub- and Super-Relations. For the moment
	// they are only dummy methods.
	
	public Collection getSubRelations() {
		// dummy method for the moment
		return new Vector();
	}
	
	public edu.udo.cs.miningmart.m4.Relation getSuperRelation() {
//		 dummy method for the moment
		return null;
	}
	
	public void setSuperRelation(edu.udo.cs.miningmart.m4.Relation superRelationship) throws M4Exception {
//		 dummy method for the moment
	}
	
	/**
	 * Remove the link to the superRelationship. The superRelationship itself is not removed.
	 */
	public void removeSuperRelation() throws M4Exception {
//		 dummy method for the moment
	}
	
	public void addSubRelationship(edu.udo.cs.miningmart.m4.Relation relationship) throws M4Exception {
//		 dummy method for the moment
	}
	
	public edu.udo.cs.miningmart.m4.Relation getSubRelation(String name) {
//		 dummy method for the moment
		return null;
	}
	
	public Collection getAllSubRelationships() {
//		 dummy method for the moment
		return new Vector();
	}
	
	public void removeSubRelationship(String name) throws M4Exception {
//		 dummy method for the moment
	}
	
	public void removeAllSubRelationships() throws M4Exception {
//		 dummy method for the moment
	}
}
/*
 * Historie
 * --------
 * 
 * $Log: Relation.java,v $
 * Revision 1.7  2006/04/11 14:10:14  euler
 * Updated license text.
 *
 * Revision 1.6  2006/04/06 16:31:14  euler
 * Prepended license remark.
 *
 * Revision 1.5  2006/01/12 11:36:07  euler
 * Bugfix
 *
 * Revision 1.4  2006/01/06 16:25:04  euler
 * Updates and bugfixes in the delete-Mechanism for M4Data objects.
 *
 * Revision 1.3  2006/01/05 14:11:22  euler
 * Bugfixes
 *
 * Revision 1.2  2006/01/03 10:43:14  euler
 * Added methods isOne/ManyToManyRelation
 *
 * Revision 1.1  2006/01/03 09:54:18  hakenjos
 * Initial version!
 *
 */
