/*
 * Created on Jun 29, 2006 10:16:26 PM
 * Copyright (C) 2006 Aelitis, 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.
 *
 * AELITIS, SAS au capital de 46,603.30 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 */
package com.aelitis.azureus.ui.swt.browser.listener.publish;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.image.FileFormat;
import org.eclipse.swt.widgets.*;

import org.bouncycastle.util.encoders.Base64;
import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.DisplayFormatters;
import org.gudy.azureus2.ui.swt.Utils;

import com.aelitis.azureus.core.messenger.ClientMessageContext;
import com.aelitis.azureus.ui.swt.browser.msg.BrowserMessage;
import com.aelitis.azureus.ui.swt.browser.txn.Transaction;
import com.aelitis.azureus.ui.swt.utils.ImageResizeException;
import com.aelitis.azureus.ui.swt.utils.ImageResizer;
import com.aelitis.azureus.ui.swt.utils.PublishUtils;
import com.aelitis.azureus.util.MapUtils;

import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.torrent.*;

import org.gudy.azureus2.pluginsimpl.local.PluginInitializer;


/**
 * Tracks the progress of creating a torrent during the publish process.
 * 
 * @author dharkness
 * @created Jul 20, 2006
 */
public class PublishTransaction extends Transaction
{
	
	
	private static final String ELEMENTS = "elements";

	public static final String PUBLISH_ATTRIBUTE_KEY = "DIRECTOR PUBLISH";  //TODO this should really be placed in DirectorPlugin.java
	
	private static final int   DEFAULT_IMAGE_BOX_SIZE = 320;
	private static final float DEFAULT_JPEG_QUALITY = 0.85f;
	
	private Shell shell;
	
	private LocalHoster hoster;
	
	private TorrentCreator creator;
	private TorrentCreatorListener creatorListener;
	private File dataFile;
	
	public void setShell(Shell shell) {
		this.shell = shell;
	}
	
	public void setLocalHoster(LocalHoster hoster) {
		this.hoster = hoster;
	}

	/**
     * Creates the transaction. Called by the manager.
     * 
     * @param id unique ID assigned by the manager
     * @param type passed by manager
     * @param context used to access the browser
     */
    public PublishTransaction ( int id , String type , ClientMessageContext context ) {
        super(id, type, context);
    }


    /**
     * Opens a file dialog so the user can choose the file to torrent.
     */
    public void chooseFile ( BrowserMessage message ) {    	
    	Utils.execSWTThread(new AERunnable() {
				public void runSupport() {
        	FileDialog dialog = new FileDialog(shell);    	
        	String file = dialog.open();    	
        	createTorrentFile(file);
				}
    	});
    }

    /**
     * Opens a file dialog so the user can choose the folder to torrent.
     */
    public void chooseFolder ( BrowserMessage message ) {
    	Utils.execSWTThread(new AERunnable() {
				public void runSupport() {
		    	DirectoryDialog dialog = new DirectoryDialog(shell);    	
		    	String file = dialog.open();
		    	createTorrentFile(file);
				}
			});
    }
    
    protected boolean canceling ( ) {
    	if(creator != null) {
    		creator.cancel();
    		creator.removeListener(creatorListener);
    		creator = null;
    		//Gudy	sendBrowserMessage("torrent","canceled");
    	}
        return true;
    }
    
    /**
     * Opens a file dialog so the user can choose the image to use as a thumbnail
     */
    public void chooseThumbnail(final BrowserMessage message) {
    	Utils.execSWTThread(new AERunnable() {
				public void runSupport() {
					_chooseThumbnail(message);
				}
    	});
    }

