/* $Id$
 *
 * Copyright (c) 1999 Xenonsoft Limited. All Rights Reserved.
 *
 * This software is protected by copyright. You are hereby notified from
 * now by reading this message. This software is also the confidential 
 * and proprietary information of Xenonsoft Limited.  ("Confidential
 * Information"). 
 *
 * This software is distributed under the Xenonsoft Public end user 
 * License ("XPeL"), where the machine-readable source code is provided 
 * under the "Open Source" model.
 * For more information, please read the file "LICENSE-XPL.txt" 
 */

// File:`HighScore.java'
//
//
// Description:
//	High score 
// 
// Author
//
//	Peter Pilgrim
//	Mon Jul 05 22:35:04 BST 1999
// 
// RCS HEADER ``HighScore.java''
// 
//	$Author$
//	$Date$
//	$Revision$    $State$    $Locker$
//

package xenon.gamekit;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Date;
import java.util.Vector;
import java.util.StringTokenizer;

/**
 * An objects that implements and maintains a highscore list
 * 
 * @author Peter Pilgrim, Mon Jul 05 21:18:10 BST 1999
 * @version $Id$
 */
public class HighScore implements Serializable
{
    public final static	int	DEFAULT_MINIMUM_POINTS=10000;
    public final static	int	DEFAULT_SIZE=10;

    private ScoreTuple []	scores;

    /** 
     * Creates a high score list of 10 
     * with a default minimum score limit of 10000 points
     */
    public HighScore()
    {
	this( DEFAULT_SIZE );
    }
    
    /** 
     * Creates a high score list of N arbitary size 
     * with a default minimum score limit of 10000 points
     * with an default player name to enter into the list.
     *
     * @param size the size 
     */
    public HighScore( int size )
    {
	this(  size, DEFAULT_MINIMUM_POINTS );
    }
    
    /** Creates a high score list of N arbitary size 
     * with a minimum points score limit.
     * with an default player name to enter into the list.
     *
     * @param size the size 
     * @param minpts the minimum points
     */
    public HighScore( int size, int minpts )
    {
	this( "XeNoNSoFT!", size, minpts );
    }
    
    /** 
     * Creates a high score list of N arbitary size 
     * with a default minimum score limit of 10000 points
     * with an initial player name to enter into the list.
     *
     * @param player the initial player name
     * @param size the size 
     */
    public HighScore( String player, int size )
    {
	this( player, size, DEFAULT_MINIMUM_POINTS );
    }
    
    /** Creates a high score list of N arbitary size 
     * with a minimum points score limit.
     * with an initial player name to enter into the list.
     *
     * @param player the initial player name
     * @param size the size 
     * @param minpts the minimum points
     */
    public HighScore( String player, int size, int minpts )
    {
	scores = new ScoreTuple[size];

	for ( int k=0; k<size; ++k ) {
	    scores[k] = new ScoreTuple( player, minpts, 0 );
	}
    }
    
    /** Gets the size of the high score list */
    public synchronized int getItemCount()
    {
	return scores.length;
    }
    
    /** Gets the score for Nth index 
     * @param index the index
     */
    public synchronized ScoreTuple getItemAt( int index )
    {
	return (ScoreTuple)scores[index].clone();
    }
    
    /** 
     * Can the score tuple beat anything on the high score list
     *
     * @param score the score 
     *
     * @return true if the score is truly high or not
     */
    public boolean isHighScore( int score )
    {
	return isHighScoreTuple( new ScoreTuple( null, score, 0 ));
    }
    
    /** 
     * Can the score tuple beat anything on the high score list
     *
     * @param tuple the score tuple
     *
     * @return true if the score is truly high or not
     */
    public synchronized boolean isHighScoreTuple( ScoreTuple tuple )
    {
	int N = scores.length;

	int index=-1;
	for ( int k=0; k<N; ++k ) {
	    if (  tuple.getScore() > scores[k].getScore() ) 
		index = k;
	}

	return ( index >= 0 ? true : false );
    }
    
