/**
 * Created on Jun 1, 2008
 *
 * 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 com.aelitis.azureus.util;

import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.PluginManager;
import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.download.DownloadException;
import org.gudy.azureus2.plugins.utils.FeatureManager;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
import org.gudy.azureus2.pluginsimpl.local.PluginInitializer;

import com.aelitis.azureus.activities.VuzeActivitiesEntry;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.download.DownloadManagerEnhancer;
import com.aelitis.azureus.core.download.EnhancedDownloadManager;
import com.aelitis.azureus.core.download.StreamManager;
import com.aelitis.azureus.core.torrent.PlatformTorrentUtils;
import com.aelitis.azureus.ui.UIFunctions;
import com.aelitis.azureus.ui.UIFunctionsManager;
import com.aelitis.azureus.ui.selectedcontent.SelectedContentV3;

/**
 * @author TuxPaper
 * @created Jun 1, 2008
 *
 */
public class PlayUtils
{
	public static final boolean COMPLETE_PLAY_ONLY = true;
	
	// deprecated but here for older versions of vzemp that still refer to it
	public static final int fileSizeThreshold = 90;
	
		/**
		 * Access to this static is deprecated - use get/setPlayableFileExtensions. For legacy EMP we need
		 * to keep it public for the moment...
		 */
	
	public static final String playableFileExtensions 	= ".avi .flv .flc .mp4 .divx .h264 .mkv .mov .mp2 .m4v .mp3 .aac, .mts, .m2ts";
	
	private static volatile String actualPlayableFileExtensions = playableFileExtensions;
	
	
	private static Boolean hasQuickTime;
	
	//private static Method methodIsExternalPlayerInstalled;

	public static boolean prepareForPlay(DownloadManager dm) {
		EnhancedDownloadManager edm = DownloadManagerEnhancer.getSingleton().getEnhancedDownload(
				dm);
	
		if (edm != null) {
	
			edm.setProgressiveMode(true);
	
			return (true);
		}
	
		return (false);
	}

	public static boolean canProgressiveOrIsComplete(TOTorrent torrent) {
		if (torrent == null) {
			return false;
		}
		try {
			DownloadManagerEnhancer enhancer = DownloadManagerEnhancer.getSingleton();
			EnhancedDownloadManager edm = DownloadManagerEnhancer.getSingleton().getEnhancedDownload(
					torrent.getHash());
	
			if (edm == null) {
				return enhancer.isProgressiveAvailable()
						&& PlatformTorrentUtils.isContentProgressive(torrent);
			}
	
			boolean complete = edm.getDownloadManager().isDownloadComplete(false);
			if (complete) {
				return true;
			}
	
			// not complete
			if (!edm.supportsProgressiveMode()) {
				return false;
			}
		} catch (TOTorrentException e) {
			return false;
		}
	
		return true;
	}

	public static boolean canUseEMP(DiskManagerFileInfo file ){
		return( isExternallyPlayable( file ));
	}
	
	public static boolean canUseEMP(TOTorrent torrent, int file_index) {
		
		return( canUseEMP( torrent, file_index, COMPLETE_PLAY_ONLY ));
	}
	
	public static boolean canUseEMP(TOTorrent torrent, int file_index, boolean complete_only ) {
		if (torrent == null) { 
			return false;
		}

		if (canPlayViaExternalEMP(torrent, file_index, complete_only)) {
			return true;
		}
		
		return false;
	}

	private static boolean canPlay(DownloadManager dm, int file_index) {
		if (dm == null) {
			return false;
		}
		TOTorrent torrent = dm.getTorrent();
		return canUseEMP(torrent,file_index);
	}

