/*
 *  Copyright (C) 2001 Philip Langdale, Matthew Aubury
 *
 *  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, 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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "ProgressListener.h"

#include "eel-gconf-extensions.h"
#include "ephy-file-helpers.h"

#include <unistd.h>
#include <libgnome/gnome-exec.h>
#include <libgnome/gnome-i18n.h>

#include "nsXPIDLString.h"
#include "nsIChannel.h"
#include "nsIFTPChannel.h"
#include "nsIFactory.h"
#include "nsXPComFactory.h"
#include "nsIMIMEInfo.h"
#include "nsCOMPtr.h"

static void
download_remove_cb (DownloaderView *dv, GProgressListener *Progress);
static void
download_resume_cb (DownloaderView *dv, GProgressListener *Progress);
static void
download_pause_cb (DownloaderView *dv, GProgressListener *Progress);

NS_IMPL_ISUPPORTS4 (GProgressListener, nsIDownload, nsIWebProgressListener,
		    nsIProgressDialog, nsISupportsWeakReference)

//---------------------------------------------------------------------------

//---------------------------------------------------------------------------

GProgressListener::GProgressListener () : mLauncher(nsnull),
					  mPersist(nsnull),
				          mHandler(nsnull),
					  mObserver(nsnull),
					  mMIMEInfo(nsnull),
					  mPercentComplete(0)
{
	NS_INIT_ISUPPORTS ();
}

GProgressListener::~GProgressListener ()
{
	/* destructor code */
}

NS_METHOD GProgressListener::InitForPersist (nsIWebBrowserPersist *aPersist,
					      nsIDOMWindow *aParent, 
					      nsIURI *aURI,
					      nsIFile *aFile,
					      DownloadAction aAction,
					      EphyEmbedPersist *ephyPersist,
					      PRBool noDialog,
					      PRInt64 aTimeDownloadStarted)
{
	nsresult rv;

	/* fill in download details */
	mAction = aAction;
	mParent = aParent;
	mNoDialog = noDialog;
	mUri = aURI;
	mFile = aFile;
	mPersist = aPersist;
	mTimeDownloadStarted = aTimeDownloadStarted;
	mEphyPersist = ephyPersist;

	/* do remaining init */
	rv = PrivateInit ();

	/* pick up progress messages */
	mPersist->SetProgressListener (this);

	/* done */
	return rv;
}

NS_METHOD GProgressListener::InitForDownload (nsIHelperAppLauncher *aLauncher,
					       nsISupports *aContext,
					       GContentHandler *aHandler,
					       DownloadAction aAction)
{
	nsresult rv;

	mNoDialog = 0;

	/* fill in download details */
	mAction = aAction;
	mParent = do_QueryInterface (aContext);
	mNoDialog = PR_TRUE;
	mHandler = aHandler;
	mLauncher = aLauncher;
	rv = mLauncher->GetDownloadInfo (getter_AddRefs (mUri),
					 &mTimeDownloadStarted,
					 getter_AddRefs (mFile));

	/* do remaining init */
	rv = PrivateInit ();

	/* pick up progress messages */
	mLauncher->SetWebProgressListener (this);

	/* done */
	return rv;
}

