/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sfx2.hxx"

#include <sfx2/progress.hxx>
#include <com/sun/star/uno/Reference.hxx>
#include <com/sun/star/task/XStatusIndicatorFactory.hpp>

#ifndef _SBX_HXX //autogen
#include <basic/sbx.hxx>
#endif

#include <svl/eitem.hxx>
#include <tools/time.hxx>

// wg. nRescheduleLocks
#include "appdata.hxx"
#include <sfx2/request.hxx>
#include <sfx2/frame.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/viewsh.hxx>
#include <sfx2/objsh.hxx>
#include <sfx2/app.hxx>
#include <sfx2/dispatch.hxx>
#include "sfxtypes.hxx"
#include <sfx2/docfile.hxx>
#include "workwin.hxx"
#include "sfx2/sfxresid.hxx"
#include "bastyp.hrc"
#include <sfx2/msg.hxx>

#include <time.h>

using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::frame;
using namespace ::com::sun::star::task;

void AddNumber_Impl( String& aNumber, sal_uInt32 nArg )
{
	if ( nArg >= 10240 )
	{
		aNumber += String::CreateFromInt32( (sal_uInt16)( ( nArg + 512 ) / 1024 ) );
		aNumber += ' ';
		aNumber += SfxResId( STR_KB );
	}
	else
	{
		aNumber += String::CreateFromInt32( nArg );
		aNumber += ' ';
		aNumber += SfxResId( STR_BYTES );
	}
}

struct SfxProgress_Impl
{
    Reference < XStatusIndicator > xStatusInd;
    String                  aText, aStateText;
	sal_uIntPtr					nMax;
	clock_t 				nCreate;
	clock_t					nNextReschedule;
	sal_Bool					bLocked, bAllDocs;
	sal_Bool					bWaitMode;
	sal_Bool					bAllowRescheduling;
	sal_Bool					bRunning;
	sal_Bool                    bIsStatusText;

	SfxProgress*            pActiveProgress;
	SfxObjectShellRef		xObjSh;
	SfxWorkWindow*			pWorkWin;
    SfxViewFrame*           pView;

							SfxProgress_Impl( const String& );
    void                    Enable_Impl( sal_Bool );

};

//========================================================================

#define TIMEOUT_PROGRESS	 	 5L	/* 10th s */
#define MAXPERCENT_PROGRESS		33

#define TIMEOUT_RESCHEDULE 		10L	/* 10th s */
#define MAXPERCENT_RESCHEDULE 	50

#define Progress
#include "sfxslots.hxx"

#define aTypeLibInfo aProgressTypeLibImpl

//========================================================================
extern sal_uIntPtr Get10ThSec();

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

void SfxProgress_Impl::Enable_Impl( sal_Bool bEnable )
{
    SfxObjectShell* pDoc = bAllDocs ? NULL : (SfxObjectShell*) xObjSh;
	SfxViewFrame *pFrame= SfxViewFrame::GetFirst(pDoc);
	while ( pFrame )
	{
		pFrame->Enable(bEnable);
        pFrame->GetDispatcher()->Lock( !bEnable );
		pFrame = SfxViewFrame::GetNext(*pFrame, pDoc);
	}

    if ( pView )
    {
        pView->Enable( bEnable );
        pView->GetDispatcher()->Lock( !bEnable );
    }

	if ( !pDoc )
        SFX_APP()->GetAppDispatcher_Impl()->Lock( !bEnable );
}

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

SfxProgress_Impl::SfxProgress_Impl( const String &/*rTitle*/ )
    :   pActiveProgress( 0 )
{
}

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

SfxProgress::SfxProgress
(
	SfxObjectShell*	pObjSh, /* SfxObjectShell, an der die Aktion ausgef"uhrt
							   wird. Kann NULL sein, dann wird die Applikation
							   verwendet */

	const String&	rText,	/* Text, der in der Statuszeile vor den Statusmonitor
							   erscheint */

	sal_uIntPtr			nRange, /* Maximalwert des Bereiches */

	sal_Bool			bAll    /* alle Dokumente oder nur das Dokument des ViewFrames
							   disablen (sal_False) */
	,sal_Bool			bWait   /* initial den Wait-Pointer aktivieren (sal_True) */
)

