/***************************************************************************
    Copyright          : (C) 2002 by Neoworks Limited. All rights reserved
    URL                : http://www.neoworks.com
 ***************************************************************************/
/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

/*
 * JukeXTrack.java
 *
 * Created on 04 April 2002, 16:50
 */

package com.neoworks.jukex.sqlimpl;

import com.neoworks.jukex.*;

import com.neoworks.util.MultiMap;
import com.neoworks.util.HashMapMultiMap;
import com.neoworks.util.StringDecorator;
import com.neoworks.connectionpool.PoolManager;

import org.apache.log4j.Category;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Iterator;
import java.net.URL;
import java.sql.*;

/**
 * This object represents a Track within the JukeX system. 
 *
 * @author Nick Vincent <a href="mailto:nick@neoworks.com">nick@neoworks.com</a>
 */
public class JukeXTrack implements Track, DatabaseObject
{
	private static final Category log = Category.getInstance(JukeXTrack.class.getName());
	private static final boolean logDebugEnabled = log.isDebugEnabled();
	private static final boolean logInfoEnabled = log.isInfoEnabled();
	
	private long _id = -1;
	MultiMap _attributes = null;
	private URL _url = null;
	private java.util.Date _updated = null;
	
	private static PoolManager _poolmanager = null;
	
	/** Creates a new instance of JukeXTrack */
	public JukeXTrack(long id, URL url, java.util.Date updated)
	{
		_id = id;
		_url = url;
		_updated = updated;
		if ( _poolmanager == null )
		{
			_poolmanager = PoolManager.getInstance();
		}
	}
	
	/**
	 * Package private for BatchLoader access
	 */
	void setAttributes(MultiMap m)
	{
		_attributes = m;
	}
	
	public Collection getAttributeValues(Attribute attribute)
	{
		if ( _attributes == null ) readAttributesFromDB();
		return (Collection) _attributes.get( attribute );
	}
	
	public void replaceAttributeValues(Attribute attribute, AttributeValue value)
	{
		if ( _attributes == null ) readAttributesFromDB();
		clearAttribute(attribute);					// Remove all existing values
		addAttributeValue( attribute , value );		// Add the new value
	}
	
	public Collection getKnownAttributes()
	{
		if ( _attributes == null ) readAttributesFromDB();
		return _attributes.keys();
	}
	
	public void addAttributeValue(Attribute attribute, AttributeValue value)
	{
		if ( _attributes == null ) readAttributesFromDB();
		Connection conn = null;
		
		try
		{
			conn = _poolmanager.getConnection( JukeXTrackStore.DB_NAME );
			PreparedStatement addEnumeration = conn.prepareStatement( "INSERT INTO AttributeValue (trackid,attributeid,attributeenumid,numericvalue) VALUES (?,?,?,?)" );
			addEnumeration.setLong( 1 , this._id );
			addEnumeration.setLong( 2 , ((DatabaseObject)attribute).getId() );
			
			if ( attribute.getType() == Attribute.TYPE_STRING )
			{
				// Add a reference to the enumeration table
				addEnumeration.setLong( 3 , ((DatabaseObject)value).getId() );
				addEnumeration.setNull( 4 , java.sql.Types.NULL );
			}
			else if ( attribute.getType() == Attribute.TYPE_INT )
			{
				addEnumeration.setNull( 3 , java.sql.Types.NULL );
				addEnumeration.setInt( 4 , value.getInt() );
			}
			else
			{
				log.error( "JukeXTrack encountered an attribute with an unknown type ["+attribute.getType()+"]" );
			}
		
			addEnumeration.executeUpdate();
			_attributes.put( attribute , value );
			addEnumeration.close();
		}
		catch ( Exception e )
		{
			log.error( "JukeXTrack encountered an exception whilst attempting to add an AttributeValue" , e );
		}
		finally
		{
			try { conn.close(); } catch ( SQLException ignore ) {}
		}
	}
	
	public void clearAttribute(Attribute attribute)
	{
		if ( _attributes == null ) readAttributesFromDB();
		Connection conn = null;
		
		try
		{
			conn = _poolmanager.getConnection( JukeXTrackStore.DB_NAME );
			PreparedStatement ps = conn.prepareStatement( "DELETE FROM AttributeValue WHERE trackid = ? AND attributeid = ?" );
			ps.setLong( 1 , _id );
			ps.setLong( 2 , ((DatabaseObject)attribute).getId() );
			ps.executeUpdate();
			ps.close();
		}
		catch ( Exception e ) 
		{
			log.error("Exception encountered attempting to clear attribute values",e);
		}
		finally 
		{
			try { conn.close(); } catch ( Exception ignore ) {}
		}
		
		_attributes.remove( attribute );
	}
	
	public URL getURL()
	{
		return _url;
	}
	
