/* -*- 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.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */

#include "libimg.h"
#include "ilIImageRenderer.h"
#include "nsIImage.h"
#include "nsIRenderingContext.h"
#include "ni_pixmp.h"
#include "il_util.h"
#include "nsGfxCIID.h"
#include "nsIDeviceContext.h"

static NS_DEFINE_IID(kIImageRendererIID, IL_IIMAGERENDERER_IID);

class ImageRendererImpl : public ilIImageRenderer {
public:
  ImageRendererImpl();

  NS_DECL_ISUPPORTS

  NS_IMETHOD NewPixmap(void* aDisplayContext, 
			                   PRInt32 aWidth, PRInt32 aHeight, 
			                   IL_Pixmap* aImage, IL_Pixmap* aMask);

  NS_IMETHOD UpdatePixmap(void* aDisplayContext, 
			                      IL_Pixmap* aImage, 
			                      PRInt32 aXOffset, PRInt32 aYOffset, 
			                      PRInt32 aWidth, PRInt32 aHeight);


  NS_IMETHOD ControlPixmapBits(void* aDisplayContext, 
				                         IL_Pixmap* aImage, PRUint32 aControlMsg);

  NS_IMETHOD DestroyPixmap(void* aDisplayContext, IL_Pixmap* aImage);
  
  NS_IMETHOD DisplayPixmap(void* aDisplayContext, 
                  			     IL_Pixmap* aImage, IL_Pixmap* aMask, 
                  			     PRInt32 aX, PRInt32 aY, 
                  			     PRInt32 aXOffset, PRInt32 aYOffset, 
                  			     PRInt32 aWidth, PRInt32 aHeight);

  NS_IMETHOD DisplayIcon(void* aDisplayContext, 
			                     PRInt32 aX, PRInt32 aY, PRUint32 aIconNumber);

  NS_IMETHOD GetIconDimensions(void* aDisplayContext, 
                        				 PRInt32 *aWidthPtr, PRInt32 *aHeightPtr, 
                        				 PRUint32 aIconNumber);

  NS_IMETHOD SetImageNaturalDimensions(IL_Pixmap* aImage, PRInt32 naturalwidth, PRInt32 naturalheight);


  NS_IMETHOD SetDecodedRect(IL_Pixmap* aImage, 
			                    PRInt32 x1, PRInt32 y1,
                                PRInt32 x2, PRInt32 y2);


};

NS_IMETHODIMP
ImageRendererImpl::SetImageNaturalDimensions(
                   IL_Pixmap* aImage, 
                   PRInt32 naturalwidth, 
                   PRInt32 naturalheight){

    nsIImage *img = (nsIImage *)aImage->client_data;

    if(img){
        nsresult rv = img->SetNaturalWidth(naturalwidth);
        rv = img->SetNaturalHeight(naturalheight);
    }
    return NS_OK;
}
ImageRendererImpl::ImageRendererImpl()
{
  NS_INIT_REFCNT();
}

NS_IMPL_ISUPPORTS1(ImageRendererImpl, ilIImageRenderer)