	private static boolean canPlay(TOTorrent torrent, int file_index) {
		if (!PlatformTorrentUtils.isContent(torrent, false)) {
			return false;
		}
	
		if (!AzureusCoreFactory.isCoreRunning()) {
			return false;
		}

		GlobalManager gm = AzureusCoreFactory.getSingleton().getGlobalManager();
		DownloadManager dm = gm.getDownloadManager(torrent);
	
	
		if (dm != null) {
			return dm.getAssumedComplete() || canUseEMP(torrent, file_index);
		}
		return canUseEMP(torrent, file_index);
	}

	public static boolean canPlayDS(Object ds, int file_index ) {
		
		if ( !( Constants.isWindows || Constants.isOSX )){
			
			return( false );
		}
		
		if (ds == null) {
			return false;
		}
		
		if (ds instanceof org.gudy.azureus2.core3.disk.DiskManagerFileInfo) {
			org.gudy.azureus2.core3.disk.DiskManagerFileInfo fi = (org.gudy.azureus2.core3.disk.DiskManagerFileInfo) ds;
			return canPlayDS(fi.getDownloadManager(), fi.getIndex());
		}
	
		DownloadManager dm = DataSourceUtils.getDM(ds);
		if (dm != null) {
			return canPlay(dm, file_index);
		}
		TOTorrent torrent = DataSourceUtils.getTorrent(ds);
		if (torrent != null) {
			return canPlay(torrent, file_index);
		}
		if (ds instanceof VuzeActivitiesEntry) {
			return ((VuzeActivitiesEntry) ds).isPlayable();
		}
		
		if (ds instanceof SelectedContentV3) {
			SelectedContentV3 sel = (SelectedContentV3) ds;
			return sel.canPlay();
		}
		
		return false;
	}
	
		// stream stuff
	
	public static boolean
	isStreamPermitted()
	{
		FeatureManager fm = PluginInitializer.getDefaultInterface().getUtilities().getFeatureManager();

		return( fm.isFeatureInstalled( "core" ));
	}
	
	private static boolean 
	canStream(
		DownloadManager 	dm, 
		int 				file_index ) 
	{
		if ( dm == null ){
			
			return( false );
		}
		
		org.gudy.azureus2.core3.disk.DiskManagerFileInfo	file;
		
		if ( file_index == -1 ){
			
			file = dm.getDownloadState().getPrimaryFile();
			if (file == null) {
				org.gudy.azureus2.core3.disk.DiskManagerFileInfo[] files = dm.getDiskManagerFileInfoSet().getFiles();
				if (files.length == 0) {
					return false;
				}
				file = files[0];
			}
			
			file_index = file.getIndex();
			
		}else{
			
			file = dm.getDiskManagerFileInfoSet().getFiles()[ file_index ];
		}
		
		if ( file.getDownloaded() == file.getLength()){
			
			return( false );
		}
		
		if ( !StreamManager.getSingleton().isStreamingUsable()){
			
			return( false );
		}
		
		TOTorrent torrent = dm.getTorrent();
				
		return( canUseEMP( torrent, file_index, false ));
	}
		
	public static boolean 
	canStreamDS(
		Object ds, 
		int file_index ) 
	{
		if ( !( Constants.isWindows || Constants.isOSX )){
			
			return( false );
		}
		
		if ( ds == null ){
			
			return( false );
		}

		if (ds instanceof org.gudy.azureus2.core3.disk.DiskManagerFileInfo) {
			org.gudy.azureus2.core3.disk.DiskManagerFileInfo fi = (org.gudy.azureus2.core3.disk.DiskManagerFileInfo) ds;
			return canStreamDS(fi.getDownloadManager(), fi.getIndex());
		}

		DownloadManager dm = DataSourceUtils.getDM(ds);
		
		if ( dm != null ){
			
			return( canStream( dm, file_index ));
		}
		
		return( false );
	}