    private void _chooseThumbnail(BrowserMessage message) {
    	final int resize_size[] = {DEFAULT_IMAGE_BOX_SIZE,DEFAULT_IMAGE_BOX_SIZE};
    	final float image_quality[] = {DEFAULT_JPEG_QUALITY};
    	Map elements = null; //will be used if several thumbnails are required on a single page
        if ( message.isParamObject() ) {
            Map parameters = message.getDecodedMap();
    		try {
    			resize_size[0] = MapUtils.getMapInt(parameters, "width", 
    					DEFAULT_IMAGE_BOX_SIZE);
    			resize_size[1] = MapUtils.getMapInt(parameters, "height", 
    					DEFAULT_IMAGE_BOX_SIZE);
    			image_quality[0] = ((Number) MapUtils.getMapObject(parameters,
						"quality", new Double(DEFAULT_JPEG_QUALITY), Number.class)).floatValue();
    			if (parameters.containsKey(ELEMENTS)){
    				elements = (Map) parameters.get(ELEMENTS);
    			}
    		} catch(Exception e) {
    			//Possible bad parameters given, use default values
    			e.printStackTrace();
    		}
        }
    	FileDialog dialog = new FileDialog(shell,SWT.OPEN);
    	dialog.setFilterNames(new String[] {"Image Files"});
    	dialog.setFilterExtensions(new String[] {"*.jpg;*.jpeg;*.bmp;*.gif;*.png;*.tiff,*.tif"});
    	final String fileName = dialog.open();
    	if(fileName != null) {
    		//Run async not to block the UI
			/*Thread runner = 
                new Thread("Thumbnail Creator") {
    				public void run() {*/
    		
    					try {
                            sendBrowserMessage("thumb", "start", elements);
                            
        					File file = new File(fileName);    				
    	    				ResizedImageInfo info = loadAndResizeImage(file,resize_size[0],resize_size[1],1);
    	    				if(info == null) {
    	    					debug("User canceled image resizing");
    	    					sendBrowserMessage("thumb", "clear", elements);
    	    				} else {
	    	    				final String thumbURL = info.url.toString();
	    	    				debug("Size : " + info.data.length);
	    	    				
	    	    				final String encoded = new String(Base64.encode(info.data));
	                            Map params = new HashMap();
	                            params.put("url", thumbURL);
	                            params.put("width", new Long(info.width));
	                            params.put("height", new Long(info.height));
	                            params.put("data", encoded);
	                            if ( elements != null ){
	                            	params.put(ELEMENTS, elements);
	                            }
	                            sendBrowserMessage("thumb", "done", params);
    	    				}
        	    		}
    					catch(ImageResizeException e) {
    						debug("Error resizing image",e);
    						sendBrowserMessage("thumb", "clear", elements);
                            
                            Map params = new HashMap();
                            params.put("message", e.getMessage());
                            sendBrowserMessage("page", "error",params);
    					}
			catch (Exception e) {
				debug("Error reading file", e);
				sendBrowserMessage("thumb", "clear", elements);

				Map params = new HashMap();
				String errmsg = e.getMessage();
				
				params.put("message",
						"Azureus cannot process this image. " + 
						"Please select another one.\n\nDetailed Error: " + errmsg);
				sendBrowserMessage("page", "error", params);
			}
                        catch (OutOfMemoryError e) {
                        	debug("Error processing the image",e);
                        	
                        	sendBrowserMessage("thumb", "clear", elements);
                            
                            Map params = new HashMap();
                            params.put("message", "Azureus cannot process this image (likely reason is that it is too big). Please select another one.");
                            sendBrowserMessage("page", "error",params);
                        	
                        }
    				/*}
			    };
			runner.setDaemon(true);
			runner.start();    	*/		
    	}
    }

 	/**
	 * Pulls the modified torrent from the result web page and saves it locally.
	 */
	public void torrentIsReady(BrowserMessage message) {
		String torrent = MapUtils.getMapString(message.getDecodedMap(),
				"torrent", null);
		if (torrent != null) {
			torrentIsReady(torrent);
		}
	}

    protected void torrentIsReady(String strTorrent) {

		try {
			strTorrent = strTorrent.replaceAll("\\n", "");
			debug("data file path = [" + dataFile.getPath() + "]");
			debug("Torrent is ready, size = " + strTorrent.length()
					+ ", content (base64) : " + strTorrent);

			byte[] torrent_data = Base64.decode(strTorrent);

			debug("Torrent Byte Length: " + torrent_data.length /* + ", content : " + new String(torrent_data) */);

			// use PluginInterface since it has nice functions for
			// setting complete and adding a download via Torrent
			PluginInterface pi = PluginInitializer.getDefaultInterface();
			Torrent torrent = pi.getTorrentManager().createFromBEncodedData(
					torrent_data);

			torrent.setDefaultEncoding();
			torrent.setComplete(dataFile);

			final Download download = pi.getDownloadManager().addDownload(torrent,
					null, dataFile);

			PublishUtils.setPublished(download);

			download.setForceStart(true);

			//Transaction is finished
			stop();
		} catch (Throwable t) {
			Debug.out("torrentIsReady", t);
		}
	} 


