/*
 * Created on Jul 17, 2006
 * Created by Alon Rohter
 * 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.
 * 
 * AELITIS, SARL au capital de 30,000 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 *
 */
package com.aelitis.azureus.core.peermanager.uploadslots;

import java.util.*;

import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.util.*;




/**
 * 
 */
public class UploadSessionPicker {

	private final LinkedList next_optimistics = new LinkedList();
	private final AEMonitor next_optimistics_mon = new AEMonitor( "UploadSessionPicker" );
	
	private final LinkedList helpers = new LinkedList();
	
	private final DownloadingRanker down_ranker = new DownloadingRanker();
	private final SeedingRanker seed_ranker = new SeedingRanker();


	protected UploadSessionPicker() {
		/*nothing*/
	}
	
	
	
	protected void registerHelper( UploadHelper helper ) {
		try {  next_optimistics_mon.enter();
			helpers.add( helper );
		
			int priority = helper.getPriority();
		
			//the higher the priority, the more optimistic unchoke chances they get
			for( int i=0; i < priority; i++ ) {
				insertHelper( helper );
			}			
		}
		finally {  next_optimistics_mon.exit();  }
	}
	
	
	
	protected void deregisterHelper( UploadHelper helper ) {
		try {  next_optimistics_mon.enter();
			helpers.remove( helper );
		
			boolean rem = next_optimistics.removeAll( Collections.singleton( helper ) );		
			if( !rem ) Debug.out( "!rem" );
		}
		finally {  next_optimistics_mon.exit();  }
	}
	
	
	
	protected void updateHelper( UploadHelper helper ) {
		try {  next_optimistics_mon.enter();
			int priority = helper.getPriority();  //new priority
		
			int count = 0;
		
			for( Iterator it = next_optimistics.iterator(); it.hasNext(); ) {
				UploadHelper h = (UploadHelper)it.next();
				if( h == helper ) {
					count++;
				
					if( count > priority ) {  //new priority is lower
						it.remove();  //trim
					}
				}
			}
		
			if( count < priority ) {  //new priority is higher
				for( int i=count; i < priority; i++ ) {
					insertHelper( helper );  //add
				}
			}
		}
		finally {  next_optimistics_mon.exit();  }
	}
	
	
	
	private void insertHelper( UploadHelper helper ) {
		int pos = RandomUtils.nextInt( next_optimistics.size() + 1 );  //pick a random location
		next_optimistics.add( pos, helper );  //store into location
	}
	
	

	
	protected int getHelperCount() {
		try {  next_optimistics_mon.enter();
			return next_optimistics.size();
		}
		finally {  next_optimistics_mon.exit();  }
	}
	
	
	
	//this picks both downloading and seeding sessions
	protected UploadSession pickNextOptimisticSession() {
		
		try {  next_optimistics_mon.enter();		
		
			HashSet failed_helpers = null;
		
			int loops_allowed = next_optimistics.size();
			
			while( loops_allowed > 0 ) {
				
				UploadHelper helper = (UploadHelper)next_optimistics.removeFirst();  //take from front
	
				next_optimistics.addLast( helper );   //add back at end
				
				if( failed_helpers == null || !failed_helpers.contains( helper ) ) {   //pre-emptive check to see if we've already tried this helper
					
					PEPeer peer;
					
					if( helper.isSeeding() ) {
						peer = seed_ranker.getNextOptimisticPeer( helper.getAllPeers() );
					}
					else {
						peer = down_ranker.getNextOptimisticPeer( helper.getAllPeers() );						
					}
					
					if( peer == null )  {  //no peers from this helper to unchoke
						
						if( failed_helpers == null )  failed_helpers = new HashSet();   //lazy alloc
						
						failed_helpers.add( helper );  //remember this helper in case we get it again in another loop round
					}
					else {   //found an optimistic peer!
						
						return new UploadSession( peer, helper.isSeeding() ? UploadSession.TYPE_SEED : UploadSession.TYPE_DOWNLOAD );						
					}
				}
				
				loops_allowed--;
			}
			
			return null;  //no optimistic peer found
			
		}
		finally {  next_optimistics_mon.exit();  }
	}
	
	
	

	private ArrayList<PEPeer> globalGetAllDownloadPeers() {
		try {  next_optimistics_mon.enter();
			ArrayList<PEPeer> all = new ArrayList<PEPeer>();
			
			for( Iterator<PEPeer> it = helpers.iterator(); it.hasNext(); ) {
				UploadHelper helper = (UploadHelper)it.next();
				
				if( !helper.isSeeding() )  {  //filter out seeding
					all.addAll( helper.getAllPeers() );
				}
			}
			
			return all;
		}
		finally {  next_optimistics_mon.exit();  }
	}
	
	
	
	//this picks downloading sessions only
	protected LinkedList<UploadSession> pickBestDownloadSessions( int max_sessions ) {
		//TODO factor download priority into best calculation?
		
		ArrayList<PEPeer> all_peers = globalGetAllDownloadPeers();
		
		if( all_peers.isEmpty() )  return new LinkedList();
		
		ArrayList<PEPeer> best = down_ranker.rankPeers( max_sessions, all_peers );
		
		if( best.size() != max_sessions ) {
			Debug.outNoStack( "best.size()[" +best.size()+ "] != max_sessions[" +max_sessions+ "]" );
		}
		
		if( best.isEmpty() ) {
			return new LinkedList<UploadSession>();
		}
		
		
		LinkedList<UploadSession> best_sessions = new LinkedList<UploadSession>();
		
		for( Iterator<PEPeer> it = best.iterator(); it.hasNext(); ) {
			PEPeer peer = it.next();
			UploadSession session = new UploadSession( peer, UploadSession.TYPE_DOWNLOAD );
			best_sessions.add( session );
		}
		
		return best_sessions;
	}
	
	
	
	
}
