/* -*- Mode: C++; tab-width: 2; 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 mozilla.org code.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 2001 Netscape Communications Corporation.
 * All Rights Reserved.
 * 
 * Contributor(s):
 *   Stuart Parmenter <pavlov@netscape.com>
 */

#include "nsImageLoader.h"

#include "imgILoader.h"

#include "nsIURI.h"
#include "nsILoadGroup.h"
#include "nsNetUtil.h"

#include "nsIPresContext.h"
#include "nsIPresShell.h"
#include "nsIFrame.h"
#include "nsIContent.h"
#include "nsIDocument.h"

#include "imgIContainer.h"

#include "nsIHTMLContent.h"

#include "nsIViewManager.h"

#include "nsStyleContext.h"

NS_IMPL_ISUPPORTS2(nsImageLoader, imgIDecoderObserver, imgIContainerObserver)

nsImageLoader::nsImageLoader() :
  mFrame(nsnull), mPresContext(nsnull)
{
}

nsImageLoader::~nsImageLoader()
{
  mFrame = nsnull;
  mPresContext = nsnull;

  if (mRequest) {
    mRequest->Cancel(NS_ERROR_FAILURE);
  }
}


void
nsImageLoader::Init(nsIFrame *aFrame, nsIPresContext *aPresContext)
{
  mFrame = aFrame;
  mPresContext = aPresContext;
}

void
nsImageLoader::Destroy()
{
  mFrame = nsnull;
  mPresContext = nsnull;

  if (mRequest) {
    mRequest->Cancel(NS_ERROR_FAILURE);
  }

  mRequest = nsnull;
}

nsresult
nsImageLoader::Load(nsIURI *aURI)
{
  if (!mFrame)
    return NS_ERROR_NOT_INITIALIZED;

  if (!aURI)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsILoadGroup> loadGroup;

  nsCOMPtr<nsIPresShell> shell;
  nsresult rv = mPresContext->GetShell(getter_AddRefs(shell));
  if ((NS_FAILED(rv)) || (!shell)) return NS_ERROR_FAILURE;

  nsCOMPtr<nsIDocument> doc;
  rv = shell->GetDocument(getter_AddRefs(doc));
  if (NS_FAILED(rv)) return rv;

  // Get the document's loadgroup
  doc->GetDocumentLoadGroup(getter_AddRefs(loadGroup));

  // Get the document URI (for the referrer).
  nsCOMPtr<nsIURI> documentURI;
  doc->GetDocumentURL(getter_AddRefs(documentURI));

  if (mRequest) {
    nsCOMPtr<nsIURI> oldURI;
    mRequest->GetURI(getter_AddRefs(oldURI));
    PRBool eq = PR_FALSE;
    aURI->Equals(oldURI, &eq);
    if (eq) {
      return NS_OK;
    }

    // Now cancel the old request so it won't hold a stale ref to us.
    mRequest->Cancel(NS_ERROR_FAILURE);
  }

  nsCOMPtr<imgILoader> il(do_GetService("@mozilla.org/image/loader;1", &rv));
  if (NS_FAILED(rv)) return rv;

  // XXX: initialDocumentURI is NULL!
  return il->LoadImage(aURI, nsnull, documentURI, loadGroup,
                       this, 
                       doc, nsIRequest::LOAD_BACKGROUND, nsnull, nsnull,
                       getter_AddRefs(mRequest));
}

                    

NS_IMETHODIMP nsImageLoader::OnStartDecode(imgIRequest *aRequest)
{
  return NS_OK;
}

NS_IMETHODIMP nsImageLoader::OnStartContainer(imgIRequest *aRequest,
                                              imgIContainer *aImage)
{
  if (aImage)
  {
    /* Get requested animation policy from the pres context:
     *   normal = 0
     *   one frame = 1
     *   one loop = 2
     */
    PRUint16 animateMode = imgIContainer::kNormalAnimMode; //default value
    nsresult rv = mPresContext->GetImageAnimationMode(&animateMode);
    if (NS_SUCCEEDED(rv))
      aImage->SetAnimationMode(animateMode);
  }
  return NS_OK;
}

NS_IMETHODIMP nsImageLoader::OnStartFrame(imgIRequest *aRequest,
                                          gfxIImageFrame *aFrame)
{
  return NS_OK;
}