    private void torrentCreationFailed(Exception cause) {
    	debug("Torrent Creation Failed", cause);
    	
    	sendBrowserMessage("torrent","failed");
    	
    	Map params = new HashMap();
		params.put("message", "Azureus cannot process this file. Please select another file.");
		sendBrowserMessage("page", "error",params);
		
    }
    
    private void createTorrentFile(String file) {
    	if(file != null) {   		
    		dataFile = new File(file);
    		try {
    			PluginInterface pi = PluginInitializer.getDefaultInterface();
    			creator = pi.getTorrentManager().createFromDataFileEx(dataFile,
						new URL("http://xxxxxxxxxxxxxxxxx:6969/announce"), false);

    			creatorListener = new TorrentCreatorListener() {

    				public void complete(Torrent torrent) {    					
    					try {
    						
    						torrent.setDefaultEncoding();
    						
    						debug("local torrent creation complete: " +torrent.getName()+ " : " +torrent.getMagnetURI() );        						    						
    						
    						final String tData = new String( Base64.encode( torrent.writeToBEncodedData() ) );
    						Map params = new HashMap();
                            params.put("data", tData);
    						sendBrowserMessage("torrent", "done", params);
    					}
    					catch (Throwable t) {
    						// TODO: handle exception
    						debug("error encoding torrent", t);
    					}

    				}

    				public void failed(TorrentException cause) {
    					torrentCreationFailed(cause);
    				}

    				public void reportActivity(String activity) {
    					//debug("creation status : " + activity);
    				}

    				public void reportPercentageDone(int percent) {
    					//debug("creation progress : " + percent);
    					Map params = new HashMap();
    					params.put("percent", new Long(percent));
    					sendBrowserMessage("torrent", "progress", params);
    				}

    			};

    			creator.addListener(creatorListener);

    			creator.start();

    		} catch (MalformedURLException e) {
    			
    			torrentCreationFailed(e);
				
    		} catch (TorrentException e) {
    			
    			torrentCreationFailed(e);
				
    		}
            
            Map params = new HashMap();
            params.put("folder", new Boolean(dataFile.isDirectory()));
            params.put("name", dataFile.getName());
            Long[] info = getSizeAndCount(dataFile);
            params.put("size", info[0]);
            params.put("num-files", info[1]);
            params.put("size-text", DisplayFormatters.formatByteCountToKiBEtc(info[0].longValue()));
            sendBrowserMessage("torrent", "chosen", params);
    	} else {
    		//No file was chosen, cancel the transaction
    		cancel();
            stop();
    	}
    }
    
    private Long[] getSizeAndCount(File folderFile) {
    	if (folderFile.isFile()) {
    		return new Long[] { new Long(folderFile.length()), new Long(1) };
    	}
    	if (folderFile.isDirectory()) {
    		long size = 0;
    		long numFiles = 0;
    		File[] files = folderFile.listFiles();
    		if (files != null) {
    			for (int i = 0; i < files.length; i++) {
						File file = files[i];
						Long[] sizeAndCount = getSizeAndCount(file);
						size += sizeAndCount[0].longValue();
						numFiles += sizeAndCount[1].longValue();
					}
    		}
    		return new Long[] { new Long(size), new Long(numFiles) };
    	}
    	return new Long[] { new Long(0), new Long(0) };
    }
    
    private class ResizedImageInfo {
    	public URL url;
    	public int width,height;
    	public byte[] data;
    	