	/**
	 * @param dmContent
	 * @return
	 *
	 * @since 3.0.4.3
	 */
	/*
	public static String getContentUrl(DownloadManager dmContent) {
		String contentPath;
		if (dmContent.isDownloadComplete(false)) {
			//use the file path if download is complete.
			org.gudy.azureus2.core3.disk.DiskManagerFileInfo primaryFile = dmContent.getDownloadState().getPrimaryFile();
			if (primaryFile == null) {
				return null;
			}
			File file = primaryFile.getFile(true);
			try {
				contentPath = file.toURL().toString();
			} catch (MalformedURLException e) {
				contentPath = file.getAbsolutePath();
			}
		} else {
			//use the stream path if download is not complete.
			contentPath = PlayUtils.getMediaServerContentURL(dmContent);
		}
		return contentPath;
	}

	public static String getMediaServerContentURL(DownloadManager dm) {
		try {
			return PlayUtils.getMediaServerContentURL(DownloadManagerImpl.getDownloadStatic(dm));
		} catch (DownloadException e) {
		}
		return null;
	}
	*/
	/**
	 * @param dl
	 *
	 * @since 3.0.2.3
	 */
	/*
	public static String getMediaServerContentURL(Download dl) {
	
		//TorrentListViewsUtils.debugDCAD("enter - getMediaServerContentURL");
	
		PluginManager pm = AzureusCoreFactory.getSingleton().getPluginManager();
		PluginInterface pi = pm.getPluginInterfaceByID("azupnpav", false);
	
		if (pi == null) {
			Logger.log(new LogEvent(LogIDs.UI3, "Media server plugin not found"));
			return null;
		}
	
		if (!pi.getPluginState().isOperational()) {
			Logger.log(new LogEvent(LogIDs.UI3, "Media server plugin not operational"));
			return null;
		}
	
		try {
			Program program = Program.findProgram(".qtl");
			boolean hasQuickTime = program == null ? false
					: (program.getName().toLowerCase().indexOf("quicktime") != -1);
	
			pi.getIPC().invoke("setQuickTimeAvailable", new Object[] {
				new Boolean(hasQuickTime)
			});
	
			Object url = pi.getIPC().invoke("getContentURL", new Object[] {
				dl
			});
			if (url instanceof String) {
				return (String) url;
			}
		} catch (Throwable e) {
			Logger.log(new LogEvent(LogIDs.UI3, LogEvent.LT_WARNING,
					"IPC to media server plugin failed", e));
		}
	
		return null;
	}
	*/
	
	public static URL getMediaServerContentURL(DiskManagerFileInfo file) {
		
		//TorrentListViewsUtils.debugDCAD("enter - getMediaServerContentURL");
	
		PluginManager pm = AzureusCoreFactory.getSingleton().getPluginManager();
		PluginInterface pi = pm.getPluginInterfaceByID("azupnpav", false);
	
		if (pi == null) {
			Logger.log(new LogEvent(LogIDs.UI3, "Media server plugin not found"));
			return null;
		}
	
		if (!pi.getPluginState().isOperational()) {
			Logger.log(new LogEvent(LogIDs.UI3, "Media server plugin not operational"));
			return null;
		}
	
		try {
			if ( hasQuickTime == null ){
				
				UIFunctions uif = UIFunctionsManager.getUIFunctions();
				
				if ( uif != null ){
					
					hasQuickTime = uif.isProgramInstalled( ".qtl", "Quicktime" );
	
					try{
						pi.getIPC().invoke("setQuickTimeAvailable", new Object[] { hasQuickTime	});
						
					}catch( Throwable e ){
						
						Logger.log(new LogEvent(LogIDs.UI3, LogEvent.LT_WARNING,
								"IPC to media server plugin failed", e));
					}
				}
			}
	
			Object url = pi.getIPC().invoke("getContentURL", new Object[] { file });
			
			if (url instanceof String) {
				return new URL( (String) url);
			}
		} catch (Throwable e) {
			Logger.log(new LogEvent(LogIDs.UI3, LogEvent.LT_WARNING,
					"IPC to media server plugin failed", e));
		}
	
		return null;
	}
	
