/* Surface
 * (C) Copyright 1995 by Ashton Mason (amason@cs.uct.ac.za)
 *
 * Permission to use, modify, copy and distribute this source code for
 * any purpose and without fee is granted, provided that this copyright
 * notice appears in all copies and supporting documentation, and that
 * credit is given where due. This source code is provided "as is" with
 * no express or implied warranty.
 */

#include "headers.h"


	// constructor


grid::grid(void)
{
  data = NULL;
  gridsize = 0;
  decimation = 1;
}


	// deconstructor


grid::~grid(void)
{
  delete data;
}


	// set the size of the grid


int grid::setSize(int size)
{
  gridsize = size;
  clipsize = size - 1;
  cliptest = clipsize - 0.0000001;

  return 1;
}


	// smooth the grid


int grid::smoothGrid(int factor)
{
  status("smoothing grid");

  if (factor < 1 || factor > 10)
  {
    error("invalid smooth factor. range is 1-10");
    return 0;
  }

	// make buffer space

  gridval *buffer = new gridval[gridsize * gridsize];
  if (!buffer)
  {
    error("not enough memory for smoothing buffer");
    return 0;
  }

  for (int pass = 1; pass <= factor; pass++)
  {

	// copy data to buffer

    memcpy(buffer, data, gridsize * gridsize * 2);

	// smooth from buffer to data

    gridval *gridp = data;
    gridval *buffp = buffer;

    for (int y = 0; y < gridsize; y++)
    {
      for (int x = 0; x < gridsize; x++)
      {
	int xdec = (x > 0);
	int ydec = gridsize * (y > 0);
	int xinc = (x < clipsize);
	int yinc = gridsize * (y < clipsize);

	int total = *(buffp - ydec - xdec)
		  + *(buffp - ydec)
		  + *(buffp - ydec + xinc)
		  + *(buffp - xdec)
		  + *(buffp)
		  + *(buffp + xinc)
		  + *(buffp + yinc - xdec)
		  + *(buffp + yinc)
		  + *(buffp + yinc + xinc);

	*(gridp++) = (gridval) (total / 9);
	buffp++;
      }
    }
  }

  delete buffer;
  return 1;
}


	// read grid from PGM file


int grid::readPGM(char *name, int cropx, int cropy)
{
  FILE *input;
  int x, y, colours;
  int xsize, ysize;
  char chr, str[100];

  printf("reading PGM file `%s'\n", name);

  if (!(input = fopen(name, "rb")))
  {
    error("can't open PGM file");
    return 0;
  }

  fscanf(input, "%s", str);
  if (strcmp(str, "P5") && strcmp(str, "P2"))
  {
    error("not a PGM file");
    return 0;
  }

	// is this an ascii PGM file?

  int ascii = strcmp(str, "P5");

  while (fscanf(input, "\n#%c", &chr))		// comments
  {
    while (chr != '\n')
    {
      fread(&chr, sizeof(char), 1, input);
    }
  }

  if (!fscanf(input, "\n%d %d", &xsize, &ysize))
  {
    error("can't find sizes in PGM file");
    return 0;
  }

  printf("size = %d x %d\n", xsize, ysize);

  while (fscanf(input, "\n#%c", &chr))		// comments
  {
    while (chr != '\n')
    {
      fread(&chr, sizeof(char), 1, input);
    }
  }

  if (!fscanf(input, "\n%d", &colours))
  {
    error( "can't find #colours in pgm");
    return 0;
  }

  while (chr != '\n')				// find start of data
  {
    fread(&chr, sizeof(char), 1, input);
  }

	// take largest subsquare

  setSize(xsize < ysize ? xsize : ysize);

	// check crop position

  if (cropx + gridsize > xsize || cropy + gridsize > ysize)
  {
    error("invalid crop position");
  }

  printf("cropping to [%d,%d - %d,%d]\n", cropx, cropy,
	  cropx + gridsize - 1, cropy + gridsize - 1);

	// declare grid data

  data = new gridval[gridsize * gridsize];
  if (!data)
  {
    error("not enough memory for grid data");
    return 0;
  }

	// make buffer space

  gridval *gridbuf = new gridval[xsize * ysize];
  if (!gridbuf)
  {
    error("out of memory for PGM buffer");
    return 0;
  }

	// read data into buffer

  gridval *bufp = gridbuf;

  for (y = ysize; y; y--)
  {
    for (x = xsize; x; x--)
    {
      int val, result;
      unsigned char byte;

      if (ascii)
      {
	result = fscanf(input, " %d ", &val);
	*(bufp++) = (gridval) (val * GRIDMAX / colours);
      }
      else
      {
	result = fread(&byte, 1, 1, input);
	*(bufp++) = (gridval) (byte * GRIDMAX / colours);
      }

      if (!result || result == EOF)
      {
	error("error in PGM file");
	return 0;
      }
    }
  }

	// copy subsquare from buffer into grid

  gridval *gridp = data;

  int buffery = cropy;
  for (y = gridsize; y; y--)
  {
    int bufferx = cropx;
    for (x = gridsize; x; x--)
    {
      *(gridp++) = *(gridbuf + buffery * xsize + bufferx);

      bufferx++;
    }
    buffery++;
  }

	// free buffer memory

  delete gridbuf;
  fclose(input);
  return 1;
}


	// read grid data from a POT file


