/* -*- Mode: C; tab-width: 4; 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): 
 *   Tim Rowley, tor@cs.brown.edu, original author
 */

#include "nsMNGDecoder.h"
#include "nsIImgDCallbk.h"
#include "nsMemory.h"
#include "libmng.h"
#include "prinrval.h"

typedef struct imng_str {

  mng_handle handle;
  PRUint32 width;
  PRUint32 height;

  PRUint8 *image;               /* full image buffer */
  PRUint8 *rowbuf;              /* ImgDCBHaveRow is destructive.  Grrr... */
  PRUint32 channels;            /* How many channels (3 or 4) */

  void *timer_id;
  
  PRUint8 *writeBuffer;         /* Bugzilla 41831 */
  PRUint32 bufferSize;
  PRUint32 bufferEnd;
  PRUint32 bufferPtr;

  PRBool resumeNeeded;          /* need to call display_resume? */
  
  il_container *ic;

} imng_struct, *imng_structp;


#define EXTRACT_STRUCTS \
  il_container *ic = (il_container *)mng_get_userdata(handle); \
  imng_structp imng_p = (imng_structp)ic->ds

#define DEBUG_tor
#ifdef DEBUG_tor
#define dprintf(x) fprintf x
#else
#define dprintf(x) 
#endif

// Callbacks for libmng
//===========================================================

static mng_bool
il_mng_openstream(mng_handle handle)
{
  return MNG_TRUE;
}


static mng_bool
il_mng_closestream(mng_handle handle)
{
  return MNG_TRUE;
}

static mng_bool
il_mng_readdata(mng_handle handle, mng_ptr buf,
                mng_uint32 size, mng_uint32 *stored)
{
  EXTRACT_STRUCTS;

  dprintf((stderr, "MNG::readdata size=%d buffered=%d\n", 
           size, imng_p->bufferEnd - imng_p->bufferPtr));

  size = PR_MIN(size, imng_p->bufferEnd - imng_p->bufferPtr);
  memcpy(buf, imng_p->writeBuffer+imng_p->bufferPtr, size);
  imng_p->bufferPtr += size;
  *stored = size;

  return MNG_TRUE;
}

static mng_bool
il_mng_processheader(mng_handle handle, mng_uint32 width, mng_uint32 height)
{
  EXTRACT_STRUCTS;

  imng_p->width = width;
  imng_p->height = height;
  ic->src_header->width = width;
  ic->src_header->height = height;

  switch (mng_get_alphadepth(handle)) {
  case 0:
    dprintf((stderr, "--- MNG ALPHA NONE\n"));
    ic->image->header.alpha_bits = 0;
    mng_set_canvasstyle(imng_p->handle, MNG_CANVAS_RGB8);
    imng_p->channels = 3;
    break;
  case 1:
    dprintf((stderr, "--- MNG ALPHA THRESHHOLD\n"));
    ic->image->header.alpha_bits = 1;
    mng_set_canvasstyle(imng_p->handle, MNG_CANVAS_RGBA8);
    imng_p->channels = 4;
    break;
  default:
    dprintf((stderr, "--- MNG ALPHA 8-BIT\n"));
    ic->image->header.alpha_bits = 8;
    mng_set_canvasstyle(imng_p->handle, MNG_CANVAS_RGBA8);
    imng_p->channels = 4;
    break;
  }

  ic->image->header.alpha_shift = 0;
  ic->image->header.is_interleaved_alpha = TRUE;

  imng_p->image =
    (unsigned char*)nsMemory::Alloc(imng_p->channels*width*height);
  memset(imng_p->image, 0, imng_p->channels*width*height);

  imng_p->rowbuf = (unsigned char*)nsMemory::Alloc(imng_p->channels*width);

  ic->imgdcb->ImgDCBImageSize();
  ic->imgdcb->ImgDCBSetupColorspaceConverter(); 

  return MNG_TRUE;
}

static mng_ptr
il_mng_getcanvasline(mng_handle handle, mng_uint32 iLinenr)
{
  EXTRACT_STRUCTS;

  return imng_p->image+imng_p->channels*imng_p->width*iLinenr;
}

static mng_bool
il_mng_refresh(mng_handle handle,
	       mng_uint32 left, mng_uint32 top,
	       mng_uint32 width, mng_uint32 height)
{
//  dprintf((stderr, "=== refresh(top=%d left=%d width=%d height=%d)\n",
//           top, left, width, height));

  EXTRACT_STRUCTS;

  for (mng_uint32 y=top; y<top+height; y++) {
    memcpy(imng_p->rowbuf, 
           imng_p->image+y*imng_p->channels*imng_p->width,
           imng_p->channels*imng_p->width);
    ic->imgdcb->ImgDCBHaveRow(0              /* color index data */,
                              imng_p->rowbuf /* rgb[a] */,
                              0              /* x-offset */,
                              imng_p->width  /* width in pixels */,
                              y              /* start row */,
                              1              /* row duplication count */,
                              ilErase        /* draw mode */,
                              mng_get_refreshpass(handle) /* pass */);
  }

  return MNG_TRUE;
}


