/*
 * 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.awt.Point;
import java.io.Serializable;
import java.io.Writer;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Vector;
import java.util.logging.Level;

import edu.udo.cs.miningmart.db.DB;
import edu.udo.cs.miningmart.exception.DbConnectionClosed;
import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.m4.Chain;
import edu.udo.cs.miningmart.m4.Concept;
import edu.udo.cs.miningmart.m4.GraphicalM4Object;
import edu.udo.cs.miningmart.m4.M4Object;
import edu.udo.cs.miningmart.m4.Relation;
import edu.udo.cs.miningmart.m4.Step;
import edu.udo.cs.miningmart.m4.utils.M4Info;
import edu.udo.cs.miningmart.m4.utils.M4InfoEntry;
import edu.udo.cs.miningmart.m4.utils.M4Table;
import edu.udo.cs.miningmart.m4.utils.Print;
import edu.udo.cs.miningmart.m4.utils.XmlInfo;

/**
 * This class corresponds to one row in the table HCI_COORD_T. 
 * 
 * @author Timm Euler
 * @version $Id: Coordinates.java,v 1.6 2006/04/11 14:10:14 euler Exp $
 */
public class Coordinates extends edu.udo.cs.miningmart.m4.core.M4Data implements Serializable, XmlInfo {
	public static String TAG_MAIN = "Coordinates";
	
	private GraphicalM4Object theObject; // the object whose coordinates are stored
	private Point theLocation;

	// possible object types:
	private static final Class[] objectTypes = { Chain.class, Relation.class, Concept.class, Step.class };
	// and their representative Strings in the table (use same order!):
	private static final String[] objectDB_Names = { "CH", "REL", "CON", "ST" };
	
	/** The M4 table name storing graphical information. */
	public static final String M4_TABLE_NAME = "hci_coord_t";
	
	/** DB level: The attribute storing the coord-entry's object IDs. */
	public static final String ATTRIB_OBJECT_ID = "obj_id";
	
	/** DB level: The attribute storing the coord-entry's object names. */
	public static final String ATTRIB_OBJECT_NAME = "obj_name";
	
	/** DB level: The attribute storing the object types. */
	public static final String ATTRIB_OBJECT_TYPE = "obj_type";
	
	/** DB level: The attribute storing the coord-entry's context object IDs. */
	public static final String ATTRIB_CONTEXT_ID = "context_id";
	
	/** DB level: The attribute storing the coord-entry's context object names. */
	public static final String ATTRIB_CONTEXT_NAME = "context_name";
	
	/** DB level: The attribute storing the context object types. */
	public static final String ATTRIB_CONTEXT_TYPE = "context_type";
	
	/** DB level: The attribute storing the coord-entry's X coordinates. */
	public static final String ATTRIB_X_COORD = "x";
	
	/** DB level: The attribute storing the coord-entry's Y coordinates. */
	public static final String ATTRIB_Y_COORD = "y";
	
	/**
	 * @see edu.udo.cs.miningmart.m4.utils.M4Table#getM4TableName()
	 */
	public String getM4TableName()
	{	return M4_TABLE_NAME;   }
	
	public Collection getDependentObjects()
	{   return new Vector();   }
		
		
	/** @see XmlInfo#getXmlVersion */
	public String getXmlVersion() {
		return XmlInfo.M4_XML_VERSION;	
	}

	/** @see XmlInfo#getXmlIdTag */
	public String getXmlIdTag() {
		return XmlInfo.TAG_XML_ID;
	}

	/** @see XmlInfo#getXmlIdTag */
	public String getObjectTag() {
		return TAG_MAIN;
	}
	
	/** Cache for getXmlInfo() */
	private static M4Info xmlInfo = null;
	/** Cache for getM4Info() */
	public static M4Info m4Info = null;

	/** @see M4Table.getIdAttributeName() */
	public String getIdAttributeName() {
		return ATTRIB_CONTEXT_ID;
	}
	
	/** @see M4Data.getM4Info() */
	public M4Info getM4Info() {
		if (m4Info == null) {
			M4InfoEntry[] m4i = {
				// we use the old field context_id to store a unique M4 id for coordinates!
				new M4InfoEntry(ATTRIB_CONTEXT_ID,  "getId",         "setId",         long.class, NOT_NULL),
				new M4InfoEntry(ATTRIB_OBJECT_ID,   "getMyM4Object", "primitiveSetMyM4Object", GraphicalM4Object.class, NOT_NULL),
				new M4InfoEntry(ATTRIB_OBJECT_NAME, "getName",       "setName",       String.class),
				new M4InfoEntry(ATTRIB_OBJECT_TYPE, "getType",       "setType",       String.class),
				new M4InfoEntry(ATTRIB_X_COORD,     "getX",          "setX",          int.class),
				new M4InfoEntry(ATTRIB_Y_COORD,     "getY",          "setY",          int.class)
			};
			m4Info = new M4Info(m4i);
		}
		return m4Info;
	}			

