/*
 * PPM read and write routines...
 *
 * $Date$
 * $Revision$
 *
 */

#include "ppmlib.h"
#include "WImage/miscdefs.h"
#include <cctype>
#include <cassert>

#if !(defined __WATCOMC__ && __WATCOMC__ < 1230)
using namespace std;
#endif


static int
skip_white (FILE *stream, int initial)
{
  int ret = initial;
  while (ret == 0 || isspace (ret))
    {
      ret = fgetc (stream);
    }
  return ret;
}


static int
skip_stuff (FILE *stream, int initial)
{
  int ret = skip_white (stream, initial);
  while (ret == (int)'#')
    {
      while (ret != (int)'\n')
        {
          ret = fgetc (stream);
        }
      ret = skip_white (stream, ret);
    }
  return ret;
}

static int
read_number (FILE *stream)
{
  int ret = 0;
  int current = skip_stuff (stream, ret);

  if (!isdigit (current))
    {
      return -1;
    }

  do
    {
      ret *= 10;
      ret += (pixel)current - '0';
      if ((current = fgetc (stream)) == EOF)
        {
          return EOF;
        }
    }
  while (isdigit (current));

  return isspace (current)? ret: -1;
}


pixel *
ppm_fromStream (FILE *in, int *height, int *width, int *cmax)
{
  enum states { s_magic, s_width, s_height, s_cmax, s_read, s_done, s_error };
  int state = s_magic;
  int ch = 0;
  pixel *ret = NULL;

  while (state < s_done)
    {
      switch (state)
        {
        case s_magic: 
          /* 
           * Look for type indicator 
           */
          ch = skip_stuff (in, ch);
          if (ch != (int)'P')
            { 
              state = s_error;
              break;
            }
          if ((ch = fgetc (in)) != (int)'6')
            {
              state = s_error;
              break;
            }
          state = s_width;
          break;
        case s_width:
          if ((*width = read_number (in)) < 0)
            {
              state = s_error;
              break;
            }
          state = s_height;
          break;
        case s_height:
          if ((*height = read_number (in)) < 0)
            {
              state = s_error;
              break;
            }
          state = s_cmax;
          break;
        case s_cmax:
          if ((*cmax = read_number (in)) < 0 || *cmax > 255)
            {
              state = s_error;
              break;
            }
          state = s_read;
          break;
        case s_read:
          ret = NEW (pixel [3* (*width) * (*height)]);
          for (int i = 0; i < (*height) && state != s_error; i++)
            {
              if (fread (&ret[i*(*width)*3], sizeof (*ret) * 3,
                         (*width), in) != (size_t)(*width))
                {
                  DELETE (ret);
                  state = s_error;
                }
            }
          state = s_done;
          break;
        default: 
          /* notreached */
          assert (0);
          state = s_error;
        }
    }

  assert (state == s_done || state == s_error);
  return state == s_done? ret: NULL;
}

int 
ppm_toStream (FILE *out, pixel *pic, int height, int width, int cmax)
{
  /*
   * Write the header...
   */
  if (fprintf (out, "P6\n%d\n%d\n%d\n", width, height, cmax) < 0)
    {
      (void)fclose (out);
      return -1;
    }

  /*
   * And the rest...
   */
  for (int i = 0; i < height; i++)
    {
      if (fwrite (&pic[i*width*3], sizeof (*pic) * 3,
                  (size_t)width, out) != (size_t)width)
        {
          (void)fclose (out);
          return -1;
        }
    }
  fputc ('\n', out);
  return 0;
}

int
ppm_write (const char *fname, pixel *pic, int height, int width, int cmax)
{
  FILE *out = fopen (fname, "wb");
  int ret;
  
  if (out == NULL)
    {
      return -1;
    }

  if ((ret = ppm_toStream (out, pic, height, width, cmax)) < 0)
    {
      return ret;
    }

  return fclose (out);
}

pixel *
ppm_read (const char *fname, int *height, int *width, int *cmax)
{
  FILE *in = fopen (fname, "rb");
  pixel *ret = NULL;

  if (in == NULL)
    {
      return NULL;
    }

  ret = ppm_fromStream (in, height, width, cmax);
  
  if (fclose (in) < 0)
    {
      return NULL;
    }
  return ret;
}