static mng_uint32
il_mng_gettickcount(mng_handle handle)
{
//  dprintf((stderr, "=== gettickcount()\n"));
  return PR_IntervalToMilliseconds(PR_IntervalNow());
}

static void
il_mng_timeout_func(void *data)
{
  mng_handle handle = (mng_handle)data;
  EXTRACT_STRUCTS;

//  dprintf((stderr, "il_mng_timeout_func\n"));

  imng_p->timer_id = 0;

  int ret = mng_display_resume(handle);
  if (ret == MNG_NEEDMOREDATA)
    imng_p->resumeNeeded = PR_TRUE;

//  dprintf((stderr, "il_mng_timeout_func display_resume returned %d\n", ret));
}

static mng_bool
il_mng_settimer(mng_handle handle, mng_uint32 msec)
{
  dprintf((stderr, "=== settimer(%d)\n", msec));

  EXTRACT_STRUCTS;

  imng_p->timer_id = 
    ic->imgdcb->ImgDCBSetTimeout(il_mng_timeout_func,
                                 (void *)handle,
                                 msec);
  return MNG_TRUE;
}  

static mng_ptr
il_mng_alloc(mng_size_t size)
{
  void *ptr = nsMemory::Alloc(size);
  memset(ptr, 0, size);
  return ptr;
}

static void
il_mng_free(mng_ptr ptr, mng_size_t size)
{
  nsMemory::Free(ptr);
}

static mng_bool
il_mng_trace(mng_handle handle, mng_int32 iFuncnr, mng_int32 iFuncseq,
             mng_pchar zFuncname)
{
  dprintf((stderr, "== trace ==  %s %d %d\n", zFuncname, iFuncnr, iFuncseq));
  return MNG_TRUE;
}

static mng_bool
il_mng_error(mng_handle hHandle, mng_int32 iErrorcode, mng_int8 iSeverity,
             mng_chunkid iChunkname, mng_uint32 iChunkseq, mng_int32 iExtra1,
             mng_int32 iExtra2, mng_pchar zErrortext)
{
  dprintf((stderr, "== error == %s\n", zErrortext));
  return MNG_TRUE;
}

// Boilerplate methods... *yawn*
//===========================================================

MNGDecoder::MNGDecoder(il_container* aContainer)
{
  NS_INIT_REFCNT();
  ilContainer = aContainer;
}


MNGDecoder::~MNGDecoder(void)
{
}


NS_IMPL_ISUPPORTS1(MNGDecoder, nsIImgDecoder)


NS_METHOD
MNGDecoder::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
{
  nsresult rv;
  if (aOuter) return NS_ERROR_NO_AGGREGATION;

  il_container *ic = new il_container();
  if (!ic) return NS_ERROR_OUT_OF_MEMORY;

  MNGDecoder *decoder = new MNGDecoder(ic);
  if (!decoder) {
    delete ic;
    return NS_ERROR_OUT_OF_MEMORY;
  }

  NS_ADDREF(decoder);
  rv = decoder->QueryInterface(aIID, aResult);
  NS_RELEASE(decoder);

  /* why are we creating and destroying this object for no reason? */
  delete ic; /* is a place holder */

  return rv;
}

// Hooking mozilla and libmng together...
//===========================================================

