/* -*- 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>
 *
 */

/*
 * Current limitations:
 *
 *  Doesn't handle PGM and PPM files for which the maximum sample value
 *  is something other than 255.
 *
 */

#include <ctype.h>

#include "nsPPMDecoder.h"

#include "nsIInputStream.h"
#include "imgIContainer.h"
#include "imgIContainerObserver.h"

#include "nspr.h"
#include "plstr.h"

#include "nsIComponentManager.h"

#include "nsRect.h"

NS_IMPL_ISUPPORTS1(nsPPMDecoder, imgIDecoder)

/*
 * "F_" stands for "find", so F_TYPE means we're looking for the image type
 * digit.
 */
enum ParseState {
  F_P,
  F_TYPE,
  F_WIDTH,
  F_HEIGHT,
  F_MAXVAL,
  F_INITIALIZE,
  F_TEXTBITDATA,
  F_TEXTDATA,
  F_RAWDATA
};

/*
 * What kind of white space do we skip.
 */
enum Skip {
  NOTHING,
  WHITESPACE,
  SINGLEWHITESPACE,
  TOENDOFLINE
};

/*
 * We look up the digit after the initial 'P' in this string to check
 * whether it's valid; the index is then used with the following
 * constants.
 */
static char ppmTypes[] = "1231456";

enum {
  TYPE_PBM = 0,
  TYPE_PGM = 1,
  TYPE_PPM = 2,
  PPMTYPE = 0x3,
  PPMRAW = 0x4
};

nsPPMDecoder::nsPPMDecoder() :
  mBuffer(nsnull),
  mBufferSize(0),
  mState(F_P),
  mSkip(NOTHING),
  mOldSkip(NOTHING),
  mType(0),
  mRowData(nsnull)
{
}

nsPPMDecoder::~nsPPMDecoder()
{
  if (mBuffer != nsnull)
    PR_Free(mBuffer);
  if (mRowData != nsnull)
    PR_Free(mRowData);
}


/** imgIDecoder methods **/

/* void init (in imgILoad aLoad); */
NS_IMETHODIMP nsPPMDecoder::Init(imgILoad *aLoad)
{
  mImageLoad = aLoad;

  mObserver = do_QueryInterface(aLoad);  // we're holding 2 strong refs to the request.

  mImage = do_CreateInstance("@mozilla.org/image/container;1");
  aLoad->SetImage(mImage);

  mFrame = do_CreateInstance("@mozilla.org/gfx/image/frame;2");
  if (!mFrame)
    return NS_ERROR_FAILURE;

  return NS_OK;
}

/* void close (); */
NS_IMETHODIMP nsPPMDecoder::Close()
{
  if (mObserver) {
    mObserver->OnStopFrame(nsnull, mFrame);
    mObserver->OnStopContainer(nsnull, mImage);
    mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
  }
  
  return NS_OK;
}