/*	[Beschreibung]

	Der Konstruktor der Klasse SfxProgress schaltet den als Parameter
	"ubergebenen SfxObjectShell und SfxViewFrames, welche dieses Dokument
	anzeigen in einen Progress-Mode. D.h. solange eine dieser SfxViewFrame
	Instanzen aktiv ist, ist der dazugeh"orige SfxDispatcher und das
	dazugeh"orige Window disabled. In der Statuszeile wird ein Balken zur
	Fortschritts-Anzeige angezeigt.
*/

:	pImp( new SfxProgress_Impl( rText ) ),
	nVal(0),
	bSuspended(sal_True)
{
	pImp->bRunning = sal_True;
	pImp->bAllowRescheduling = Application::IsInExecute();;

	pImp->xObjSh = pObjSh;
	pImp->aText = rText;
	pImp->nMax = nRange;
	pImp->bLocked = sal_False;
	pImp->bWaitMode = bWait;
	pImp->bIsStatusText = sal_False;
	pImp->nCreate = Get10ThSec();
	pImp->nNextReschedule = pImp->nCreate;
	DBG( DbgOutf( "SfxProgress: created for '%s' at %luds",
				  rText.GetBuffer(), pImp->nCreate ) );
	pImp->bAllDocs = bAll;
	pImp->pWorkWin = 0;
    pImp->pView = 0;

	pImp->pActiveProgress = GetActiveProgress( pObjSh );
	if ( pObjSh )
		pObjSh->SetProgress_Impl(this);
	else if( !pImp->pActiveProgress )
        SFX_APP()->SetProgress_Impl(this);
	Resume();
}

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

SfxProgress::~SfxProgress()

/*	[Beschreibung]

	Der Destruktor der Klasse SfxProgress restauriert den alten Zustand;
	die Dokumente werden wieder freigeschaltet und die Statuszeile zeigt
	wieder Items an.
*/

{
	Stop();
    if ( pImp->xStatusInd.is() )
        pImp->xStatusInd->end();

	if( pImp->bIsStatusText == sal_True )
		GetpApp()->HideStatusText( );
	delete pImp;
}

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

void SfxProgress::Stop()

/*	[Beschreibung]

	Vorzeitiges Beenden des <SfxProgress>.
*/

{
    if( pImp->pActiveProgress )
    {
        if ( pImp->xObjSh.Is() && pImp->xObjSh->GetProgress() == this )
            pImp->xObjSh->SetProgress_Impl(0);
        return;
    }

	if ( !pImp->bRunning )
		return;
	pImp->bRunning = sal_False;
	DBG( DbgOutf( "SfxProgress: destroyed at %luds", Get10ThSec() ) );

	Suspend();
	if ( pImp->xObjSh.Is() )
		pImp->xObjSh->SetProgress_Impl(0);
	else
        SFX_APP()->SetProgress_Impl(0);
	if ( pImp->bLocked )
        pImp->Enable_Impl(sal_True);
}

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

void SfxProgress::SetText
(
	const String&	/*	neuer Text */
)

/*	[Beschreibung]

	"Andert den Text, der links neben dem Fortschritts-Balken
	angezeigt wird.
*/

{
	if( pImp->pActiveProgress ) return;
    if ( pImp->xStatusInd.is() )
    {
        pImp->xStatusInd->reset();
        pImp->xStatusInd->start( pImp->aText, pImp->nMax );
    }
}

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

const String& SfxProgress::GetStateText_Impl() const
{
	return pImp->aStateText;
}

// -----------------------------------------------------------------------
/*
IMPL_STATIC_LINK( SfxProgress, SetStateHdl, PlugInLoadStatus*, pStatus )
{
	INetRequest* pReq = 0;
	const INetHint *pHint = PTR_CAST( INetHint, pStatus->pHint );
	pReq = PTR_CAST( INetRequest, pStatus->pBC );

	String aString;
	if( pReq )
		aString = SfxMedium::GetStatusString( pHint->GetId(), pReq, pHint );
	if( aString.Len() )
	{
		GetpApp()->ShowStatusText( aString );
		if( pThis )
			pThis->pImp->bIsStatusText = sal_True;
	}
	return 0;
}
*/

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

// muss in AppDaten
static sal_uIntPtr nLastTime = 0;

long TimeOut_Impl( void*, void* pArgV )
{
    Timer *pArg = (Timer*)pArgV;
	if( Time::GetSystemTicks() - nLastTime > 3000 )
	{
		GetpApp()->HideStatusText();
		nLastTime = 0;
		delete pArg;
	}
	else pArg->Start();
	return 0;
}

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

