/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Mozilla Communicator client code, 
 * released March 31, 1998. 
 *
 * The Initial Developer of the Original Code is Netscape Communications 
 * Corporation.  Portions created by Netscape are
 * Copyright (C) 1998-1999 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 *     Daniel Veditz <dveditz@netscape.com>
 *     Pierre Phaneuf <pp@ludusdesign.com>
 */

#include "nscore.h"
#include "nsFileSpec.h"
#include "pratom.h"

#include "nsISupports.h"
#include "nsIServiceManager.h"

#include "nsIURL.h"

#include "nsNetUtil.h"
#include "nsIBufferInputStream.h"
#include "nsIInputStream.h"
#include "nsIStreamListener.h"

#include "nsISoftwareUpdate.h"
#include "nsSoftwareUpdateIIDs.h"

#include "nsXPITriggerInfo.h"
#include "nsXPInstallManager.h"
#include "nsInstallProgressDialog.h"
#include "nsInstallResources.h"
#include "nsSpecialSystemDirectory.h"
#include "nsFileStream.h"
#include "nsProxyObjectManager.h"
#include "nsIDOMWindow.h"

#include "nsIAppShellComponentImpl.h"
#include "nsIPrompt.h"

static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
static NS_DEFINE_IID(kAppShellServiceCID, NS_APPSHELL_SERVICE_CID );
static NS_DEFINE_IID(kProxyObjectManagerCID, NS_PROXYEVENT_MANAGER_CID);
static NS_DEFINE_IID(kStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID);

static NS_DEFINE_CID(kDialogParamBlockCID, NS_DialogParamBlock_CID);

#define XPINSTALL_BUNDLE_URL "chrome://xpinstall/locale/xpinstall.properties"

nsXPInstallManager::nsXPInstallManager()
  : mTriggers(0), mItem(0), mNextItem(0), mNumJars(0), mFinalizing(PR_FALSE), mCancelled(PR_FALSE)
{
    NS_INIT_ISUPPORTS();

    // we need to own ourself because we have a longer
    // lifetime than the scriptlet that created us.
    NS_ADDREF_THIS();

    // get the resourced xpinstall string bundle
    mStringBundle = nsnull;
    nsIStringBundleService *service;
    nsresult rv = nsServiceManager::GetService( kStringBundleServiceCID, 
                                       NS_GET_IID(nsIStringBundleService),
                                       (nsISupports**) &service );
    if (NS_SUCCEEDED(rv) && service)
    {
        nsILocale* locale = nsnull;
        rv = service->CreateBundle( XPINSTALL_BUNDLE_URL, locale,
                                    getter_AddRefs(mStringBundle) );
        nsServiceManager::ReleaseService( kStringBundleServiceCID, service );
    }
}



nsXPInstallManager::~nsXPInstallManager()
{    
    if (mTriggers) 
        delete mTriggers;
}


NS_IMPL_ADDREF( nsXPInstallManager );
NS_IMPL_RELEASE( nsXPInstallManager );

NS_IMETHODIMP 
nsXPInstallManager::QueryInterface(REFNSIID aIID,void** aInstancePtr)
{
  if (!aInstancePtr)
    return NS_ERROR_NULL_POINTER;

  if (aIID.Equals(NS_GET_IID(nsIXPINotifier)))
    *aInstancePtr = NS_STATIC_CAST(nsIXPINotifier*,this);
  else if (aIID.Equals(NS_GET_IID(nsIStreamListener)))
    *aInstancePtr = NS_STATIC_CAST(nsIStreamListener*,this);
  else if (aIID.Equals(NS_GET_IID(nsPIXPIManagerCallbacks)))
    *aInstancePtr = NS_STATIC_CAST(nsPIXPIManagerCallbacks*,this);
  else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink)))
    *aInstancePtr = NS_STATIC_CAST(nsIProgressEventSink*,this);
  else if (aIID.Equals(NS_GET_IID(nsIInterfaceRequestor)))
    *aInstancePtr = NS_STATIC_CAST(nsIInterfaceRequestor*,this);
  else if (aIID.Equals(NS_GET_IID(nsISupports)))
    *aInstancePtr = NS_STATIC_CAST( nsISupports*, NS_STATIC_CAST(nsIXPINotifier*,this));
  else
    *aInstancePtr = 0;

  nsresult status;
  if ( !*aInstancePtr )
    status = NS_NOINTERFACE;
  else 
  {
    NS_ADDREF_THIS();
    status = NS_OK;
  }

  return status;
}