NS_METHOD GProgressListener::PrivateInit (void)
{
	nsresult rv;

	/* setup this download */
	mInterval            = 500000;     /* in microsecs == 500ms == 0.5s */
	mPriorKRate          = 0;
	mRateChanges         = 0;
	mRateChangeLimit     = 2;          /* only update rate every second */
	mCheckedCanPause     = PR_FALSE;
	mCanPause            = PR_FALSE;
	mIsPaused            = PR_FALSE;
	mAbort               = PR_FALSE;
	PRInt64 now          = PR_Now ();
	mLastUpdate          = now;

//	mStartTime           = (mTimeDownloadStarted != 0 ? 
//				mTimeDownloadStarted : now);
	mStartTime           = now; //Stupid mozilla race condition

	mElapsed             = now - mStartTime;

	if (!mNoDialog)
	{
		gchar *filename, *source, *dest;
		nsAutoString uTmp;
		nsCAutoString cTmp;

		rv = mFile->GetLeafName (uTmp);
		filename = g_strdup (NS_ConvertUCS2toUTF8(uTmp).get());

		rv = mFile->GetPath (uTmp);
		dest = g_strdup (NS_ConvertUCS2toUTF8(uTmp).get());

		rv = mUri->GetSpec (cTmp);
		source = g_strdup (cTmp.get());
		DownloaderView *dv;
		dv = ephy_embed_shell_get_downloader_view (embed_shell);
		downloader_view_add_download (dv, filename, source, dest, (gpointer)this);
		g_signal_connect (G_OBJECT (dv), 
				  "download_remove",
				  G_CALLBACK (download_remove_cb),
				  this);
		g_signal_connect (G_OBJECT (dv), 
				  "download_pause",
				  G_CALLBACK (download_pause_cb),
				  this);
		g_signal_connect (G_OBJECT (dv), 
				  "download_resume",
				  G_CALLBACK (download_resume_cb),
				  this);
		mDownloaderView = dv;
	}

	/* done */
	return NS_OK;
}

NS_IMETHODIMP GProgressListener::Init(nsIURI *aSource,
                                      nsILocalFile *aTarget,
                                      const PRUnichar *aDisplayName,
				      nsIMIMEInfo *aMIMEInfo,
                                      PRInt64 aStartTime,
                                      nsIWebBrowserPersist *aPersist)
{
        mUri = aSource;
        mFile = aTarget;
        mTimeDownloadStarted = aStartTime;
        mStartTime = aStartTime;
        mPersist = aPersist;
	mMIMEInfo = aMIMEInfo;
        mAction = ACTION_NONE;
	if(mMIMEInfo)
	{
		nsMIMEInfoHandleAction mimeAction;
		if(NS_SUCCEEDED(mMIMEInfo->GetPreferredAction(&mimeAction)))
		{
			mAction = (mimeAction == nsIMIMEInfo::useHelperApp) ?
				  ACTION_SAVEFORHELPER : ACTION_NONE;
		}
	}
        mNoDialog = 0;

        return PrivateInit();
}

NS_IMETHODIMP GProgressListener::Open(nsIDOMWindow *aParent)
{
        mParent = aParent;
        mNoDialog = 0;

        return NS_OK;
}

/* attribute long long startTime; */
NS_IMETHODIMP GProgressListener::GetStartTime(PRInt64 *aStartTime)
{
        *aStartTime = mStartTime;
        return NS_OK;
}

/* attribute nsIURI source; */
NS_IMETHODIMP GProgressListener::GetSource(nsIURI * *aSource)
{
        NS_IF_ADDREF(*aSource = mUri);
        return NS_OK;
}

/* attribute nsILocalFile target; */
NS_IMETHODIMP GProgressListener::GetTarget(nsILocalFile * *aTarget)
{
        nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(mFile);
        NS_IF_ADDREF(*aTarget = localFile);
        return NS_OK;
}

NS_IMETHODIMP GProgressListener::GetMIMEInfo(nsIMIMEInfo * *aMIMEInfo)
{
	NS_IF_ADDREF(*aMIMEInfo = mMIMEInfo);
	return NS_OK;
}

/* attribute nsIObserver observer; */
NS_IMETHODIMP GProgressListener::GetObserver(nsIObserver * *aObserver)
{
        NS_IF_ADDREF(*aObserver = mObserver);
        return NS_OK;
}
NS_IMETHODIMP GProgressListener::SetObserver(nsIObserver * aObserver)
{
        mObserver = aObserver;
        return NS_OK;
}

/* attribute nsIWebProgressListener listener; */
NS_IMETHODIMP GProgressListener::GetListener(nsIWebProgressListener * *aListener)
{
        *aListener = nsnull;
        return NS_OK;
}
NS_IMETHODIMP GProgressListener::SetListener(nsIWebProgressListener * aListener)
{
        return NS_OK;
}