sal_Bool SfxProgress::SetStateText
(
	sal_uLong			nNewVal,	/* neuer Wert f"ur die Fortschritts-Anzeige */
	const String&	rNewVal,	/* Status als Text */
	sal_uLong			nNewRange	/* neuer Maximalwert, 0 f"ur Beibehaltung des alten */
)

{
	pImp->aStateText = rNewVal;
	return SetState( nNewVal, nNewRange );
}

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

sal_Bool SfxProgress::SetState
(
	sal_uLong	nNewVal,	/* neuer Wert f"ur die Fortschritts-Anzeige */

	sal_uLong	nNewRange	/* neuer Maximalwert, 0 f"ur Beibehaltung des alten */
)
/*	[Beschreibung]

	Setzen des aktuellen Status; nach einem zeitlichen Versatz
	wird Reschedule aufgerufen.


	[R"uckgabewert]

	sal_Bool				TRUE
						Fortfahren mit der Aktion

						FALSE
						Abbrechen der Aktion
*/

{
	// wurde via Stop-Button angehalten?
//	if ( pImp->IsCancelled() )
//		return sal_False;

	if( pImp->pActiveProgress ) return sal_True;

	// neuen Wert "ubernehmen
	sal_Bool bOver=sal_False;
	nVal = nNewVal;

	// neuer Range?
	if ( nNewRange && nNewRange != pImp->nMax )
	{
		DBG( DbgOutf( "SfxProgress: range changed from %lu to %lu",
					  pImp->nMax, nNewRange ) );
		pImp->nMax = nNewRange;
		bOver = sal_True;
	}

    if ( !pImp->xStatusInd.is() )
	{
        // get the active ViewFrame of the document this progress is working on
        // if it doesn't work on a document, take the current ViewFrame
        SfxObjectShell* pObjSh = pImp->xObjSh;
        pImp->pView = SfxViewFrame::Current();
        DBG_ASSERT( pImp->pView || pObjSh, "Can't make progress bar!");
        if ( pObjSh && ( !pImp->pView || pObjSh != pImp->pView->GetObjectShell() ) )
        {
            // current document does not belong to current ViewFrame; take it's first visible ViewFrame
            SfxViewFrame* pDocView = SfxViewFrame::GetFirst( pObjSh );
            if ( pDocView )
                pImp->pView = pDocView;
            else
            {
                // don't show status indicator for hidden documents (only valid while loading)
                SfxMedium* pMedium = pObjSh->GetMedium();
                SFX_ITEMSET_ARG( pMedium->GetItemSet(), pHiddenItem, SfxBoolItem, SID_HIDDEN, sal_False );
                if ( !pHiddenItem || !pHiddenItem->GetValue() )
                {
                    // not in a view, perhaps it's just loading
                    //SfxFrame* pFrame = pMedium->GetLoadTargetFrame();
                    //if ( pFrame && pFrame->GetCurrentViewFrame() )
                    //{
                        // recycling frame
                        //pImp->pView = pFrame->GetCurrentViewFrame();
                    //}
                    //else
                    {
                        SFX_ITEMSET_ARG( pMedium->GetItemSet(), pIndicatorItem, SfxUnoAnyItem, SID_PROGRESS_STATUSBAR_CONTROL, sal_False );
                        Reference< XStatusIndicator > xInd;
                        if ( pIndicatorItem && (pIndicatorItem->GetValue()>>=xInd) )
                            pImp->xStatusInd = xInd;
                    }
                }
            }
        }
        else if ( pImp->pView )
        {
            pImp->pWorkWin = SFX_APP()->GetWorkWindow_Impl( pImp->pView );
            if ( pImp->pWorkWin )
                pImp->xStatusInd = pImp->pWorkWin->GetStatusIndicator();
        }

        if ( pImp->xStatusInd.is() )
        {
            pImp->xStatusInd->start( pImp->aText, pImp->nMax );
            pImp->pView = NULL;
        }
	}

    if ( pImp->xStatusInd.is() )
    {
        pImp->xStatusInd->setValue( nNewVal );
    }

	return sal_True;
}

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

void SfxProgress::Resume()

/*	[Beschreibung]

	Nimmt die Anzeige des Status nach einer Unterbrechung wieder auf.

	[Querverweise]
	<SfxProgress::Suspend()>
*/

