package ftp_client;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;

// Element d'une table qui affiche des transferts, herite de TableItem
// Cette classe a pour but de simplifier les elements en liant l'item a une tache,
// lui permette de gerer lui meme si il a besoin d'etre rafraichi,
// la barre de progression
// ainsi que quelques actions sur le transfert,
// et de gerer lui même son menu contextuel
public class TransfertItem extends TableItem {

	/* Variables */
	Task tsk; // Tache liee
	enum TYPE { NONE, DOWNLOAD, UPLOAD }; // Type de transfert
	TYPE type;
	
	// Variable pour le calcul de vitesse de transfert
	long otime; // Temps du dernier rafraichissement
	long obytesSent; // Le nombre d'octets envoyes lors du dernier rafraichissement
	double speed; // La vitesse du transfert
	
	long oFileSize; // La taille du fichier
	String sFileSize; // La taille du fichier en chaine de caractere
	
	
	/* Progress Bar */
	TableEditor editor; // Permettant de lier la barre de progression a une cellule
	ProgressBar pb;		// Barre de progression
	
	
	/* Constructor */
	public TransfertItem(Table parent, Task tk){
		super(parent, SWT.NONE);
		
		oFileSize = -1; // On a pas encore la taille du fichier, alors on met -1
		sFileSize = "-";
		
		this.tsk = tk;
		
											 type = TYPE.NONE;
			 if(tsk instanceof DownloadTask) type = TYPE.DOWNLOAD;
		else if(tsk instanceof UploadTask)	 type = TYPE.UPLOAD;
		
	    // La barre de progression sera entre 0 et 100
		pb = new ProgressBar(parent, SWT.NONE);
		pb.setMinimum(0);
		pb.setMaximum(100);
		pb.setSelection(0);
		
		// On lie la barre de progression a une cellule
		editor = new TableEditor(parent);
		editor.grabHorizontal = editor.grabVertical = true;
		editor.setEditor(pb, this, 4);

		// On met le texte (noms des fichiers, type de transfert)
		// Si automatique, alors soit "<<<", ">>>" ou "---" dans la direction
		// Sinon juste "<<", ">>" ou "--"
		this.setText(new String[]{(type==TYPE.DOWNLOAD?tsk.rmtPath:"")+tsk.srcFile, "", (type==TYPE.UPLOAD?tsk.rmtPath:"")+tsk.dstFile, "", "-"});
		if(tsk.getAuto()) TransfertItem.this.setText(1, type==TYPE.DOWNLOAD?"<<<":type==TYPE.UPLOAD?">>>":"---");
		else	TransfertItem.this.setText(1, type==TYPE.DOWNLOAD?"<<":type==TYPE.UPLOAD?">>":"--");

		// On reprend peut etre un telechargement
		obytesSent = tsk.bytesSent;
	
		// On previent qu'on a cree un nouveau TransfertItem
		addTransfertItemListener(til);
	
		// On ecoute les changements d'etat de la tache
		tsk.addTaskListener(taskListener);
		
		// Destructeur propre de l'element (entraine logiquement la destruction de la tache si besoin)
		this.addDisposeListener(new DisposeListener() {
		public void widgetDisposed(DisposeEvent e) {
			if(pb!=null){pb.dispose(); pb = null; editor.dispose(); editor = null; }
			removeTransfertItemListener(til);
			
			if(tsk != null) new Thread(){
				public void run(){
					tsk.removeTaskListener(taskListener);
					tsk.Dispose();
					tsk = null;
				}
			}.start();
			fireTransfertItemDisposed();
		}
		});				
	}
	
