package Darts;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
import java.util.Enumeration;

public class DartDBNode extends DefaultMutableTreeNode 
                        implements Serializable {

	/** 
	 * 
	 */
	private static final long serialVersionUID = 1L;
	//
	// Constants
	//
	public static final int ROOT_NODE 		= 1; // TODO Make this an enum
	public static final int TOURNAMENT_NODE = 2;
	public static final int GAME_NODE 		= 3;
	public static final int PLAYER_NODE 	= 4;
	public static final int SCORE_NODE 		= 5;
	public static final int BAD_NODE 		= 6;
	
	//
	// Member Data
	//
	int 	nodeType;				// For all types.
	String	name;					// For all types.
	
	public int		dartValues[] = null;	// For type=SCORE: Where the 3 darts landed 
	public int		dartMultiplers[] = null;// For type=SCORE: = 1, 2 or 3

	private transient static boolean dirty = false;  // Database has changed since last save.
	
	//
	// Methods
	//
	
	/**
	 * Constructor
	 * @throws Exception 
	 */
	public DartDBNode( DartDBNode newParent ) {
		
		int  childCount = 0;
		
		if( newParent == null )
			nodeType = ROOT_NODE;
		else {
			nodeType = newParent.getNodeType() + 1;
			childCount = newParent.getChildCount();
		}
			
		switch (nodeType) {
		case ROOT_NODE:
			name = "Tournaments";
			break;
		case TOURNAMENT_NODE:
			int tournamentNum = childCount + 1;
			name = "Tournament #" + tournamentNum;
			break;
		case GAME_NODE:
			name = "Game on " + new Date().toString();
			break;
		case PLAYER_NODE:
			name = "Player<" + (childCount + 1) + ">";
			break;
		case SCORE_NODE:
			name = "not played";
			dartValues = new int[3];
			dartMultiplers = new int[3];
			for (int i = 0; i < 3; i++) {
				dartValues[i] = 0;
				dartMultiplers[i] = 0;
			}
			break;
		default:
			// throw new Exception( "Bad dart node type passed to constructor."
			// );
			name = "Error: Bad node type.";
		}
		// Setup a pointer to itself (is this needed?)
		super.setUserObject( this );
		// Add this new node as the parent's last child.
		if( newParent != null )
			newParent.add( this );
		
		// Mark whole tree as changed.
		dirty = true;
	}
	
	
	/**
	 * whosTurnIsIt()
	 *   Find the next player of a game. This does not change the DB.
	 *   Return null if not a game node, only 1 player or game is played out.
	 *     Players    Rounds 
	 *         2        4
	 *         3        6
	 *         4        4
	 *         5 and up 5
	 *         
	 * @return A "Player" node or null if game is over
	 */
	public DartDBNode whosTurnIsIt() {
		if( nodeType != GAME_NODE )
			return null; // It's note a game node.
		
		// Calculate the number of rounds we are going to go.
		int	  round = this.getChildCount();
		if( round < 2 )
			return null; // One player can't play.
		round = round==2 ? 4 : ( round==3 ? 6 : (round>5 ? 5 : round) );
		
		DartDBNode nextPlayer = null;
		
		// Find the first player with the least played rounds.
		for( Enumeration<DartDBNode> e1 = this.children() ; e1.hasMoreElements() ;) {
			DartDBNode player = e1.nextElement();
			if( player.getChildCount() < round ) {
				nextPlayer = player;
				round = player.getChildCount();
			}
		}
		return nextPlayer;
	}
	
	/**
	 * scoreToStealFrom()
	 *  Given the current player, this returns the scores they can 
	 *  steal.  This returns null if none (ie, first round).
	 * @return Score that can be stolen by the next player (or null)
	 */
	public DartDBNode scoreToStealFrom() {
		if( nodeType != PLAYER_NODE  ||  isLeaf() )
			return null;
		try {
			int  numOfPlayers = parent.getChildCount();
			int  myPlayerNum = parent.getIndex( this ); // row
			int  myTurn      = getChildCount();         // column
			
			return (DartDBNode) parent.getChildAt( (myPlayerNum-myTurn+numOfPlayers) % numOfPlayers )
									  .getChildAt( myTurn-1 );
		}
		catch( Exception e ) {
			return null;
		}
	}
	
	/**
	 * stealeeScore() :)
	 *   Given a score, this returns the score it can steal from.
	 *     stealee.turn = myTurn - 1
	 *     stealee.playerNum = (myPlayerNum - myTurn) % numOfPlayers
	 * @return The score "this" score can steal from or null.
	 */
	public DartDBNode stealeeScore() {
		MutableTreeNode  player = parent;
		if( nodeType != SCORE_NODE  ||  parent.getIndex(this) == 0 )
			return null;
		try {
			MutableTreeNode  game = (MutableTreeNode) player.getParent();
			int  numOfPlayers = game.getChildCount();
			int  myPlayerNum = game.getIndex( player ); // row
			int  myTurn      = player.getIndex( this ); // column
			
			return (DartDBNode) game.getChildAt( (myPlayerNum-myTurn+numOfPlayers) % numOfPlayers )
			                        .getChildAt( myTurn-1 );
		}
		catch( Exception e ) {
			return null;
		}
	}
	
	
	/**
	 * stealerScore()  :(
	 *     stealer.turn = myTurn + 1
	 *     stealer.playerNum = (myPlayerNum + myTurn + 1) % numOfPlayers
	 * @return The score that can steal from "this" score or null.
	 */
	public DartDBNode stealerScore() {
		if( nodeType != SCORE_NODE )
			return null;
		try {
			MutableTreeNode  player = parent;
			MutableTreeNode  game = (MutableTreeNode) player.getParent();
			int  numOfPlayers = game.getChildCount();
			int  myPlayerNum = game.getIndex( player ); // row
			int  myTurn      = player.getIndex( this ); // column
			
			return (DartDBNode) game.getChildAt( (myPlayerNum+myTurn+1) % numOfPlayers ).getChildAt( myTurn+1 );
		}
		catch( Exception e ) {
			return null;
		}
	}
	
	
	@Override
	public void remove( MutableTreeNode aChild ) {
		super.remove( aChild );
		dirty = true;
	}

	/**
	 * toString()
	 * This is called by JTree for node names.
	 */
	@Override
	public String toString() {
		if( nodeType != SCORE_NODE )
			return name; // <---------------- RETURN

		String str = "";
		for( int i=0; i<dartMultiplers.length; i++ ) {
			if( dartMultiplers[i] > 0 ) {
				if( str.length() > 0 )
					str += ", ";
				if( dartMultiplers[i] == 1 )
					str += dartValues[i];
				else
					str += dartValues[i] + "x" + dartMultiplers[i];
			}
		}
		return "Threw: " + str;
	}
	
	
	public void setName( String newName ) {
		if( nodeType != SCORE_NODE )
			name = newName;
		else {
			// Parse score
			//  Example:  18, 20x3, 3
			if( newName.startsWith("Threw:") )
				newName = newName.substring(6); // Remove "Threw:"
			String scores[] = newName.split( "," );
			int i = 0;
			for( String score : scores ) {
				String vals[] = score.split( "x" );
				if( vals.length == 1  ||  vals.length == 2 ) {
					dartValues[i] = parsePossitiveInt( vals[0], 0 );
					if( vals.length == 2 )
						dartMultiplers[i] = parsePossitiveInt( vals[1], 1 );
					else
						dartMultiplers[i] = 1;
					i++;
				}
			}
		}
		
		// Mark whole tree as changed.
		dirty = true;
	}
  public DisplayScore calculateScore() {
		assert( nodeType != SCORE_NODE );
		DisplayScore displayScore = new DisplayScore();
		displayScore.name = parent.toString();
		
		DartDBNode  stealee = stealeeScore(); // I can steal from
		DartDBNode  stealer = stealerScore(); // can steal from me
		
		displayScore.gained = 0;
		displayScore.lost = 0;
		displayScore.finalScore = 0;
		for( int i = 0; i < 3; i++ )
			{
			displayScore.value[i] = dartValues[i]; // can this be done with a ".copy"
			displayScore.thrown[i] = dartMultiplers[i];

			int dartValue = dartValues[i];

			// Did we steal this dart from someone else?
			if( stealee == null )
				displayScore.stolen[i] = 0;
			else
				{
				// steal the dart
				displayScore.stolen[i] = dartValue==0 ? 0 : stealee.stolenDartValueCount( dartValue );

				// But wait!! Maybe we have already stolen it.
				for( int j = 0; j < i; j++ )
					if( dartValues[j] == dartValue )
						displayScore.stolen[i] = 0;
				
				if( displayScore.stolen[i] > 0 )
					displayScore.gained += displayScore.stolen[i] * dartValue;
				}

			displayScore.dartScore[i] = displayScore.value[i]
					* (displayScore.thrown[i] + displayScore.stolen[i]);
			
			// Check if this dart was stolen
			displayScore.gone[i] = dartValue==0 ? false : ( stealer != null && stealer.dartValueCount( dartValue ) > 0 );
			if( displayScore.gone[i] )
				displayScore.lost += displayScore.dartScore[i];
			else
				displayScore.finalScore += displayScore.dartScore[i];
			}
		return displayScore;
	}

	/**
	 * dartValueCount():
	 * @param value - Dart value to look for
	 * @return The number of non-stolen times that value was seen
	 */
	public int dartValueCount( int value )
		{
		int numOfValues = 0;
		assert( nodeType != SCORE_NODE );

		for( int i = 0; i < 3; i++ )
			if( dartValues[i] == value )
				numOfValues += dartMultiplers[i];
		return numOfValues;
		}

	/**
	 * stolenDartValueCount():
	 * @param value - Dart value to look for
	 * @return The number of times that value was seen included stolen ones.
	 */
	public int stolenDartValueCount( int value )
		{
		DartDBNode  stealee = this;
		int numStolen = 0;
		assert( nodeType != SCORE_NODE );

		while( stealee != null  &&  stealee.dartValueCount(value) > 0)
			{
			numStolen += stealee.dartValueCount( value );
			stealee = stealee.stealeeScore();
			}
		return numStolen;
		}

	/**
	 * parsePossitiveInt
	 *  Parse a string looking for a number.
	 * @param s The string to parse
	 * @param errorVal The value to return if there is a problem parsing the number.
	 * @return Possitive int or errorVal if problem.
	 */
	public static int parsePossitiveInt( String  s, int errorVal ) {
		int  value = 0;
		try {
			value = Integer.parseInt( s.trim() );
			if( value < 0 )
				value = errorVal;
		}
		catch( Exception e ) {
			value = errorVal;
		}
		return value;
	}
	
	public boolean saveToFile( String  filename ) {
		if( nodeType != ROOT_NODE  ||  !isRoot() )
			return false;
		try {
			FileOutputStream fos = new FileOutputStream( filename );
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			
			oos.writeObject( this );
			
			oos.close();
		}
		catch( Exception e ) {
			return false;
		}
		
		// Mark whole tree as clean (no changes since last save).
		dirty = false;
		return true;
	}
	
	public static DartDBNode loadFromFile( String filename ) {
		
		DartDBNode rootNode = null;
		try {
			FileInputStream fis = new FileInputStream( filename );
			ObjectInputStream ois = new ObjectInputStream( fis );
			
			rootNode = (DartDBNode) ois.readObject();
			
			ois.close();
		}
		catch( Exception e ) {
			return null;
		}
		
		// Mark whole tree as clean (no changes since last save).
		dirty = false;
		return rootNode;
	}
	
	// Get and Set methods
	
	public int getNodeType() {
		return nodeType;
	}
	
	static public boolean getDirty() {
		return dirty;
	}

	@SuppressWarnings("unchecked")
  @Override
  public Enumeration<DartDBNode> children() {
	  return super.children();
	}

}