/* readonly attribute PRInt32 percentComplete; */
NS_IMETHODIMP GProgressListener::GetPercentComplete(PRInt32 *aPercentComplete)
{
        return *aPercentComplete = mPercentComplete;
}

/* attribute wstring displayName; */
NS_IMETHODIMP GProgressListener::GetDisplayName(PRUnichar * *aDisplayName)
{
        *aDisplayName = nsnull;
        return NS_OK;
}
NS_IMETHODIMP GProgressListener::SetDisplayName(const PRUnichar * aDisplayName)
{
        return NS_OK;
}

NS_IMETHODIMP GProgressListener::GetPersist(nsIWebBrowserPersist * *aPersist)
{
        NS_IF_ADDREF(*aPersist = mPersist);
        return NS_OK;
}

NS_IMETHODIMP GProgressListener::SetDialog(nsIDOMWindow *aDialog)
{
        return NS_OK;
}

NS_IMETHODIMP GProgressListener::GetDialog(nsIDOMWindow * *aDialog)
{
        *aDialog = nsnull;
        return NS_OK;
}

/* attribute PRBool cancelDownloadOnClose; */
NS_IMETHODIMP  GProgressListener::GetCancelDownloadOnClose(PRBool *aCancelDownloadOnClose)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP  GProgressListener::SetCancelDownloadOnClose(PRBool aCancelDownloadOnClose)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP GProgressListener::LaunchHandler (PersistHandlerInfo *handler)
{
	nsresult rv;
	nsCOMPtr<nsIExternalHelperAppService> helperService =
		do_GetService (NS_EXTERNALHELPERAPPSERVICE_CONTRACTID);

	nsCOMPtr<nsPIExternalAppLauncher> appLauncher =
		do_QueryInterface (helperService, &rv);
	if (NS_SUCCEEDED(rv))
	{
		appLauncher->DeleteTemporaryFileOnExit(mFile);
	}

	nsAutoString uFileName;
        
	mFile->GetPath(uFileName);
        const nsACString &cFileName = NS_ConvertUCS2toUTF8(uFileName);
	
	char *fname = g_strdup(PromiseFlatCString(cFileName).get());
	ephy_file_launch_application (handler->command,
				      fname,
				      handler->need_terminal);
	g_free (fname);

	return NS_OK;
}

/*
 * void onStateChange (in nsIWebProgress aWebProgress, 
 *                     in nsIRequest aRequest, 
 *		       in long aStateFlags, 
 *		       in unsigned long aStatus);
 */
NS_IMETHODIMP GProgressListener::OnStateChange (nsIWebProgress *aWebProgress,
						 nsIRequest *aRequest,
						 PRUint32 aStateFlags,
						 PRUint32 aStatus)
{
	if (mAbort) return NS_ERROR_FAILURE;

	if (aStateFlags & nsIWebProgressListener::STATE_STOP)
	{
		switch (mAction)
                {
		case ACTION_SAVEFORHELPER:
			LaunchHelperApp();
			break;
			
		case ACTION_NONE:
			if (mLauncher)
			{
				mLauncher->CloseProgressWindow ();
			}
			break;
		case ACTION_OBJECT_NOTIFY:

			g_return_val_if_fail (IS_EPHY_EMBED_PERSIST (mEphyPersist),
					      NS_ERROR_FAILURE);
			
			PersistHandlerInfo *handler;
			
			g_object_get (mEphyPersist,
		      		      "handler", &handler,        
		      		      NULL);

			if (handler)
			{
				LaunchHandler (handler);
			}
			
			g_signal_emit_by_name (mEphyPersist, "completed");
		}
		
		if (!mNoDialog)
		{
			downloader_view_set_download_status (mDownloaderView, 
							     DOWNLOAD_STATUS_COMPLETED, 
							     (gpointer)this);
		}
	}

	/* done */
	return NS_OK;
}

/*
 * void onProgressChange (in nsIWebProgress aWebProgress, 
 *                        in nsIRequest aRequest, 
 *                        in long aCurSelfProgress, 
 *                        in long aMaxSelfProgress, 
 *                        in long aCurTotalProgress, 
 *                        in long aMaxTotalProgress); 
 */