	// Lorsqu'un TransfertItem est detruit, il faut faire en sorte de relier la barre de progression a sa cellule, 
	// car il se peut qu'elle ne se replace pas bien toute seule... TableEditor pas au point...
	TransfertItemListener til = new TransfertItemListener(){
		public void TransfertItemDisposed() {
			 	editor.setColumn(4);
		}		
	};
	
	
	/* Task's statut changed */
	// Ecoute des changements d'etat de la tache
	Task.TaskListener taskListener = new Task.TaskListener() {
			
		// Si la tache est detruite, on ne veut plus etre rafraichi
		public void Disposed(Task who) {
			fireWantToBeUpdate(TransfertItem.this, false);
		}

		// Si la tache debute, on veut etre rafraichi, et on se dit "En transfert"
		public void Started(Task who) {
			setEtat("En transfert", new Color(getDisplay(), 220, 220, 255));
			fireWantToBeUpdate(TransfertItem.this, true);
		}

		// Si la tache s'est arrete, on ne veut plus etre rafraichi, et on se dit "Arrete
		public void Stopped(Task who) {
			if(who.isAborted()) return;
			setEtat("Arrêté", new Color(getDisplay(), 220, 220, 220));
			fireWantToBeUpdate(TransfertItem.this, false);
		}

		// Meme principe qu'au dessus...
		public void Stopping(Task who) {
			setEtat("En arrêt", new Color(getDisplay(), 150, 150, 150));
			fireWantToBeUpdate(TransfertItem.this, false);
		}

		public void Finished(Task who) {
			setEtat("Terminé", new Color(getDisplay(), 200, 255, 200));
			fireWantToBeUpdate(TransfertItem.this, false);
		}

		public void Aborted(Task who) {
			setEtat("Annulé", new Color(getDisplay(), 255, 200, 200));
			fireWantToBeUpdate(TransfertItem.this, false);
		}

		public void Unaborted(Task who) {
			setEtat("En attente");
			fireWantToBeUpdate(TransfertItem.this, false);
		}
		
		// Si on passe en manuel ou en automatique, mettre a jour les "<<<", ">>>", "---", "<<", ">>", "--"
		public void Automatic(final Task who, final boolean yes) {
			Display.getDefault().asyncExec(new Runnable() {
							public void run() {
								if(yes) TransfertItem.this.setText(1, type==TYPE.DOWNLOAD?"<<<":type==TYPE.UPLOAD?">>>":"---");
								else	TransfertItem.this.setText(1, type==TYPE.DOWNLOAD?"<<":type==TYPE.UPLOAD?">>":"--");
							}
			});
		}
		
	};
	
	// Changer l'etat, avec couleur de fond par defaut (ici blanc)
	public void setEtat(final String etat){
		setEtat(etat, new Color(getDisplay(), 255, 255, 255));
	}
	// Changer l'etat en precisant une couleur de fond
	public void setEtat(final String etat, final Color clr){
		Display.getDefault().asyncExec(new Runnable() {
						public void run() {
				boolean enTransfert = etat.equals("En transfert");
				if(pb != null) pb.setVisible(enTransfert); // Si on est en transfert, il faut afficher la barre de progression
				if(!enTransfert){
					updateProgress(); // Si on est pas en transfert, afficher quand meme ou on en est
					TransfertItem.this.setText(3, ""); // Forcement, on ne transfert pas, donc il n'y a pas de vitesse de transfert a afficher
				}
				TransfertItem.this.setText(4, etat); // On affiche l'etat a la place de la barre de progression
				TransfertItem.this.setBackground(clr); // On change la couleur de fond
						}
				});
	}
	
	/* Update progress */
	public void updateProgress(){
		if(oFileSize != tsk.getFileSize()) sFileSize = Misc.sizeToString(tsk.getFileSize()); // Si la taille du fichier change, on la met a jour
		// Calcul de la vitesse de telechargement
		long bytesSent = tsk.getTotalBytesSent();
		long time = Calendar.getInstance().getTimeInMillis();
		if((time-otime) > 2000){ // On l'affiche que toutes les 2 secondes, sinon ça va trop vite et on a pas le temps de lire !				
			speed = (bytesSent-obytesSent)*(1000.0/ (time-otime));
			obytesSent = bytesSent;
			otime = time;
			this.setText(3, Misc.sizeToString(speed)+"/s");
		}
		// On met a jour la barre de progression
		if(bytesSent < tsk.getFileSize() && tsk.getFileSize()!=0) pb.setSelection((int)(tsk.getProgress()*100));
		// On affiche le nombre d'octets envoyes et la taille du fichier
		String sizeInfo = "";
		if(bytesSent < tsk.getFileSize())
			 sizeInfo = "("+Misc.sizeToString(tsk.getTotalBytesSent())+"/"+sFileSize+")";
		else sizeInfo = "("+sFileSize+")"; // Si le nombre d'octets envoyes et la taille du fichier sont egaux, autant n'afficher que la taille du fichier
		this.setText(5, percent(tsk.getProgress())+"% "+sizeInfo);
	}
	
	/* Calcul percent */
	// Calcul du pourcentage du transfert
	static float percent(double ratio){
		try{ // Divide by 0
			return ((int)(ratio*1000))/10.0f; // 1 chiffre après la virgule
		}catch(Exception e){
			return 0;
		}
	}
	
