/*
 * File    : FMFileManagerImpl.java
 * Created : 12-Feb-2004
 * By      : parg
 * 
 * Azureus - a Java Bittorrent client
 *
 * 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 ( see the LICENSE file ).
 *
 * 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 com.aelitis.azureus.core.diskmanager.file.impl;

/**
 * @author parg
 *
 */

import java.util.*;
import java.io.File;

import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.core3.config.*;

import com.aelitis.azureus.core.diskmanager.file.*;
import com.aelitis.azureus.core.util.CaseSensitiveFileMap;
import com.aelitis.azureus.core.util.LinkFileMap;

public class 
FMFileManagerImpl
	implements FMFileManager
{
	public static final boolean DEBUG	= false;
	
	protected static FMFileManagerImpl	singleton;
	protected static AEMonitor			class_mon	= new AEMonitor( "FMFileManager:class" );
	
	
	public static FMFileManager
	getSingleton()
	{
		try{
			class_mon.enter();
		
			if ( singleton == null ){
				
				singleton = new FMFileManagerImpl();
			}
			
			return( singleton );
			
		}finally{
			
			class_mon.exit();
		}
	}
	
	protected LinkedHashMap		map;
	protected AEMonitor			map_mon	= new AEMonitor( "FMFileManager:Map");

	protected HashMap<Object,LinkFileMap>			links		= new HashMap<Object,LinkFileMap>();
	protected AEMonitor			links_mon	= new AEMonitor( "FMFileManager:Links");

	protected boolean			limited;
	protected int				limit_size;
	
	protected AESemaphore		close_queue_sem;
	protected List				close_queue;
	protected AEMonitor			close_queue_mon	= new AEMonitor( "FMFileManager:CQ");
	
	protected List				files;
	protected AEMonitor			files_mon		= new AEMonitor( "FMFileManager:File");
	
	protected 
	FMFileManagerImpl()
	{
		limit_size = COConfigurationManager.getIntParameter( "File Max Open" );
		
		limited		= limit_size > 0;
	
		if ( DEBUG ){
			
			System.out.println( "FMFileManager::init: limit = " + limit_size );
			
			files = new ArrayList();
		}
		
		map	= new LinkedHashMap( limit_size, (float)0.75, true );	// ACCESS order selected - this means oldest

		if ( limited ){
			
			close_queue_sem	= new AESemaphore("FMFileManager::closeqsem");
			
			close_queue		= new LinkedList();
			
			Thread	t = new AEThread("FMFileManager::closeQueueDispatcher")
				{
					public void
					runSupport()
					{
						closeQueueDispatch();
					}
				};
			
			t.setDaemon(true);
			
			t.start();
		}
	}
	
	protected LinkFileMap
	getLinksEntry(
		TOTorrent	torrent )
	{
		Object	links_key;
		
		try{
			
			links_key = torrent.getHashWrapper();
			
		}catch( Throwable e ){
			
			Debug.printStackTrace(e);
			
			links_key	= "";
		}
		
		LinkFileMap	links_entry = links.get( links_key );
		
		if ( links_entry == null ){
			
			links_entry	= new LinkFileMap();
			
			links.put( links_key, links_entry );
		}
		
		return( links_entry );
	}
	
	public void
	setFileLinks(
		TOTorrent 		torrent,
		LinkFileMap		new_links )
	{
		try{
			links_mon.enter();
			
			LinkFileMap	links_entry = getLinksEntry( torrent );
			
			Iterator<LinkFileMap.Entry>	it = new_links.entryIterator();
			
			while( it.hasNext()){
				
				LinkFileMap.Entry	entry = it.next();
				
				int		index	= entry.getIndex();
				
				File	source 	= entry.getFromFile();
				File	target	= entry.getToFile();
				
				// System.out.println( "setLink:" + source + " -> " + target );
				
				if ( target != null && !source.equals(target)){
					
					if ( index >= 0 ){
					
						links_entry.put( index, source, target );
						
					}else{
						
						links_entry.putMigration( source, target );
					}
				}else{
					
					links_entry.remove( index, source );
				}
			}
		}finally{
			
			links_mon.exit();
		}
	}
	
	public File
	getFileLink(
		TOTorrent	torrent,
		int			file_index,
		File		file )
	{
			// this function works on the currently defined links and will only accept
			// them as valid if their 'from' location matches the 'file' being queried. 
			// if not the origial file is returned, NOT null
		
			// These semantics are important during file-move operations as the move-file
			// logic does not update links until AFTER the move is complete. If we don't
			// verify the 'from' path in this case then teh old existing linkage overrides
			// the new destination during the move process and causes it to fail with
			// 'file already exists'. There is possibly an argument that we shouldn't
			// take links into account when moving
		
		try{
			links_mon.enter();
			
			LinkFileMap	links_entry = getLinksEntry( torrent );

			LinkFileMap.Entry	entry = links_entry.getEntry( file_index, file );
			
			File res = null;
			
			if ( entry == null ){
				
				res = file;
				
			}else{
				
				if ( file.equals( entry.getFromFile())){
					
					res = entry.getToFile();
					
				}else{
					
					res = file;
				}
			}
			
			// System.out.println( "getLink:" + file + " -> " + res );
			
			return( res );
			
		}finally{
			
			links_mon.exit();
		}
	}
	
	public FMFile
	createFile(
		FMFileOwner	owner,
		File		file,
		int			type )
	
		throws FMFileManagerException
	{
		FMFile	res;
		
		if ( AEDiagnostics.USE_DUMMY_FILE_DATA ){
			
			res = new FMFileTestImpl( owner, this, file, type );
			
		}else{
		
			if ( limited ){
	
				res = new FMFileLimited( owner, this, file, type );
				
			}else{
				
				res = new FMFileUnlimited( owner, this, file, type );
			}
		}
			
		if (DEBUG){
			
			try{
				files_mon.enter();
			
				files.add( res );
				
			}finally{
				
				files_mon.exit();
			}
		}
		
		return( res );
	}
	
	
	protected void
	getSlot(
		FMFileLimited	file )
	{
			// must close the oldest file outside sync block else we'll get possible deadlock
		
		FMFileLimited	oldest_file = null;
		
		try{
			map_mon.enter();
		
			if (DEBUG ){
				System.out.println( "FMFileManager::getSlot: " + file.getName() +", map_size = " + map.size());
			}
			
			if ( map.size() >= limit_size ){
				
				Iterator it = map.keySet().iterator();
				
				oldest_file = (FMFileLimited)it.next();
				
				it.remove();
			}
			
			map.put( file, file );
			
		}finally{
			
			map_mon.exit();
		}
		
		if ( oldest_file != null ){
			
			closeFile( oldest_file );			

		}
	}
	
	protected void
	releaseSlot(
		FMFileLimited	file )
	{
		if ( DEBUG ){
			System.out.println( "FMFileManager::releaseSlot: " + file.getName());
		}
		
		try{
			map_mon.enter();
			
			map.remove( file );
			
		}finally{
			
			map_mon.exit();
		}
	}
	
	protected void
	usedSlot(
		FMFileLimited	file )
	{	
		if ( DEBUG ){
			System.out.println( "FMFileManager::usedSlot: " + file.getName());
		}
		
		try{
			map_mon.enter();
		
				// only update if already present - might not be due to delay in
				// closing files
			
			if ( map.containsKey( file )){
				
				map.put( file, file );		// update MRU
			}
		}finally{
			
			map_mon.exit();
		}
	}
	
	protected void
	closeFile(
		FMFileLimited	file )
	{
		if ( DEBUG ){
			System.out.println( "FMFileManager::closeFile: " + file.getName());
		}

		try{
			close_queue_mon.enter();
			
			close_queue.add( file );
			
		}finally{
			
			close_queue_mon.exit();
		}
		
		close_queue_sem.release();
	}
	
	protected void
	closeQueueDispatch()
	{
		while(true){
			
			if ( DEBUG ){
				
				close_queue_sem.reserve(1000);
				
			}else{
				
				close_queue_sem.reserve();
			}
			
			FMFileLimited	file = null;
			
			try{
				close_queue_mon.enter();
			
				if ( close_queue.size() > 0 ){
					
					file = (FMFileLimited)close_queue.remove(0);

					if ( DEBUG ){
						
						System.out.println( "FMFileManager::closeQ: " + file.getName() + ", rem = " + close_queue.size());
					}
				}
			}finally{
				
				close_queue_mon.exit();
			}
			
			if ( file != null ){
				
				try{
					file.close(false);
				
				}catch( Throwable e ){
				
					Debug.printStackTrace( e );
				}
			}
			
			if ( DEBUG ){
				
				try{
					files_mon.enter();
					
					int	open = 0;
					
					for (int i=0;i<files.size();i++){

						FMFileLimited	f = (FMFileLimited)files.get(i);
						
						if ( f.isOpen()){
							
							open++;
						}
					}
					
					System.out.println( "INFO: files = " + files.size() + ", open = " + open );
					
				}finally{
					
					files_mon.exit();
				}
			}
		}
	}
	
	protected void
	generate(
		IndentWriter	writer )
	{
		writer.println( "FMFileManager slots" );
		
		try{
			writer.indent();
			
			try{
				map_mon.enter();
							
				Iterator it = map.keySet().iterator();
					
				while( it.hasNext()){
					
					FMFileLimited	file = (FMFileLimited)it.next();
					
					writer.println( file.getString());
				}
							
			}finally{
				
				map_mon.exit();
			}
		}finally{
			
			writer.exdent();
		}
	}
	protected static void
	generateEvidence(
		IndentWriter	writer )
	{
		getSingleton();
		
		singleton.generate( writer );
	}
}