NS_IMETHODIMP GProgressListener::
			OnProgressChange (nsIWebProgress *aWebProgress,
					  nsIRequest *aRequest,
					  PRInt32 aCurSelfProgress,
					  PRInt32 aMaxSelfProgress,
					  PRInt32 aCurTotalProgress,
					  PRInt32 aMaxTotalProgress)
{
	if (mAbort) return NS_ERROR_FAILURE;

	/* FIXME maxsize check here */

	if (mNoDialog) return NS_OK;

	if (!mCheckedCanPause)
	{
		mCheckedCanPause = PR_TRUE;

		nsresult rv;
		nsCOMPtr<nsIFTPChannel> channel = 
			do_QueryInterface (aRequest, &rv);

		mCanPause = (NS_SUCCEEDED (rv) ? PR_TRUE : PR_FALSE);
	}
	mRequest = aRequest;

	PRInt64 now = PR_Now ();

	/* get out if we're updating too quickly */
	if ((now - mLastUpdate < mInterval) && 
	    (aMaxTotalProgress != -1) &&  
	    (aCurTotalProgress < aMaxTotalProgress))
	{
		return NS_OK;
	}

	/* compute elapsed time */
	mLastUpdate = now;
	mElapsed = now - mStartTime;

	/* compute size done */
	PRInt32 currentKBytes = (PRInt32)(aCurTotalProgress / 1024.0 + 0.5);

	/* compute total size */
	PRInt32 totalKBytes = (PRInt32)(aMaxTotalProgress / 1024.0 + 0.5);

	/* compute progress value */
	gfloat progress = -1;
	if (aMaxTotalProgress > 0)
	{
		progress = (gfloat)aCurTotalProgress /
			(gfloat)aMaxTotalProgress;
	}

	/* compute download rate */
	gfloat speed = -1;
	PRInt64 currentRate;
	if (mElapsed)
	{
		currentRate = ((PRInt64)(aCurTotalProgress)) * 1000000 / 
			mElapsed;
	}
	else
	{
		currentRate = 0;
	}		

	if (!mIsPaused)
	{
		if (currentRate)
		{
			PRFloat64 currentKRate = ((PRFloat64)currentRate)/1024;
			if (currentKRate != mPriorKRate)
			{
				if (mRateChanges++ == mRateChangeLimit)
				{
					mPriorKRate = currentKRate;
					mRateChanges = 0;
				}
				else
				{
					currentKRate = mPriorKRate;
				}
			}
			else
			{
				mRateChanges = 0;
			}
			
			 speed = currentKRate;
		}
	}
	
	/* compute time remaining */
	gint remaining = -1;
	if (currentRate && (aMaxTotalProgress > 0))
	 {
		 remaining = 
			 (gint)((aMaxTotalProgress - aCurTotalProgress)
				/currentRate +.5);
	}

	downloader_view_set_download_progress (mDownloaderView,
					       mElapsed,
					       remaining,
					       speed,
					       totalKBytes,
					       currentKBytes,
					       progress,
					       mCanPause,
					       (gpointer)this);

	/* done */
	return NS_OK;
}

/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location); */
NS_IMETHODIMP GProgressListener::
			OnLocationChange(nsIWebProgress *aWebProgress,
					 nsIRequest *aRequest, nsIURI *location)
{
    return NS_OK;
}

/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */
NS_IMETHODIMP GProgressListener::
			OnStatusChange(nsIWebProgress *aWebProgress,
				       nsIRequest *aRequest, nsresult aStatus,
				       const PRUnichar *aMessage)
{
    return NS_OK;
}

/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long state); */
NS_IMETHODIMP GProgressListener::
			OnSecurityChange(nsIWebProgress *aWebProgress,
					 nsIRequest *aRequest,
					 PRUint32 state)
{
    return NS_OK;
}

//---------------------------------------------------------------------------