	/* Listeners */
	// Raccourcis clavier
	void keyDown(KeyEvent e){
		switch(e.keyCode){
			case SWT.CR	: { // "Entree" pause/relance le transfert si possible
					if(tsk==null || tsk.isDisposed()) break;
					if(tsk.isWorking()){
						new Thread(){
							public void run(){
								Scheduler.stopTask(tsk);
							}
						}.start();
					}else if(!tsk.isAborted() && tsk.hasStopped()){
						new Thread(){
							public void run(){
								tsk.getScheduler().startTask(tsk);
							}
						}.start();
					}else if(tsk.aborted){
						new Thread(){
							public void run(){
								tsk.unAbort();
								tsk.getScheduler().startTask(tsk, false);
							}
						}.start();
					}
				}
				break;
			case SWT.DEL : TransfertItem.this.dispose(); break; // "Suppr" supprime le transfert
		}
	}
	// Clique droit
	void rightClick(MouseEvent e){
		Menu mn = new Menu(this.getParent());
		if(tsk!=null){ // Si la tache est detruite, certaines options n'ont pas lieu d'etre
			if(!tsk.isDisposed()){
				if(tsk.isWorking()){
					MenuItem mi = new MenuItem(mn, SWT.PUSH);
					mi.setText("Mettre en pause");
					mi.addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() {
						public void widgetSelected(SelectionEvent event) {
							if(tsk == null || tsk.isDisposed()) return;
							new Thread(){
								public void run(){
									if(tsk!=null) Scheduler.stopTask(tsk); // Si la tache existe toujours, on la stop
								}
							}.start();
						}
					});
				}else if(!tsk.isAborted() && tsk.hasStopped()){
					MenuItem mi = new MenuItem(mn, SWT.PUSH);
					mi.setText("Reprendre");
					mi.addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() {
						public void widgetSelected(SelectionEvent event) {
							if(tsk == null || tsk.isDisposed()) return;
							new Thread(){
								public void run(){
									if(tsk!=null) tsk.getScheduler().startTask(tsk); // Si la tache existe toujours, on la demarre
								}
							}.start();
						}
					});
				}else if(tsk.aborted){
					MenuItem mi = new MenuItem(mn, SWT.PUSH);
					mi.setText("Débloquer");
					mi.addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() {
						public void widgetSelected(SelectionEvent event) {
							if(tsk == null || tsk.isDisposed()) return;
							new Thread(){
								public void run(){ // On debloque la tache
									tsk.unAbort();
									tsk.getScheduler().startTask(tsk, false);
								}
							}.start();
						}
					});
				}
			}
			if(!tsk.isFinished() && !tsk.isDisposed()){
				if(!tsk.getAuto()){
					MenuItem mi = new MenuItem(mn, SWT.PUSH);
					mi.setText("Démarrage automatique");
					mi.addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() {
						public void widgetSelected(SelectionEvent event) {
							if(tsk!=null) tsk.setAuto(true);
						}
					});
				}else{
					MenuItem mi = new MenuItem(mn, SWT.PUSH);
					mi.setText("Démarrage manuel");
					mi.addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() {
						public void widgetSelected(SelectionEvent event) {
							if(tsk!=null) tsk.setAuto(false);
						}
					});
				}
			}
		}
		MenuItem mi = new MenuItem(mn, SWT.PUSH);
		mi.setText("Supprimer");
		mi.addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				TransfertItem.this.dispose();
			}
		});
		
		Point pt = new Point (e.x, e.y);
		pt = this.getParent().toDisplay (pt);
		mn.setLocation (pt.x, pt.y);
		mn.setVisible (true);
	}
	
	
	/* Communicate with main */
	// Interface de demande de rafraichissement
	public interface UpdateTransfertItemListener {
		void wantToBeUpdated(TransfertItem who, boolean yes);
	}	

	private final Collection<UpdateTransfertItemListener> updateTransfertItemListener	= new ArrayList<UpdateTransfertItemListener>();
	
	public void fireWantToBeUpdate(TransfertItem who, boolean yes) {
		for(UpdateTransfertItemListener listener : updateTransfertItemListener)
			listener.wantToBeUpdated(this, yes);
	}
	
	public void addUpdateTransfertItemListener(UpdateTransfertItemListener listener) {
		updateTransfertItemListener.add(listener);
	}

	public void removeUpdateTransfertItemListener(UpdateTransfertItemListener listener) {
		updateTransfertItemListener.remove(listener);
	}

	
	public interface TransfertItemListener {
		void TransfertItemDisposed();
	}	

	private static final Collection<TransfertItemListener> transfertItemListener	= new ArrayList<TransfertItemListener>();
	
	public static void fireTransfertItemDisposed() {
		Display.getDefault().asyncExec(new Runnable() {
				public void run() {
					for(TransfertItemListener listener : transfertItemListener)
						listener.TransfertItemDisposed();
				}
		});
	}
	
	public static void addTransfertItemListener(TransfertItemListener listener) {
		transfertItemListener.add(listener);
	}

	public static void removeTransfertItemListener(TransfertItemListener listener) {
		transfertItemListener.remove(listener);
	}
		
	/* Subclassing not allowed in SWT */
	protected void checkSubclass() {}
}
