/*
 * Created on 08-Oct-2004
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package org.gudy.azureus2.core3.disk.impl;

/**
 * @author parg
 * @author MjrTom
 *			2005/Oct/08: startPriority/resumePriority handling and minor clock fixes
 *			2006/Jan/02: refactoring, change booleans to statusFlags
 */

import org.gudy.azureus2.core3.disk.*;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceList;

public class DiskManagerPieceImpl
	implements DiskManagerPiece
{
    //private static final LogIDs LOGID = LogIDs.PIECES;

	private static final byte	PIECE_STATUS_NEEDED		= 0x01;	//want to have the piece
	private static final byte	PIECE_STATUS_WRITTEN	= 0x20;	//piece fully written to storage
	private static final byte	PIECE_STATUS_CHECKING	= 0x40;	//piece is being hash checked
       
    private static final byte PIECE_STATUS_MASK_DOWNLOADABLE	=
    	PIECE_STATUS_CHECKING | PIECE_STATUS_WRITTEN | PIECE_STATUS_NEEDED;

    										// 0x65;    // Needed IS once again included in this
		
	private static final byte PIECE_STATUS_MASK_NEEDS_CHECK 	= PIECE_STATUS_CHECKING | PIECE_STATUS_WRITTEN;

    //private static boolean statusTested =false;

    private final DiskManagerHelper	diskManager;
	private final int				pieceNumber;
	
		/** the number of blocks in this piece: can be short as this gives up to .5GB piece sizes with 16K blocks */
	
	private final short				nbBlocks;
	
	// to save memory the "written" field is only maintained for pieces that are
	// downloading. A value of "null" means that either the piece hasn't started 
	// download or that it is complete.
	// access to "written" is single-threaded (by the peer manager) apart from when
	// the disk manager is saving resume data.
	// actually this is not longer strictly true, as setDone is called asynchronously
	// however, this issue can be worked around by working on a reference to the written data
	// as problems only occur when switching from all-written to done=true, both of which signify
	// the same state of affairs.
	
	protected volatile boolean[]	written;

    private byte         statusFlags;
    
	/** it's *very* important to accurately maintain the "done" state of a piece. Currently the statusFlags
	 * are updated in a non-thread-safe manner so a 'done' field is maintained seperatly.  Synchronizing
	 * access to statusFlags or done would cause a tremendous performance hit.
	 */
    private short		read_count;
    
    private boolean		done;
    
	public DiskManagerPieceImpl(final DiskManagerHelper _disk_manager, final int pieceIndex, int length )
	{
		diskManager =_disk_manager;
		pieceNumber = pieceIndex;
		
		nbBlocks =(short)((length +DiskManager.BLOCK_SIZE -1) /DiskManager.BLOCK_SIZE);

		statusFlags = PIECE_STATUS_NEEDED;
	}
	
	public DiskManager getManager()
	{
		return diskManager;
	}
	
	public int getPieceNumber()
	{
		return pieceNumber;
	}
	
	/**
	 * @return int number of bytes in the piece
	 */
	public int getLength()
	{
		return( diskManager.getPieceLength( pieceNumber ));
	}
	
	public int getNbBlocks()
	{
		return nbBlocks;
	}
	
	public short
	getReadCount()
	{
		return( read_count );
	}
	
	public void
	setReadCount(
		short	c )
	{
		read_count	= c;
	}
	
    public int getBlockSize(final int blockNumber)
    {
        if ( blockNumber == nbBlocks -1 ){
        
        	int	len = getLength() % DiskManager.BLOCK_SIZE;
        	
        	if ( len != 0 ){
        		
        		return( len );
        	}
        }
        
        return DiskManager.BLOCK_SIZE;
    }
    
    public boolean
    isSkipped()
    {
		final DMPieceList pieceList =diskManager.getPieceList(pieceNumber);
		for (int i =0; i <pieceList.size(); i++){
			if ( !pieceList.get(i).getFile().isSkipped()){
				return( false );
			}
		}
		return( true );
    }
    
	public boolean isNeeded()
	{
		return (statusFlags &PIECE_STATUS_NEEDED) !=0;
	}

	public boolean calcNeeded()
	{
		boolean filesNeeded =false;
		final DMPieceList pieceList =diskManager.getPieceList(pieceNumber);
		for (int i =0; i <pieceList.size(); i++)
		{
			final DiskManagerFileInfoImpl file =pieceList.get(i).getFile();
			final long fileLength =file.getLength();
			filesNeeded |=fileLength >0 &&file.getDownloaded() <fileLength &&!file.isSkipped();
		}
		if (filesNeeded)
		{
			statusFlags |=PIECE_STATUS_NEEDED;
			return true;
		}
		statusFlags &=~PIECE_STATUS_NEEDED;
		return false;
	}

	public void clearNeeded()
	{
		statusFlags &=~PIECE_STATUS_NEEDED;
	}

	public void setNeeded()
	{
		statusFlags |=PIECE_STATUS_NEEDED;
	}

	public void setNeeded(boolean b)
	{
		if (b)
			statusFlags |=PIECE_STATUS_NEEDED;
		else
			statusFlags &=~PIECE_STATUS_NEEDED;
	}

	public boolean isWritten()
	{
		return (statusFlags &PIECE_STATUS_WRITTEN) !=0;
	}


	/** written[] can be null, in which case if the piece is Done,
	*  all blocks are complete otherwise no blocks are complete
	*/
	public boolean[] getWritten()
	{
		return written;
	}

	public boolean isWritten(final int blockNumber)
	{
		if (done)
			return true;
		final boolean[] writtenRef =written;
		if (writtenRef ==null)
			return false;
		return writtenRef[blockNumber];
	}

	public int getNbWritten()
	{
		if (done)
			return nbBlocks;
		final boolean[] writtenRef =written;
		if (writtenRef ==null)
			return 0;
		int res =0;
		for (int i =0; i <nbBlocks; i++ )
		{
			if (writtenRef[i])
				res++;
		}
		return res;
	}

	public void setWritten(final int blockNumber)
	{
		if (written ==null)
			written =new boolean[nbBlocks];
		final boolean[] written_ref =written;
		
		written_ref[blockNumber] =true;
		for (int i =0; i <nbBlocks; i++)
		{
			if (!written_ref[i])
				return;
		}
		statusFlags |=PIECE_STATUS_WRITTEN;
	}

	public boolean isChecking()
	{
		return (statusFlags &PIECE_STATUS_CHECKING) !=0;
	}

	public void setChecking()
	{
		statusFlags |=PIECE_STATUS_CHECKING;
	}

    public boolean isNeedsCheck()
    {
    	return !done &&(statusFlags &PIECE_STATUS_MASK_NEEDS_CHECK) ==PIECE_STATUS_WRITTEN;
    }


    // this cannot be implemented the same as others could be
	// because the true state of Done is only determined by
	// having gone through setDoneSupport()
	public boolean calcDone()
	{
		return done;
	}

	public boolean isDone()
	{
		return done;
	}

	public void setDone(boolean b)
	{
		// we delegate this operation to the disk manager so it can synchronise the activity
        if (b !=done)
        {
            diskManager.setPieceDone(this, b);
        }
	}

	/** this is ONLY used by the disk manager to update the done state while synchronized
	 *i.e. don't use it else where!
	 * @param b
	 */
	
	public void setDoneSupport(final boolean b)
	{
        done =b;
        if (done)
            written =null;
	}

	public void setDownloadable()
	{		
		setDone(false);
		statusFlags &=~(PIECE_STATUS_MASK_DOWNLOADABLE);
		calcNeeded();	// Needed wouldn't have been calced before if couldn't download more
	}

	public boolean isDownloadable()
	{
		return !done &&(statusFlags &PIECE_STATUS_MASK_DOWNLOADABLE) == PIECE_STATUS_NEEDED;
	}
	
	/**
	 * @return true if the piece is Needed and not Done
	 */
	public boolean isInteresting()
	{
		return !done &&(statusFlags &PIECE_STATUS_NEEDED) != 0;
	}
    
	public void reset()
	{
		setDownloadable();
		written =null;
	}

	public void reDownloadBlock(int blockNumber)
	{
		final boolean[] written_ref = written;
		if (written_ref !=null)
		{
			written_ref[blockNumber] =false;
			setDownloadable();
		}
	}
    
 
    /*
    public static final void testStatus()
    {
        if (statusTested)
            return;
        
        statusTested =true;
        int originalStatus =statusFlags;
        
        for (int i =0; i <0x100; i++)
        {
            statusFlags =i;
            Logger.log(new LogEvent(this, LOGID, LogEvent.LT_INFORMATION,
                "Done:" +isDone()
                +"  Checking:" +isChecking()
                +"  Written:" +isWritten()
                +"  Downloaded:" +isDownloaded()
                +"  Requested:" +isRequested()
                +"  Needed:" +isNeeded()
                +"  Interesting:" +isInteresting()
                +"  Requestable:" +isRequestable()
                +"  EGMActive:" +isEGMActive()
                +"  EGMIgnored:" +isEGMIgnored()
            ));
        }
        statusFlags =originalStatus;
    }
    */
	
	public String
	getString()
	{
		String	text = "";
		
		text += ( isNeeded()?"needed,":"" );
		text += ( isDone()?"done,":"" );
		
		if ( !isDone()){
			text += ( isDownloadable()?"downable,":"" );
			text += ( isWritten()?"written":("written " + getNbWritten())) + ",";
			text += ( isChecking()?"checking":"" );
		}
		
		if ( text.endsWith(",")){
			text = text.substring(0,text.length()-1);
		}
		return( text );
	}
}