	/** @see XmlInfo.getXmlInfo() */
	public M4Info getXmlInfo() {
		if (xmlInfo == null) {
			M4InfoEntry[] m4i = {
				new M4InfoEntry("M4Object",      "getMyM4Object",    "setMyM4Object",    GraphicalM4Object.class),
				new M4InfoEntry("Name",          "getName",          "setName",          String.class),
				new M4InfoEntry("X_Coord",       "getX",             "setX",             int.class),
				new M4InfoEntry("Y_Coord",       "getY",             "setY",             int.class)
			};
			xmlInfo = new M4Info(m4i);
		}
		return xmlInfo;
	}	
	
	public Coordinates(DB db) { super(db); }
	
	/* temporary HACK?
	public long getId() {
		long currentId = super.getId();
		if (currentId == 0) {
			GraphicalM4Object myM4 = this.getMyM4Object();
			if (myM4 != null) {
				return myM4.getId();
			}
		}
		return super.getId();
	}
	*/
	
	/**
	 * Returns the Object that this Coordinates belongs to.
	 * 
	 * @return GraphicalM4Object
	 */
	public GraphicalM4Object getMyM4Object() {   
		return theObject;   
	}
	
	public String getName() {
		if (this.getMyM4Object() != null) {
			return this.getMyM4Object().getName();
		}
		return null;
	}
	
	public void setName(String name) {
		// do nothing! The name is that of the M4 object which is set anyway.
	}
	
	public void setType(String type) {
		// do nothing! The type is that of the M4 object which is set anyway.
	}
	
	public String getType() throws M4Exception {
		if (this.getMyM4Object() != null) {
			return this.checkObjectType(this.getMyM4Object());
		}
		return null;
	}
	
	public Collection getObjectsInNamespace(Class namespace) {
		return new Vector();
	}
	
	/**
	 * Set the object that this Coordinates belongs to.
	 * 
	 * @param theM4Object the object
	 */
	public void setMyM4Object(GraphicalM4Object theM4Object) throws M4Exception { 
	 	edu.udo.cs.miningmart.m4.core.GraphicalM4Object.graph2coord.setReciprocalReferences(
	 			(edu.udo.cs.miningmart.m4.core.GraphicalM4Object) theM4Object, 
				this);
	}
	
	/**
	 * Do not use.
	 * 
	 * @param m4Object
	 */
	public void primitiveSetMyM4Object(GraphicalM4Object m4Object) {
		this.setDirty();
		this.theObject = m4Object;
	}
	
	/**
	 * Returns the Location.
	 * @return Point
	 */
	public Point getLocation() throws M4Exception {
		if (this.theLocation == null) {
			this.readFromDb();
			if (this.theLocation == null) { // if nothing in DB for this object:
				this.theLocation = new Point();
				this.theLocation.setLocation(1,1); // set some default location 
			}
		}
		return theLocation;
	}
	
	/**
	 * Sets the Location.
	 * @param theLocation The Location to set
	 */
	public void setLocation(Point theLocation) {
		this.setDirty();
		this.theLocation = theLocation;  
	}
	
	/**
	 * Set the x coordinate.
	 * 
	 * @param x_coord the x coordinate
	 */
	public void setX(int x_coord)
	{   if (this.theLocation == null)
		{   this.theLocation = new Point();  }
		this.theLocation.setLocation(x_coord, this.theLocation.y);
	}
	
	/**
	 * Set the y coordinate.
	 * 
	 * @param y_coord the y coordinate
	 */
	public void setY(int y_coord)
	{   if (this.theLocation == null)
		{   this.theLocation = new Point();  }
		this.theLocation.setLocation(this.theLocation.x, y_coord);
	}
	
	/**
	 * Get the X coordinate.
	 * 
	 * @return int the x coordinate
	 */
	public int getX()
	{   if (this.theLocation == null)
		{   return -1;  }
		else return this.theLocation.x;
	}
	
	/**
	 * Get the Y coordinate.
	 * 
	 * @return int the y coordinate
	 */
	public int getY()
	{   if (this.theLocation == null)
		{   return -1;  }
		else return this.theLocation.y;
	}

	// Methods from interface XmlInfo:

	public Collection exportLocal(Writer out, Collection dependent)
	{   return null;   }

	public void importLocal(String tag, String embedded) {}
	