	/*
	private static final boolean isExternalEMPInstalled() {
		if(!loadEmpPluginClass()) {
			return false;
		}
		
		if (methodIsExternalPlayerInstalled == null) {
			return false;
		}
		
		try {

			Object retObj = methodIsExternalPlayerInstalled.invoke(null, new Object[] {});
			
			if (retObj instanceof Boolean) {
				return ((Boolean) retObj).booleanValue();
			}
		} catch (Throwable e) {
			e.printStackTrace();
			if (e.getMessage() == null
					|| !e.getMessage().toLowerCase().endsWith("only")) {
				Debug.out(e);
			}
		}

		return false;
		
	}*/
	

	private static AtomicInteger dm_uid = new AtomicInteger();
	
	private static Map<String,Object[]>	ext_play_cache = 
		new LinkedHashMap<String,Object[]>(100,0.75f,true)
		{
			protected boolean 
			removeEldestEntry(
		   		Map.Entry<String,Object[]> eldest) 
			{
				return size() > 100;
			}
		};	
		
	public static boolean isExternallyPlayable(Download d, int file_index, boolean complete_only ) {
				
		if ( d == null ){
			
			return( false );
		}
		
		boolean use_cache = d.getState() != Download.ST_DOWNLOADING;
		
		String 	cache_key 	= null;
		long	now 		= 0;
		
		if ( use_cache ){
			
			Integer uid = (Integer)d.getUserData( PlayUtils.class );
			
			if ( uid == null ){
				
				uid = dm_uid.getAndIncrement();
				
				d.setUserData( PlayUtils.class, uid );
			}
			
			cache_key = uid+"/"+file_index+"/"+complete_only;
			
			Object[] cached;
			
			synchronized( ext_play_cache ){
				
				cached = ext_play_cache.get( cache_key );
			}
			
			now = SystemTime.getMonotonousTime();
			
			if ( cached != null ){
			
				if ( now - (Long)cached[0] < 60*1000 ){
					
					return((Boolean)cached[1]);
				}
			}
		}
		
		boolean result = isExternallyPlayableSupport(d, file_index, complete_only);
		
		if ( use_cache ){
		
			synchronized( ext_play_cache ){

				ext_play_cache.put( cache_key, new Object[]{ now, result });
			}
		}
		
		return( result );
	}
	
	private static boolean isExternallyPlayableSupport(Download d, int file_index, boolean complete_only ) {
		
		int primary_file_index = -1;

		if ( file_index == -1 ){
			

			DownloadManager dm = PluginCoreUtils.unwrap(d);
			
			if ( dm == null ) {
				
				return( false );
			}
			
			DiskManagerFileInfo file = null;
			try {
				file = PluginCoreUtils.wrap(dm.getDownloadState().getPrimaryFile());
			} catch (DownloadException e) {
				return false;
			}
			
			if ( file == null ){
				
				return( false );
			}
						
			if ( file.getDownloaded() != file.getLength()) {
				
				if ( complete_only || getMediaServerContentURL( file ) == null ){
					
					return( false );
				}
			}
			
			primary_file_index = file.getIndex();

		}else{
			
			DiskManagerFileInfo file = d.getDiskManagerFileInfo( file_index );
			
			if ( file.getDownloaded() != file.getLength()) {
				
				if ( complete_only || getMediaServerContentURL( file ) == null ){
					
					return( false );
				}
			}
			
			primary_file_index = file_index;
		}

		if ( primary_file_index == -1 ){
			
			return false;
		}
		
		return( isExternallyPlayable( d.getDiskManagerFileInfo()[primary_file_index] ));
	}
	
