/*
 * Created on 2 juil. 2003
 * Copyright (C) 2003, 2004, 2005, 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 org.gudy.azureus2.ui.swt.views;

import java.net.URL;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.disk.DiskManagerPiece;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerStats;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerScraperResponse;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.ui.swt.Messages;
import org.gudy.azureus2.ui.swt.TorrentUtil;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.components.BufferedLabel;
import org.gudy.azureus2.ui.swt.components.BufferedTruncatedLabel;
import org.gudy.azureus2.ui.swt.debug.ObfusticateImage;
import org.gudy.azureus2.ui.swt.debug.UIDebugGenerator;
import org.gudy.azureus2.ui.swt.mainwindow.Colors;
import org.gudy.azureus2.ui.swt.mainwindow.Cursors;
import org.gudy.azureus2.ui.swt.maketorrent.MultiTrackerEditor;
import org.gudy.azureus2.ui.swt.maketorrent.TrackerEditorListener;

/**
 * View of General information on the torrent
 * 
 * @author Olivier
 * 
 */
public class GeneralView extends AbstractIView implements ParameterListener,
		ObfusticateImage
{

  private Display display;
  private DownloadManager manager = null;
  boolean pieces[];
  int loopFactor;

  Composite genComposite;
  Composite gFile;
  Canvas piecesImage;
  Image pImage;
  BufferedLabel piecesPercent;
  Canvas availabilityImage;
  Image aImage;
  BufferedLabel availabilityPercent;
  Group gTransfer;
  BufferedLabel timeElapsed;
  BufferedLabel timeRemaining;
  BufferedLabel download;
  BufferedLabel downloadSpeed;
  //Text 			maxDLSpeed;
  BufferedLabel upload;
  BufferedLabel uploadSpeed;
  //Text 			maxULSpeed;
  //Text maxUploads;
  BufferedLabel totalSpeed;
  BufferedLabel ave_completion;
  BufferedLabel distributedCopies;
  BufferedLabel seeds;
  BufferedLabel peers;
  Group gInfo;
  BufferedLabel fileName;
  BufferedLabel torrentStatus;
  BufferedLabel fileSize;
  BufferedLabel saveIn;
  BufferedLabel hash;
  BufferedTruncatedLabel tracker_status;
  BufferedLabel trackerUpdateIn;
  Menu menuTracker;
  MenuItem itemSelect;
  
  BufferedTruncatedLabel trackerUrlValue;
  
  BufferedLabel pieceNumber;
  BufferedLabel pieceSize;
  Control lblComment;
  BufferedLabel creation_date;
  Control user_comment;
  BufferedLabel hashFails;
  BufferedLabel shareRatio;
  Button		updateButton;
  
  private int graphicsUpdate = COConfigurationManager.getIntParameter("Graphics Update");

  private Composite parent;

  /**
   * Initialize GeneralView
   */
  public GeneralView() {
  }

	public void dataSourceChanged(Object newDataSource) {
		if (newDataSource == null)
			manager = null;
		else if (newDataSource instanceof Object[])
			manager = (DownloadManager)((Object[])newDataSource)[0];
		else
			manager = (DownloadManager)newDataSource;

		refreshInfo();
	}

  /* (non-Javadoc)
   * @see org.gudy.azureus2.ui.swt.IView#initialize(org.eclipse.swt.widgets.Composite)
   */
  public void initialize(Composite composite) {
  	parent = composite;

    genComposite = new Canvas(parent, SWT.NULL);


    GridLayout genLayout = new GridLayout();
    genLayout.marginHeight = 0;
    try {
    	genLayout.marginTop = 5;
    } catch (NoSuchFieldError e) {
    	// pre 3.1
    }
    genLayout.marginWidth = 2;
    genLayout.numColumns = 1;
    genComposite.setLayout(genLayout);

    refreshInfo();
    COConfigurationManager.addParameterListener("Graphics Update", this);
  }
  
  private void refreshInfo() {
  	if (manager == null || parent == null)
  		return;
  	
  	Utils.disposeComposite(genComposite, false);
  	
    pieces = new boolean[manager.getNbPieces()];

    this.display = parent.getDisplay();

    gFile = new Composite(genComposite, SWT.SHADOW_OUT);
    GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
    gFile.setLayoutData(gridData);
    GridLayout fileLayout = new GridLayout();
    fileLayout.marginHeight = 0;
    fileLayout.marginWidth = 10;
    fileLayout.numColumns = 3;
    gFile.setLayout(fileLayout);

    Label piecesInfo = new Label(gFile, SWT.LEFT);
    Messages.setLanguageText(piecesInfo, "GeneralView.section.downloaded");
    gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
    piecesInfo.setLayoutData(gridData);

    piecesImage = new Canvas(gFile, SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    gridData.widthHint = 150;
    gridData.heightHint = 25;
    piecesImage.setLayoutData(gridData);

    piecesPercent = new BufferedLabel(gFile, SWT.RIGHT);
    gridData = new GridData(GridData.HORIZONTAL_ALIGN_END);
    gridData.widthHint = 50;
    piecesPercent.setLayoutData(gridData);

    Label availabilityInfo = new Label(gFile, SWT.LEFT);
    Messages.setLanguageText(availabilityInfo, "GeneralView.section.availability");
    gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
    availabilityInfo.setLayoutData(gridData);

    availabilityImage = new Canvas(gFile, SWT.NULL);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    gridData.widthHint = 150;
    gridData.heightHint = 25;
    availabilityImage.setLayoutData(gridData);
    Messages.setLanguageText(availabilityImage, "GeneralView.label.status.pieces_available.tooltip");

    availabilityPercent = new BufferedLabel(gFile, SWT.RIGHT);
    gridData = new GridData(GridData.HORIZONTAL_ALIGN_END);
    gridData.widthHint = 50;
    availabilityPercent.setLayoutData(gridData);
    Messages.setLanguageText(availabilityPercent.getWidget(), "GeneralView.label.status.pieces_available.tooltip");
    
    gTransfer = new Group(genComposite, SWT.SHADOW_OUT);
    Messages.setLanguageText(gTransfer, "GeneralView.section.transfer"); //$NON-NLS-1$
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    gTransfer.setLayoutData(gridData);

    GridLayout layoutTransfer = new GridLayout();
    layoutTransfer.numColumns = 6;
    gTransfer.setLayout(layoutTransfer);

    Label label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.timeelapsed"); //$NON-NLS-1$
    timeElapsed = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    timeElapsed.setLayoutData(gridData);
    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.remaining"); //$NON-NLS-1$
    timeRemaining = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    timeRemaining.setLayoutData(gridData);
    label = new Label(gTransfer, SWT.LEFT); //$NON-NLS-1$
    Messages.setLanguageText(label, "GeneralView.label.shareRatio");
    shareRatio = new BufferedLabel(gTransfer, SWT.LEFT); //$NON-NLS-1$
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    shareRatio.setLayoutData(gridData);

    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.downloaded"); //$NON-NLS-1$
    download = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    download.setLayoutData(gridData);
    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.downloadspeed"); //$NON-NLS-1$
    downloadSpeed = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    downloadSpeed.setLayoutData(gridData);
    label = new Label(gTransfer, SWT.LEFT); //$NON-NLS-1$
    Messages.setLanguageText(label, "GeneralView.label.hashfails");
    hashFails = new BufferedLabel(gTransfer, SWT.LEFT); //$NON-NLS-1$
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    hashFails.setLayoutData(gridData);
    
    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.uploaded"); //$NON-NLS-1$
    upload = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    upload.setLayoutData(gridData);    
    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.uploadspeed"); //$NON-NLS-1$
    uploadSpeed = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    gridData.horizontalSpan = 3;
    uploadSpeed.setLayoutData(gridData);
    
    	// blah
    
    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.seeds");
    seeds = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    seeds.setLayoutData(gridData);

    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.peers"); 
    peers = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    gridData.horizontalSpan = 3;
    peers.setLayoutData(gridData);

    
    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.totalspeed"); 
    totalSpeed = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    totalSpeed.setLayoutData(gridData);
    
    
    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.swarm_average_completion"); 
    ave_completion = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    ave_completion.setLayoutData(gridData);
    
    label = new Label(gTransfer, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.distributedCopies"); 
    distributedCopies = new BufferedLabel(gTransfer, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    distributedCopies.setLayoutData(gridData);
    

    ////////////////////////

    gInfo = new Group(genComposite, SWT.SHADOW_OUT);
    Messages.setLanguageText(gInfo, "GeneralView.section.info"); 
    gridData = new GridData(GridData.FILL_BOTH);
    gInfo.setLayoutData(gridData);

    GridLayout layoutInfo = new GridLayout();
    layoutInfo.numColumns = 4;
    gInfo.setLayout(layoutInfo);

    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.filename"); //$NON-NLS-1$
    fileName = new BufferedLabel(gInfo, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    fileName.setLayoutData(gridData);

    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.status"); //$NON-NLS-1$
    torrentStatus = new BufferedLabel(gInfo, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    torrentStatus.setLayoutData(gridData);

    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.savein"); //$NON-NLS-1$
    saveIn = new BufferedLabel(gInfo, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    saveIn.setLayoutData(gridData);

    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.totalsize"); //$NON-NLS-1$
    fileSize = new BufferedLabel(gInfo, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    fileSize.setLayoutData(gridData);

    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.numberofpieces"); //$NON-NLS-1$
    pieceNumber = new BufferedLabel(gInfo, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    pieceNumber.setLayoutData(gridData);

    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.hash"); //$NON-NLS-1$
    hash = new BufferedLabel(gInfo, SWT.LEFT);
    Messages.setLanguageText(hash.getWidget(), "GeneralView.label.hash.tooltip", true);
    
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    hash.setLayoutData(gridData);
    	// click on hash -> copy to clipboard
    hash.setCursor(Cursors.handCursor);
    hash.setForeground(Colors.blue);
    label.addMouseListener(new MouseAdapter() {
    	public void mouseDoubleClick(MouseEvent arg0) {
    		String hash_str = hash.getText();
    		if(hash_str != null && hash_str.length() != 0)
    			new Clipboard(display).setContents(new Object[] {hash_str.replaceAll(" ","")}, new Transfer[] {TextTransfer.getInstance()});
    	}
    	public void mouseDown(MouseEvent arg0) {
    		String hash_str = hash.getText();
    		if(hash_str != null && hash_str.length() != 0)
    			new Clipboard(display).setContents(new Object[] {hash_str.replaceAll(" ","")}, new Transfer[] {TextTransfer.getInstance()});
    	}
    });
    hash.addMouseListener(new MouseAdapter() {
    	public void mouseDoubleClick(MouseEvent arg0) {
    		String hash_str = hash.getText();
    		if(hash_str != null && hash_str.length() != 0)
    			new Clipboard(display).setContents(new Object[] {hash_str.replaceAll(" ","")}, new Transfer[] {TextTransfer.getInstance()});
    	}
    	public void mouseDown(MouseEvent arg0) {
    		String hash_str = hash.getText();
    		if(hash_str != null && hash_str.length() != 0)
    			new Clipboard(display).setContents(new Object[] {hash_str.replaceAll(" ","")}, new Transfer[] {TextTransfer.getInstance()});
    	}
    });
    
    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.trackerurl"); //$NON-NLS-1$
    label.setCursor(Cursors.handCursor);
    label.setForeground(Colors.blue);
    label.addMouseListener(new MouseAdapter() {
      public void mouseDoubleClick(MouseEvent arg0) {
        String announce = trackerUrlValue.getText();
        if(announce != null && announce.length() != 0)
          new Clipboard(display).setContents(new Object[] {announce}, new Transfer[] {TextTransfer.getInstance()});
      }
      public void mouseDown(MouseEvent arg0) {
        String announce = trackerUrlValue.getText();
        if(announce != null && announce.length() != 0)
          new Clipboard(display).setContents(new Object[] {announce}, new Transfer[] {TextTransfer.getInstance()});
      }
    });
    
    menuTracker = new Menu(genComposite.getShell(),SWT.POP_UP);
    itemSelect = new MenuItem(menuTracker,SWT.CASCADE);
    Messages.setLanguageText(itemSelect, "GeneralView.menu.selectTracker");
    MenuItem itemEdit = new MenuItem(menuTracker,SWT.NULL);
    Messages.setLanguageText(itemEdit, "MyTorrentsView.menu.editTracker");
     
    itemEdit.addListener(
    	SWT.Selection,
    	new Listener()
    	{
    		public void 
    		handleEvent(Event e)
    		{
    			final TOTorrent	torrent = manager.getTorrent();
    		
    			if ( torrent == null ){
    				return;
    			}
    			
	    		List	group = TorrentUtils.announceGroupsToList( torrent );
	    		
	    		new MultiTrackerEditor(null,group,
	    				new TrackerEditorListener()
	    				{
	    					public void
	    					trackersChanged(
	    							String	str,
									String	str2,
									List	_group )
	    					{
	    						TorrentUtils.listToAnnounceGroups( _group, torrent );
	    						
	    						try{
	    							TorrentUtils.writeToFile( torrent );
	    						}catch( Throwable e2 ){
	    							
	    							Debug.printStackTrace( e2 );
	    						}
	    						
	    						TRTrackerAnnouncer	tc = manager.getTrackerClient();
	    						
	    						if ( tc != null ){
	    							
	    							tc.resetTrackerUrl( true );
	    						}
	    					}
	    				}, true);	
    		}  		
    	});
    
    final Listener menuListener = new Listener() 
    {
      public void 
      handleEvent(Event e)
      {
         if( e.widget instanceof MenuItem) {
        	
          String text = ((MenuItem)e.widget).getText();
                   
          	
          TOTorrent	torrent = manager.getTorrent();
          	
          TorrentUtils.announceGroupsSetFirst(torrent,text);   
          	
          try{
          	TorrentUtils.writeToFile(torrent);
          	
          }catch( TOTorrentException f){
          		
          	Debug.printStackTrace( f );
          }
          	
          TRTrackerAnnouncer	tc = manager.getTrackerClient();
          	
          if ( tc != null ){
          		
          	tc.resetTrackerUrl( false );
          }    	
        }
      }
    };
    
    menuTracker.addListener(SWT.Show,new Listener() {
      public void handleEvent(Event e) {
    		Menu menuSelect = itemSelect.getMenu();
    		if(menuSelect != null && ! menuSelect.isDisposed()) {
    		  menuSelect.dispose();
    		}
    		if(manager == null || genComposite == null || genComposite.isDisposed())
    		  return;
     		List groups = TorrentUtils.announceGroupsToList(manager.getTorrent());        		
    		menuSelect = new Menu(genComposite.getShell(),SWT.DROP_DOWN);
    		itemSelect.setMenu(menuSelect);
    		Iterator iterGroups = groups.iterator();
    		while(iterGroups.hasNext()) {
    		  List trackers = (List) iterGroups.next();
    		  MenuItem menuItem = new MenuItem(menuSelect,SWT.CASCADE);
    		  Messages.setLanguageText(menuItem,"wizard.multitracker.group");
    		  Menu menu = new Menu(genComposite.getShell(),SWT.DROP_DOWN);
    		  menuItem.setMenu(menu);
    		  Iterator iterTrackers = trackers.iterator();
    		  while(iterTrackers.hasNext()) {
    		    String url = (String) iterTrackers.next();
    		    MenuItem menuItemTracker = new MenuItem(menu,SWT.CASCADE);
    		    menuItemTracker.setText(url);
    		    menuItemTracker.addListener(SWT.Selection,menuListener);
    		  }
    		}
      }
    });
    
    trackerUrlValue = new BufferedTruncatedLabel(gInfo, SWT.LEFT,70);        
    
    trackerUrlValue.addMouseListener(new MouseAdapter() {
			public void mouseDown(MouseEvent event) {
				if (event.button == 3
						|| (event.button == 1 && event.stateMask == SWT.CONTROL)) {
					menuTracker.setVisible(true);
				} else if (event.button == 1) {
					String url = trackerUrlValue.getText();
					if (url.startsWith("http://") || url.startsWith("https://")) {
						int pos = -1;
						if ((pos = url.indexOf("/announce")) != -1) {
							url = url.substring(0, pos + 1);
						}
						Utils.launch(url);
					}
				}
			}
		});
    
    
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    gridData.horizontalSpan = 1;
    trackerUrlValue.setLayoutData(gridData);
    
    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.size"); //$NON-NLS-1$
    pieceSize = new BufferedLabel(gInfo, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    pieceSize.setLayoutData(gridData);
    
    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.tracker"); //$NON-NLS-1$
    tracker_status = new BufferedTruncatedLabel(gInfo, SWT.LEFT,150);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    tracker_status.setLayoutData(gridData);    

    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.creationdate"); //$NON-NLS-1$
    creation_date = new BufferedLabel(gInfo, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    creation_date.setLayoutData(gridData);
        
    label = new Label(gInfo, SWT.LEFT);
    Messages.setLanguageText(label, "GeneralView.label.updatein"); //$NON-NLS-1$
    trackerUpdateIn = new BufferedLabel(gInfo, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    trackerUpdateIn.setLayoutData(gridData);
    

    updateButton = new Button(gInfo, SWT.PUSH);
    Messages.setLanguageText(updateButton, "GeneralView.label.trackerurlupdate"); //$NON-NLS-1$
    gridData = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
		updateButton.setLayoutData(gridData);
	    
		updateButton.addSelectionListener(new SelectionAdapter() {
	      public void widgetSelected(SelectionEvent event) {
	        manager.requestTrackerAnnounce(false);
	      }
	    });
    
    label = new Label(gInfo, SWT.LEFT);
    
    	// row
    
    label = new Label(gInfo, SWT.LEFT);
    label.setCursor(Cursors.handCursor);
    label.setForeground(Colors.blue);
    Messages.setLanguageText(label, "GeneralView.label.user_comment"); //$NON-NLS-1$

    try {
    	user_comment = new Link(gInfo, SWT.LEFT | SWT.WRAP);
    	((Link)user_comment).addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent e) {
					Utils.launch(e.text);
				}
			});
    } catch (Throwable e) {
    	user_comment = new Label(gInfo, SWT.LEFT | SWT.WRAP);
    }
    
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    gridData.horizontalSpan = 3;
    user_comment.setLayoutData(gridData);
    
    label.addMouseListener(new MouseAdapter() {
    	private void editComment() {
    		TorrentUtil.promptUserForComment(new DownloadManager[] {manager});
    	}

        public void mouseDoubleClick(MouseEvent arg0) {editComment();}
        public void mouseDown(MouseEvent arg0) {editComment();}
      });

    label = new Label(gInfo, SWT.LEFT);
    gridData = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
    label.setLayoutData(gridData);
    Messages.setLanguageText(label, "GeneralView.label.comment"); //$NON-NLS-1$
    
    try {
    	lblComment = new Link(gInfo, SWT.LEFT | SWT.WRAP);
    	((Link)lblComment).addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent e) {
					Utils.launch(e.text);
				}
			});
    } catch (Throwable e) {
    	lblComment = new Label(gInfo, SWT.LEFT | SWT.WRAP);
    }
    gridData = new GridData(GridData.FILL_BOTH);
    gridData.horizontalSpan = 3;
    lblComment.setLayoutData(gridData);
 
    
    piecesImage.addListener(SWT.Paint, new Listener() {
      public void handleEvent(Event e) {
        if (e.count == 0 && e.width > 0 && e.height > 0) {
          updatePiecesInfo(true);
        }
      }
    });
    availabilityImage.addListener(SWT.Paint, new Listener() {
      public void handleEvent(Event e) {
        if (e.count == 0 && e.width > 0 && e.height > 0) {
          updateAvailability();
        }
      }
    });

    if( Constants.isOSX ) {
      Shell shell = genComposite.getShell();
      Point size = shell.getSize();
      shell.setSize(size.x-1,size.y-1);
      shell.setSize(size);
    }
    
    genComposite.layout();
    //Utils.changeBackgroundComposite(genComposite,MainWindow.getWindow().getBackground());
  }

  /* (non-Javadoc)
   * @see org.gudy.azureus2.ui.swt.IView#getComposite()
   */
  public Composite getComposite() {
    return genComposite;
  }

  /* (non-Javadoc)
   * @see org.gudy.azureus2.ui.swt.IView#refresh()
   */
  public void refresh() {
    if(getComposite() == null || getComposite().isDisposed() || manager == null)
      return;

    loopFactor++;
    if ((loopFactor % graphicsUpdate) == 0) {
      updateAvailability();
      updatePiecesInfo(false);      
    }
    
    
    
    DiskManager dm = manager.getDiskManager();
    
    String	remaining;
    String	eta			= DisplayFormatters.formatETA(manager.getStats().getETA());
    
    if ( dm != null ){
    	
    	long	rem = dm.getRemainingExcludingDND();
    	
    	String	data_rem = DisplayFormatters.formatByteCountToKiBEtc( rem );
    	
			// append data length unless we have an eta value and none left
    	 
    	if ( rem > 0 ){
    			
    		remaining = eta + (eta.length()==0?"":" ") + data_rem;
    		
    	}else{
    		
    			// no bytes left, don't show remaining bytes unless no eta
    		
    		if ( eta.length() == 0 ){
    			
    			remaining = data_rem;
    		}else{
    			
    			remaining = eta;
    		}
    	}
    }else{
    	
    		// only got eta value, just use that
    	
    	remaining = eta;
    }
    
    
    setTime(manager.getStats().getElapsedTime(), remaining );
            
    TRTrackerScraperResponse hd = manager.getTrackerScrapeResponse();
    String seeds_str = manager.getNbSeeds() +" "+ MessageText.getString("GeneralView.label.connected");
    String peers_str = manager.getNbPeers() +" "+ MessageText.getString("GeneralView.label.connected");
    if(hd != null && hd.isValid()) {
      seeds_str += " ( " + hd.getSeeds() +" "+ MessageText.getString("GeneralView.label.in_swarm") + " )";
      peers_str += " ( " + hd.getPeers() +" "+ MessageText.getString("GeneralView.label.in_swarm") + " )";
    } else {
      //seeds += " (?)";
      //peers += " (?)";
    }
    String _shareRatio = "";
    int sr = manager.getStats().getShareRatio();
    
    if(sr == -1) _shareRatio = Constants.INFINITY_STRING;
    if(sr >  0){ 
      String partial = "" + sr%1000;
      while(partial.length() < 3) partial = "0" + partial;
      _shareRatio = (sr/1000) + "." + partial;
    
    }
    
    DownloadManagerStats	stats = manager.getStats();
    
    String swarm_speed = DisplayFormatters.formatByteCountToKiBEtcPerSec( stats.getTotalAverage() ) + " ( " +DisplayFormatters.formatByteCountToKiBEtcPerSec( stats.getTotalAveragePerPeer())+ " " +MessageText.getString("GeneralView.label.averagespeed") + " )";    
    
    String swarm_completion = "";
    String distributedCopies = "0.000";
    String piecesDoneAndSum = ""+manager.getNbPieces();
    
    PEPeerManager pm = manager.getPeerManager();
    if( pm != null ) {
    	int comp = pm.getAverageCompletionInThousandNotation();
    	if( comp >= 0 ) {
    		swarm_completion = DisplayFormatters.formatPercentFromThousands( comp );
    	}
    	
    	piecesDoneAndSum = pm.getPiecePicker().getNbPiecesDone() + "/" + piecesDoneAndSum;
    	
    	distributedCopies = new DecimalFormat("0.000").format(pm.getPiecePicker().getMinAvailability()-pm.getNbSeeds()-(pm.isSeeding()&&stats.getDownloadCompleted(false)==1000?1:0));
    }
    
    

    setStats(
    		DisplayFormatters.formatDownloaded(stats),
    		DisplayFormatters.formatByteCountToKiBEtc(stats.getTotalDataBytesSent()),
    		DisplayFormatters.formatByteCountToKiBEtcPerSec(stats.getDataReceiveRate()),
    		DisplayFormatters.formatByteCountToKiBEtcPerSec(stats.getDataSendRate()),
    		swarm_speed,
    		""+manager.getStats().getDownloadRateLimitBytesPerSecond() /1024,
    		""+(manager.getStats().getUploadRateLimitBytesPerSecond() /1024),
      	seeds_str,
      	peers_str,
      	DisplayFormatters.formatHashFails(manager),
      	_shareRatio,
      	swarm_completion,
      	distributedCopies
    );
      
    setTracker(manager);
    
    TOTorrent	torrent = manager.getTorrent();
    
    setInfos(
      manager.getDisplayName(),
	  DisplayFormatters.formatByteCountToKiBEtc(manager.getSize()),
	  DisplayFormatters.formatDownloadStatus(manager),
      manager.getSaveLocation().toString(),
      TorrentUtils.nicePrintTorrentHash(torrent),
      piecesDoneAndSum,
      manager.getPieceLength(),
      manager.getTorrentComment(),
      DisplayFormatters.formatDate(manager.getTorrentCreationDate()*1000),
      manager.getDownloadState().getUserComment());
    
    
    //A special layout, for OS X and Linux, on which for some unknown reason
    //the initial layout fails.
    if (loopFactor == 2) {
      getComposite().layout(true);     
    }
  }

  /* (non-Javadoc)
   * @see org.gudy.azureus2.ui.swt.IView#delete()
   */
  public void delete() {
	if (aImage != null)
		aImage.dispose();
	aImage = null;
	if (pImage != null)
		pImage.dispose();
	pImage = null;
  Utils.disposeComposite(genComposite);    
    COConfigurationManager.removeParameterListener("Graphics Update", this);
  }

  public String getData() {
    return "GeneralView.title.short"; //$NON-NLS-1$
  }

  /* (non-Javadoc)
   * @see org.gudy.azureus2.ui.swt.IView#getFullTitle()
   */
  public String getFullTitle() {
    return MessageText.getString("GeneralView.title.full"); //$NON-NLS-1$
  }

  private void updateAvailability() {
  	if (manager == null)
  		return;

  	try{
  		this_mon.enter();
  	
  		final int[] available;
  		
  		PEPeerManager	pm = manager.getPeerManager();
  		
	    if (manager.getPeerManager() == null) {
	      if (availabilityPercent.getText() != "")
	      	
	        availabilityPercent.setText("");
	    
	      	available	= new int[manager.getNbPieces()];
	    }else{
	    	available	= pm.getAvailability();
	    }
	    
	    if (display == null || display.isDisposed())
	      return;
	
	    if (availabilityImage == null || availabilityImage.isDisposed()) {
	      return;
	    }
	    Rectangle bounds = availabilityImage.getClientArea();
	    
	    int xMax = bounds.width - 2;
	    
	    int yMax = bounds.height - 2;
	    
	    if (xMax < 10 || yMax < 5){
	        return;
	    }
	    
	    if (aImage != null && !aImage.isDisposed()){
	      aImage.dispose();
	    }
	    aImage = new Image(display, bounds.width, bounds.height);

	    GC gc = new GC(availabilityImage);
	    GC gcImage = new GC(aImage);
	    
	    try{
		    gcImage.setForeground(Colors.grey);
		    gcImage.drawRectangle(0, 0, bounds.width-1, bounds.height-1);
		    int allMin = 0;
		    int allMax = 0;
		    int total = 0;
		    String sTotal = "000";
		    if (available != null) {
		
		      allMin = available.length==0?0:available[0];
		      allMax = available.length==0?0:available[0];
		      int nbPieces = available.length;
		      for (int i = 0; i < nbPieces; i++) {
		        if (available[i] < allMin)
		          allMin = available[i];
		        if (available[i] > allMax)
		          allMax = available[i];
		      }
		      int maxAboveMin = allMax - allMin;
		      if (maxAboveMin == 0) {
		        // all the same.. easy paint
		        gcImage.setBackground(Colors.blues[allMin == 0 ? Colors.BLUES_LIGHTEST : Colors.BLUES_DARKEST]);
		        gcImage.fillRectangle(1, 1, xMax, yMax);
		      } else {
		        for (int i = 0; i < nbPieces; i++) {
		          if (available[i] > allMin)
		            total++;
		        }
		        total = (total * 1000) / nbPieces;
		        sTotal = "" + total;
		        if (total < 10) sTotal = "0" + sTotal;
		        if (total < 100) sTotal = "0" + sTotal;
		  
		        for (int i = 0; i < xMax; i++) {
		          int a0 = (i * nbPieces) / xMax;
		          int a1 = ((i + 1) * nbPieces) / xMax;
		          if (a1 == a0)
		            a1++;
		          if (a1 > nbPieces)
		            a1 = nbPieces;
		          int max = 0;
		          int min = available[a0];
		          int Pi = 1000;
		          for (int j = a0; j < a1; j++) {
		            if (available[j] > max)
		              max = available[j];
		            if (available[j] < min)
		              min = available[j];
		            Pi *= available[j];
		            Pi /= (available[j] + 1);
		          }
		          int pond = Pi;
		          if (max == 0)
		            pond = 0;
		          else {
		            int PiM = 1000;
		            for (int j = a0; j < a1; j++) {
		              PiM *= (max + 1);
		              PiM /= max;
		            }
		            pond *= PiM;
		            pond /= 1000;
		            pond *= (max - min);
		            pond /= 1000;
		            pond += min;
		          }
		          int index;
		          if (pond <= 0 || allMax == 0) {
		            index = 0;
		          } else {
		            // we will always have allMin, so subtract that
		            index = (pond - allMin) * (Colors.BLUES_DARKEST - 1) / maxAboveMin + 1;
		            // just in case?
		            if (index > Colors.BLUES_DARKEST) {
		              index = Colors.BLUES_DARKEST;
		            }
		          }
		            
		          gcImage.setBackground(Colors.blues[index]);
		          gcImage.fillRectangle(i+1, 1, 1, yMax);
		        }
		      }
		    }
		    if (availabilityPercent == null || availabilityPercent.isDisposed()) {
		      return;
		    }
		    availabilityPercent.setText(allMin + "." + sTotal);
		    gc.drawImage(aImage, bounds.x, bounds.y);
	    }finally{
	    	
		    gcImage.dispose();
		    gc.dispose();
	    }
  	}finally{
  		
  		this_mon.exit();
  	}
  }

  private void updatePiecesInfo(boolean bForce) {
  	if (manager == null)
  		return;

  	try{
  		this_mon.enter();
  
	    if (display == null || display.isDisposed())
	      return;
	
	    if (piecesImage == null || piecesImage.isDisposed())
	      return;
	    
	    DiskManager	dm = manager.getDiskManager();

	    boolean valid = !bForce;
	    
        boolean[] new_pieces = new boolean[manager.getNbPieces()];
	        	
	    if ( dm != null ){
	      		      	
	      	DiskManagerPiece[]	dm_pieces = dm.getPieces();
	      	
	 		for (int i=0;i<pieces.length;i++){
	      		 	
	 			new_pieces[i] = dm_pieces[i].isDone();
	       }
	    }

	    if ( pieces == null ){
	    	
	    	valid	= false;
	    	
	    }else{
	    	
		    for (int i = 0; i < pieces.length; i++) {
		    
		    	if (pieces[i] != new_pieces[i]){
		    		
		            valid = false;
		            
		            break;
		        }
		    }
	    }

	    pieces	= new_pieces;
	    
	    if (!valid) {
	      Rectangle bounds = piecesImage.getClientArea();
	      int xMax = bounds.width - 2;
	      int yMax = bounds.height - 2 - 6;
	      if (xMax < 10 || yMax < 5){
	        return;
	      }
	      
          int total = manager.getStats().getDownloadCompleted(true);
	      
	      if (pImage != null && !pImage.isDisposed()){
		        pImage.dispose();
	      }
	      
	      pImage = new Image(display, bounds.width, bounds.height);
	      
	      GC gcImage = new GC(pImage);
	      try{
		      gcImage.setForeground(Colors.grey);
		      gcImage.drawRectangle(0, 0, bounds.width-1, bounds.height-1);
		      gcImage.drawLine(1,6,xMax,6);
		
		      if (pieces != null && pieces.length != 0) {
		        int nbPieces = pieces.length;
		        
		        for (int i = 0; i < xMax; i++) {
		          int a0 = (i * nbPieces) / xMax;
		          int a1 = ((i + 1) * nbPieces) / xMax;
		          if (a1 == a0)
		            a1++;
		          if (a1 > nbPieces)
		            a1 = nbPieces;
		          int nbAvailable = 0;
		          for (int j = a0; j < a1; j++) {
		            if (pieces[j]) {
		              nbAvailable++;
		            }
		            int index = (nbAvailable * Colors.BLUES_DARKEST) / (a1 - a0);
		            gcImage.setBackground(Colors.blues[index]);
		            gcImage.fillRectangle(i+1,7,1,yMax);
		          }
		        }
		      }
		          
		      // draw file % bar above
		      int limit = (xMax * total) / 1000;
		      gcImage.setBackground(Colors.colorProgressBar);
		      gcImage.fillRectangle(1,1,limit,5);
		      if (limit < xMax) {
		        gcImage.setBackground(Colors.blues[Colors.BLUES_LIGHTEST]);
		        gcImage.fillRectangle(limit+1,1,xMax-limit,5);
		      }
	      }finally{
	
	    	  gcImage.dispose();
	      }
	
	      if (piecesPercent != null && !piecesPercent.isDisposed())
	        piecesPercent.setText(DisplayFormatters.formatPercentFromThousands(total));
	
	      if (pImage == null || pImage.isDisposed()) {
	        return;
	      }
	      
	      GC gc = new GC(piecesImage);
	      gc.drawImage(pImage, bounds.x, bounds.y);
	      gc.dispose();
	    }
  	}finally{
  		
  		this_mon.exit();
  	}
  }

  private void setTime(String elapsed, String remaining) {
    timeElapsed.setText( elapsed );
    timeRemaining.setText( remaining);
  }

  private void 
  setStats(
  	String dl, String ul, 
	String dls, String uls,
	String ts, 
	String dl_speed, String ul_speed,
	String s, 
	String p,
	String hash_fails,
	String share_ratio,
	String ave_comp,
	String distr_copies
	) 
  {
    if (display == null || display.isDisposed())
      return;
    
	download.setText( dl );
	downloadSpeed.setText( dls );
	upload.setText( ul );
	uploadSpeed.setText( uls );
	totalSpeed.setText( ts );
	ave_completion.setText( ave_comp );
	distributedCopies.setText(distr_copies);
	
	/*
	if ( !maxDLSpeed.getText().equals( dl_speed )){
		
		maxDLSpeed.setText( dl_speed );
	}
	
	if ( !maxULSpeed.getText().equals( ul_speed )){
		
		maxULSpeed.setText( ul_speed );
	}
	*/
	
	seeds.setText( s);
	peers.setText( p); 
	hashFails.setText( hash_fails);
	shareRatio.setText( share_ratio);     
  }

  private void setTracker( DownloadManager	_manager ){
    if (display == null || display.isDisposed())
      return;
    
    String	status 	= _manager.getTrackerStatus();
    int		time	= _manager.getTrackerTime();
     
    TRTrackerAnnouncer	trackerClient = _manager.getTrackerClient();
	
	tracker_status.setText( status );
		
	if ( time < 0 ){
		
		trackerUpdateIn.setText( MessageText.getString("GeneralView.label.updatein.querying"));
		
	}else{
 	    
		trackerUpdateIn.setText(  TimeFormatter.formatColon( time )); 
	}
    
    boolean	update_state;
    
    String trackerURL = null;
    
    if ( trackerClient != null ){
    	
    	URL	temp = trackerClient.getTrackerUrl();
    	
    	if ( temp != null ){
    		
    		trackerURL	= temp.toString();
    	}
    }
    
    if ( trackerURL == null ){
    	
       	TOTorrent	torrent = _manager.getTorrent();
       	
       	if( torrent != null ){
       		
       		trackerURL = torrent.getAnnounceURL().toString();
       	}
    }
    
    if ( trackerURL != null ){
    	
		trackerUrlValue.setText( trackerURL );
				
		if((trackerURL.startsWith("http://")||trackerURL.startsWith("https://"))) {
		  trackerUrlValue.setForeground(Colors.blue);
		  trackerUrlValue.setCursor(Cursors.handCursor);
		  Messages.setLanguageText(trackerUrlValue.getWidget(), "GeneralView.label.trackerurlopen.tooltip", true);
		} else {
		  trackerUrlValue.setForeground(null);
		  trackerUrlValue.setCursor(null);
		  Messages.setLanguageText(trackerUrlValue.getWidget(), null);	
		  trackerUrlValue.setToolTipText(null);
		}
   	}
    
    if ( trackerClient != null ){
    	
    	update_state = ((SystemTime.getCurrentTime()/1000 - trackerClient.getLastUpdateTime() >= TRTrackerAnnouncer.REFRESH_MINIMUM_SECS ));
    	
    }else{
    	update_state = false;
    }
    
    if ( updateButton.getEnabled() != update_state ){
    
    	updateButton.setEnabled( update_state );
    }
  }

  private void setInfos(
    final String _fileName,
    final String _fileSize,
    final String _torrentStatus,
    final String _path,
    final String _hash,
    final String _pieceData,
    final String _pieceLength,
    final String _comment,
	final String _creation_date,
	final String _user_comment) {
    if (display == null || display.isDisposed())
      return;
    Utils.execSWTThread(new AERunnable(){
      public void runSupport() {
				fileName.setText(_fileName );
		fileSize.setText( _fileSize);
				torrentStatus.setText(_torrentStatus);
		saveIn.setText( _path);
		hash.setText( _hash);
		pieceNumber.setText( _pieceData); //$NON-NLS-1$
		pieceSize.setText( _pieceLength);
				creation_date.setText(_creation_date);

				boolean do_relayout = false;
				do_relayout = setCommentAndFormatLinks(lblComment, _comment) | do_relayout;
				do_relayout = setCommentAndFormatLinks(user_comment, _user_comment) | do_relayout;
				if (do_relayout) {gInfo.layout();}
				
			}
		});
	}
 
  private static boolean setCommentAndFormatLinks(Control c, String new_comment) {
	  String old_comment = (String)c.getData("comment");
	  if (new_comment == null) {new_comment = "";}
	  if (new_comment.equals(old_comment)) {return false;}
	
	  c.setData("comment", new_comment);
	  if (c instanceof Label) {
		  ((Label) c).setText(new_comment);
	  } else if (c instanceof Link) {
						String sNewComment;
		  sNewComment = new_comment.replaceAll(
								"([^=\">][\\s]+|^)(http://[\\S]+)", "$1<A HREF=\"$2\">$2</A>");
						// need quotes around url
		  sNewComment = sNewComment.replaceAll("(href=)(htt[^\\s>]+)", "$1\"$2\"");

						// Examples:
						// http://cowbow.com/fsdjl&sdfkj=34.sk9391 moo
						// <A HREF=http://cowbow.com/fsdjl&sdfkj=34.sk9391>moo</a>
						// <A HREF="http://cowbow.com/fsdjl&sdfkj=34.sk9391">moo</a>
						// <A HREF="http://cowbow.com/fsdjl&sdfkj=34.sk9391">http://moo.com</a>
		  ((Link)c).setText(sNewComment);
			  	        	}
	  return true;
			  	    
			          }

  public void parameterChanged(String parameterName) {
    graphicsUpdate = COConfigurationManager.getIntParameter("Graphics Update");
  }

	public Image obfusticatedImage(Image image, Point shellOffset) {
		UIDebugGenerator.obfusticateArea(image, (Control) fileName.getWidget(),
				shellOffset, manager.toString());
		UIDebugGenerator.obfusticateArea(image, (Control) saveIn.getWidget(),
				shellOffset, Debug.secretFileName(saveIn.getText()));
		return image;
}
}