	public void doPrint(Level verbosity, String message) {
		Print p = this.getPrintObject();
		if (p != null) {
			p.doPrint(verbosity, message);
		}
		else if (verbosity == Print.MAX) {
			System.err.println("Coordinates: " + message);
		}
	}
	
	public void doPrint(Exception ex)
	{   this.doPrint(Print.MAX, ex.getMessage());   }

	private Print getPrintObject() {   
		if (this.getM4Db() != null) {
			return this.getM4Db().getCasePrintObject();
		}
		GraphicalM4Object gm4o = this.getMyM4Object();
		if (gm4o != null) {
			return gm4o.getCasePrintObject();
		}
		return null;
	}

	/*
	public Object genericGetter(String nameOfGetter) throws M4Exception
	{
		if (nameOfGetter.equals("getMyM4Object")) {
			return this.getMyM4Object();
		}
		else if (nameOfGetter.equals("getName")) {
			return this.getName();
		}
		else if (nameOfGetter.equals("getX")) {
			return new Integer(this.getX());
		}
		else if (nameOfGetter.equals("getY")) {
			return new Integer(this.getY());
		}
		else throw new M4Exception("Unknown getter: " + "Coordinates." + nameOfGetter);
	}
	
	public void genericSetter(String nameOfSetter, Class parameterClassOfSetter, Object objectToSet)
		throws M4Exception
	{
		if (nameOfSetter.equals("setMyM4Object")) {
			this.setMyM4Object((GraphicalM4Object) objectToSet);
		}
		else if (nameOfSetter.equals("setName")) {
			this.setName((String) objectToSet);
		}
		else if (nameOfSetter.equals("setX")) {
			this.setX(((Integer) objectToSet).intValue());
		}
		else if (nameOfSetter.equals("setY")) {
			this.setY(((Integer) objectToSet).intValue());
		}
		else throw new M4Exception("Unknown setter: " + "Coordinates." + nameOfSetter);
	}
	*/

	/** Helper method of delete() and store(). */
	public void delete() throws M4Exception {
		String query = "DELETE FROM " + M4_TABLE_NAME + " WHERE ";
		if (this.getMyM4Object() == null) { // can happen for temporary objects
			query += this.getIdAttributeName() + " = " + this.getId();
		}
		else {
			query += ATTRIB_OBJECT_ID + " = " + this.getMyM4Object().getId();
		}
		  
		try {
			this.getM4Db().executeM4SqlWrite(query);
		}
		catch (SQLException sqle)
		{   throw new M4Exception(this.createErrorMsgPrefix() + sqle.getMessage());   }	
		catch (DbConnectionClosed d)
		{   throw new M4Exception(this.createErrorMsgPrefix() + d.getMessage());   }	
	}	
	
	/** Helper method of delete() and store(). */
	private String createErrorMsgPrefix() {
		return ("SQL Error when deleting coordinates of M4 object (Id: "
				+ this.getMyM4Object().getId() + ", Name: " + this.getMyM4Object().getName() + "): ");
	}
	
	/**
	 * This method writes back the information that is present to
	 * the database table, regardless of whether the local dirty-Flag is set
	 * or not.
	 */
	public void store() throws M4Exception {
		 // delete all HCI information regarding this tuple first:
		this.delete();
		
		// if no graphical object is available, that was it:
		if (this.getMyM4Object() == null) {
			return;
		}
		
		// if no M4 id is set yet, that was it:
		if (this.getId() == 0) {
			return;
		}
				
		String query =
			"INSERT INTO " + M4_TABLE_NAME + " (" +
				ATTRIB_OBJECT_ID + ", " +
				ATTRIB_OBJECT_NAME + ", " +
				ATTRIB_OBJECT_TYPE + ", " +
				ATTRIB_CONTEXT_ID + ", " +
				ATTRIB_X_COORD + ", " +
				ATTRIB_Y_COORD + ") VALUES (" +
				this.getMyM4Object().getId() + ", '" +
			    this.getMyM4Object().getName() + "', '" +
				this.checkObjectType(this.getMyM4Object()) + "', " +
				this.getId() + ", " +
				this.getLocation().x + ", " + 
				this.getLocation().y + ")";

		try {				
			this.getM4Db().executeM4SqlWrite(query);
		}
		catch (SQLException sqle) {
			throw new M4Exception(this.createErrorMsgPrefix() + sqle.getMessage());
		}
		catch (DbConnectionClosed d) {
			throw new M4Exception(this.createErrorMsgPrefix() + d.getMessage());
		}	
	}

