/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Mozilla 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/MPL/
 * 
 * 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 the Mozilla browser.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications, Inc.  Portions created by Netscape are
 * Copyright (C) 1999, Mozilla.  All Rights Reserved.
 * 
 * Contributor(s):
 *   Conrad Carlen <conrad@ingress.com>
 */

#include "CThrobber.h"
#include "CBrowserWindow.h"

#include <LString.h>
#include <LStream.h>
#include <UDrawingState.h>

#include "nsIWidget.h"
#include "nsWidgetsCID.h"
#include "nsIComponentManager.h"
#include "nsIDeviceContext.h"
#include "nsITimer.h"
#include "nsFont.h"
#include "nsIFontMetrics.h"
#include "prprf.h"

// CIDs
static NS_DEFINE_IID(kChildCID,          NS_CHILD_CID);
static NS_DEFINE_IID(kISupportsIID,      NS_ISUPPORTS_IID);

// Static variables
map<nsIWidget*, CThrobber*> CThrobber::mgThrobberMap;

// Constants
const PRUint32 kThrobFrequency = 66;   // animation frequency in milliseconds

//*****************************************************************************
//***    CThrobber: constructors/destructor
//*****************************************************************************

CThrobber::CThrobber() :
   mNumImages(2), mCompletedImages(0), mRunning(false)
{
   NS_INIT_REFCNT(); // caller must add ref as normal
   
   AddThrobber(this);
}


CThrobber::CThrobber(LStream*	inStream) :
   LControl(inStream),
   mNumImages(2), mCompletedImages(0), mRunning(false)
{
   mRefCnt = 1; // PowerPlant is making us, and it sure isn't going to do an AddRef.
  
   LStr255  tempStr;

   mDefImageURL[0] = '\0';
   *inStream >> (StringPtr) tempStr;
   memcpy(mDefImageURL, (char *)&tempStr[1], (PRInt32)tempStr.Length());
   mDefImageURL[(PRInt32)tempStr.Length()] = '\0';
   
   mAnimImageURL[0] = '\0';
   *inStream >> (StringPtr) tempStr;
   memcpy(mAnimImageURL, (char *)&tempStr[1], (PRInt32)tempStr.Length());
   mAnimImageURL[(PRInt32)tempStr.Length()] = '\0';
}


CThrobber::~CThrobber()
{
   if (mWidget)
      mWidget->Destroy();
   DestroyImages();
   RemoveThrobber(this);
}

NS_IMPL_ISUPPORTS0(CThrobber)


#if 0
void CThrobber::Notify(nsIImageRequest *aImageRequest,
                       nsIImage *aImage,
                       nsImageNotification aNotificationType,
                       PRInt32 aParam1, PRInt32 aParam2,
                       void *aParam3)
{
   if (aNotificationType == nsImageNotification_kImageComplete)
   {
      mCompletedImages++;
      
      // Remove ourselves as an observer of the image request object, because
      // the image request objects each hold a reference to us. This avoids a
      // circular reference problem. If we don't, our ref count will never reach
      // 0 and we won't get destroyed and neither will the image request objects

      aImageRequest->RemoveObserver((nsIImageRequestObserver*)this);
   }
}

void  CThrobber::NotifyError(nsIImageRequest *aImageRequest,
                             nsImageError aErrorType)
{
}

#endif

void CThrobber::FinishCreateSelf()
{
   CBrowserWindow *ourWindow = dynamic_cast<CBrowserWindow*>(LWindow::FetchWindowObject(Compat_GetMacWindow()));
   ThrowIfNil_(ourWindow);
   
   // Get the widget from the browser window
   nsCOMPtr<nsIWidget>  parentWidget;
   ourWindow->GetWidget(getter_AddRefs(parentWidget));
   ThrowIfNil_(parentWidget);
   
   FocusDraw();
   
	Rect portFrame;
	CalcPortFrameRect(portFrame);
	nsRect r(portFrame.left, portFrame.top, portFrame.right - portFrame.left, portFrame.bottom - portFrame.top);
   
  // Create widget
   nsresult rv;
   
   mWidget = do_CreateInstance(kChildCID, &rv);
   if (!mWidget)
      Throw_(NS_ERROR_GET_CODE(rv));
   mWidget->Create(parentWidget, r, HandleThrobberEvent, NULL);
   
   rv = LoadImages();
   if (NS_SUCCEEDED(rv))
      AddThrobber(this); 
}


void CThrobber::ShowSelf()
{
   mWidget->Show(PR_TRUE);
}


void CThrobber::HideSelf()
{
   mWidget->Show(PR_FALSE);
}


void CThrobber::DrawSelf()
{
#if 0
   // Draw directly with the rendering context instead of passing an
   // update event through event sink. By the time this routine is
   // called, PowerPlant has taken care of the location, z order, and clipping
   // of each view. Since focusing puts the the origin at our top left corner,
   // all we have to do is get the bounds of the widget and put that at (0,0)

   StColorPortState	origState(UQDGlobals::GetCurrentPort());

   nsCOMPtr<nsIRenderingContext> cx = getter_AddRefs(mWidget->GetRenderingContext());
   nsRect bounds;
   nsIImageRequest *imgreq;
   nsIImage *img;

   mWidget->GetClientBounds(bounds);
   bounds.x = bounds.y = 0;

   //cx->SetClipRect(bounds, nsClipCombine_kReplace, clipState);

   PRUint32 index = mRunning ? kAnimImageIndex : kDefaultImageIndex;
   imgreq = index < mImages->size() ? (*mImages)[index] : nsnull;
   img = imgreq ? imgreq->GetImage() : nsnull;
   
   if (img)
   {
     cx->DrawImage(img, 0, 0);
     NS_RELEASE(img);
   }
#endif
}