NS_IMETHODIMP
ImageRendererImpl::NewPixmap(void* aDisplayContext, 
			                       PRInt32 aWidth, PRInt32 aHeight, 
                      	     IL_Pixmap* aImage, IL_Pixmap* aMask)
{
  nsIDeviceContext *dc = (nsIDeviceContext *)aDisplayContext;
  nsIImage  *img;
  nsresult  rv;
  nsMaskRequirements maskflag;

  static NS_DEFINE_IID(kImageCID, NS_IMAGE_CID);
  static NS_DEFINE_IID(kImageIID, NS_IIMAGE_IID);

 if (!aImage)
    return NS_ERROR_NULL_POINTER;
  
  // initialize in case of failure
  NS_ASSERTION(!aImage->bits, "We have bits already?");
  aImage->bits = nsnull;
  aImage->haveBits = PR_FALSE;
  if (aMask)
  {
    aMask->haveBits = PR_FALSE;
    aMask->bits = nsnull;
  }

  // Create a new image object
  rv = nsComponentManager::CreateInstance(kImageCID, nsnull, kImageIID, (void **)&img);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // Have the image match the depth and color space associated with the
  // device.
  // XXX We probably don't want to do that for monomchrome images (e.g., XBM)
  // or one-bit deep GIF images.
  PRInt32 depth;
  IL_ColorSpace *colorSpace;

  rv = dc->GetILColorSpace(colorSpace);
  if (NS_FAILED(rv)) {
    return rv;
  }
  depth = colorSpace->pixmap_depth;

  // Initialize the image object
 
  if(aMask == nsnull) 
    maskflag = nsMaskRequirements_kNoMask; 
  else
    maskflag = nsMaskRequirements_kNeeds1Bit;

  if(aImage->header.alpha_bits == 8)
      maskflag = nsMaskRequirements_kNeeds8Bit;

  rv = img->Init(aWidth, aHeight, depth, maskflag);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // Update the pixmap image and mask information

  // Don't get the bits here, because we can't guarantee that this address
  // will still be valid when we start using it. We *must* wait until we're
  // inside a lock/unlocks pixels block before getting the bits address
  // aImage->bits = img->GetBits(); 
  aImage->haveBits = PR_TRUE;

  aImage->client_data = img;  // we don't need to add a ref here, because there's
                              // already one from the call to create the image object
 
  aImage->header.width = aWidth;
  aImage->header.height = aHeight;
  aImage->header.widthBytes = img->GetLineStride();

  if (aMask) {
    // see comment about about getting the bits here
    // aMask->bits = img->GetAlphaBits();
    aMask->haveBits = PR_TRUE;

    aMask->client_data = img;
    // We must add another reference here, because when the mask's pixmap is
    // destroyed it will release a reference
    NS_ADDREF(img);
    aMask->header.width = aWidth;
    aMask->header.height = aHeight;
    aMask->header.widthBytes = img->GetAlphaLineStride();
  }

  // Replace the existing color space with the color space associated
  // with the device.
  IL_ReleaseColorSpace(aImage->header.color_space);
  aImage->header.color_space = colorSpace;

  // XXX Why do we do this on a per-image basis?
  if (8 == depth) {
    IL_ColorMap *cmap = &colorSpace->cmap;
    nsColorMap *nscmap = img->GetColorMap();
    PRUint8 *mapptr = nscmap->Index;
    int i;
                
    for (i=0; i < cmap->num_colors; i++) {
      *mapptr++ = cmap->map[i].red;
      *mapptr++ = cmap->map[i].green;
      *mapptr++ = cmap->map[i].blue;
    }

    img->ImageUpdated(dc, nsImageUpdateFlags_kColorMapChanged, nsnull);
                
    if (aImage->header.transparent_pixel) {
      PRUint8 red, green, blue;
      PRUint8 *lookup_table = (PRUint8 *)aImage->header.color_space->cmap.table;
      red = aImage->header.transparent_pixel->red;
      green = aImage->header.transparent_pixel->green;
      blue = aImage->header.transparent_pixel->blue;
      aImage->header.transparent_pixel->index = lookup_table[((red >> 3) << 10) |
                                                             ((green >> 3) << 5) |
                                                             (blue >> 3)];
    }
  }
  return NS_OK;
}


NS_IMETHODIMP
ImageRendererImpl::UpdatePixmap(void* aDisplayContext, 
				                        IL_Pixmap* aImage, 
				                        PRInt32 aXOffset, PRInt32 aYOffset, 
				                        PRInt32 aWidth, PRInt32 aHeight)
{
  nsIDeviceContext *dc = (nsIDeviceContext *)aDisplayContext;
  nsIImage         *img = (nsIImage *)aImage->client_data;
  nsRect            drect(aXOffset, aYOffset, aWidth, aHeight);

  img->ImageUpdated(dc, nsImageUpdateFlags_kBitsChanged, &drect);
  return NS_OK;
}