NS_METHOD GProgressListener::LaunchHelperApp (void)
{
	if (!mMIMEInfo)
		return NS_ERROR_FAILURE;

	nsresult rv;

	nsCOMPtr<nsIFile> helperFile;
	rv = mMIMEInfo->GetPreferredApplicationHandler(getter_AddRefs(helperFile));
	if(NS_FAILED(rv)) return NS_ERROR_FAILURE;

	nsCAutoString helperFileName;
	rv = helperFile->GetNativePath(helperFileName);
	if(NS_FAILED(rv)) return NS_ERROR_FAILURE;

	nsMIMEInfoHandleAction mimeAction;
	rv = mMIMEInfo->GetPreferredAction(&mimeAction);
	if(NS_FAILED(rv)) return NS_ERROR_FAILURE;

	nsCOMPtr<nsIExternalHelperAppService> helperService =
		do_GetService (NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
	if (NS_SUCCEEDED(rv))
	{
		nsCOMPtr<nsPIExternalAppLauncher> appLauncher =
			do_QueryInterface (helperService, &rv);
		if (NS_SUCCEEDED(rv))
		{
			appLauncher->DeleteTemporaryFileOnExit(mFile);
		}
	}

	nsCAutoString cFileName;
	mFile->GetNativePath(cFileName);
	if(NS_FAILED(rv)) return NS_ERROR_FAILURE;

	nsXPIDLString helperDesc;
	rv = mMIMEInfo->GetApplicationDescription(getter_Copies(helperDesc));
	if(NS_FAILED(rv)) return NS_ERROR_FAILURE;

	gboolean terminalHelper = 
		helperDesc.Equals(NS_LITERAL_STRING("runInTerminal")) ?
		TRUE : FALSE;

	ephy_file_launch_application (helperFileName.get(),
				      cFileName.get(),
				      terminalHelper);

	return NS_OK;
}

nsresult GProgressListener::Pause (void)
{
        nsresult rv;

        if (mCanPause && !mIsPaused)
        {
                rv = mRequest->Suspend ();
                if (NS_SUCCEEDED (rv))
                {
                        mIsPaused = PR_TRUE;
                }
        }
        else
        {
                rv = NS_ERROR_FAILURE;
        }

        return rv;
}

nsresult GProgressListener::Resume (void)
{
        nsresult rv;

        if (mCanPause && mIsPaused)
        {
                rv = mRequest->Resume ();
                if (NS_SUCCEEDED (rv))
                {
                        mIsPaused = PR_FALSE;
                }
        }
        else
        {
                rv = NS_ERROR_FAILURE;
        }

        return rv;
}

nsresult GProgressListener::Abort (void)
{
        mAction = ACTION_NONE;

        if (mIsPaused)
        {
                Resume ();
        }

        mAbort = PR_TRUE;

        if (mObserver)
        {
                mObserver->Observe(NS_ISUPPORTS_CAST(nsIProgressDialog*, this),
                                             "oncancel", nsnull);
                OnStateChange(nsnull, nsnull,
                              nsIWebProgressListener::STATE_STOP, 0);
        }

        if (mPersist)
        {
                return mPersist->CancelSave ();
        }

        if (mLauncher)
        {
                return mLauncher->Cancel ();
        }
        else
        {
                return NS_ERROR_FAILURE;
        }
}

NS_DEF_FACTORY (GProgressListener, GProgressListener);

/**
 * NS_NewProgressListenerFactory:
 */ 
nsresult NS_NewProgressListenerFactory(nsIFactory** aFactory)
{
        NS_ENSURE_ARG_POINTER(aFactory);
        *aFactory = nsnull;

        nsGProgressListenerFactory *result = new nsGProgressListenerFactory;
        if (result == NULL)
        {
                return NS_ERROR_OUT_OF_MEMORY;
        }
    
        NS_ADDREF(result);
        *aFactory = result;

        return NS_OK;
}

static void
download_remove_cb (DownloaderView *dv, GProgressListener *Progress)
{
	Progress->Abort();
}

static void
download_resume_cb (DownloaderView *dv, GProgressListener *Progress)
{
	Progress->Resume();
}

static void
download_pause_cb (DownloaderView *dv, GProgressListener *Progress)
{
	Progress->Pause();
}
