/*
 * Created on Jul 31, 2009
 * Created by Paul Gardner
 * 
 * Copyright 2009 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.devices.impl;

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

import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.AsyncDispatcher;

import com.aelitis.azureus.core.devices.Device;
import com.aelitis.azureus.core.devices.DeviceManagerListener;
import com.aelitis.azureus.core.devices.DeviceMediaRenderer;
import com.aelitis.azureus.core.devices.DeviceTemplate;
import com.aelitis.azureus.core.drivedetector.DriveDetectedInfo;
import com.aelitis.azureus.core.drivedetector.DriveDetectedListener;
import com.aelitis.azureus.core.drivedetector.DriveDetectorFactory;
import com.aelitis.azureus.util.MapUtils;

public class 
DeviceDriveManager 
	implements DriveDetectedListener
{
	private DeviceManagerImpl		manager;
	
	private Map<String,DeviceMediaRendererManual>	device_map = new HashMap<String, DeviceMediaRendererManual>();
	
	private AsyncDispatcher	async_dispatcher = new AsyncDispatcher();
	
	private boolean	listener_added;
	
	protected
	DeviceDriveManager(
		DeviceManagerImpl		_manager )
	{
		manager = _manager;
		
		manager.addListener(
			new DeviceManagerListener()
			{
				public void
				deviceAdded(
					Device		device )
				{	
				}
				
				public void
				deviceChanged(
					Device		device )
				{
				}
				
				public void
				deviceAttentionRequest(
					Device		device )
				{
				}
				
				public void
				deviceRemoved(
					Device		device )
				{
					synchronized( device_map ){
						
						Iterator<Map.Entry<String,DeviceMediaRendererManual>> it = device_map.entrySet().iterator();
						
						while( it.hasNext()){
							
							Map.Entry<String,DeviceMediaRendererManual> entry = it.next();
							
							if ( entry.getValue() == device ){
								
								it.remove();
							}
						}
					}
				}

				public void
				deviceManagerLoaded()
				{
				}
			});
		
		if ( manager.getAutoSearch()){
			
			listener_added = true;
			
			DriveDetectorFactory.getDeviceDetector().addListener( this );
		}
	}
	
	protected void
	search()
	{
		async_dispatcher.dispatch(
			new AERunnable()
			{
				public void
				runSupport()
				{
					if ( listener_added ){
						
						DriveDetectedInfo[] info = DriveDetectorFactory.getDeviceDetector().getDetectedDriveInfo();
						
						for ( DriveDetectedInfo i: info ){
							
							driveRemoved( i );
							
							driveDetected( i );
						}
						
						return;
					}
					
					try{
							// this should synchronously first any discovered drives
						
						DriveDetectorFactory.getDeviceDetector().addListener( DeviceDriveManager.this );

					}finally{
						
						DriveDetectorFactory.getDeviceDetector().removeListener( DeviceDriveManager.this );
					}
				}
			});
	}
	
	public void 
	driveDetected(
		final DriveDetectedInfo info )
 {
		//System.out.println("DD " + info.getLocation() + " via " + Debug.getCompressedStackTrace());
		async_dispatcher.dispatch(new AERunnable() {
			public void runSupport() {
				
				Map<String, Object> infoMap = info.getInfoMap();

				boolean isWritableUSB = MapUtils.getMapBoolean(infoMap, "isWritableUSB", false);
				
				File root = info.getLocation();

				String sProdID = MapUtils.getMapString(infoMap, "ProductID",
						MapUtils.getMapString(infoMap, "Product Name", "")).trim();
				String sVendor = MapUtils.getMapString(infoMap, "VendorID",
						MapUtils.getMapString(infoMap, "Vendor Name", "")).trim();

				// Historically, we gave IDs to Motorola, Samsung, and HTC phones
				// based on their vendor and product id only.  We need to maintain
				// this ID in order to not create duplicates.
				// Fortunately, both Motorola and Samsung include their model id in the 
				// sProdID.  HTC doesn't, however, their PID doesn't identify unique
				// models anyway, so including that wouldn't have helped anyway
				if ((sVendor.equalsIgnoreCase("htc") && sProdID.equalsIgnoreCase("android phone"))
						|| (sVendor.toLowerCase().contains("motorola") && sProdID.length() > 0)
						|| sVendor.equalsIgnoreCase("samsung")) {
					
					if (isWritableUSB && sVendor.equalsIgnoreCase("samsung")) {
						// Samsungs that start with Y are MP3 players
						// Samsungs that don't have a dash aren't smart phones (none that we know of anyway..)
						// Fake not writable so we remove the device instead of adding it
						isWritableUSB = !sProdID.startsWith("Y")
								&& sProdID.matches(".*[A-Z]-.*");
					}

					String name = sProdID.startsWith(sVendor) ? "" : sVendor;
					if (sVendor.length() > 0) {
						name += " ";
					}
					name += sProdID;

					String id = "android.";
					id += sProdID.replaceAll(" ", ".").toLowerCase();
					if (sVendor.length() > 0) {
						id += "." + sVendor.replaceAll(" ", ".").toLowerCase();
					}
					
					if (isWritableUSB) {
						addDevice(name, id, root, new File(root, "videos"), true);
					} else {
						//Fixup old bug where we were adding Samsung hard drives as devices
						Device existingDevice = getDeviceMediaRendererByClassification(id);
						if (existingDevice != null) {
							existingDevice.remove();
						}
					}
					return;
				} else if (isWritableUSB && sVendor.toLowerCase().equals("rim")) {
					String name = sVendor;
  				if (name.length() > 0) {
  					name += " ";
  				}
  				name += sProdID;
					String id = "";
					id += sProdID.replaceAll(" ", ".").toLowerCase();
					if (sVendor.length() > 0) {
						id += "." + sVendor.replaceAll(" ", ".").toLowerCase();
					}
					DeviceMediaRendererManual device = addDevice(name, id, root, new File(root, "videos"), false);
					if (device != null) {
						device.setImageID("bb");
					}
					return;
				}
				
				if (!isWritableUSB) {
					return;
				}

				if (root.exists()) {

					File[] folders = root.listFiles();

					if (folders != null) {

						Set<String> names = new HashSet<String>();

						for (File file : folders) {

							names.add(file.getName().toLowerCase());
						}

						if (names.contains("psp") && names.contains("video")) {
							addDevice("PSP", "sony.PSP", root, new File(root, "VIDEO"), false);
							return;
						}
					}
				}
				
				String pid = MapUtils.getMapString(infoMap, "PID", null);
				String vid = MapUtils.getMapString(infoMap, "VID", null);
				if (pid != null && vid != null) {
					String name = sProdID.startsWith(sVendor) ? "" : sVendor;
  				if (name.length() > 0) {
  					name += " ";
  				}
  				name += sProdID;

  				String id = "";
					id += sProdID.replaceAll(" ", ".").toLowerCase();
					id += "." + pid.toLowerCase();
					if (sVendor.length() > 0) {
						id += "." + sVendor.replaceAll(" ", ".").toLowerCase();
					}
					id += "." + vid.toLowerCase();
					
					// cheap hack to detect the PSP when it has no psp or video dir
					if (id.equals("\"psp\".ms.02d2.sony.054c")
							|| id.equals("\"psp\".ms.0381.sony.054c")) {
						if (addDevice("PSP", "sony.PSP", root, new File(root, "VIDEO"), false) != null) {
							return;
						}
					}
					
					addDevice(name, id, root, new File(root, "video"), true);
				}
			}
		});
	}
	
	protected DeviceMediaRenderer getDeviceMediaRendererByClassification(String target_classification) {
		DeviceImpl[] devices = manager.getDevices();
		
		for ( DeviceImpl device: devices ){
			
			if ( device instanceof DeviceMediaRenderer ){
			
				DeviceMediaRenderer renderer = (DeviceMediaRenderer)device;
				
				String classification = renderer.getClassification();
			
				if ( classification.equalsIgnoreCase( target_classification )){
																
					return renderer;
				}
			}
		}

		return null;
	}

	protected DeviceMediaRendererManual addDevice(
			String target_name, 
			String target_classification,
			File root,
			File target_directory,
			boolean generic)
	{
		
		DeviceMediaRenderer existingDevice = getDeviceMediaRendererByClassification(target_classification);
		if (existingDevice instanceof DeviceMediaRendererManual ) {
			mapDevice( (DeviceMediaRendererManual) existingDevice, root, target_directory );
			
			existingDevice.setGenericUSB(generic);
			return (DeviceMediaRendererManual) existingDevice;
		}
		
		DeviceTemplate[] templates = manager.getDeviceTemplates( Device.DT_MEDIA_RENDERER );
		
		DeviceMediaRendererManual	renderer = null;
		
		for ( DeviceTemplate template: templates ){
			
			if ( template.getClassification().equalsIgnoreCase( target_classification )){
				
				try{
					renderer = (DeviceMediaRendererManual)template.createInstance( target_name );

					break;
					
				}catch( Throwable e ){
					
					log( "Failed to add device", e );
				}
			}
		}
		
		if ( renderer == null ){
			
				// damn, the above doesn't work until devices is turned on...
			
			try{
				renderer = (DeviceMediaRendererManual)manager.createDevice( Device.DT_MEDIA_RENDERER, null, target_classification, target_name, true );
				
			}catch( Throwable e ){
				
				log( "Failed to add device", e );
			}
		}
		
		if ( renderer != null ){
			
			try{
				renderer.setAutoCopyToFolder( true );
				// This will cause a change event
				renderer.setGenericUSB(generic);
				
				mapDevice( renderer, root, target_directory );
				
				return renderer;
				
			}catch( Throwable e ){
				
				log( "Failed to add device", e );
			}
		}
		return renderer;
	}

	public void 
	driveRemoved(
		final DriveDetectedInfo info )
	{
		async_dispatcher.dispatch(
			new AERunnable()
			{
				public void
				runSupport()
				{
					unMapDevice( info.getLocation());
				}
			});
	}
	
	protected void
	mapDevice(
		DeviceMediaRendererManual		renderer,
		File							root,
		File							copy_to )
	{
		DeviceMediaRendererManual	existing;
		
		synchronized( device_map ){
			
			existing = device_map.put( root.getAbsolutePath(), renderer );
		}
		
		if ( existing != null && existing != renderer ){
			
			log( "Unmapped " + existing.getName() + " from " + root );
			
			existing.setCopyToFolder( null );
		}
		
		log( "Mapped " + renderer.getName() + " to " + root );

		renderer.setCopyToFolder( copy_to );
		
		renderer.setLivenessDetectable( true );
		
		renderer.alive();
	}
	
	protected void
	unMapDevice(
		File							root )
	{
		DeviceMediaRendererManual existing;
		
		synchronized( device_map ){
			
			existing = device_map.remove( root.getAbsolutePath());
		}
		
		if ( existing != null ){
			
			log( "Unmapped " + existing.getName() + " from " + root );

			existing.setCopyToFolder( null );
			
			existing.dead();
		}
	}
	
	protected void
	log(
		String str )
	{
		manager.log( "DriveMan: " + str );
	}
	
	protected void
	log(
		String 		str,
		Throwable 	e )
	{
		manager.log( "DriveMan: " + str, e );
	}
}