	// check if the given object is allowed as an object in the M4 table
	private String checkObjectType(M4Object theObject) throws M4Exception
	{
		if (theObject == null)
		{   throw new M4Exception("Error when storing/retrieving coordinates of an M4 object" + 
			                          ": got NULL object for primary key!");   
        }
        
        Class type = theObject.getClass();
		// check if we get an "Impl-Object":
		// while (edu.udo.cs.miningmart.m4.M4Object.class.isAssignableFrom(type))
		// {   type = type.getSuperclass();   }
		
        // check theObject's type and return its DB String:
		for (int i = 0; i < objectTypes.length; i++)
		{
			if (objectTypes[i].isAssignableFrom(type))
			{	return objectDB_Names[i]; 	}
		}
		
		// if the type was not foreseen, throw exception:
		throw new M4Exception("Error when storing/retrieving coordinates of M4 object (Id: " +
		                      this.getMyM4Object().getId() + ", Name: " + this.getMyM4Object().getName() + "): wrong object type!: " + type);
	}
	
	protected void removeAllM4References() throws M4Exception {
		this.setMyM4Object(null);
	}
	
	/** @see M4Object#print */
	public void print() {}

	/** 
	 * Unfortunately we must overwrite the superclass method
	 * because we want to be compatible with old coordinates data!
	 * Once all coordinates of all objects in all cases have been stored
	 * with the new version, we hope to be able to remove this method so that the
	 * generic mechanism applies.
	 * 
	 * @see M4Object#readFromDb  
	 */ 
	public void readFromDb() throws M4Exception {
		
		String attributes = ATTRIB_OBJECT_NAME + ", "
		                  + ATTRIB_CONTEXT_ID  + ", "
						  + ATTRIB_Y_COORD     + ", "
						  + ATTRIB_X_COORD ;

		String query = "SELECT " + attributes
		             + " FROM "  + this.getM4TableName();
		
		if (this.getMyM4Object() != null) {
			query += " WHERE " + ATTRIB_OBJECT_ID
		             + " = "     + this.getMyM4Object().getId();
		}
		else {
			if (this.getId() == 0) {
				// this should not happen, it means that no coordinates
				// object exists and no M4 object either
				throw new M4Exception("Coordinates.readFromDb(): cannot load coordinates because the object whose coordinates are to be loaded is not set!");
			}
			query += " WHERE " + this.getIdAttributeName() + " = " + this.getId();
		}
		
		String error = "SQL Error when retrieving coordinates of M4 object (Id: " +
			           this.getMyM4Object().getId() + ", Name: " + this.getMyM4Object().getName() + "): ";
			    
	    ResultSet rs = null; 
		try {
			rs = this.getM4Db().executeM4SqlRead(query);
			if (rs.next()) {
				// safety check for debugging:
			    String objectName = rs.getString(ATTRIB_OBJECT_NAME);
			    if ( ! objectName.equals(this.getMyM4Object().getName())) {
			    	throw new M4Exception("Error loading coordinates for M4 object '" +
			    			this.getMyM4Object().getId()+ ", Name: " + this.getMyM4Object().getName() + 
							"): The name of this object in " + M4_TABLE_NAME + " is different, though M4-Id matches!"); 
			    }
			    this.myName = objectName;
			    
			    long myId = rs.getLong(ATTRIB_CONTEXT_ID);
			    // correct old data:
			    if (myId == 1 || (this.getMyM4Object() != null && myId == this.getMyM4Object().getId())) {
			    	myId = this.getM4Db().getNextM4SequenceValue();
			    }
			    this.setId(myId);
			    
			    int xCoord = rs.getInt(ATTRIB_X_COORD);
			    int yCoord = rs.getInt(ATTRIB_Y_COORD);

			    Point p = new Point(xCoord, yCoord);
			    this.setLocation(p);				    
			}
			rs.close();
		}
		catch (SQLException sqle)
		{   throw new M4Exception(error + sqle.getMessage());   }	
		catch (DbConnectionClosed d)
		{   throw new M4Exception(error + d.getMessage());   }			
		finally {
			DB.closeResultSet(rs);
		}
	}
}
/*
 * Historie
 * --------
 * 
 * $Log: Coordinates.java,v $
 * Revision 1.6  2006/04/11 14:10:14  euler
 * Updated license text.
 *
 * Revision 1.5  2006/04/06 16:31:13  euler
 * Prepended license remark.
 *
 * Revision 1.4  2006/01/23 10:13:00  euler
 * Bugfix
 *
 * Revision 1.3  2006/01/20 15:06:21  euler
 * Bugfix
 *
 * Revision 1.2  2006/01/12 13:20:10  euler
 * Bugfix
 *
 * Revision 1.1  2006/01/12 11:33:11  euler
 * Changed the way coordinates are stored completely.
 *
 * Revision 1.1  2006/01/03 09:54:05  hakenjos
 * Initial version!
 *
 */