    	public ResizedImageInfo(URL url,int width, int height, byte[] data) {
    		this.url = url;
    		this.width = width;
    		this.height = height;
    		this.data = data;
    	}
    }
    
    
	private ResizedImageInfo loadAndResizeImage(final File f, final int width,
			final int height, float quality) throws Exception {
		ImageLoader loader = new ImageLoader();
		final Display display = shell.getDisplay();
		Image source = null;
		try {
			source = new Image(shell.getDisplay(), f.getAbsolutePath());
		} catch (Error e) {
		}
		
		if (source == null) {
			throw new ImageResizeException("Unable to read image.  Please choose another.");
		}

		// If size is already an exact match, and the file isn't too big, use
		// original file
		Rectangle bounds = source.getBounds();
		try {
			if (bounds.width == width && bounds.height == height
					&& f.length() < 60000 && false) {
				URL url = hoster.hostFile(f);
				final FileInputStream fos = new FileInputStream(f);
				byte[] buf = new byte[(int) f.length()];
				fos.read(buf);
				fos.close();

				ResizedImageInfo result = new ResizedImageInfo(url, width, height, buf);
				return result;
			}
		} catch (Exception e) {
			Debug.out(e);
		}

		ImageResizer resizer = new ImageResizer(display, width, height, shell);

		Image output = resizer.resize(source);
		if (output == null)
			return null;
		ImageData data = output.getImageData();
		//Dispose the image
		if (output != null && !output.isDisposed()) {
			output.dispose();
		}

		//debug("final ByteArrayOutputStream baos = new ByteArrayOutputStream();");
		final ByteArrayOutputStream baos = new ByteArrayOutputStream();

		loader.data = new ImageData[] { data
		};
		
		String ext;
		if (SWT.getVersion() >= 3500) {
			// XXX Bug in SWT which borks some PNGs.. thus we can't use PNG saving
			//     at all until they fix it.. See 
			//     https://bugs.eclipse.org/bugs/show_bug.cgi?id=172290
			loader.save(baos, SWT.IMAGE_PNG);
			ext = ".png";
		} else {
			try {
				Class cJPGFF = Class.forName("org.eclipse.swt.internal.image.JPEGFileFormat");

				Constructor jpgConst = cJPGFF.getDeclaredConstructor(new Class[0]);

				jpgConst.setAccessible(true);

				FileFormat format = (FileFormat) jpgConst.newInstance(new Object[0]);

				Field field = cJPGFF.getDeclaredField("encoderQFactor");

				field.setAccessible(true);

				field.setInt(format, (int) (quality * 100));

				Class claLEDataOS = Class.forName("org.eclipse.swt.internal.image.LEDataOutputStream");

				Constructor le_constructor = claLEDataOS.getDeclaredConstructor(new Class[] { OutputStream.class
				});

				le_constructor.setAccessible(true);

				Object le_stream = le_constructor.newInstance(new Object[] { baos
				});

				Method unloadIntoStream = cJPGFF.getMethod("unloadIntoStream",
						new Class[] {
							ImageLoader.class,
							claLEDataOS
						});

				try {
					unloadIntoStream.invoke(format, new Object[] {
						loader,
						le_stream
					});
				} catch (Exception ex) {
					//Too bad for us here
					//However we don't want to try the other way, as it may be an io 
					//exception, ie the stream is corrupted...
					ex.printStackTrace();
				}
			} catch (Exception e) {
				e.printStackTrace();
				// The reflection way failed, do it the normal way with default 
				// (0.75) quality...
				loader.save(baos, SWT.IMAGE_JPEG);
			}
			ext = ".jpg";
		}


		byte[] bs = baos.toByteArray();

		File fDest = File.createTempFile("thumbnail", ext);
		FileOutputStream fos = new FileOutputStream(fDest);
		fos.write(bs);
		fos.close();

		URL url = hoster.hostFile(fDest);
		ResizedImageInfo result = new ResizedImageInfo(url, width, height, bs);

		return result;
	}