int grid::readPOT(char *name, int cropx, int cropy)
{
  gif pot;

  printf("reading POT file `%s'\n", name);
  pot.readFile(name);

	// take largest subsquare

  printf("size = %d x %d\n", pot.xsize / 2, pot.ysize);
  setSize(pot.xsize / 2 < pot.ysize ? pot.xsize / 2: pot.ysize);

	// check crop position

  if (cropx + gridsize > pot.xsize / 2 || cropy + gridsize > pot.ysize)
  {
    error("invalid crop position");
  }

  printf("cropping to [%d,%d - %d,%d]\n", cropx, cropy,
	  cropx + gridsize - 1, cropy + gridsize - 1);

	// make space for grid data

  data = new gridval[gridsize * gridsize];
  if (!data)
  {
    error("not enough memory for grid data");
    return 0;
  }

	// copy subsquare from buffer into grid

  gridval *gridp = data;

  int poty = cropy;
  for (int y = gridsize; y; y--)
  {
    int potx = cropx;
    for (int x = gridsize; x; x--)
    {
      *(gridp++) = (gridval)
	(*(pot.data + poty * pot.xsize + potx) * 256 +
	 *(pot.data + poty * pot.xsize + potx + pot.xsize / 2));

      potx++;
    }
    poty++;
  }

  pot.destroyData();
  return 1;
}


	// read grid data from an embedded 16-bit TGA file


int grid::readTGA(char *name, int cropx, int cropy)
{
  tga targa;

  printf("reading embedded TGA file `%s'\n", name);
  targa.readTGA(name);

	// take largest subsquare

  printf("size = %d x %d\n", targa.xsize, targa.ysize);
  setSize(targa.xsize < targa.ysize ? targa.xsize : targa.ysize);

	// check crop position

  if (cropx + gridsize > targa.xsize || cropy + gridsize > targa.ysize)
  {
    error("invalid crop position");
  }

  printf("cropping to [%d,%d - %d,%d]\n", cropx, cropy,
	  cropx + gridsize - 1, cropy + gridsize - 1);

  if (targa.mapped)
  {
    error("not a 24-bit TGA file");
  }

	// make space for grid data

  data = new gridval[targa.xsize * targa.ysize];
  if (!data)
  {
    error("not enough memory for grid data");
    return 0;
  }

	// copy subsquare from buffer into grid

  gridval *gridp = data;

  int targay = cropy;
  for (int y = gridsize; y; y--)
  {
    int targax = cropx;
    for (int x = gridsize; x; x--)
    {
      unsigned char *pixel = targa.data + (targay * targa.xsize + targax) * 3;
      *(gridp++) = (gridval) (*pixel * 256 + *(pixel + 1));

      targax++;
    }
    targay++;
  }

  targa.destroyData();
  return 1;
}


	// read grid data from GIF file