NS_IMETHODIMP
nsXPInstallManager::InitManager(nsXPITriggerInfo* aTriggers)
{
    nsresult rv = NS_OK;
    nsCOMPtr<nsIDialogParamBlock> ioParamBlock;
    PRBool OKtoInstall = PR_FALSE;

    mTriggers = aTriggers;

    if ( !mTriggers || mTriggers->Size() == 0 )
    {
        rv = NS_ERROR_INVALID_POINTER;
        NS_RELEASE_THIS();
        return rv;
    }

    //-----------------------------------------------------
    // Create the nsIDialogParamBlock to pass the trigger
    // list to the dialog
    //-----------------------------------------------------
    rv = nsComponentManager::CreateInstance(kDialogParamBlockCID,
                                            nsnull,
                                            NS_GET_IID(nsIDialogParamBlock),
                                            getter_AddRefs(ioParamBlock));

    
    if ( NS_SUCCEEDED( rv ) ) 
    {
      
      LoadDialogWithNames(ioParamBlock);

      // Now do the stuff to create a window and pass the JS args to it.
      NS_WITH_SERVICE(nsIAppShellService, appShell, kAppShellServiceCID, &rv );
      if ( NS_SUCCEEDED( rv ) ) 
      {
        nsCOMPtr<nsIDOMWindow> hiddenWindow;
        JSContext* jsContext;
        rv = appShell->GetHiddenWindowAndJSContext( getter_AddRefs(hiddenWindow), &jsContext);
        if (NS_SUCCEEDED(rv))
        {
          void* stackPtr;
          jsval *argv = JS_PushArguments( jsContext,
                                          &stackPtr,
                                          "sss%ip",
                                          "chrome://xpinstall/content/institems.xul",
                                          "_blank",
                                          "chrome,modal",
                                          (const nsIID*)(&NS_GET_IID(nsIDialogParamBlock)),
                                          (nsISupports*)ioParamBlock);

          if (argv)
          {
            nsCOMPtr<nsIDOMWindow> newWindow;
            rv = hiddenWindow->OpenDialog( jsContext,
                                           argv,
                                           4,
                                           getter_AddRefs( newWindow));
            if (NS_SUCCEEDED(rv))
            {
              JS_PopArguments( jsContext, stackPtr);

              //Now get which button was pressed from the ParamBlock
              PRInt32 buttonPressed = 0;
              ioParamBlock->GetInt( 0, &buttonPressed );
              OKtoInstall = buttonPressed ? PR_FALSE : PR_TRUE;
            }
          }
          else
          {
            rv = NS_ERROR_FAILURE; // fix, better error code??
          }
        }
      }
    }

    // --- create and open the progress dialog
    if (NS_SUCCEEDED(rv) && OKtoInstall)
    {
        nsInstallProgressDialog* dlg;
        nsCOMPtr<nsISupports>    Idlg;

        dlg = new nsInstallProgressDialog(this);

        if ( dlg )
        {
            rv = dlg->QueryInterface( NS_GET_IID(nsIXPIProgressDlg), 
                                      getter_AddRefs(mDlg) );
            if (NS_SUCCEEDED(rv))
            {
                NS_WITH_SERVICE( nsIProxyObjectManager, pmgr, kProxyObjectManagerCID, &rv);
                if (NS_SUCCEEDED(rv))
                {
                    rv = pmgr->GetProxyObject( NS_UI_THREAD_EVENTQ, NS_GET_IID(nsIXPIProgressDlg),
                            mDlg, PROXY_SYNC | PROXY_ALWAYS, getter_AddRefs(mProxy) );

                }
                
                if (NS_SUCCEEDED(rv))
                {        
                    rv = mDlg->Open(ioParamBlock);
                }
            }
        }
        else
            rv = NS_ERROR_OUT_OF_MEMORY;
    }

    PRInt32 cbstatus = 0;  // callback status
    if (NS_SUCCEEDED(rv) && !OKtoInstall )
        cbstatus = nsInstall::USER_CANCELLED;
    else if (!NS_SUCCEEDED(rv))
        cbstatus = nsInstall::UNEXPECTED_ERROR;

    if ( cbstatus != 0 )
    {
        // --- inform callbacks of error
        for (PRUint32 i = 0; i < mTriggers->Size(); i++)
        {
            mTriggers->SendStatus( mTriggers->Get(i)->mURL.GetUnicode(),
                                   cbstatus );
        }

        // -- must delete ourselves if not continuing
        NS_RELEASE_THIS();
    }

    return rv;
}

