/*
 * Created on Nov 20, 2008
 * Created by Paul Gardner
 * 
 * Copyright 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; version 2 of the License only.
 * 
 * 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.core.cnetwork.impl;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.util.AEDiagnostics;
import org.gudy.azureus2.core3.util.AEDiagnosticsEvidenceGenerator;
import org.gudy.azureus2.core3.util.AEDiagnosticsLogger;
import org.gudy.azureus2.core3.util.AEThread2;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.core3.util.IndentWriter;
import org.gudy.azureus2.core3.util.SimpleTimer;
import org.gudy.azureus2.core3.util.TimerEvent;
import org.gudy.azureus2.core3.util.TimerEventPerformer;

import com.aelitis.azureus.core.cnetwork.ContentNetwork;
import com.aelitis.azureus.core.cnetwork.ContentNetworkException;
import com.aelitis.azureus.core.cnetwork.ContentNetworkListener;
import com.aelitis.azureus.core.cnetwork.ContentNetworkManager;
import com.aelitis.azureus.core.custom.Customization;
import com.aelitis.azureus.core.custom.CustomizationManager;
import com.aelitis.azureus.core.custom.CustomizationManagerFactory;

import com.aelitis.azureus.core.messenger.PlatformMessengerException;
import com.aelitis.azureus.core.messenger.config.PlatformContentNetworkMessenger;
import com.aelitis.azureus.core.messenger.config.PlatformContentNetworkMessenger.contentNetworkDetails;
import com.aelitis.azureus.core.messenger.config.PlatformContentNetworkMessenger.listNetworksListener;

import com.aelitis.azureus.core.util.CopyOnWriteList;
import com.aelitis.azureus.core.vuzefile.VuzeFile;
import com.aelitis.azureus.core.vuzefile.VuzeFileComponent;
import com.aelitis.azureus.core.vuzefile.VuzeFileHandler;
import com.aelitis.azureus.core.vuzefile.VuzeFileProcessor;

public class 
ContentNetworkManagerImpl 
	implements ContentNetworkManager, AEDiagnosticsEvidenceGenerator
{
	private static final boolean LOAD_ALL_NETWORKS	= true;
	
	private static final String CONFIG_FILE		= "cnetworks.config";
	
	private static ContentNetworkManagerImpl singleton = new ContentNetworkManagerImpl();
		

	public static void
	preInitialise()
	{
		VuzeFileHandler.getSingleton().addProcessor(
			new VuzeFileProcessor()
			{
				public void
				process(
					VuzeFile[]		files,
					int				expected_types )
				{
					for (int i=0;i<files.length;i++){
						
						VuzeFile	vf = files[i];
						
						VuzeFileComponent[] comps = vf.getComponents();
						
						for (int j=0;j<comps.length;j++){
							
							VuzeFileComponent comp = comps[j];
							
							if ( comp.getType() == VuzeFileComponent.COMP_TYPE_CONTENT_NETWORK ){
								
								try{								
									((ContentNetworkManagerImpl)getSingleton()).importNetwork( comp.getContent());
								
									comp.setProcessed();
									
								}catch( Throwable e ){
									
									log( "Failed to import from vuze file", e );
							
									Debug.out( e );
								}
							}
						}
					}
				}
			});		
	}
	
	public static ContentNetworkManager
	getSingleton()
	{
		return( singleton );
	}
	
	private List<ContentNetworkImpl>	networks = new ArrayList<ContentNetworkImpl>();
	
	private CopyOnWriteList<ContentNetworkListener>	listeners = new CopyOnWriteList<ContentNetworkListener>();
	
	protected
	ContentNetworkManagerImpl()
	{
		loadConfig();
		
		addNetwork( new ContentNetworkVuze( this ));
		
		AEDiagnostics.addEvidenceGenerator( this );
		
		CustomizationManager cust_man = CustomizationManagerFactory.getSingleton();
		
		Customization cust = cust_man.getActiveCustomization();
		
		if ( cust != null ){
			
			String cust_name 	= COConfigurationManager.getStringParameter( "cnetworks.custom.name", "" );
			String cust_version = COConfigurationManager.getStringParameter( "cnetworks.custom.version", "0" );
			
			boolean	new_name 	= !cust_name.equals( cust.getName());
			boolean	new_version = Constants.compareVersions( cust_version, cust.getVersion() ) < 0;
			
			if ( new_name || new_version ){
				
				try{
					InputStream[] streams = cust.getResources( Customization.RT_CNETWORKS );
					
					for (int i=0;i<streams.length;i++){
						
						InputStream is = streams[i];
						
						try{
							VuzeFile vf = VuzeFileHandler.getSingleton().loadVuzeFile(is);
							
							if ( vf != null ){
								
								VuzeFileComponent[] comps = vf.getComponents();
								
								for (int j=0;j<comps.length;j++){
									
									VuzeFileComponent comp = comps[j];
									
									int type = comp.getType();
									
									if ( type == VuzeFileComponent.COMP_TYPE_CONTENT_NETWORK ){
										
										try{
												// change here as we always add all networks upfront so
												// we always set the customisation flag regardless of
												// whether existing or not
											
											ContentNetwork imported = importNetwork( comp.getContent());
											
											imported.setPersistentProperty( ContentNetwork.PP_IS_CUSTOMIZATION, true );
											
											comp.setProcessed();
											
										}catch( Throwable e ){
											
											log( "Failed to import customisation network", e );
										}
									}
								}
							}
						}finally{
							
							try{
								is.close();
								
							}catch( Throwable e ){
							}
						}
					}
				}finally{
					
					COConfigurationManager.setParameter( "cnetworks.custom.name", cust.getName());
					COConfigurationManager.setParameter( "cnetworks.custom.version", cust.getVersion());
				}
			}
		}
		
		
		SimpleTimer.addPeriodicEvent(
				"MetaSearchRefresh",
				23*60*60*1000,
				new TimerEventPerformer()
				{
					public void 
					perform(
						TimerEvent 	event ) 
					{
						checkForUpdates();
					}
				});
		
		if ( networks.size() > 1 || LOAD_ALL_NETWORKS ){
			
			new AEThread2( "CNetwork:init",	true )
			{
				public void
				run()
				{
					checkForUpdates();
				}
			}.start();
		}
	}
	
	protected void
	checkForUpdates()
	{
		synchronized( this ){
			
				// vuze network always present, no need to check this for updates as we don't auto
				// update it
			
			if ( networks.size() < 2 && !LOAD_ALL_NETWORKS ){
			
				return;
			}
		}
		
		PlatformContentNetworkMessenger.listNetworksAync(new listNetworksListener() {
			public void networkListReturned(List<contentNetworkDetails> cnets) {

				try{
					String	str = "";
					
					for ( contentNetworkDetails details: cnets ){
					
						str += (str.length()==0?"":", ") + details.getString();
						
						ContentNetwork existing = getContentNetwork( details.getID());
						
						if ( existing != null ){
							
							ContentNetworkImpl new_net = createNetwork( details );
							
							addNetwork( new_net );
							
						}else{
							
							if ( LOAD_ALL_NETWORKS ){
								
								ContentNetworkImpl new_net = createNetwork( details );
								
								addNetwork( new_net );
							}
						}
					}
					
					log( "Latest networks: " + str );
					
				}catch( Throwable e ){

					log( "Failed to load network list", e );
				}
			}
		}, 11110);
	}
	
	protected ContentNetworkImpl
	importNetwork(
		Map		content )
	
		throws IOException
	{
		ContentNetworkImpl network = ContentNetworkImpl.importFromBEncodedMapStatic( this, content );
		
		return( addNetwork( network ));
	}
	
	// @see com.aelitis.azureus.core.cnetwork.ContentNetworkManager#addContentNetwork(long)
	public void 
	addContentNetwork(
		final long 	id ) 
	
		throws ContentNetworkException
	{
		try{
			PlatformContentNetworkMessenger.listNetworksAync(new listNetworksListener() {
				public void networkListReturned(List<contentNetworkDetails> cnets) {
					if (cnets == null) {

						Exception e = new PlatformMessengerException( "No networks returned" );

						for ( ContentNetworkListener l : listeners ) {

							l.networkAddFailed(id, e);
						}

						return;
					}
					
					for ( contentNetworkDetails details: cnets ){
						
						if ( details.getID() == id ){
							
							ContentNetworkImpl new_net;
							try {

								new_net = createNetwork( details );

								addNetwork( new_net );
							} catch (ContentNetworkException e) {

								for ( ContentNetworkListener l : listeners ) {

									l.networkAddFailed(id, e);
								}

							}

							return;
						}
					}

					Exception e = new ContentNetworkException(
									"Content Network with id " + id + " not found");

					for ( ContentNetworkListener l : listeners ) {

						l.networkAddFailed(id, e);
					}
					
				}
			}, 500);
										

		}catch( Throwable e ){
			
			ContentNetworkException e2 = new ContentNetworkException( "Failed to list permitted networks", e );

			for ( ContentNetworkListener l : listeners ) {

				l.networkAddFailed(id, e2);
			}
			
			throw e2;
		}
	}
	
	public ContentNetwork
	getContentNetworkForURL(
		String		url )
	{
		try{
			String	host = new URL( url ).getHost();
			
			for ( ContentNetwork cn: getContentNetworks()){
				
				String	site = (String)cn.getProperty( ContentNetwork.PROPERTY_SITE_HOST );
				
				if ( site != null && site.endsWith( host )){
					
					return( cn );
				}
			}
		}catch( Throwable e ){
			
			Debug.printStackTrace(e);
		}
		
		return( null );
	}
	
	public ContentNetwork
	getStartupContentNetwork()
	{
		ContentNetwork[] networks = getContentNetworks();
		
		for ( ContentNetwork network: networks ){
			
			if ( network.isStartupNetwork()){
				
				return( network );
			}
		}
		
		return( getContentNetwork( ContentNetwork.CONTENT_NETWORK_VUZE ));
	}
	
	protected ContentNetworkImpl
	createNetwork(
		contentNetworkDetails		details )
	
		throws ContentNetworkException
	{	
		String main_url = details.getMainURL();
		String icon_url	= details.getIconURL();
		
		String site_dns;
		
		try{
			site_dns = new URL( main_url ).getHost();
			
		}catch( Throwable e ){
			
			log( "Failed to get main-url host", e );
			
			throw( new ContentNetworkException( "main url invald", e ));
		}
		
			// propagate persistent property defaults and exclusions as currently not returned by webapp
		
		ContentNetworkImpl existing = getContentNetwork( details.getID());
		
		Map<String,Object> 	pprop_defaults		= null;
		Set<Integer>		service_exclusions	= null;
		
		if ( existing != null ){
			
			pprop_defaults = existing.getPersistentPropertyDefaults();
			
			if ( existing instanceof ContentNetworkVuzeGeneric ){
				
				service_exclusions	= ((ContentNetworkVuzeGeneric)existing).getServiceExclusions();
			}
		}
		
		return( 
			new ContentNetworkVuzeGeneric( 
					this,
					details.getID(),
					details.getVersion(),
					details.getName(),
					pprop_defaults,
					service_exclusions,
					site_dns,
					main_url,
					icon_url,
					null,
					null,
					null,
					null,
					null,
					null ));

	}
	
	public ContentNetwork[] 
	getContentNetworks() 
	{
		synchronized( this ){
			
			return((ContentNetwork[])networks.toArray( new ContentNetworkImpl[ networks.size()] ));
		}
	}
	
	public ContentNetworkImpl 
	getContentNetwork(
		long id ) 
	{
		synchronized( this ){
			
			for ( int i=0;i<networks.size();i++ ){
					
				ContentNetworkImpl network = networks.get(i);
				
				if ( network.getID() == id ){
				
					return( network );
				}
			}
			
			return( null );
		}
	}
	
	protected ContentNetworkImpl
	addNetwork(
		ContentNetworkImpl		network )
	{
		boolean	replace = false;
		
		synchronized( this ){
		
			Iterator<ContentNetworkImpl> it = networks.iterator();
			
			while( it.hasNext()){
				
				ContentNetworkImpl existing_network = it.next();
				
				if ( existing_network.getID() == network.getID()){
					
					if ( network.getVersion() > existing_network.getVersion()){
						
						try{
							existing_network.updateFrom( network );
						
						}catch( Throwable e ){
							
							Debug.printStackTrace( e );
						}
						
						network = existing_network;
						
						replace = true;
						
						break;
						
					}else{
						
						log( "Network " + existing_network.getString() + " already up to date" );
						
						return( existing_network );
					}
				}
			}

			if ( replace ){
			
				log( "Updated network: " + network.getString());
				
			}else{
				
				log( "Added network: " + network.getString());
				
				networks.add( network );
			}
			
				// we never persist the vuze network
			
			if ( network.getID() != ContentNetwork.CONTENT_NETWORK_VUZE ){
				
				saveConfig();
			}
		}
		
		Iterator<ContentNetworkListener>	 it = (Iterator<ContentNetworkListener>)listeners.iterator();
		
		while( it.hasNext()){
			
			try{
				if ( replace ){
					
					it.next().networkChanged( network );
					
				}else{
					
					it.next().networkAdded( network );
				}
			}catch( Throwable e ){
				
				Debug.out( e );
			}
		}
		
		return( network );
	}
	
	protected void
	removeNetwork(
		ContentNetworkImpl		network )
	{
		synchronized( this ){

			if ( !networks.remove( network )){
				
				return;
			}
		
			network.destroy();
			
			saveConfig();
		}
		
		log( "Removed network: " + network.getString());
		
		Iterator<ContentNetworkListener>	 it = (Iterator<ContentNetworkListener>)listeners.iterator();
		
		while( it.hasNext()){
			
			try{
				it.next().networkRemoved( network );
				
			}catch( Throwable e ){
				
				Debug.out( e );
			}
		}
	}
	
	protected void
	loadConfig()
	{
		if ( FileUtil.resilientConfigFileExists( CONFIG_FILE )){
			
			Map	map = FileUtil.readResilientConfigFile( CONFIG_FILE );
			
			List list = (List)map.get( "networks" );
			
			if ( list != null ){
				
				for (int i=0;i<list.size();i++){
					
					Map	cnet_map = (Map)list.get(i);
					
					try{
						
						ContentNetworkImpl cn = ContentNetworkImpl.importFromBEncodedMapStatic( this, cnet_map );
						
						if ( cn.getID() != ContentNetwork.CONTENT_NETWORK_VUZE ){
							
							networks.add( cn );
						}
					}catch( Throwable e ){
						
						log( "Failed to load " + cnet_map, e );
					}
				}
			}
		}
	}
	
	protected void
	saveConfig()
	{
		Map	map = new HashMap();
		
		List list = new ArrayList();
		
		map.put( "networks", list );
		
		Iterator<ContentNetworkImpl> it = networks.iterator();
		
		while( it.hasNext()){
			
			ContentNetworkImpl network = it.next();

			if ( network.getID() == ContentNetwork.CONTENT_NETWORK_VUZE ){
				
				continue;
			}
			
			Map	cnet_map = new HashMap();
			
			try{
				network.exportToBEncodedMap( cnet_map );
			
				list.add( cnet_map );
				
			}catch( Throwable e ){
				
				log( "Failed to save " + network.getName(), e );
			}
		}
		
		if ( list.size() == 0 ){
			
			FileUtil.deleteResilientConfigFile( CONFIG_FILE );
			
		}else{
			
			FileUtil.writeResilientConfigFile( CONFIG_FILE, map );
		}
	}
	
	public void
	addListener(
		ContentNetworkListener		listener )
	{
		listeners.add( listener );
	}
	
	public void
	removeListener(
		ContentNetworkListener		listener )
	{
		listeners.remove( listener );
	}
	
	public void
	generate(
		IndentWriter		writer )
	{
		writer.println( "Content Networks" );
			
		try{
			writer.indent();

			synchronized( this ){
				
				Iterator<ContentNetworkImpl> it = networks.iterator();
				
				while( it.hasNext()){
					
					ContentNetworkImpl network = it.next();
					
					writer.println( network.getString());
				}
			}			
		}finally{
			
			writer.exdent();
		}
	}
	
	public static void 
	log(
		String 		s,
		Throwable 	e )
	{
		AEDiagnosticsLogger diag_logger = AEDiagnostics.getLogger( "CNetworks" );

		diag_logger.log( s );
		diag_logger.log( e );
	}
	
	public static void 
	log(
		String 	s )
	{	
		AEDiagnosticsLogger diag_logger = AEDiagnostics.getLogger( "CNetworks" );

		diag_logger.log( s );
	}
}