	public static int[]
	getExternallyPlayableFileIndexes(
			Download d,
			boolean complete_only)
 {
		DiskManagerFileInfo[] fileInfos = d.getDiskManagerFileInfo();
		int count = d.getDiskManagerFileCount();
		int[] playableIndexes = {};
		for (int i = 0; i < count; i++) {
			DiskManagerFileInfo fileInfo = d.getDiskManagerFileInfo(i);
			if (complete_only && fileInfo.getLength() != fileInfo.getDownloaded()) {
				continue;
			}
			if (isExternallyPlayable(fileInfo)) {
				int[] newPlayableIndexes = new int[playableIndexes.length + 1];
				System.arraycopy(playableIndexes, 0, newPlayableIndexes, 0,
						playableIndexes.length);
				newPlayableIndexes[playableIndexes.length] = i;
				playableIndexes = newPlayableIndexes;
			}
		}
		return playableIndexes;
	}
	
	private static boolean
	isExternallyPlayable(
		DiskManagerFileInfo	file )
	{		
		String	name = file.getFile( true ).getName();
		
		try{
			Download dl = file.getDownload();
			
			if ( dl != null ){
			
				String is = PluginCoreUtils.unwrap( dl ).getDownloadState().getAttribute( DownloadManagerState.AT_INCOMP_FILE_SUFFIX );
				
				if ( is != null && name.endsWith( is )){
					
					name = name.substring( 0, name.length() - is.length()); 
				}
			}
		}catch( Throwable e ){
		}
		
		int extIndex = name.lastIndexOf(".");
		
		if ( extIndex > -1 ){
			
			String ext = name.substring(extIndex);
			
			if ( ext == null ){
				
				return false;
			}
			
			ext = ext.toLowerCase();
			
			if ( getPlayableFileExtensions().indexOf(ext) > -1 ){
				
				return true;
			}
		}
		
		return false;
	}
	
	public static boolean isExternallyPlayable(TOTorrent torrent, int file_index, boolean complete_only ) {
		if (torrent == null) {
			return false;
		}
		try {
			Download download = AzureusCoreFactory.getSingleton().getPluginManager().getDefaultPluginInterface().getDownloadManager().getDownload(torrent.getHash());
			if (download != null) {
				return isExternallyPlayable(download, file_index, complete_only);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return false;
	}
	
	private static final boolean canPlayViaExternalEMP(TOTorrent torrent, int file_index, boolean complete_only ) {
		if (torrent == null) {
			return false;
		}

		return isExternallyPlayable(torrent, file_index, complete_only );
	}

	public static String
	getPlayableFileExtensions()
	{
		return( actualPlayableFileExtensions );
	}
	
		/**
		 * This method available for player plugins to extend playable set if needed
		 * @param str
		 */
	
	public static void
	setPlayableFileExtensions(
		String	str )
	{
		actualPlayableFileExtensions = str;
	}
	

	/**
	 * @deprecated but still used by EMP
	 */
	public static int getPrimaryFileIndex(Download dl) {
		EnhancedDownloadManager edm = DownloadManagerEnhancer.getSingleton().getEnhancedDownload( PluginCoreUtils.unwrap(dl) );
		
		if ( edm == null ) {
			
			return -1;
		}

		return edm.getPrimaryFileIndex();
	}
	
	public static boolean
	isEMPAvailable()
	{
		PluginInterface pi = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByID( "azemp");
		
		if ( pi == null || pi.getPluginState().isDisabled()){
			
			return( false );
		}
		
		return( true );
	}

	
	public static boolean 
	playURL( 
		URL url, String name )
	{
		try{
			PluginInterface pi = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByID( "azemp");
			
			if ( pi == null || pi.getPluginState().isDisabled()){
				
				return( false );
			}
		
			Class<?> ewp_class = pi.getPlugin().getClass().getClassLoader().loadClass( "com.azureus.plugins.azemp.ui.swt.emp.EmbeddedPlayerWindowSWT" );
			
			if ( ewp_class != null ){
				
				Method ow = ewp_class.getMethod( "openWindow", URL.class, String.class );
				
				if ( ow != null ){
					
					ow.invoke( null, url, name );
					
					return( true );
				}
			}
			
			return( false );
			
		}catch( Throwable e ){
			
			Debug.out( e);
			
			return( false );
		}
	}
}