NS_IMETHODIMP nsXPInstallManager::DialogOpened(nsISupports* aWindow)
{
  nsresult rv;
  nsCOMPtr<nsIDOMWindow> win = do_QueryInterface(aWindow, &rv);
  DownloadNext();
  return rv;
}


NS_IMETHODIMP nsXPInstallManager::DownloadNext()
{
    nsresult rv;

    if (mCancelled)
    {
        Shutdown();
        return NS_OK; // don't try to download anymore
    }

    if ( mNextItem < mTriggers->Size() )
    {
        // download the next item in list

        mItem = (nsXPITriggerItem*)mTriggers->Get(mNextItem++);

        NS_ASSERTION( mItem, "bogus Trigger slipped through" );
        NS_ASSERTION( mItem->mURL.Length() > 0, "bogus trigger");
        if ( !mItem || mItem->mURL.Length() == 0 )
        {
            // XXX serious problem with trigger! try to carry on
            rv = DownloadNext();
        }
        else if ( mItem->IsFileURL() )
        {
            // don't need to download, just point at local file
            rv = NS_NewFileSpecWithSpec( nsFileSpec(nsFileURL(mItem->mURL)),
                    getter_AddRefs(mItem->mFile) );
            if (NS_FAILED(rv))
            {
                // serious problem with trigger! try to carry on
                mTriggers->SendStatus( mItem->mURL.GetUnicode(), 
                                       nsInstall::DOWNLOAD_ERROR );
                mItem->mFile = 0;
            }

            rv = DownloadNext();
        }
        else
        {
            // We have one to download
            // --- figure out a temp file name
            nsSpecialSystemDirectory temp(nsSpecialSystemDirectory::OS_TemporaryDirectory);
            PRInt32 pos = mItem->mURL.RFindChar('/');
            if ( pos != -1 )
            {
                nsString jarleaf;
                mItem->mURL.Right( jarleaf, mItem->mURL.Length() - pos);
                temp += jarleaf;
            }
            else
                temp += "xpinstall.xpi";

            temp.MakeUnique();

            rv = NS_NewFileSpecWithSpec( temp, getter_AddRefs(mItem->mFile) );
            if (NS_SUCCEEDED(rv))
            {
                 // --- start the download
                nsCOMPtr<nsIURI> pURL;
                rv = NS_NewURI(getter_AddRefs(pURL), mItem->mURL);
                
                if (NS_SUCCEEDED(rv)) 
                {
                    nsCOMPtr<nsIChannel> channel;
                
                    rv = NS_OpenURI(getter_AddRefs(channel), pURL, nsnull, this);
                
                    if (NS_SUCCEEDED(rv))
                    {
                        rv = channel->AsyncRead(0, -1, nsnull, this);
                    }
                }
            }

            if (NS_FAILED(rv))
            {
                // announce failure
                mTriggers->SendStatus( mItem->mURL.GetUnicode(), 
                                       nsInstall::DOWNLOAD_ERROR );
                mItem->mFile = 0;
                // carry on with the next one
                rv = DownloadNext();
            }
        }
    }
    else
    {
        // all downloaded, queue them for installation

        // can't cancel from here on cause we can't undo installs in a multitrigger
        if (mProxy)
            mProxy->StartInstallPhase();
        else if (mDlg)
            mDlg->StartInstallPhase();

        NS_WITH_SERVICE(nsISoftwareUpdate, softupdate, nsSoftwareUpdate::GetCID(), &rv);
        if (NS_SUCCEEDED(rv))
        {
            for (PRUint32 i = 0; i < mTriggers->Size(); ++i)
            {
                mItem = (nsXPITriggerItem*)mTriggers->Get(i);
                if ( mItem && mItem->mFile )
                {
                    rv = softupdate->InstallJar(mItem->mFile,
                                                mItem->mURL.GetUnicode(),
                                                mItem->mArguments.GetUnicode(),
                                                mItem->mFlags,
                                                this );
                    if (NS_SUCCEEDED(rv))
                        PR_AtomicIncrement(&mNumJars);
                    else
                        mTriggers->SendStatus( mItem->mURL.GetUnicode(),
                                               nsInstall::UNEXPECTED_ERROR );
                }
            }
        }
        else
        {
            ; // XXX gotta clean up all those files...
        }

        if ( mNumJars == 0 )
        {
            // We must clean ourself up now -- we won't be called back
            Shutdown();
        }
    }

    return rv;
}