{
	if( pImp->pActiveProgress ) return;
	if ( bSuspended )
	{
		DBG( DbgOutf( "SfxProgress: resumed" ) );
        if ( pImp->xStatusInd.is() )
        {
            pImp->xStatusInd->start( pImp->aText, pImp->nMax );
            pImp->xStatusInd->setValue( nVal );
        }

		if ( pImp->bWaitMode )
		{
			if ( pImp->xObjSh.Is() && !pImp->bAllDocs )
			{
				for ( SfxViewFrame *pFrame = SfxViewFrame::GetFirst(pImp->xObjSh);
						pFrame;
						pFrame = SfxViewFrame::GetNext( *pFrame, pImp->xObjSh ) )
					pFrame->GetWindow().EnterWait();
			}
		}

		if ( pImp->xObjSh )
		{
            SfxViewFrame *pFrame = SfxViewFrame::GetFirst(pImp->xObjSh);
            if ( pFrame )
                pFrame->GetBindings().ENTERREGISTRATIONS();
		}

		bSuspended = sal_False;
	}
}

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

void SfxProgress::Suspend()

/*	[Beschreibung]

	Unterbricht die Anzeige des Status

	[Querverweise]
	<SfxProgress::Resume()>
*/

{
	if( pImp->pActiveProgress ) return;
	if ( !bSuspended )
	{
		DBG( DbgOutf( "SfxProgress: suspended" ) );
		bSuspended = sal_True;

        if ( pImp->xStatusInd.is() )
        {
            pImp->xStatusInd->reset();
        }

		if ( pImp->xObjSh.Is() && !pImp->bAllDocs )
		{
			for ( SfxViewFrame *pFrame =
					SfxViewFrame::GetFirst(pImp->xObjSh);
					pFrame;
					pFrame = SfxViewFrame::GetNext( *pFrame, pImp->xObjSh ) )
				pFrame->GetWindow().LeaveWait();
		}
		if ( pImp->xObjSh.Is() )
		{
			SfxViewFrame *pFrame = SfxViewFrame::GetFirst(pImp->xObjSh);
            if ( pFrame )
                pFrame->GetBindings().LEAVEREGISTRATIONS();
		}
	}
}

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

void SfxProgress::Lock()
{
	if( pImp->pActiveProgress ) return;
	// kein Reschedule bei Embedded-Objekten,
	// da wir gegen das OLE Protokoll wehrlos sind
	if ( !pImp->xObjSh.Is() )
	{
		for ( SfxObjectShell *pDocSh = SfxObjectShell::GetFirst();
			  pDocSh;
			  pDocSh = SfxObjectShell::GetNext(*pDocSh) )
		{
			SfxObjectCreateMode eMode = pDocSh->GetCreateMode();
			if ( ( eMode == SFX_CREATE_MODE_EMBEDDED ) ||
				 ( eMode == SFX_CREATE_MODE_PREVIEW ) )
			{
				DBG( DbgOutf( "SfxProgress: not locked because EMBEDDED/PREVIEW found" ) );
				pImp->bAllowRescheduling = sal_False;
			}
		}
	}
	else
	{
		SfxObjectCreateMode eMode = pImp->xObjSh->GetCreateMode();
		if ( ( eMode == SFX_CREATE_MODE_EMBEDDED ) ||
			 ( eMode == SFX_CREATE_MODE_PREVIEW ) )
		{
			DBG( DbgOutf( "SfxProgress: not locked because ObjectShell is EMBEDDED/PREVIEW" ) );
			pImp->bAllowRescheduling = sal_False;
		}
	}

    pImp->Enable_Impl( sal_False );

	DBG( DbgOutf( "SfxProgress: locked" ) );
	pImp->bLocked = sal_True;
}

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

void SfxProgress::UnLock()
{
	if( pImp->pActiveProgress ) return;
	if ( !pImp->bLocked )
		return;

	DBG( DbgOutf( "SfxProgress: unlocked" ) );
	pImp->bLocked = sal_False;
    pImp->Enable_Impl(sal_True);
}

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

void SfxProgress::Reschedule()

/*	[Beschreibung]

	Reschedule von au"sen rufbar

*/

{
	SFX_STACK(SfxProgress::Reschedule);

	if( pImp->pActiveProgress ) return;
    SfxApplication* pApp = SFX_APP();
    if ( pImp->bLocked && 0 == pApp->Get_Impl()->nRescheduleLocks )
	{
        DBG_ASSERTWARNING( pApp->IsInAsynchronCall_Impl(),
							"Reschedule in synchron-call-stack" );

        SfxAppData_Impl *pAppData = pApp->Get_Impl();
		++pAppData->nInReschedule;
		Application::Reschedule();
		--pAppData->nInReschedule;
	}
}

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