    /*
    private ResizedImageInfo loadAndResizeImage(File f,int thumbnail_size,float quality) throws Exception {
    	
    	debug("BufferedImage src = ImageIO.read(f);");
    	BufferedImage src = ImageIO.read(f);                      
        
        BufferedImage image;
        
        int width = src.getWidth(),height = src.getHeight();
        
        float wRatio = (float)width / (float)thumbnail_size;
        float hRatio = (float)height / (float)thumbnail_size;
        
        
        
        boolean mustResize = false;
        
        if(wRatio >= hRatio && wRatio > 1) {
        	mustResize = true;
        	width = thumbnail_size;
        	height = (int) (src.getHeight() / wRatio);
        }
        if(hRatio > wRatio && hRatio > 1) {
        	mustResize = true;
        	width = (int) (src.getWidth() / hRatio);
        	height = thumbnail_size;
        	
        }
        
        if(mustResize) {
        	debug("mustResize");
        	
        	debug("Image img = src.getScaledInstance(width,height,Image.SCALE_FAST);");
        	Image img = src.getScaledInstance(width,height,Image.SCALE_FAST);
        	
        	img.
        	
        	debug("BufferedImage copy = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);");
        	BufferedImage copy = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        	
        	debug("Graphics2D g2 = copy.createGraphics();");
        	Graphics2D g2 = copy.createGraphics();
        	
        	debug("g2.setColor(Color.white);");
        	g2.setColor(Color.white);
        	
        	debug("g2.fillRect(0,0,width,height);");
        	g2.fillRect(0,0,width,height);
        	
        	debug("g2.drawImage(img, 0, 0, null);");
        	g2.drawImage(img, 0, 0, new ImageObserver() {
        		public boolean imageUpdate(Image arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
        			debug("image update");
        			return true;
        		}
        	});
        	
        	debug("g2.dispose();");
        	g2.dispose();
        	
        	src = copy;
        }
        
        
        debug("File fDest = File.createTempFile(\"thumbnail\",\".jpg\");");
        File fDest = File.createTempFile("thumbnail",".jpg");
        
        debug("final FileOutputStream fos = new FileOutputStream(fDest);");
		final FileOutputStream fos = new FileOutputStream(fDest);
		
		debug("final ByteArrayOutputStream baos = new ByteArrayOutputStream();");
		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
		
		
		debug("OutputStream os = new OutputStream() {...};");
		OutputStream os = new OutputStream() {
			public void write(int arg0) throws java.io.IOException {fos.write(arg0);baos.write(arg0);};
			public void flush() throws java.io.IOException {fos.flush();baos.flush();};
			public void write(byte[] arg0) throws java.io.IOException {fos.write(arg0);baos.write(arg0);};
			public void write(byte[] arg0, int arg1, int arg2) throws java.io.IOException {fos.write(arg0);baos.write(arg0);};			
		};
		
		ImageOutputStream output = null;
		
		try {
			//Get Writer and set compression
			debug("Iterator iter = ImageIO.getImageWritersByFormatName(\"JPG\");");
			Iterator iter = ImageIO.getImageWritersByFormatName("JPG");
			
			if (iter.hasNext()) {
				
				debug("ImageWriter writer = (ImageWriter)iter.next();");
				ImageWriter writer = (ImageWriter)iter.next();
				
				debug("ImageWriteParam iwp = writer.getDefaultWriteParam();");
				ImageWriteParam iwp = writer.getDefaultWriteParam();
				
				debug("ImageWriteParam iwp = writer.getDefaultWriteParam();");
				
				debug("iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);");
				iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);		        
		        
				debug("iwp.setCompressionQuality(quality);");
		        iwp.setCompressionQuality(quality);
		        
		        debug("ImageOutputStream output = new MemoryCacheImageOutputStream(os);");
		        output = new MemoryCacheImageOutputStream(os);
		        
		        debug("writer.setOutput(output);");
		        writer.setOutput(output);
		        
		        debug("IIOImage imageOut =  new IIOImage(src, null, null);");
		        IIOImage imageOut =  new IIOImage(src, null, null);
		        
		        debug("writer.write(null, imageOut, iwp);");
		        writer.write(null, imageOut, iwp);
		    }
        }catch (Exception e) {
        	e.printStackTrace();
		} finally {
			output.close();			
		}
		
		URL url = hoster.hostFile(fDest);
		ResizedImageInfo result = new ResizedImageInfo(url,width,height,baos.toByteArray());
		return result;
        
        
    }
    */
}