void CThrobber::ResizeFrameBy(SInt16		inWidthDelta,
                					SInt16		inHeightDelta,
                					Boolean	   inRefresh)
{
	LControl::ResizeFrameBy(inWidthDelta, inHeightDelta, inRefresh);
	AdjustFrame(inRefresh);
}


void CThrobber::MoveBy(SInt32		inHorizDelta,
				           SInt32		inVertDelta,
							  Boolean	inRefresh)
{
	LControl::MoveBy(inHorizDelta, inVertDelta, inRefresh);
	AdjustFrame(inRefresh);
}

	
void CThrobber::Start()
{
   mRunning = true;
}


void CThrobber::Stop()
{
   mRunning = false;
   FocusDraw();
   mWidget->Invalidate(PR_TRUE);
}


void CThrobber::AdjustFrame(Boolean inRefresh)
{
	FocusDraw();

	Rect portFrame;
	CalcPortFrameRect(portFrame);
	nsRect r(portFrame.left, portFrame.top, portFrame.right - portFrame.left, portFrame.bottom - portFrame.top);
   
   mWidget->Resize(r.x, r.y, r.width, r.height, inRefresh); 		
}


NS_METHOD CThrobber::LoadImages()
{
  nsresult rv = NS_ERROR_FAILURE;

#if 0
  mImages = new vector<nsIImageRequest*>(mNumImages, nsnull);
  if (nsnull == mImages) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  rv = NS_NewImageGroup(&mImageGroup);
  if (NS_OK != rv) {
    return rv;
  }

  nsIDeviceContext *deviceCtx = mWidget->GetDeviceContext();
  mImageGroup->Init(deviceCtx, nsnull);
  NS_RELEASE(deviceCtx);

  mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  if (NS_OK != rv) {
    return rv;
  }
  mTimer->Init(ThrobTimerCallback, this, kThrobFrequency, NS_PRIORITY_NORMAL, NS_TYPE_REPEATING_SLACK);
    
    nscolor bgcolor = NS_RGB(0, 0, 0);
  
  // Get the default image
  if (strlen(mDefImageURL)) {
      (*mImages)[kDefaultImageIndex] = mImageGroup->GetImage(mDefImageURL,
                                               (nsIImageRequestObserver *)this,
                                               nsnull /*&bgcolor*/,
                                               mFrameSize.width,
                                               mFrameSize.height, 0);
  }

  // Get the animated image
  if (strlen(mAnimImageURL)) {
      (*mImages)[kAnimImageIndex] = mImageGroup->GetImage(mAnimImageURL,
                                               (nsIImageRequestObserver *)this,
                                               nsnull /*&bgcolor*/,
                                               mFrameSize.width,
                                               mFrameSize.height, 0);
  }

  mWidget->Invalidate(PR_TRUE);
#endif

  return rv;
}

void CThrobber::DestroyImages()
{
  if (mTimer)
  {
    mTimer->Cancel();
  }

#if 0
  if (mImageGroup)
  {
    mImageGroup->Interrupt();
    
    for (vector<nsIImageRequest*>::iterator iter = mImages->begin(); iter < mImages->end(); ++iter)
    {
      NS_IF_RELEASE(*iter);
    }
    NS_RELEASE(mImageGroup);
  }

  if (mImages)
  {
    delete mImages;
    mImages = nsnull;
  }
#endif
}

void CThrobber::Tick()
{
  if (mRunning) {
    FocusDraw();
    mWidget->Invalidate(PR_TRUE);
  } else if (mCompletedImages == (PRUint32)mNumImages) {
      FocusDraw();
    mWidget->Invalidate(PR_TRUE);
    mCompletedImages = 0;
  }

#ifndef REPEATING_TIMERS
  nsresult rv;
  mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  if (NS_OK == rv) {
    mTimer->Init(ThrobTimerCallback, this, kThrobFrequency);
  }
#endif
}



CThrobber* CThrobber::FindThrobberForWidget(nsIWidget* aWidget)
{
   map<nsIWidget*, CThrobber*>::iterator iter = mgThrobberMap.find(aWidget);
   if (iter == mgThrobberMap.end())
      return nsnull;
   else
      return iter->second;
}


void CThrobber::AddThrobber(CThrobber* aThrobber)
{
   pair<nsIWidget*, CThrobber*>  entry(aThrobber->mWidget, aThrobber);
   mgThrobberMap[aThrobber->mWidget] = aThrobber;
}


void CThrobber::RemoveThrobber(CThrobber* aThrobber)
{
   map<nsIWidget*, CThrobber*>::iterator iter = mgThrobberMap.find(aThrobber->mWidget);
   if (iter != mgThrobberMap.end())
      mgThrobberMap.erase(iter);
}


nsEventStatus PR_CALLBACK CThrobber::HandleThrobberEvent(nsGUIEvent *aEvent)
{
  CThrobber* throbber = FindThrobberForWidget(aEvent->widget);
  if (nsnull == throbber) {
    return nsEventStatus_eIgnore;
  }

  switch (aEvent->message)
  {
    case NS_PAINT:
      break;

    case NS_MOUSE_LEFT_BUTTON_UP:
      // Broadcast a message
      break;

    case NS_MOUSE_ENTER:
      aEvent->widget->SetCursor(eCursor_hyperlink);
      break;

    case NS_MOUSE_EXIT:
      aEvent->widget->SetCursor(eCursor_standard);
      break;
  }

  return nsEventStatus_eIgnore;
}


void CThrobber::ThrobTimerCallback(nsITimer *aTimer, void *aClosure)
{
  CThrobber* throbber = (CThrobber*)aClosure;
  throbber->Tick();
}
