/**
* Created on Apr 17, 2007
* Created by Alan Snyder
* Copyright (C) 2007 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 63.529,40 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/

package com.aelitis.azureus.core.networkmanager.admin.impl;

import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminSpeedTestScheduler;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminSpeedTester;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminSpeedTesterResult;

import org.gudy.azureus2.core3.util.*;
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.DownloadManagerPeerListener;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.peer.PEPiece;
import org.gudy.azureus2.core3.security.SECertificateListener;
import org.gudy.azureus2.core3.security.SESecurityManager;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.download.DownloadStats;
import org.gudy.azureus2.plugins.download.DownloadRemovalVetoException;
import org.gudy.azureus2.plugins.download.DownloadException;
import org.gudy.azureus2.plugins.peers.Peer;
import org.gudy.azureus2.plugins.peers.PeerManager;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.plugins.torrent.TorrentAttribute;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
import org.gudy.azureus2.pluginsimpl.local.torrent.TorrentImpl;

import java.security.cert.X509Certificate;
import java.util.*;
import java.io.File;
import java.net.URL;
import java.text.SimpleDateFormat;


public class NetworkAdminSpeedTesterBTImpl 
	extends NetworkAdminSpeedTesterImpl
	implements NetworkAdminSpeedTester
{
    public static final String DOWNLOAD_AVE = "download-ave";
    public static final String UPLOAD_AVE = "upload-ave";
    public static final String DOWNLOAD_STD_DEV = "download-std-dev";
    public static final String UPLOAD_STD_DEV = "upload-std-dev";

    private static int testMode = TEST_TYPE_UPLOAD_ONLY;

    private static TorrentAttribute speedTestAttrib;

    private static NetworkAdminSpeedTesterResult	lastResult;

   
    protected static void
    startUp(
    	PluginInterface	plugin )
    {
    	speedTestAttrib = plugin.getTorrentManager().getPluginAttribute(NetworkAdminSpeedTesterBTImpl.class.getName()+".test.attrib");
  
    	org.gudy.azureus2.plugins.download.DownloadManager dm = plugin.getDownloadManager();
    	Download[] downloads = dm.getDownloads();

    	if(downloads!=null){
    		int num = downloads.length;
    		for(int i=0; i<num; i++){
    			Download	download = downloads[i];
    			if( download.getBooleanAttribute(speedTestAttrib) ){
    				try{
    					if (download.getState() != Download.ST_STOPPED ){
    						try{
    							download.stop();
    						}catch( Throwable e ){
    							Debug.out(e);
    						}
    					}
    					download.remove(true,true);
     				}catch(Throwable e ){
    					Debug.out("Had "+e.getMessage()+" while trying to remove "+downloads[i].getName());
    				}
    			}
    		}
    	}
    }

    protected static NetworkAdminSpeedTesterResult 
    getLastResult()
    {
    	return( lastResult );
    }

    
    private PluginInterface plugin;

     
    private boolean	test_started;
    private boolean	test_completed;
    
    private boolean	use_crypto;
    
    private volatile boolean	aborted;
    private String				deferred_abort;

    /**
     *
     * @param pi - PluginInterface is used to get Manager classes.
     */
    public NetworkAdminSpeedTesterBTImpl(PluginInterface pi){
        plugin = pi;
    }

    public int
    getTestType()
    {
    	return( NetworkAdminSpeedTestScheduler.TEST_TYPE_BT );
    }

    public void setMode(int mode) {
        testMode = mode;
    }

    public int
    getMode()
    {
    	return( testMode );
    }
    
    public void
    setUseCrypto(
    	boolean	_use_crypto )
    {
    	use_crypto = _use_crypto;
    }
    
    public boolean
    getUseCrypto()
    {
    	return( use_crypto );
    }
    
    /**
     * The downloads have been stopped just need to do the testing.
     * @param tot - Torrent recieved from testing service.
     */
    public synchronized void 
    start( 
    	TOTorrent	tot )
    {
    	if ( test_started ){
    		
    		Debug.out( "Test already started!" );
    		
    		return;
    	}
    	
    	test_started = true;
    	
        //OK lets start the test.
        try{
            TorrentUtils.setFlag( tot, TorrentUtils.TORRENT_FLAG_LOW_NOISE, true );
            
            Torrent torrent = new TorrentImpl(tot);
            String fileName = torrent.getName();

            sendStageUpdateToListeners(MessageText.getString("SpeedTestWizard.stage.message.preparing"));
            	
            //create a blank file of specified size. (using the temporary name.)
            File saveLocation = AETemporaryFileHandler.createTempFile();
            File baseDir = saveLocation.getParentFile();
            File blankFile = new File(baseDir,fileName);
            File blankTorrentFile = new File( baseDir, "speedTestTorrent.torrent" );
            torrent.writeToFile(blankTorrentFile);

            URL	announce_url = torrent.getAnnounceURL();
            
            if ( announce_url.getProtocol().equalsIgnoreCase( "https" )){
            	
            	SESecurityManager.setCertificateHandler( 
            			announce_url,
            			new SECertificateListener()
            			{
            				public boolean
            				trustCertificate(
            					String			resource,
            					X509Certificate	cert )
            				{
            					return( true );
            				}
            			});
            }
            
            Download speed_download = plugin.getDownloadManager().addDownloadStopped( torrent, blankTorrentFile ,blankFile);

            speed_download.setBooleanAttribute(speedTestAttrib,true);

            DownloadManager core_download = PluginCoreUtils.unwrap( speed_download );
            
            core_download.setPieceCheckingEnabled( false );
            
            	// make sure we've got a bunch of upload slots
            
            core_download.getDownloadState().setIntParameter( DownloadManagerState.PARAM_MAX_UPLOADS, 32 );
            core_download.getDownloadState().setIntParameter( DownloadManagerState.PARAM_MAX_UPLOADS_WHEN_SEEDING, 32 );
            
            if ( use_crypto ){
            	
            	core_download.setCryptoLevel( NetworkManager.CRYPTO_OVERRIDE_REQUIRED );
            }
            
            core_download.addPeerListener(
            		new DownloadManagerPeerListener()
            		{
            			public void
            			peerManagerWillBeAdded(
            				PEPeerManager	peer_manager )
            			{
              				DiskManager	disk_manager = peer_manager.getDiskManager();
              				
                			DiskManagerPiece[]	pieces = disk_manager.getPieces();

                            int startPiece = setStartPieceBasedOnMode(testMode,pieces.length);
                            
                            for ( int i=startPiece; i<pieces.length; i++ ){
                                pieces[i].setDone( true );
                			}
            			}
            			
            			public void
            			peerManagerAdded( PEPeerManager	peer_manager )
            			{
            			}
            			
            			public void
            			peerManagerRemoved(PEPeerManager	manager )
            			{    				
            			}
            			
            			public void
            			peerAdded(PEPeer 	peer )
            			{	
            			}
            				
            			public void
            			peerRemoved(PEPeer	peer )
            			{	
            			}
            				
            			public void
            			pieceAdded(PEPiece 	piece )
            			{	
            			}
            				
            			public void
            			pieceRemoved(PEPiece		piece )
            			{	
            			}
                	});
 
            speed_download.moveTo( 1 );
            
            speed_download.setFlag( Download.FLAG_DISABLE_AUTO_FILE_MOVE, true );
            
            core_download.initialize();
            
            core_download.setForceStart( true );
            
            TorrentSpeedTestMonitorThread monitor = new TorrentSpeedTestMonitorThread( speed_download );
            
            monitor.start();

            //The test has now started!!

        }catch( Throwable e){
        	
        	test_completed = true;
        	
            abort( "Could not start test", e );
        }
    }

	
    public void
    complete(
    	NetworkAdminSpeedTesterResult		result )
    {
    	sendResultToListeners( result );
    }
    
    protected void
    abort(
    	String		reason,
    	Throwable	cause )
    {
    	String	msg;
    	
    	if ( cause instanceof RuntimeException ){
    		
    		msg = Debug.getNestedExceptionMessageAndStack( cause );
    			
    	}else{
    		
    		msg = Debug.getNestedExceptionMessage( cause );
    	}
    	
    	abort( reason + ": " + msg );
    }
    
	public void 
	abort( 
		String reason )
	{
		reason = "Test aborted: " + reason;
		
		synchronized( this ){
			
			if ( aborted ){
				
				return;
			}
			
			aborted = true;
		
				// we need to defer the reporting of a failure until the test is complete
				// as this prevents us from starting another test while the current one is
				// terminating
			
			if ( test_started && !test_completed ){
				
				deferred_abort = reason;
				
				return;
			}
		}
		
        sendResultToListeners( new BitTorrentResult( reason ));
    }


    /**
	 * Get the result for 
	 * @return Result object of speed test.
	 */
	public NetworkAdminSpeedTesterResult getResult(){
        return lastResult;
    }


    // ------------------ private methods ---------------

    /**
     * Depending on the mode we want to upload all the set all, none or only
     * half the pieces to done.
     * @param mode - int that maps to NetworkAdminSpeedTestScheduler.TEST_TYPE...
     * @param totalPieces - total pieces in this test torrent.
     * @return - int - the starting piece number to setDone to true.
     */
    private static int setStartPieceBasedOnMode(int mode, int totalPieces){

        //if(mode==TEST_TYPE_UPLOAD_AND_DOWNLOAD){
        //    //upload half the pieces
        //    return totalPieces/2;
        //}else
        if(mode==TEST_TYPE_UPLOAD_ONLY){
            //upload all the pieces
            return 0;
        }else if(mode==TEST_TYPE_DOWNLOAD_ONLY){
            //download all the pieces
            return totalPieces;
        }
        else
            throw new IllegalStateException("Did not recognize the NetworkAdmin Speed Test type. mode="+mode);
    }


    /**   -------------------- helper class to monitor test. ------------------- **/
    private class TorrentSpeedTestMonitorThread
        extends Thread
    {
        List historyDownloadSpeed = new LinkedList();  //<Long>
        List historyUploadSpeed = new LinkedList();    //<Long>
        List timestamps = new LinkedList();            //<Long>

        Download testDownload;
 
        public static final long MAX_TEST_TIME = 2*60*1000; //Limit test to 2 minutes.
        public static final long MAX_PEAK_TIME = 30 * 1000; //Limit to 30 seconds at peak.
        long startTime;
        long peakTime;
        long peakRate;

        public static final String AVE = "ave";
        public static final String STD_DEV = "stddev";

        public TorrentSpeedTestMonitorThread( Download d )
        {
            testDownload = d;
        }

        public void run()
        {
        	try{
	            Set	connected_peers 	= new HashSet();
	            Set	not_choked_peers 	= new HashSet();
	            Set	not_choking_peers 	= new HashSet();
	                           
	            try{
	            
	                startTime = SystemTime.getCurrentTime();
	                peakTime = startTime;
	
	                boolean testDone=false;
	                long lastTotalTransferredBytes=0;

                      sendStageUpdateToListeners(MessageText.getString("SpeedTestWizard.stage.message.starting"));
                      while( !( testDone || aborted )){
	
	                	int state = testDownload.getState();
	                	
	                	if ( state == Download.ST_ERROR ){

                            String enteredErrorState = MessageText.getString("SpeedTestWizard.abort.message.entered.error"
                                    , new String[] {testDownload.getErrorStateDetails()} );
                            abort( enteredErrorState );
	                		
	                		break;
	                	}
	                	
	                	if (  state == Download.ST_STOPPED ){

                            abort( MessageText.getString("SpeedTestWizard.abort.message.entered.queued") );
	                		
	                		break;
	                	}
	                	
	                		// can flick out of force-mode when transitioning from downloading
	                		// to seeding - easiest fix is:
	                	
	                	if ( !testDownload.isForceStart()){
	                		
	                		testDownload.setForceStart( true );
	                	}
	                	
	                	PeerManager pm = testDownload.getPeerManager();
	                	
	                	if ( pm != null ){
	                		
	                		Peer[] peers = pm.getPeers();
	                		
	                		for ( int i=0;i<peers.length;i++){
	                			
	                			Peer peer = peers[i];
	                			
	                				// use the IP as the key so we don't count reconnects multiple times
	                			
	                			String	key = peer.getIp();
	                			
	                			connected_peers.add( key );
	                			
	                			if ( !peer.isChoked()){
	                				
	                				not_choked_peers.add( key );
	                			}
	                			
	                			if ( !peer.isChoking()){
	                				
	                				not_choking_peers.add( key );
	                			}
	                		}
	                	}
	                	
	                    long currTime = SystemTime.getCurrentTime();
	                    DownloadStats stats = testDownload.getStats();
	                    historyDownloadSpeed.add( autoboxLong(stats.getDownloaded()) );
	                    historyUploadSpeed.add( autoboxLong(stats.getUploaded()) );
	                    timestamps.add( autoboxLong(currTime) );
	
	                    updateTestProgress(currTime,stats);
	
	                    lastTotalTransferredBytes = checkForNewPeakValue( stats, lastTotalTransferredBytes, currTime );
	
	                    testDone = checkForTestDone();
	                    if(testDone)
	                        break;
	
	                    try{ Thread.sleep(1000); }
	                    catch(InterruptedException ie){
	                        //someone interrupted this thread for a reason. "test is now over"
                            abort( MessageText.getString("SpeedTestWizard.abort.message.interrupted") );
	 
	                        break;
	                    }
	
	                }
	
	                //It is time to stop the test.
	                try{
	                	if ( testDownload.getState() != Download.ST_STOPPED){
	                		try{
	                			testDownload.stop();
	                		}catch( Throwable e ){
	                			Debug.printStackTrace(e);
	                		}
	                	}
	                	testDownload.remove(true,true);
	                	
	                }catch(DownloadException de){
	                	
	                    abort( "TorrentSpeedTestMonitorThread could not stop the torrent "+testDownload.getName(), de);
	                    
	                }catch(DownloadRemovalVetoException drve){

	                	abort( "TorrentSpeedTestMonitorTheard could not remove the torrent "+testDownload.getName(), drve);
	                }
	
	            }catch(Exception e){

                    abort( MessageText.getString("SpeedTestWizard.abort.message.execution.failed"),e );
                }
	
	            if ( !aborted ){
	            	
	            		// check the stats for peers we connected to during the test
                    String connectStats = MessageText.getString("SpeedTestWizard.stage.message.connect.stats",
                            new String[]{""+connected_peers.size()
                                    ,""+not_choked_peers.size()
                                    ,""+not_choking_peers.size()
                            });
                    sendStageUpdateToListeners(connectStats);

                    if ( connected_peers.size() == 0 ){

                        abort( MessageText.getString("SpeedTestWizard.abort.message.failed.peers") );
	
	                }else if ( not_choking_peers.size() == 0 && testMode!=TEST_TYPE_DOWNLOAD_ONLY ){

                        abort( MessageText.getString("SpeedTestWizard.abort.message.insufficient.slots") );
		            	
		            }else if ( not_choked_peers.size() == 0 && testMode!=TEST_TYPE_UPLOAD_ONLY){

                        abort( MessageText.getString("SpeedTestWizard.abort.message.not.unchoked") );
		            }
	            }
	            
	            if ( !aborted ){
	            	
		            //calculate the measured download rate.
		            NetworkAdminSpeedTesterResult r = calculateDownloadRate();
		
		            lastResult = r;
		            
		            	// TODO: persist it
		            
		            //Log the result.
		            AEDiagnosticsLogger diagLogger = AEDiagnostics.getLogger("v3.STres");
		            diagLogger.log(r.toString());
		
		            complete(r);
	            }
        	}finally{
        		
        		synchronized( NetworkAdminSpeedTesterBTImpl.this ){
        			
        			test_completed	= true;
        			
        			if ( deferred_abort != null ){
        				
        		        sendResultToListeners( new BitTorrentResult( deferred_abort ));
        			}
        		}
        	}
        }//run.

        /**
         * Calculate the test progression as a value between 0-100.
         * @param currTime - current time as long.
         * @param stats - Download stats
         */
        public void updateTestProgress(long currTime, DownloadStats stats){

            //do two calculations. Frist based on the total time allowed for the test
            long totalDownloadTimeUsed = currTime-startTime;
            float percentTotal = ((float)totalDownloadTimeUsed/(float)MAX_TEST_TIME);

            //second for the time since the peak value has been reached.
            long totalTestTimeUsed = currTime-peakTime;
            float percentDownload = ((float)totalTestTimeUsed/(float)MAX_PEAK_TIME);

            //the larger of the two wins.
            float reportedProgress = percentTotal;
            if( percentDownload>reportedProgress )
                reportedProgress=percentDownload;

            int progressBarVal = Math.round( reportedProgress*100.0f );
            StringBuffer msg = new StringBuffer("progress: ");
            msg.append(  progressBarVal );
            //include the upload and download values.
            msg.append(" : download ave ");
            msg.append( stats.getDownloadAverage() );
            msg.append(" : upload ave ");
            msg.append( stats.getUploadAverage() );
            msg.append(" : ");
            int totalTimeLeft = (int)((MAX_TEST_TIME-totalDownloadTimeUsed)/1000);
            msg.append(totalTimeLeft);
            msg.append(" : ");
            int testTimeLeft = (int)((MAX_PEAK_TIME-totalTestTimeUsed)/1000);
            msg.append(testTimeLeft);

            sendStageUpdateToListeners( msg.toString() );

        }//updateTestProgress


        /**
         * Calculate the avererage and standard deviation for a history.
         * @param history - List of Long values but that contains the sum downloaded at that time.
         * @return Map<String,Double> with values "ave" and "stddev" set
         */
        //Map<String,Double> calculate(List<Long> history)
        private Map calculate(List history){

            //convert the list of long values that sum the value into a list of deltas.
            List deltas = convertSumToDeltas(history);

            //sort
            Collections.sort(deltas);

            //remove the top and bottom 10% of the sample. This removes outliers from the mean.
            final int nSamples = deltas.size();
            final int nRemove = nSamples/10;

            //removing high values.
            for(int i=nSamples-1; i<nSamples-nRemove; i--){
                deltas.remove(i);
            }
            //remove low values.
            for(int i=0; i<nRemove; i++){
                deltas.remove(0);
            }
            //sum values
            long sumBytes=0;
            int j=0;
            while(j<deltas.size()){
                sumBytes += autoboxLong( deltas.get(j) );
                j++;
            }
            //calculate average.
            double aveRate = (double) ( sumBytes/deltas.size() );
            //Debug.out("ave rate:"+aveRate);

            //calculate standard deviation.
            double variance = 0.0;
            double s;
            for(j=0;j<deltas.size();j++){
                //Debug.out( j+","+deltas.get(j) );

                s = ( autoboxLong(deltas.get(j)) - aveRate );
                variance += s*s;
            }
            double stddev = Math.sqrt( variance/(j-1) );

            //Map<String,Double> retVal = new HashMap<String,Double>();
            Map retVal = new HashMap();
            retVal.put(AVE, autoboxDouble(aveRate));
            retVal.put(STD_DEV,autoboxDouble(stddev));
            return retVal;
        }//calculate


        /**
         * Convert a list of sums into a list of download rates per second.
         * @param sumHistory - List<Long> with download sum for each second.
         * @return - List<Long> with the download rate for each second.
         */
        private List convertSumToDeltas(List sumHistory){
            //find the first element to inlcude in the stat.
            int numStats = sumHistory.size();
            int i = findIndexPeak(numStats);

            List deltas = new ArrayList(numStats);
            long prevSumDownload = autoboxLong(sumHistory.get(i-1));
            long currSumDownload;
            while(i<numStats){

                currSumDownload = autoboxLong( sumHistory.get(i) );
                Long currDelta = autoboxLong( currSumDownload - prevSumDownload );

                deltas.add(currDelta);
                i++;
                prevSumDownload = currSumDownload;
            }//while

            return deltas;
        }//convertSumToDeltas

        private int findIndexPeak(int numStats) {
            long thisTime;
            int i;
            for(i=0;i<numStats;i++ ){
                thisTime = autoboxLong( timestamps.get(i) );
                if(thisTime>peakTime){
                    break;
                }
            }//for
            return i;
        }

        /**
         * Based on the previous data cancluate an average and a standard deviation.
         * Return this data in a Map object.
         * @return Map<String,Float> as a contain for stats. Map keys are "ave" and "dev".
         */
        NetworkAdminSpeedTesterResult calculateDownloadRate()
        {
            //calculate the BT download rate.
            //Map<String,Double> resDown = calculate(historyDownloadSpeed);
            Map resDown = calculate(historyDownloadSpeed);

            //calculate the BT upload rate.
            //Map<String,Double> resUp = calculate(historyUploadSpeed);
            Map resUp = calculate(historyUploadSpeed);

            return new BitTorrentResult(resUp,resDown);
        }//calculateDownloadRate


        /**
         * In this version the test is limited to MAX_TEST_TIME since the start of the test
         * of MAX_PEAK_TIME (i.e. time since the peak download rate has been reached). Which
         * ever condition is first will finish the download.
         * @return true if the test done condition has been reached.
         */
        boolean checkForTestDone(){

            long currTime = SystemTime.getCurrentTime();
            //have we reached the max time for this test?
            if( (currTime-startTime)>MAX_TEST_TIME ){
                return true;
            }

            //have we been near the peak download value for max time?
            return (currTime - peakTime) > MAX_PEAK_TIME;
        }//checkForTestDone


        /**
         * We set a new "peak" value if it has exceeded the previous peak value by 10%.
         * @param stat -
         * @param lastTotalDownload -
         * @param currTime -
         * @return total downloaded so far.
         */
        long checkForNewPeakValue(DownloadStats stat, long lastTotalDownload, long currTime)
        {
            //upload only used the "uploaded" data. The "download only" and "both" uses download.
            long totTransferred;
            if(testMode==TEST_TYPE_UPLOAD_ONLY){
                totTransferred = stat.getUploaded();
            }else{
                totTransferred = stat.getDownloaded();
            }
            long currTransferRate = totTransferred-lastTotalDownload;

            //if the current rate is 10% greater then the previous max, reset the max, and test timer.
            if( currTransferRate > peakRate ){
                peakRate = (long) (currTransferRate*1.1);
                peakTime = currTime;
            }

            return totTransferred;
        }//checkForNewPeakValue

    }//class TorrentSpeedTestMonitorThread

    class BitTorrentResult implements NetworkAdminSpeedTesterResult{

        long time;
        int downspeed;
        int upspeed;
        boolean hadError = false;
        String lastError = "";

        /**
         * Build a Result for a successful test.
         * @param uploadRes - Map<String,Double> of upload results.
         * @param downloadRes - Map<String,Double> of download results.
         */
        public BitTorrentResult(Map uploadRes, Map downloadRes){
            time = SystemTime.getCurrentTime();
            Double dAve = (Double)downloadRes.get(TorrentSpeedTestMonitorThread.AVE);
            Double uAve = (Double)uploadRes.get(TorrentSpeedTestMonitorThread.AVE);
            downspeed = dAve.intValue();
            upspeed = uAve.intValue();
        }

        /**
         * Build a Result if the test failed with an error.
         * @param errorMsg - why the test failed.
         */
        public BitTorrentResult(String errorMsg){
            time = SystemTime.getCurrentTime();
            hadError=true;
            lastError = errorMsg;
        }

        public NetworkAdminSpeedTester getTest() {
        	return( NetworkAdminSpeedTesterBTImpl.this );
        }
        
        public long getTestTime() {
            return time;
        }

        public int getDownloadSpeed() {
            return downspeed;
        }

        public int getUploadSpeed() {
            return upspeed;
        }

        public boolean hadError() {
            return hadError;
        }

        public String getLastError() {
            return lastError;
        }

        public String getResultString(){
            StringBuffer sb = new StringBuffer();

            //Time
            SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmss z");
            String d = format.format( new Date(time) );
            sb.append(d).append(" ");

            sb.append("type: BT test ");

            //Get test info.
            sb.append("mode: ").append( getMode() );

            //Get crypto
            sb.append(" encrypted: ");
            if( use_crypto ){
                sb.append("y");
            }else{
                sb.append("n");
            }

            if(hadError){
                //Error
                sb.append(" Last Error: ").append(lastError);
            }else{
                //Result
                sb.append(" download speed: ").append(downspeed).append(" bits/sec");
                sb.append(" upload speed: ").append(upspeed).append(" bits/sec");
            }

            return sb.toString();
        }//getString


        public String toString(){
            StringBuffer sb = new StringBuffer("[com.aelitis.azureus.core.networkmanager.admin.impl.NetworkAdminSpeedTesterBTImpl");

            sb.append(" ").append( getResultString() ).append(" ");
            sb.append("]");

            return sb.toString();
        }
    }//class BitTorrentResult


    private static long autoboxLong(Object o){
        return autoboxLong( (Long) o );
    }

    private static long autoboxLong(Long l){
        return l.longValue();
    }

    private static Long autoboxLong(long l){
        return new Long(l);
    }

    private static Double autoboxDouble(double d){
        return new Double(d);
    }
}//class
