/*
 * Created on 22 Aug 2008
 * Created by Allan Crooks
 * Copyright (C) 2008 Vuze 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.ui.console.commands;

import java.io.File;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.AEThread2;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.plugins.PluginException;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.PluginManager;
import org.gudy.azureus2.plugins.installer.InstallablePlugin;
import org.gudy.azureus2.plugins.installer.PluginInstallationListener;
import org.gudy.azureus2.plugins.installer.PluginInstaller;
import org.gudy.azureus2.plugins.installer.StandardPlugin;
import org.gudy.azureus2.plugins.update.Update;
import org.gudy.azureus2.plugins.update.UpdateCheckInstance;
import org.gudy.azureus2.plugins.update.UpdateCheckInstanceListener;
import org.gudy.azureus2.plugins.update.UpdateManager;
import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader;
import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderAdapter;
import org.gudy.azureus2.pluginsimpl.update.sf.SFPluginDetails;
import org.gudy.azureus2.pluginsimpl.update.sf.SFPluginDetailsLoaderFactory;
import org.gudy.azureus2.ui.console.ConsoleInput;
import org.gudy.azureus2.ui.console.util.TextWrap;

public class Plugin extends IConsoleCommand {

	public Plugin()
	{
		super("plugin");
	}
	
	public String getCommandDescriptions()
	{
		return("plugin [various options]\t\tRun with no parameter for more help.");
	}
	
	public void printHelpExtra(PrintStream out, List args) {
		out.println("> -----");
		out.println("Subcommands:");
		out.println("install\t[pluginid]\tLists plugins available to install or installs a given plugin");
		out.println("location\t\tLists where plugins are being loaded from");
		out.println("list\t\t\tList all running plugins");
		out.println("listall\t\t\tList all plugins - running or not");
		out.println("status pluginid\t\tPrints the status of a given plugin");
		out.println("startup pluginid on|off\tEnables or disables the plugin running at startup");
		out.println("uninstall pluginid\t\tUninstalls a plugin");
		out.println("update\t\tUpdates all plugins with outstanding updates");
		out.println("> -----");
	}
	
	public void execute(String commandName, final ConsoleInput ci, List args) {
		if (args.isEmpty()) {
			printHelpExtra(ci.out, args);
			return;
		}

		String subcmd = (String)args.get(0);
		if (!java.util.Arrays.asList(new String[] {
				"location", "list", "listall", "status", "startup", "install", "uninstall", "update"
			}).contains(subcmd)) {
			ci.out.println("Invalid subcommand: " + subcmd);
			ci.out.println();
			return;
		}
		
		PluginManager plugin_manager = ci.getCore().getPluginManager();
		
		if (subcmd.equals("list") || subcmd.equals("listall")) {
			boolean all_plugins = subcmd.equals("listall");
			ci.out.println("> -----");
			PluginInterface[] plugins = plugin_manager.getPluginInterfaces();
			TreeSet plugin_ids = new TreeSet(String.CASE_INSENSITIVE_ORDER);
			for (int i=0; i<plugins.length; i++) {
				if (!all_plugins && !plugins[i].getPluginState().isOperational()) {continue;}
				String plugin_id = plugins[i].getPluginID();
				plugin_ids.add(plugin_id);
			}
			TextWrap.printList(plugin_ids.iterator(), ci.out, "   ");
			ci.out.println("> -----");
			return;
		}
		
		if (subcmd.equals("location")) {
			// Taken from ConfigSectionPlugins.
			File fUserPluginDir = FileUtil.getUserFile("plugins");
			String sep = File.separator;
			
			String sUserPluginDir;
			
			try{
				sUserPluginDir = fUserPluginDir.getCanonicalPath();
			}catch( Throwable e ){
				sUserPluginDir = fUserPluginDir.toString();
			}
			
			if (!sUserPluginDir.endsWith(sep)) {
				sUserPluginDir += sep;
			}

			File fAppPluginDir = FileUtil.getApplicationFile("plugins");
			
			String sAppPluginDir;
			
			try{
				sAppPluginDir = fAppPluginDir.getCanonicalPath();
			}catch( Throwable e ){
				sAppPluginDir = fAppPluginDir.toString();
			}

			if (!sAppPluginDir.endsWith(sep)) {
				sAppPluginDir += sep;
			}
			
			ci.out.println("Shared plugin location:");
			ci.out.println("  " + sAppPluginDir);
			ci.out.println("User plugin location:");
			ci.out.println("  " + sUserPluginDir);
			ci.out.println();
			return;
		}
			
		if ( subcmd.equals( "update" )){
			
			if ( args.size() != 1 ){
				
				ci.out.println( "Usage: update" );
				
				return;
			}
						
			UpdateManager update_manager = plugin_manager.getDefaultPluginInterface().getUpdateManager();
			
			final UpdateCheckInstance	checker = update_manager.createUpdateCheckInstance();
			
			checker.addListener(
				new UpdateCheckInstanceListener()
				{
					public void
					cancelled(
						UpdateCheckInstance		instance )
					{
						
					}
					
					public void
					complete(
						UpdateCheckInstance		instance )
					{
						Update[] 	updates = instance.getUpdates();
												
						try{
	
							for ( Update update: updates ){

								ci.out.println( "Updating " + update.getName());
								
								for ( ResourceDownloader rd: update.getDownloaders()){
									
									rd.addListener(
		 								new ResourceDownloaderAdapter()
		 								{
		 									public void
		 									reportActivity(
		 										ResourceDownloader	downloader,
		 										String				activity )
		 									{	
		 										ci.out.println( "\t" + activity );
		 									}
		 									
		 									public void
		 									reportPercentComplete(
		 										ResourceDownloader	downloader,
		 										int					percentage )
		 									{
		 										ci.out.println( "\t" + percentage + "%" );
		 												
		 									}
		 								});
									
									rd.download();
								}
							}
							
							boolean	restart_required = false;
							
							for (int i=0;i<updates.length;i++){

								if ( updates[i].getRestartRequired() == Update.RESTART_REQUIRED_YES ){
									
									restart_required = true;
								}
							}
							
							if ( restart_required ){
								
								ci.out.println( "**** Restart required to complete update ****" );
							}
						}catch( Throwable e ){
							
							ci.out.println( "Plugin update failed: " + Debug.getNestedExceptionMessage( e ));
						}
					}
				});
			
			checker.start();
			
			return;
		}
		
		if ( subcmd.equals( "install" )){
			
			if ( args.size() == 1 ){
				
				ci.out.println( "Contacting plugin repository for list of available plugins..." );
			
				try{
					SFPluginDetails[] plugins = SFPluginDetailsLoaderFactory.getSingleton().getPluginDetails();
					
					for ( SFPluginDetails p: plugins ){
						
						String category = p.getCategory();
						
						if ( category != null ){
							
							if ( category.equalsIgnoreCase( "hidden" ) || ( category.equalsIgnoreCase( "core" ))){
								
								continue;
							}
						}
						
						String id = p.getId();
						
						if (  plugin_manager.getPluginInterfaceByID( id, false ) == null ){
							
							String desc = p.getDescription();
							
							int pos = desc.indexOf( "<br" );
							
							if ( pos > 0 ){
								
								desc = desc.substring( 0, pos );
							}
							
							ci.out.println( "\t" + id + ": \t\t" + desc );
						}
					}
					
				}catch( Throwable e ){
					
					ci.out.println( "Failed to list plugins: " + Debug.getNestedExceptionMessage( e ));
				}
			}else{
				
				String target_id = (String)args.get(1);
				
				if (  plugin_manager.getPluginInterfaceByID( target_id, false ) != null ){

					ci.out.println( "Plugin '" + target_id + "' already installed" );
					
					return;
				}			
				
				final PluginInstaller installer = plugin_manager.getPluginInstaller();
				
				try{
			 		final StandardPlugin sp = installer.getStandardPlugin( target_id );
								
			 		if ( sp == null ){
			 			
			 			ci.out.println( "Plugin '" + target_id + "' is unknown" );
						
						return;
			 		}
			 		
			 		new AEThread2( "Plugin Installer" )
			 		{
			 			public void
			 			run()
			 			{
			 				try{
								Map<Integer, Object> properties = new HashMap<Integer, Object>();
						
								properties.put( UpdateCheckInstance.PT_UI_STYLE, UpdateCheckInstance.PT_UI_STYLE_NONE );
									
								properties.put(UpdateCheckInstance.PT_UI_DISABLE_ON_SUCCESS_SLIDEY, true);
				
								final AESemaphore sem = new AESemaphore( "plugin-install" );
									
								final boolean[] restart_required = { false };
								
								UpdateCheckInstance instance = 
									installer.install(
										new InstallablePlugin[]{ sp },
										false,
										properties,
										new PluginInstallationListener() {
				
											public void
											completed()
											{
												ci.out.println( "Installation complete" );
												
												sem.release();
											}
											
											public void
											cancelled()
											{
												ci.out.println( "Installation cancelled" );
																			
												sem.release();
											}
											
											public void
											failed(
												PluginException	e )
											{
												ci.out.println( "Installation failed: " + Debug.getNestedExceptionMessage( e ));
							
												sem.release();
											}
										});
								
								instance.addListener(
									new UpdateCheckInstanceListener() {
				
										public void
										cancelled(
											UpdateCheckInstance		instance )
										{
											ci.out.println( "Installation cancelled" );
										}
										
										public void
										complete(
											UpdateCheckInstance		instance )
										{
						  					Update[] updates = instance.getUpdates();
						 					
						 					for ( final Update update: updates ){
						 						
						 						ResourceDownloader[] rds = update.getDownloaders();
						 					
						 						for ( ResourceDownloader rd: rds ){
						 							
						 							rd.addListener(
						 								new ResourceDownloaderAdapter()
						 								{
						 									public void
						 									reportActivity(
						 										ResourceDownloader	downloader,
						 										String				activity )
						 									{	
						 										ci.out.println( "\t" + activity );
						 									}
						 									
						 									public void
						 									reportPercentComplete(
						 										ResourceDownloader	downloader,
						 										int					percentage )
						 									{
						 										ci.out.println( "\t" + percentage + "%" );
						 												
						 									}
						 								});
						 							
						 								// go ahead and actually initiate the install, someone has to do it!
						 							
						 							try{
						 								rd.download();
						 								
						 							}catch( Throwable e ){
						 							}
						 						}
					 						
						 						if ( update.getRestartRequired() != Update.RESTART_REQUIRED_NO ){
						 							
						 							restart_required[0] = true;
						 						}
					 						}
										}
									});
										
								sem.reserve();
								
								if ( restart_required[0] ){
									
									ci.out.println( "**** Restart required to complete installation ****" );
								}
			 				}catch( Throwable e ){
			 					
								ci.out.println( "Install failed: " + Debug.getNestedExceptionMessage( e ));
							}
			 			}
			 		}.start();

				}catch( Throwable e ){
				
					ci.out.println( "Install failed: " + Debug.getNestedExceptionMessage( e ));
				}
			}
			return;
		}
		
		// Commands from this point require a plugin ID.
		if (args.size() == 1) {
			ci.out.println("No plugin ID given.");
			ci.out.println();
			return;
		}
		
		String plugin_id = (String)args.get(1);
		PluginInterface plugin = plugin_manager.getPluginInterfaceByID(plugin_id, false);
		if (plugin == null) {
			ci.out.println("Invalid plugin ID: " + plugin_id);
			ci.out.println();
			return;
		}
		
		if (subcmd.equals("status")) {
			ci.out.println("ID     : " + plugin.getPluginID());
			ci.out.println("Name   : " + plugin.getPluginName());
			ci.out.println("Version: " + plugin.getPluginVersion());
			ci.out.println("Running: " + plugin.getPluginState().isOperational());
			ci.out.println("Runs at startup: " + plugin.getPluginState().isLoadedAtStartup());
			if (!plugin.getPluginState().isBuiltIn()) {
				ci.out.println("Location: " + plugin.getPluginDirectoryName());
			}
			ci.out.println();
			return;
		}
		
		if (subcmd.equals("startup")) {
			if (args.size() == 2) {
				ci.out.println("Need to pass either \"on\" or \"off\"");
				ci.out.println();
				return;
			}
			String enabled_mode = (String)args.get(2);
			if (enabled_mode.equals("on")) {
				plugin.getPluginState().setLoadedAtStartup(true);
			}
			else if (enabled_mode.equals("off")) {
				plugin.getPluginState().setLoadedAtStartup(false);
			}
			else {
				ci.out.println("Need to pass either \"on\" or \"off\"");
				ci.out.println();
				return;
			}
			ci.out.println("Done.");
			ci.out.println();
			return;
		}
		
		if ( subcmd.equals( "uninstall" )){
			
			PluginInterface pi = plugin_manager.getPluginInterfaceByID( plugin_id, false );
			
			if (  pi == null ){

				ci.out.println( "Plugin '" + plugin_id + "' is not installed" );
				
				return;
			}			
			
			final PluginInstaller installer = plugin_manager.getPluginInstaller();
			
			try{
		 		final StandardPlugin sp = installer.getStandardPlugin( plugin_id );
							
		 		if ( sp == null ){
		 			
		 			ci.out.println( "Plugin '" + plugin_id + "' is not a standard plugin" );
					
					return;
		 		}
		 		
				final PluginInstaller uninstaller = plugin_manager.getPluginInstaller();
				
				Map<Integer, Object> properties = new HashMap<Integer, Object>();

				final AESemaphore sem = new AESemaphore( "plugin-uninstall" );

				UpdateCheckInstance instance = 
					uninstaller.uninstall(
						new PluginInterface[]{ pi },
						new PluginInstallationListener() {
							
							public void
							completed()
							{
								ci.out.println( "Uninstallation complete" );
								
								sem.release();
							}
							
							public void
							cancelled()
							{
								ci.out.println( "Uninstallation cancelled" );
								
								sem.release();
							}
							
							public void
							failed(
								PluginException	e )
							{
								ci.out.println( "Uninstallation failed: " + Debug.getNestedExceptionMessage( e ));
								
								sem.release();
							}
						},
						properties );
				
				instance.addListener(
						new UpdateCheckInstanceListener() {
	
							public void
							cancelled(
								UpdateCheckInstance		instance )
							{
								ci.out.println( "InsUninstallationtallation cancelled" );
							}
							
							public void
							complete(
								UpdateCheckInstance		instance )
							{
			  					Update[] updates = instance.getUpdates();
			 					
			 					for ( final Update update: updates ){
			 						
			 						ResourceDownloader[] rds = update.getDownloaders();
			 					
			 						for ( ResourceDownloader rd: rds ){
			 										 							
			 							try{
			 								rd.download();
			 								
			 							}catch( Throwable e ){
			 							}
			 						}
		 						}
							}
						});
						
				sem.reserve();
				
				Object obj = properties.get( UpdateCheckInstance.PT_UNINSTALL_RESTART_REQUIRED );
				
				if ( obj instanceof Boolean && (Boolean)obj ){
					
					ci.out.println( "**** Restart required to complete uninstallation ****" );			
				}
			}catch( Throwable  e ){
				
				ci.out.println( "Uninstall failed: " + Debug.getNestedExceptionMessage( e ));

			}
		}
	}
}