NS_IMETHODIMP nsImageLoader::OnDataAvailable(imgIRequest *aRequest,
                                             gfxIImageFrame *aFrame,
                                             const nsRect *aRect)
{
  // Background images are not displayed incrementally, they are displayed after the entire 
  // image has been loaded.
  // Note: Images referenced by the <img> element are displayed incrementally in nsImageFrame.cpp
  return NS_OK;
}

NS_IMETHODIMP nsImageLoader::OnStopFrame(imgIRequest *aRequest,
                                         gfxIImageFrame *aFrame)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;
  
#ifdef NS_DEBUG
// Make sure the image request status's STATUS_FRAME_COMPLETE flag has been set to ensure
// the image will be painted when invalidated
  if (aRequest) {
   PRUint32 status = imgIRequest::STATUS_ERROR;
   nsresult rv = aRequest->GetImageStatus(&status);
   if (NS_SUCCEEDED(rv)) {
     NS_ASSERTION((status & imgIRequest::STATUS_FRAME_COMPLETE), "imgIRequest::STATUS_FRAME_COMPLETE not set");
   }
  }
#endif

  // Draw the background image
  RedrawDirtyFrame(nsnull);
  return NS_OK;
}

NS_IMETHODIMP nsImageLoader::OnStopContainer(imgIRequest *aRequest,
                                             imgIContainer *aImage)
{
  return NS_OK;
}

NS_IMETHODIMP nsImageLoader::OnStopDecode(imgIRequest *aRequest,
                                          nsresult status,
                                          const PRUnichar *statusArg)
{
  return NS_OK;
}

NS_IMETHODIMP nsImageLoader::FrameChanged(imgIContainer *aContainer,
                                          gfxIImageFrame *newframe,
                                          nsRect * dirtyRect)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;

  nsRect r(*dirtyRect);

  float p2t;
  mPresContext->GetPixelsToTwips(&p2t);
  r.x = NSIntPixelsToTwips(r.x, p2t);
  r.y = NSIntPixelsToTwips(r.y, p2t);
  r.width = NSIntPixelsToTwips(r.width, p2t);
  r.height = NSIntPixelsToTwips(r.height, p2t);

  RedrawDirtyFrame(&r);

  return NS_OK;
}


void
nsImageLoader::RedrawDirtyFrame(const nsRect* aDamageRect)
{
  // Determine damaged area and tell view manager to redraw it
  nsPoint offset;
  nsIView* view;

  // NOTE: It is not sufficient to invalidate only the size of the image:
  //       the image may be tiled! 
  //       The best option is to call into the frame, however lacking this
  //       we have to at least invalidate the frame's bounds, hence
  //       as long as we have a frame we'll use its size.
  //

  // Invalidate the entire frame
  // XXX We really only need to invalidate the client area of the frame...    
  nsRect bounds = mFrame->GetRect();
  bounds.x = bounds.y = 0;

  // XXX this should be ok, but there is some crappy ass bug causing it not to work
  // XXX seems related to the "body fixup rule" dealing with the canvas and body frames...
#if 0
  // Invalidate the entire frame only if the frame has a tiled background
  // image, otherwise just invalidate the intersection of the frame's bounds
  // with the damaged rect.
  nsStyleContext* styleContext;
  mFrame->GetStyleContext(&styleContext);
  const nsStyleBackground* bg = styleContext->GetStyleBackground();

  if ((bg->mBackgroundFlags & NS_STYLE_BG_IMAGE_NONE) ||
      (bg->mBackgroundRepeat == NS_STYLE_BG_REPEAT_OFF)) {
    // The frame does not have a background image so we are free
    // to invalidate only the intersection of the damage rect and
    // the frame's bounds.

    if (aDamageRect) {
      bounds.IntersectRect(*aDamageRect, bounds);
    }
  }

#endif
  if ((bounds.width > 0) && (bounds.height > 0)) {

    // XXX We should tell the frame the damage area and let it invalidate
    // itself. Add some API calls to nsIFrame to allow a caller to invalidate
    // parts of the frame...
    if (mFrame->HasView()) {
      view = mFrame->GetView();
    } else {
      mFrame->GetOffsetFromView(mPresContext, offset, &view);
      bounds.x += offset.x;
      bounds.y += offset.y;
    }

    nsIViewManager* vm = view->GetViewManager();
    if (vm) {
      vm->UpdateView(view, bounds, NS_VMREFRESH_NO_SYNC);    
    }
  }
}