NS_IMETHODIMP
ImageRendererImpl::SetDecodedRect(   IL_Pixmap* aImage, 
				                PRInt32 x1, PRInt32 y1,
                                PRInt32 x2, PRInt32 y2)
{

  nsIImage         *img;
  
  if(aImage == NULL)
      return NS_OK;

  img= (nsIImage *)aImage->client_data;
  img->SetDecodedRect(x1, y1, x2, y2);

  return NS_OK;
}


NS_IMETHODIMP
ImageRendererImpl::ControlPixmapBits(void* aDisplayContext, 
				                             IL_Pixmap* aImage, PRUint32 aControlMsg)
{
  nsIDeviceContext *dc = (nsIDeviceContext *)aDisplayContext;
  
  if (!aImage) 
      return NS_ERROR_NULL_POINTER;
  
  nsIImage *img = (nsIImage *)aImage->client_data;
  if (!img) 
      return NS_ERROR_UNEXPECTED;

  PRBool   isMask = aImage->header.is_mask;
  nsresult rv = NS_OK;
      
  switch (aControlMsg)
  {
    case IL_LOCK_BITS:
      rv = img->LockImagePixels(isMask);
      if (NS_FAILED(rv)) 
          return rv;
      // the pixels may have moved, so need to update the bits ptr
      aImage->bits = (isMask) ? img->GetAlphaBits() : img->GetBits();
      break;
      
    case IL_UNLOCK_BITS:
      rv = img->UnlockImagePixels(isMask);
      break;
      
    case IL_RELEASE_BITS:
      rv = img->Optimize(dc);
      break;
      
    default:
      NS_NOTREACHED("Uknown control msg");
  }

  return rv;
}

NS_IMETHODIMP
ImageRendererImpl::DestroyPixmap(void* aDisplayContext, IL_Pixmap* aImage)
{
  nsIImage *img = (nsIImage *)aImage->client_data;

  aImage->client_data = nsnull;
  if (img) {
    NS_RELEASE(img);
  }
  return NS_OK;
}
  

NS_IMETHODIMP
ImageRendererImpl::DisplayPixmap(void* aDisplayContext, 
				                         IL_Pixmap* aImage, IL_Pixmap* aMask, 
                        				 PRInt32 aX, PRInt32 aY, 
                        				 PRInt32 aXOffset, PRInt32 aYOffset, 
                        				 PRInt32 aWidth, PRInt32 aHeight)
{
  // Image library doesn't drive the display process.
  // XXX Why is this part of the API?
      return NS_ERROR_NOT_IMPLEMENTED;
}


NS_IMETHODIMP 
ImageRendererImpl::DisplayIcon(void* aDisplayContext, 
			                         PRInt32 aX, PRInt32 aY, PRUint32 aIconNumber)
{
  // XXX Why is this part of the API?
      return NS_ERROR_NOT_IMPLEMENTED;
}


NS_IMETHODIMP
ImageRendererImpl::GetIconDimensions(void* aDisplayContext, 
				                             PRInt32 *aWidthPtr, PRInt32 *aHeightPtr, 
                        				     PRUint32 aIconNumber)
{
  // XXX Why is this part of the API?
      return NS_ERROR_NOT_IMPLEMENTED;
}

extern "C" NS_GFX_(nsresult)
NS_NewImageRenderer(ilIImageRenderer  **aInstancePtrResult)
{
  NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr");
  if (nsnull == aInstancePtrResult) {
    return NS_ERROR_NULL_POINTER;
  }

  ilIImageRenderer *renderer = new ImageRendererImpl();
  if (renderer == nsnull) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  return renderer->QueryInterface(NS_GET_IID(ilIImageRenderer), (void **)aInstancePtrResult);
}