NS_IMETHODIMP
nsXPInstallManager::CancelInstall()
{
  mCancelled = PR_TRUE; // set by dialog thread
  return NS_OK;
}

void nsXPInstallManager::Shutdown()
{
    if (mProxy)
    {
        // proxy exists: we're being called from script thread
        mProxy->Close();
        mProxy = 0;
    }
    else if (mDlg)
        mDlg->Close();

    mDlg = 0;

    NS_RELEASE_THIS();
}

/*-----------------------------------------------------
** LoadDialogWithNames loads the param block for the 
** javascript/xul dialog with the list of modules to 
** be installed and their source locations
**----------------------------------------------------*/
void nsXPInstallManager::LoadDialogWithNames(nsIDialogParamBlock* ioParamBlock)
{

    nsXPITriggerItem *triggerItem;
    nsString moduleName, URL;
    PRInt32 offset = 0, numberOfDialogTreeElements = 0;
    PRUint32 i=0, paramIndex=0;

    //Must multiply the number of triggers by 2 because
    //the dialog contains the name and url as 2 separate elements
    numberOfDialogTreeElements = (mTriggers->Size() * 2);     
    ioParamBlock->SetNumberStrings(numberOfDialogTreeElements);

    ioParamBlock->SetInt(0,2); //set the Ok and Cancel buttons
    ioParamBlock->SetInt(1,numberOfDialogTreeElements);

    for (i=0; i < mTriggers->Size(); i++)
    {
        triggerItem = mTriggers->Get(i);
    
        //Check to see if this trigger item has a pretty name
        if((moduleName = triggerItem->mName) != "")
        {
            ioParamBlock->SetString(paramIndex, moduleName.ToNewUnicode());
            paramIndex++;
            URL = triggerItem->mURL;
            offset = URL.RFind("/");
            if (offset != -1)
            {
                URL.Cut(offset + 1, URL.mLength - 1);
                ioParamBlock->SetString(paramIndex, URL.ToNewUnicode());
            }
            paramIndex++;
        }
        else
        {
            //triggerItem does not have a pretty name so parse the url for the file name
            //then use that as the pretty name
            moduleName = triggerItem->mURL;
            URL = triggerItem->mURL;
            offset = moduleName.RFind("/");
            if (offset != -1)
            {
                moduleName.Cut(0, offset + 1);
                ioParamBlock->SetString(paramIndex, moduleName.ToNewUnicode());
                paramIndex++;
                URL.Cut(offset + 1, URL.mLength - 1); 
                ioParamBlock->SetString(paramIndex, URL.ToNewUnicode());
            }
            paramIndex++;
        }
    }
}


NS_IMETHODIMP
nsXPInstallManager::OnStartRequest(nsIChannel* channel, nsISupports *ctxt)
{
    mItem->mFile->OpenStreamForWriting();
    return NS_OK;
}