	private void readAttributesFromDB()
	{
		_attributes = new HashMapMultiMap();
		Connection conn = null;

		TrackStore trackStore = TrackStoreFactory.getTrackStore();

		try
		{
			conn = _poolmanager.getConnection( JukeXTrackStore.DB_NAME );
			
			String sql = "SELECT Attribute.name, Attribute.type, AttributeValue.numericvalue, AttributeEnum.value FROM Track, Attribute, AttributeValue LEFT JOIN AttributeEnum ON AttributeValue.attributeenumid = AttributeEnum.id WHERE Track.id = AttributeValue.trackid AND AttributeValue.attributeid = Attribute.id AND Track.id = ?";
	
			PreparedStatement ps = conn.prepareStatement( sql );
			ps.setLong( 1 , _id );
			
			ResultSet rs = ps.executeQuery();
			
			String currAttributeName = null;
			int currAttributeType = -1;
			int currAttributeNumericValue = -1;
			String currAttributeStringValue = null;
			Attribute currAttribute = null;
			AttributeValue currAttributeValue = null;
			while (rs.next())
			{
				currAttributeName = rs.getString( 1 );
				currAttributeType = rs.getInt( 2 );
				currAttributeNumericValue = rs.getInt( 3 );
				currAttributeStringValue = rs.getString( 4 );
				
				//if (logDebugEnabled) log.debug ("Name: "+ currAttributeName+"  Type: "+currAttributeType+"  StringVal: "+currAttributeStringValue+"  NumVal: "+currAttributeNumericValue);
				
				currAttribute = trackStore.getAttribute( currAttributeName );
				if ( currAttributeType == Attribute.TYPE_STRING ) currAttributeValue = new JukeXAttributeValue( currAttribute , currAttributeStringValue );
				if ( currAttributeType == Attribute.TYPE_INT ) currAttributeValue = new JukeXAttributeValue( currAttribute , currAttributeNumericValue );
				
				_attributes.put( currAttribute , currAttributeValue );
			}
			ps.close();
		}
		catch (Exception e)
		{
			log.error( "Encountered an exception whilst reading attributes from the database" , e );
		}
		finally
		{
			try { conn.close(); } catch (SQLException ignore) {}
		}
	}
	
	public String toString()
	{
		List strings = new LinkedList();
			
		TrackStore ts = TrackStoreFactory.getTrackStore();
		Set attrSet = ts.getAttributes();
				
		Iterator attrs = attrSet.iterator();
		while ( attrs.hasNext() )
		{
			Attribute a = (Attribute) attrs.next();
			Collection c = this.getAttributeValues( a );
			
			if ( c != null )
			{	
				Iterator vals = c.iterator();
				while ( vals.hasNext() )
				{
					AttributeValue val = (AttributeValue) vals.next();
					strings.add( a.getName() + ": " + val.toString() );
				}
			}
		}
		
		return StringDecorator.boxAroundStrings( ( (String[]) strings.toArray( new String[0] ) ) , _url.toString() );
	}
	
	public java.util.Date getUpdatedDate()
	{
		return _updated;
	}
	
	public void setUpdatedDate( java.util.Date newdate )
	{
		log.error( "Updating track "+_id+" date to: " + newdate + " ["+newdate.getTime()+"]" );
		Connection conn = null;
		try
		{
			conn = _poolmanager.getConnection( JukeXTrackStore.DB_NAME );
			PreparedStatement ps = conn.prepareStatement( "UPDATE Track SET updated = ? WHERE id = ?" );
			ps.setLong( 1 , newdate.getTime() );
			ps.setLong( 2 , this._id );
			
			ps.executeUpdate();
			_updated = newdate;
			ps.close();
		}
		catch ( SQLException se )
		{
			log.error( "Exception whilst changing modified date on track with id="+this._id , se );
		}
		finally
		{
			try { conn.close(); } catch ( SQLException ignore ) {}
		}
	}
	
	public long getId()
	{
		return _id;
	}
	
	/**
	 * Return the first AttributeValue object for a specified Attribute
	 *
	 * @param attribute The Attribute to get the value of
	 * @return The AttributeValue corresponding to the specified Attribute
	 */
	public AttributeValue getAttributeValue(Attribute attribute)
	{
		if ( _attributes == null ) readAttributesFromDB();
		Collection vals = this._attributes.get( attribute );
		if (vals != null)
		{
			Iterator i = vals.iterator();
			if (i.hasNext())
			{
				return (AttributeValue) i.next();
			}
		}
		log.warn("No values for attribute "+attribute.getName());
		return null;
	}

	/**
	 * Get an AttributeValue on this Track by name
	 *
	 * @param attributename The name of the Attribute to query for
	 * @return The AttributeValue corresponding to the named attribute
	 */
	public AttributeValue getAttributeValue(String attributename)
	{
		Attribute a = TrackStoreFactory.getTrackStore().getAttribute( attributename );
		if (a != null)
		{
			return getAttributeValue( a );
		}
		log.warn("Cannot find attribute name " + attributename);
		return null;
	}
	
}