/* void flush (); */
NS_IMETHODIMP nsPPMDecoder::Flush()
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* unsigned long writeFrom (in nsIInputStream inStr, in unsigned long count); */
NS_IMETHODIMP nsPPMDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRUint32 *_retval)
{
  nsresult rv;

  if (mBuffer == nsnull) {
    mBuffer = (char *)PR_Malloc(count);
    mBufferSize = count;
  } else if (mBuffer != nsnull && mBufferSize != count) {
    PR_Free(mBuffer);
    mBuffer = (char *)PR_Malloc(count);
    mBufferSize = count;
  }
  if (!mBuffer)
    return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */

  // read the data from the input stream...
  PRUint32 readLen;
  rv = inStr->Read(mBuffer, count, &readLen);
  if (NS_FAILED(rv)) return rv;

  if (mState == F_P && mObserver)
    mObserver->OnStartDecode(nsnull);

  char *p = mBuffer;
  char *bufferEnd = mBuffer+readLen;
  char *s;
  int n;

  while (p < bufferEnd) {
    if (mSkip == WHITESPACE) {
      while (p < bufferEnd && *p != '#'
	      && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r'))
	p++;
      if (p == bufferEnd)
	break;
      if (*p == '#') {
	mOldSkip = WHITESPACE;
	mSkip = TOENDOFLINE;
	continue;
      }
      mSkip = NOTHING;
    } else if (mSkip == SINGLEWHITESPACE) {
      if (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\r')
	return NS_ERROR_FAILURE;
      p++;
      mSkip = NOTHING;
    } else if (mSkip == TOENDOFLINE) {
      while (p < bufferEnd && *p != '\n')
	p++;
      if (p == bufferEnd)
	break;
      mSkip = mOldSkip;
      mOldSkip = NOTHING;
      if (mSkip != NOTHING)
	continue;
    }
    switch (mState) {
    case F_P:
      if (*p != 'P')
	return NS_ERROR_FAILURE;
      p++;
      mState = F_TYPE;
      break;
    case F_TYPE:
      if ((s = PL_strchr(ppmTypes, *p)) == NULL)
	return NS_ERROR_FAILURE;
      mType = s-ppmTypes;
      p++;
      mState = F_WIDTH;
      mDigitCount = 0;
      mSkip = WHITESPACE;
      break;
    case F_WIDTH:
    case F_HEIGHT:
    case F_MAXVAL:
      while (mDigitCount < sizeof(mDigits) && p < bufferEnd && isdigit(*p))
	mDigits[mDigitCount++] = *p++;
      if (mDigitCount == sizeof(mDigits))
	return NS_ERROR_FAILURE;		// number too big
      if (p == bufferEnd)
	break;
      if (*p == '#') {
	mOldSkip = NOTHING;
	mSkip = TOENDOFLINE;
	break;
      }
      mDigits[mDigitCount] = 0;
      n = strtol(mDigits, (char **)NULL, 10);
      if (mState == F_WIDTH) {
	mWidth = n;
	mDigitCount = 0;
	mState = F_HEIGHT;
	mSkip = WHITESPACE;
      } else if (mState == F_HEIGHT) {
	mHeight = n;
	mDigitCount = 0;
	if ((mType & PPMTYPE) != TYPE_PBM) {
	  mState = F_MAXVAL;
	  mSkip = WHITESPACE;
	} else
	  mState = F_INITIALIZE;
      } else if (mState == F_MAXVAL) {
	mMaxValue = n;
	mDigitCount = 0;
	mState = F_INITIALIZE;
      }
      break;
    case F_INITIALIZE:
      mImage->Init(mWidth, mHeight, mObserver);
      if (mObserver)
        mObserver->OnStartContainer(nsnull, mImage);
      mFrame->Init(0, 0, mWidth, mHeight, gfxIFormats::RGB, 24);
      mImage->AppendFrame(mFrame);
      if (mObserver)
        mObserver->OnStartFrame(nsnull, mFrame);
      mRow = 0;
      mBytesPerRow = 3*mWidth;
      mFrame->GetImageBytesPerRow(&mFrameBytesPerRow);
      mRowData = (PRUint8 *)PR_Malloc(mFrameBytesPerRow);
      mRowDataFill = 0;
      if (mType & PPMRAW) {
	mState = F_RAWDATA;
	mSkip = SINGLEWHITESPACE;
      } else if ((mType & PPMTYPE) == TYPE_PBM) {
	mState = F_TEXTBITDATA;
	mSkip = WHITESPACE;
      } else {
	mState = F_TEXTDATA;
	mSkip = WHITESPACE;
	mDigitCount = 0;
      }
      break;
    case F_TEXTBITDATA:
      {
	PRUint8 c = *p++;
	if (c == '1') {
	  mRowData[mRowDataFill++] = 0;
	  mRowData[mRowDataFill++] = 0;
	  mRowData[mRowDataFill++] = 0;
	} else {
	  mRowData[mRowDataFill++] = 255;
	  mRowData[mRowDataFill++] = 255;
	  mRowData[mRowDataFill++] = 255;
	}
      }
      mSkip = WHITESPACE;
      rv = checkSendRow();
      if (NS_FAILED(rv)) return rv;
      break;
    case F_TEXTDATA:
      while (mDigitCount < sizeof(mDigits) && p < bufferEnd && isdigit(*p))
	mDigits[mDigitCount++] = *p++;
      if (mDigitCount == sizeof(mDigits))
	return NS_ERROR_FAILURE;             // number too big
      if (p == bufferEnd)
	break;
      mDigits[mDigitCount] = 0;
      n = strtol(mDigits, (char **)NULL, 10);
      mDigitCount = 0;
      switch (mType & PPMTYPE) {
      case 1:
	mRowData[mRowDataFill++] = n;
	mRowData[mRowDataFill++] = n;
	mRowData[mRowDataFill++] = n;
	break;
      case 2:
	mRowData[mRowDataFill++] = n;
	break;
      }
      mSkip = WHITESPACE;
      rv = checkSendRow();
      if (NS_FAILED(rv)) return rv;
      break;
    case F_RAWDATA:
      if (mType & PPMRAW) {
	switch (mType & PPMTYPE) {
	case TYPE_PBM:
	  {
	    PRUint32 c = *p++;
	    int i = 0;
	    while (mRowDataFill < mBytesPerRow && i < 8) {
	      if (c & 0x80) {
		mRowData[mRowDataFill++] = 0;
		mRowData[mRowDataFill++] = 0;
		mRowData[mRowDataFill++] = 0;
	      } else {
		mRowData[mRowDataFill++] = 255;
		mRowData[mRowDataFill++] = 255;
		mRowData[mRowDataFill++] = 255;
	      }
	      c <<= 1;
	      i++;
	    }
	  }
	  break;
	case TYPE_PGM:
	  {
	    PRUint8 c = *p++;
	    mRowData[mRowDataFill++] = c;
	    mRowData[mRowDataFill++] = c;
	    mRowData[mRowDataFill++] = c;
	  }
	  break;
	case TYPE_PPM:
	  if (mMaxValue == 255) {
	    PRUint32 bytesInBuffer = bufferEnd-p;
	    PRUint32 bytesNeeded = mBytesPerRow-mRowDataFill;
	    PRUint32 chunk = PR_MIN(bytesInBuffer, bytesNeeded);
	    memcpy(mRowData+mRowDataFill, p, chunk);
	    p += chunk;
	    mRowDataFill += chunk;
	  } else {
	    mRowData[mRowDataFill++] = *p++;
	  }
	  break;
	}
	rv = checkSendRow();
	if (NS_FAILED(rv)) return rv;
      }
      break;
    }
  }
  return NS_OK;
}

NS_METHOD nsPPMDecoder::checkSendRow()
{
  nsresult rv;

  if (mRowDataFill == mBytesPerRow) {
    rv = mFrame->SetImageData(mRowData, mFrameBytesPerRow, mRow*mFrameBytesPerRow);
    if (NS_FAILED(rv)) return rv;
    nsRect r(0, mRow, mWidth, 1);
    if (mObserver)
      mObserver->OnDataAvailable(nsnull, mFrame, &r);
    mRow++;
    mRowDataFill = 0;
  }
  return NS_OK;
}