NS_IMETHODIMP
nsXPInstallManager::OnStopRequest(nsIChannel* channel, nsISupports *ctxt,
                                  nsresult status, const PRUnichar *errorMsg)
{
    nsresult rv;

    switch( status ) 
    {

        case NS_BINDING_SUCCEEDED:
            rv = NS_OK;
            break;

        case NS_BINDING_FAILED:
        case NS_BINDING_ABORTED:
            rv = status;
            // XXX need to note failure, both to send back status
            // to the callback, and also so we don't try to install
            // this probably corrupt file.
            break;

        default:
            rv = NS_ERROR_ILLEGAL_VALUE;
    }

    mItem->mFile->CloseStream();

    if (!NS_SUCCEEDED(rv))
    {
        nsFileSpec fspec;
        rv = mItem->mFile->GetFileSpec(&fspec);
        if ( NS_SUCCEEDED(rv) && fspec.Exists() )
            fspec.Delete(0);

        mItem->mFile = 0;

        mTriggers->SendStatus( mItem->mURL.GetUnicode(),
                               nsInstall::DOWNLOAD_ERROR );
    }
    DownloadNext();
    return rv;
}
NS_IMETHODIMP
nsXPInstallManager::OnDataAvailable(nsIChannel* channel, nsISupports *ctxt, 
                                    nsIInputStream *pIStream,
                                    PRUint32 sourceOffset, 
                                    PRUint32 length)
{
    PRUint32 amt;
    PRInt32  result;
    nsresult err;
    char buffer[1025];
    
    if (mCancelled)
    {
        // XXX forcing a CloseStream doesn't appear to call closesocket() (on Win32 at least),
        // XXX so the socket lingers open but no bits are flowing in apparently; need to follow up
        return OnStopRequest(channel, ctxt, NS_BINDING_ABORTED, (const PRUnichar *)0);
    }

    do 
    {
        err = pIStream->Read(buffer, 1024, &amt);
        if (amt == 0) break;
        if (NS_FAILED(err)) 
        {
            //printf("pIStream->Read Failed!  %d", err);   
            return err;
        }
        err = mItem->mFile->Write( buffer, amt, &result);
        //printf("mItem->mFile->Write err:%d   amt:%d    result:%d\n", err, amt, result);
        if (NS_FAILED(err) || result != (PRInt32)amt) 
        {
            //printf("mItem->mFile->Write Failed!  err:%d   amt:%d    result:%d\n", err, amt, result);
            return NS_ERROR_FAILURE;
        }
    } while (amt > 0);

    return NS_OK;
}

NS_IMETHODIMP 
nsXPInstallManager::OnProgress(nsIChannel *channel, nsISupports *ctxt, PRUint32 aProgress, PRUint32 aProgressMax)
{
    if (!mCancelled)
        return mProxy->SetProgress(aProgress, aProgressMax);

    return NS_OK;
}

NS_IMETHODIMP 
nsXPInstallManager::OnStatus(nsIChannel *channel, nsISupports *ctxt, const PRUnichar *aMsg)
{
    if (!mCancelled)
        return mProxy->SetActionText(aMsg);

    return NS_OK;
}

// nsIInterfaceRequestor method
NS_IMETHODIMP 
nsXPInstallManager::GetInterface(const nsIID & eventSinkIID, void* *_retval)
{
    return QueryInterface(eventSinkIID, (void**)_retval);
}

// IXPINotifier methods

NS_IMETHODIMP 
nsXPInstallManager::BeforeJavascriptEvaluation(const PRUnichar *URL)
{
    nsresult rv = NS_OK;

    mFinalizing = PR_FALSE;

    return rv;
}

NS_IMETHODIMP 
nsXPInstallManager::AfterJavascriptEvaluation(const PRUnichar *URL)
{
    PR_AtomicDecrement( &mNumJars );
    if ( mNumJars == 0 )
        Shutdown();

    return NS_OK;
}

NS_IMETHODIMP 
nsXPInstallManager::InstallStarted(const PRUnichar *URL, const PRUnichar *UIPackageName)
{
    mProxy->SetActionText(nsnull);
    return mProxy->SetHeading( nsString(UIPackageName).GetUnicode() );
}

NS_IMETHODIMP 
nsXPInstallManager::ItemScheduled(const PRUnichar *message)
{
    return mProxy->SetActionText( nsString(message).GetUnicode() );
}

NS_IMETHODIMP 
nsXPInstallManager::FinalizeProgress(const PRUnichar *message, PRInt32 itemNum, PRInt32 totNum)
{
    if (!mFinalizing)
    {
        mFinalizing = PR_TRUE;
        if (mStringBundle)
        {
            nsString rsrcName = "FinishingInstallMsg";
            const PRUnichar* ucRsrcName = rsrcName.GetUnicode();
            PRUnichar* ucRsrcVal = nsnull;
            nsresult rv = mStringBundle->GetStringFromName(ucRsrcName, &ucRsrcVal);
            if (NS_SUCCEEDED(rv) && ucRsrcVal)
            {
                mProxy->SetActionText( ucRsrcVal );
                nsCRT::free(ucRsrcVal);
            }
        }
    }
    return mProxy->SetProgress( itemNum, totNum );
}

NS_IMETHODIMP 
nsXPInstallManager::FinalStatus(const PRUnichar *URL, PRInt32 status)
{
    mTriggers->SendStatus( URL, status );
    return NS_OK;
}

NS_IMETHODIMP 
nsXPInstallManager::LogComment(const PRUnichar* comment)
{
    return NS_OK;
}