NS_IMETHODIMP
MNGDecoder::ImgDInit()
{
  if( ilContainer != NULL ) {
    imng_structp imng_p;
    imng_p = (imng_structp) nsMemory::Alloc(sizeof(imng_struct));
    if (!imng_p) 
        return PR_FALSE;
    memset(imng_p, 0, sizeof(imng_struct));

    imng_p->writeBuffer = (PRUint8 *)nsMemory::Alloc(4096);
    imng_p->bufferSize = 4096;
    
    ilContainer->image->header.width = ilContainer->dest_width;
    ilContainer->image->header.height = ilContainer->dest_height;
    ilContainer->ds = imng_p;
    imng_p->ic = ilContainer;
    
    /* Initialize the container's source image header. */
    /* Always decode to 24 bit pixdepth */
    
    NI_ColorSpace *src_color_space = ilContainer->src_header->color_space;
    src_color_space->type = NI_TrueColor;
    src_color_space->pixmap_depth = 24;
    src_color_space->bit_alloc.index_depth = 0;
    
    /* pass ic as user data */
    imng_p->handle = 
      mng_initialize(ilContainer, il_mng_alloc, il_mng_free, NULL);

////////////
// Gamma correction - gross hack, but it's what mozilla's PNG
//                    decoder does and nobody has complained yet...
    double LUT_exponent, CRT_exponent = 2.2, display_exponent;

    /* set up gamma correction for Mac, Unix and (Win32 and everything else)
     * using educated guesses for display-system exponents; do preferences
     * later */

#if defined(XP_MAC)
    LUT_exponent = 1.8 / 2.61;
#elif defined(XP_UNIX)
# if defined(__sgi)
    LUT_exponent = 1.0 / 1.7;   /* typical default for SGI console */
# elif defined(NeXT)
    LUT_exponent = 1.0 / 2.2;   /* typical default for NeXT cube */
# else
    LUT_exponent = 1.0;         /* default for most other Unix workstations */
# endif
#else
    LUT_exponent = 1.0;         /* virtually all PCs and most other systems */
#endif

    /* (alternatively, could check for SCREEN_GAMMA environment variable) */
    display_exponent = LUT_exponent * CRT_exponent;
    mng_set_dfltimggamma(imng_p->handle, 0.45455);
    mng_set_displaygamma(imng_p->handle, display_exponent);
////////////

    mng_setcb_openstream(imng_p->handle, il_mng_openstream);
    mng_setcb_closestream(imng_p->handle, il_mng_closestream);
    mng_setcb_readdata(imng_p->handle, il_mng_readdata);
    mng_setcb_processheader(imng_p->handle, il_mng_processheader);
    mng_setcb_getcanvasline(imng_p->handle, il_mng_getcanvasline);
    mng_setcb_refresh(imng_p->handle, il_mng_refresh);
    mng_setcb_gettickcount(imng_p->handle, il_mng_gettickcount);
    mng_setcb_settimer(imng_p->handle, il_mng_settimer);
    mng_setcb_memalloc(imng_p->handle, il_mng_alloc);
    mng_setcb_memfree(imng_p->handle, il_mng_free);
    mng_set_suspensionmode(imng_p->handle, MNG_TRUE);

    if (mng_readdisplay(imng_p->handle) == MNG_NEEDMOREDATA)
      imng_p->resumeNeeded = PR_TRUE;
  }
  return NS_OK;
}


NS_IMETHODIMP 
MNGDecoder::ImgDWriteReady(PRUint32 *max_read)
{
  dprintf((stderr, "MNG::ImgDWriteReady() = "));

  imng_structp imng_p = (imng_structp)ilContainer->ds;

  /* add a bit, because libimg actually believes us if max_read==0 */
  *max_read = imng_p->bufferSize - imng_p->bufferEnd + 1024;
  
  dprintf((stderr, "%d\n", *max_read));

  return NS_OK;
}


NS_IMETHODIMP
MNGDecoder::ImgDWrite(const unsigned char *buf, int32 len)
{
  dprintf((stderr, "MNG::ImgDWrite(%d)\n", len));

  if (ilContainer != NULL) {
    imng_structp imng_p = (imng_structp)ilContainer->ds;

    if (imng_p->bufferEnd+len > imng_p->bufferSize) {
      imng_p->bufferSize *= 2;
      imng_p->writeBuffer = (PRUint8 *)
        nsMemory::Realloc(imng_p->writeBuffer, imng_p->bufferSize);
    }

    memcpy(imng_p->writeBuffer+imng_p->bufferEnd, buf, len);
    imng_p->bufferEnd += len;

    if (imng_p->resumeNeeded) {
//      dprintf((stderr, "MNG::ImgDWrite display_resume\n"));
      imng_p->resumeNeeded = PR_FALSE;
      int ret = mng_display_resume(imng_p->handle);
      if (ret == MNG_NEEDMOREDATA)
        imng_p->resumeNeeded = PR_TRUE;
//      dprintf((stderr, "MNG::ImgDWrite display_resume returned %d\n", ret));
    }
  }
  return NS_OK;
}


NS_IMETHODIMP 
MNGDecoder::ImgDComplete()
{
  return NS_OK;
}


NS_IMETHODIMP 
MNGDecoder::ImgDAbort()
{
  if( ilContainer != NULL ) {
    imng_structp imng_p = (imng_structp)ilContainer->ds;

    if (!imng_p)
      return NS_OK;

    if (imng_p->timer_id) {
      ilContainer->imgdcb->ImgDCBClearTimeout(imng_p->timer_id);
      imng_p->timer_id = 0;
    }

    mng_display_freeze(imng_p->handle);
    mng_cleanup(&imng_p->handle);
    nsMemory::Free(imng_p->writeBuffer);
    nsMemory::Free(imng_p->image);
    nsMemory::Free(imng_p->rowbuf);
    nsMemory::Free(imng_p);
    imng_p = NULL;
  }
  return NS_OK;
}