    /** 
     * Add the score tuple to the high score list
     *
     * @param tuple the score tuple
     *
     * @return true if the score is added or not
     */
    public synchronized boolean addScoreTuple( ScoreTuple tuple )
    {
	int N = scores.length;

	int index=-1;
	for ( int k=N-1; k>=0; --k ) {
	    if (  tuple.getScore() > scores[k].getScore() ) 
		index = k;
	}

	if (index < 0 )
	    // Score did not beat anything in the high score list.
	    return (false);

	// We know the insertion index, "It's time to shuffle the
	// pack", as Harry Redknapp, the manager of West Ham United FC
	// would say. But don't even try it, mate, because I am
	// strictly and truly a MANCHESTER UNITED FC SUPPORTER,
	// WE ARE THE TREBLE CUP WINNER IN 1999, BABY BOP!
	for ( int k=N-2 ; k>=index; --k ) 
	    scores[k+1] = scores[k];

	// Clone it before we store it the list
	scores[index] = (ScoreTuple)tuple.clone();
	
	return (true);
    }

    /**
     * Loads the high scores from an appropriate input stream
     * 
     * In JDK 1.2 one should use a lockable file.
     *
     * @param is the input stream
     *
     * @return <code>boolean</code> true if the load was successful.
     */
    public synchronized boolean load( InputStream is )
    {
	// "Loading is always more complex than writing, because of
	// the all the sanity checking you must do."
	//
	// Peter Pilgrim Tue Jul 06 17:37:43 BST 1999

	boolean retval=false;
	Vector dlist = new Vector();
	
	try {
	    BufferedReader breader = 
		new BufferedReader( new InputStreamReader( is ) );
	    String	line;
	    boolean	firstLine=true;
	    int		elemSize=-1;
	    
	    while ( ( line = breader.readLine() ) != null ) {
		line = line.trim();
		if ( line.length() < 1 )
		    continue;			// Blank line.
		if ( line.startsWith("#") )
		    continue;			// Comment line.

		if ( firstLine ) {
		    // The first line to get the number of high score
		    // elements in the stream expected.
		    firstLine = false;
		    try {
			elemSize = Integer.parseInt(line);
			if ( elemSize < 1 ) {
			    System.err.println( 
				"cannot read a zero or negative number "+
				"of high score elements." );
			    return (false);
			}
		    }
		    catch (NumberFormatException nfe ){
			elemSize = -1;
		    }
		    continue;
		}
		
		ScoreTuple tuple = ScoreTuple.parseString( line );
		if ( tuple != null)
		    dlist.addElement( tuple );
	    }

	    // Use a bit logic to validate that we read all of the
	    // expected high score elements from the stream.
	    int N = dlist.size();
	    if ( elemSize > 0 && N == elemSize ) {
		ScoreTuple [] newScores = new ScoreTuple[elemSize];
		for ( int q=0; q<elemSize; ++q ) 
		    newScores[q] = (ScoreTuple)dlist.elementAt(q);
		scores = newScores;
		retval = true;			/* !!! */
	    }
	    else {
		if ( elemSize > 0 && N < elemSize )
		    System.err.println( 
			"expected "+elemSize+" high score elements, but only "+
			"read "+N+" elements from the stream." );
		retval = false;
	    }
	}
	catch (IOException ioe) {
	    System.err.println( "I/O Exception: "+ioe.getMessage() );
	    retval = false;
	}
	
	return (retval);
    }
    
    /**
     * Saves the high scores to an appropriate output stream
     * 
     * In JDK 1.2 one should use a lockable file.
     *
     * @param os the output stream
     * @param header the optional header string
     *
     */
    public synchronized void save( OutputStream os, String header )
    {
	PrintWriter	pwriter = null;
	BufferedWriter	bwriter = null;
	
	bwriter = new BufferedWriter( new OutputStreamWriter( os ) );
	pwriter = new PrintWriter( bwriter );
	
	if ( header != null )
	    pwriter.println( "# "+ header );
	pwriter.println( "# Date: "+ new Date() );
	pwriter.println( "# Writen byte: "+ getClass().getName() );
	pwriter.println( "# \n" );
	pwriter.println( scores.length );
	for ( int q=0; q<scores.length; ++q )
	    pwriter.println( scores[q].getOutputString() );
	pwriter.println( "# finito" );
	pwriter.flush();
    }

    
}

// fini