int grid::readGIF(char *name, int cropx, int cropy)
{
  gif input;

  printf("reading GIF file `%s'\n", name);
  input.readFile(name);

	// take largest subsquare

  printf("size = %d x %d\n", input.xsize, input.ysize);
  setSize(input.xsize < input.ysize ? input.xsize : input.ysize);

	// check crop position

  if (cropx + gridsize > input.xsize || cropy + gridsize > input.ysize)
  {
    error("invalid crop position");
  }

  printf("cropping to [%d,%d - %d,%d]\n", cropx, cropy,
	  cropx + gridsize - 1, cropy + gridsize - 1);

	// make space for grid data

  data = new gridval[input.xsize * input.xsize];
  if (!data)
  {
    error("not enough memory for grid data");
    return 0;
  }

	// copy subsquare from buffer into grid

  gridval *gridp = data;

  int inputy = cropy;
  for (int y = gridsize; y; y--)
  {
    int inputx = cropx;
    for (int x = gridsize; x; x--)
    {
      *(gridp++) = (gridval)
	(*(input.data + inputy * input.xsize + inputx) * GRIDMAX / 255);

      inputx++;
    }
    inputy++;
  }

  input.destroyData();
  return 1;
}


	// read grid from .GRD file


int grid::readGRD(char *name)
{
  FILE *input;
  char str[4];

  printf("reading GRD file `%s'\n", name);

  data = new gridval[1024 * 1024];
  if (!data)
  {
    error("not enough memory for grid data");
    return 0;
  }

  if (!(input = fopen(name, "rb")))
  {
    error("can't open GRD file");
    return 0;
  }

  fread(str, 1, 3, input);
  if (strncmp(str, "GRD", 3))
  {
    error("not a GRD file");
    return 0;
  }

  if (fread(data, sizeof(gridval), 1024 * 1024, input) != 1024 * 1024)
  {
    error("error reading from GRD file");
    return 0;
  }

  fclose(input);
  setSize(1024);

  return 1;
}


	// read grid data from an SRF file


int grid::readSRF(char *name)
{
  gif srf;

  printf("reading grid file `%s'\n", name);
  srf.readFile(name);

  printf("size = %d x %d\n", srf.xsize, srf.ysize / 2);

  if (srf.ysize != srf.xsize * 2)
  {
    error("SRF file is not square");
    return 0;
  }

	// make space for grid data

  data = new gridval[(srf.xsize / decimation) * (srf.xsize / decimation)];
  if (!data)
  {
    error("not enough memory for grid data");
    return 0;
  }

  setSize(srf.xsize / decimation);

  if (decimation != 1)
  {
    printf("decimating by %d, new size = %d x %d\n", decimation,
    srf.xsize / decimation, srf.xsize / decimation);
  }

	// extract the SRF data

  const int gridlength = srf.xsize * srf.xsize;

  register unsigned char *bytep = srf.data;
  gridval *gridp = data;

  for (int y = 0; y < gridsize; y++)
  {
    for (int x = 0; x < gridsize; x++)
    {
      *(gridp++) = (gridval)(*bytep * 256 + *(bytep + gridlength));

	// skip columns

      bytep += decimation;
    }

	// skip rows

    bytep += (decimation - 1) * srf.xsize;
  }

  srf.destroyData();
  return 1;
}


	// create a new grid


int grid::create(int size)
{

	// make space for grid data

  data = new gridval[size * size];
  if (!data)
  {
    error("not enough memory for result grid");
    return 0;
  }

  setSize(size);

  return 1;
}