void SfxProgress::SetWaitMode
(
	sal_Bool	bWait		/*	TRUE
							Wartecursor wird verwendet

							FALSE
							Es wird kein Wartecursor verwendet */
)

/*	[Beschreibung]

	Wartecursor-Modus umschalten.

*/

{
	if( pImp->pActiveProgress ) return;
	if ( !bSuspended && pImp->bWaitMode != bWait )
	{
		if ( bWait )
		{
			if ( pImp->xObjSh.Is() && !pImp->bAllDocs )
			{
				for ( SfxViewFrame *pFrame =
						SfxViewFrame::GetFirst(pImp->xObjSh);
						pFrame;
						pFrame = SfxViewFrame::GetNext( *pFrame, pImp->xObjSh ) )
					pFrame->GetWindow().EnterWait();
			}
		}
		else
		{
			if ( pImp->xObjSh.Is() && !pImp->bAllDocs )
			{
				for ( SfxViewFrame *pFrame =
						SfxViewFrame::GetFirst(pImp->xObjSh);
						pFrame;
						pFrame = SfxViewFrame::GetNext( *pFrame, pImp->xObjSh ) )
					pFrame->GetWindow().LeaveWait();
			}
		}
	}

	pImp->bWaitMode = bWait;
}

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

sal_Bool SfxProgress::GetWaitMode() const

/*	[Beschreibung]

	Wartecursor-Modus abfragen.

*/

{
	return pImp->bWaitMode;
}

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

SfxProgress* SfxProgress::GetActiveProgress
(
	SfxObjectShell* pDocSh	  /*  <SfxObjectShell>, die nach einem laufenden
									<SfxProgress> gefragt werden soll, oder
									0, wenn ein f"ur die gesamte Applikation
									laufender SfxProgress erfragt werden soll.
									Der Pointer braucht nur zum Zeitpunkt des
									Aufrufs g"ultig zu sein. */
)

/*	[Beschreibung]

	Mit dieser Methode kann erfragt werden, ob und welcher <SfxProgress>-
	f"ur eine bestimmte Instanz von SfxObjectShell oder gar die gesamte
	Applikation zur Zeit aktiv ist. Dies kann z.B. zum Abfangen von
	Time-Out-Events etc. verwendet werden.

	Anstelle eines Pointer auf den SfxProgress der SfxObjectShell wird
	ggf. der auf den SfxProgress der Applikation geliefert, mit der
	Abfrage 'SfxProgress::GetActiveProgress(pMyDocSh)' wird also
	insofern vorhanden der SfxProgress von 'pMyDocSh' geliefert,
	sonst der SfxProgress der Applikation bzw. ein 0-Pointer.


	[Anmerkung]

	"auft kein SfxProgress an der Applikation und ebenfalls keiner an
	der angegebenen SfxObjectShell, dann wird immer 0 zur"uckgeliefert,
	auch wenn an einer anderen SfxObjectShell ein SfxProgress l"uft.


	[Querverweise]

	<SfxApplication::GetProgress()const>
	<SfxObjectShell::GetProgress()const>
*/

{
    if ( !SfxApplication::Get() )
        return 0;

	SfxProgress *pProgress = 0;
	if ( pDocSh )
		pProgress = pDocSh->GetProgress();
	if ( !pProgress )
		pProgress = SFX_APP()->GetProgress();
	return pProgress;
}

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

void SfxProgress::EnterLock()
{
	SFX_APP()->Get_Impl()->nRescheduleLocks++;
}

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

void SfxProgress::LeaveLock()
{
	SfxAppData_Impl *pImp = SFX_APP()->Get_Impl();
	DBG_ASSERT( 0 != pImp->nRescheduleLocks, "SFxProgress::LeaveLock but no locks" );
	pImp->nRescheduleLocks--;
}

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

FASTBOOL SfxProgress::StatusBarManagerGone_Impl
(
	SfxStatusBarManager	*	// dieser <SfxStatusBarManager> wird zerst"ort
)

/*	[Beschreibung]

	Interne Methode zum Benachrichtigen des SfxProgress, da\s der angegebene
	SfxStatusBarManger zerst"ort wird. Damit der Progress ihn loslassen
	kann.
*/

{
	return sal_True;
}

