/******************************************************************************
 * Copyright (c) 2004, Eric G. Miller
 *
 * This code is based in part on the earlier work of Frank Warmerdam
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ******************************************************************************
 * shpsort
 *
 * Rewrite a shapefile sorted by a field or by the geometry.  For polygons,
 * sort by area, for lines sort by length and do nothing for all others.
 *
 * $Log: shpsort.c,v $
 * Revision 1.3  2004-07-06 21:23:17  fwarmerdam
 * minor const warning fix
 *
 * Revision 1.2  2004/07/06 21:20:49  fwarmerdam
 * major upgrade .. sort on multiple fields
 *
 * Revision 1.4  2004/06/30 18:19:53  emiller
 * handle POINTZ, POINTM
 *
 * Revision 1.3  2004/06/30 17:40:32  emiller
 * major rewrite allows sorting on multiple fields.
 *
 * Revision 1.2  2004/06/23 23:19:58  emiller
 * use tuple copy, misc changes
 *
 * Revision 1.1  2004/06/23 21:38:17  emiller
 * Initial revision
 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include "shapefil.h"

enum FieldOrderEnum {DESCENDING, ASCENDING};
enum FieldTypeEnum {
  FIDType = -2, 
  SHPType = -1, 
  StringType = FTString,
  LogicalType = FTLogical,
  IntegerType = FTInteger,
  DoubleType = FTDouble
};

struct DataUnion {
  int null;
  union {
    int i;
    double d;
    char *s;
  } u;
};

struct DataStruct {
  int record;
  struct DataUnion *value;
};

/* 
   globals used in sorting, each element could have a pointer to
   a single data struct, but that's still nShapes pointers more
   memory.  Alternatively, write a custom sort rather than using
   library qsort.
*/
int nFields;
int *fldIdx;
int *fldOrder;
int *fldType;
int shpType;
int nShapes;

static struct DataStruct * build_index (SHPHandle shp, DBFHandle dbf);
static char * dupstr (const char *);
static void copy_related (const char *inName, const char *outName, 
			  const char *old_ext, const char *new_ext);
static char ** split(const char *arg, const char *delim);
static int compare(const void *, const void *);
static double area2d_polygon (int n, double *x, double *y);
static double shp_area (SHPObject *feat);
static double length2d_polyline (int n, double *x, double *y);
static double shp_length (SHPObject *feat);

int main (int argc, char *argv[]) {

  SHPHandle  inSHP, outSHP;
  DBFHandle  inDBF, outDBF;
  int        len; 
  int        i;
  char       **fieldNames;
  char       **strOrder = 0;
  struct DataStruct *index;
  int        width;
  int        decimals;
  SHPObject  *feat;
  void       *tuple;

  if (argc < 4) {
    printf("USAGE: shpsort <infile> <outfile> <field[;...]> [<(ASCENDING|DESCENDING)[;...]>]\n");
    exit(EXIT_FAILURE);
  }

  inSHP = SHPOpen (argv[1], "rb");
  if (!inSHP) {
    fputs("Couldn't open shapefile for reading!\n", stderr);
    exit(EXIT_FAILURE);
  }
  SHPGetInfo(inSHP, &nShapes, &shpType, NULL, NULL);

  /* If we can open the inSHP, open its DBF */
  inDBF = DBFOpen (argv[1], "rb");
  if (!inDBF) {
    fputs("Couldn't open dbf file for reading!\n", stderr);
    exit(EXIT_FAILURE);
  }

  /* Parse fields and validate existence */
  fieldNames = split(argv[3], ";");
  if (!fieldNames) {
    fputs("ERROR: parsing field names!\n", stderr);
    exit(EXIT_FAILURE);
  }
  for (nFields = 0; fieldNames[nFields] ; nFields++) {
    continue;
  }

  fldIdx = malloc(sizeof *fldIdx * nFields);
  if (!fldIdx) {
    fputs("malloc failed!\n", stderr);
    exit(EXIT_FAILURE);
  }
  for (i = 0; i < nFields; i++) {
    len = (int)strlen(fieldNames[i]);
    while(len > 0) {
      --len;
      fieldNames[i][len] = (char)toupper((unsigned char)fieldNames[i][len]); 
    }
    fldIdx[i] = DBFGetFieldIndex(inDBF, fieldNames[i]);
    if (fldIdx[i] < 0) {
      /* try "SHAPE" */
      if (strcmp(fieldNames[i], "SHAPE") == 0) {
	fldIdx[i] = -1;
      }
      else if (strcmp(fieldNames[i], "FID") == 0) {
	fldIdx[i] = -2;
      }
      else {
	fprintf(stderr, "ERROR: field '%s' not found!\n", fieldNames[i]);
	exit(EXIT_FAILURE);
      }
    }
  }


  /* set up field type array */
  fldType = malloc(sizeof *fldType * nFields);
  if (!fldType) {
    fputs("malloc failed!\n", stderr);
    exit(EXIT_FAILURE);
  }
  for (i = 0; i < nFields; i++) {
    if (fldIdx[i] < 0) {
      fldType[i] = fldIdx[i];
    }
    else {
      fldType[i] = DBFGetFieldInfo(inDBF, fldIdx[i], NULL, &width, &decimals);
      if (fldType[i] == FTInvalid) {
	fputs("Unrecognized field type in dBASE file!\n", stderr);
	exit(EXIT_FAILURE);
      }
    }
  }


  /* set up field order array */
  fldOrder = malloc(sizeof *fldOrder * nFields);
  if (!fldOrder) {
    fputs("malloc failed!\n", stderr);
    exit(EXIT_FAILURE);
  }
  for (i = 0; i < nFields; i++) {
    /* default to ascending order */
    fldOrder[i] = ASCENDING;
  }
  if (argc > 4) {
    strOrder = split(argv[4], ";");
    if (!strOrder) {
      fputs("ERROR: parsing fields ordering!\n", stderr);
      exit(EXIT_FAILURE);
    }
    for (i = 0; i < nFields && strOrder[i]; i++) {
      if (strcmp(strOrder[i], "DESCENDING") == 0) {
	fldOrder[i] = DESCENDING;
      }
    }
  }

  /* build the index */
  index = build_index (inSHP, inDBF);

  /* Create output shapefile */
  outSHP = SHPCreate(argv[2], shpType);
  if (!outSHP) {
    fprintf(stderr, "%s:%d: couldn't create output shapefile!\n",
	    __FILE__, __LINE__);
    exit(EXIT_FAILURE);
  }
  
  /* Create output dbf */
  outDBF = DBFCloneEmpty(inDBF, argv[2]);
  if (!outDBF) {
    fprintf(stderr, "%s:%d: couldn't create output dBASE file!\n",
	    __FILE__, __LINE__);
    exit(EXIT_FAILURE);
  }

  /* Copy projection file, if any */
  copy_related(argv[1], argv[2], ".shp", ".prj");

  /* Copy metadata file, if any */
  copy_related(argv[1], argv[2], ".shp", ".shp.xml");

  /* Write out sorted results */
  for (i = 0; i < nShapes; i++) {
    feat = SHPReadObject(inSHP, index[i].record);
    if (SHPWriteObject(outSHP, -1, feat) < 0) {
      fprintf(stderr, "%s:%d: error writing shapefile!\n", __FILE__, __LINE__);
      exit(EXIT_FAILURE);
    }
    tuple = (void *) DBFReadTuple(inDBF, index[i].record);
    if (DBFWriteTuple(outDBF, i, tuple) < 0) {
      fprintf(stderr, "%s:%d: error writing dBASE file!\n", __FILE__, __LINE__);
      exit(EXIT_FAILURE);
    }
  }
  SHPClose(inSHP);
  SHPClose(outSHP);
  DBFClose(inDBF);
  DBFClose(outDBF);

  return EXIT_SUCCESS;

}

static char ** split(const char *arg, const char *delim)
{
  char *copy = dupstr(arg);
  char *cptr = copy;
  char **result = NULL;
  char **tmp;
  int i = 0;

  for (cptr = strtok(copy, delim); cptr; cptr = strtok(NULL, delim)) {
    tmp = realloc (result, sizeof *result * (i + 1));
    if (!tmp && result) {
      while (i > 0) {
	free(result[--i]);
      }
      free(result);
      free(copy);
      return NULL;
    }
    result = tmp;
    result[i++] = dupstr(cptr);
  }

  free(copy);

  if (i) {
    tmp = realloc(result, sizeof *result * (i + 1));
    if (!tmp) {
      while (i > 0) {
	free(result[--i]);
      }
      free(result);
      free(copy);
      return NULL;
    }
    result = tmp;
    result[i++] = NULL;
  }

  return result;
}


static void copy_related (const char *inName, const char *outName, 
			  const char *old_ext, const char *new_ext) 
{
  char *in;
  char *out;
  FILE *inFile;
  FILE *outFile;
  int  c;
  size_t name_len = strlen(inName);
  size_t old_len  = strlen(old_ext); 
  size_t new_len  = strlen(new_ext);

  in = malloc(name_len - old_len + new_len + 1);
  strncpy(in, inName, (name_len - old_len));
  strcpy(&in[(name_len - old_len)], new_ext);
  inFile = fopen(in, "rb");
  if (!inFile) {
    free(in);
    return;
  }
  name_len = strlen(outName);
  out = malloc(name_len - old_len + new_len + 1);
  strncpy(out, outName, (name_len - old_len));
  strcpy(&out[(name_len - old_len)], new_ext);
  outFile = fopen(out, "wb");
  if (!out) {
    fprintf(stderr, "%s:%d: couldn't copy related file!\n",
	    __FILE__, __LINE__);
    free(in);
    free(out);
    return;
  }
  while ((c = fgetc(inFile)) != EOF) {
    fputc(c, outFile);
  }
  fclose(inFile);
  fclose(outFile);
  free(in);
  free(out);
}

static char * dupstr (const char *src)
{
  char *dst = malloc(strlen(src) + 1);
  char *cptr;
  if (!dst) {
    fprintf(stderr, "%s:%d: malloc failed!\n", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
  }
  cptr = dst;
  while ((*cptr++ = *src++))
    ;
  return dst;
}

#ifdef DEBUG
static void PrintDataStruct (struct DataStruct *data) {
  int i, j;
  for (i = 0; i < nShapes; i++) {
    printf("data[%d] {\n", i);
    printf("\t.record = %d\n", data[i].record);
    for (j = 0; j < nFields; j++) {
      printf("\t.value[%d].null = %d\n", j, data[i].value[j].null);
      if (!data[i].value[j].null) {
	switch(fldType[j]) {
	case FIDType:
	case IntegerType:
	case LogicalType:
	  printf("\t.value[%d].u.i = %d\n", j, data[i].value[j].u.i);
	  break;
	case DoubleType:
	case SHPType:
	  printf("\t.value[%d].u.d = %f\n", j, data[i].value[j].u.d);
	  break;
	case StringType:
	  printf("\t.value[%d].u.s = %s\n", j, data[i].value[j].u.s);
	  break;
	}
      }
    }
    puts("}");
  }
}
#endif

static struct DataStruct * build_index (SHPHandle shp, DBFHandle dbf) {
  struct DataStruct *data;
  SHPObject  *feat;
  int i;
  int j;

  /* make array */
  data = malloc (sizeof *data * nShapes);
  if (!data) {
    fputs("malloc failed!\n", stderr);
    exit(EXIT_FAILURE);
  }

  /* populate array */
  for (i = 0; i < nShapes; i++) {
    data[i].value = malloc(sizeof data[0].value[0] * nFields);
    if (0 == data[i].value) {
      fputs("malloc failed!\n", stderr);
      exit(EXIT_FAILURE);
    }
    data[i].record = i;
    for (j = 0; j < nFields; j++) {
      data[i].value[j].null = 0;
      switch (fldType[j]) {
      case FIDType:
	data[i].value[j].u.i = i;
	break;
      case SHPType:
	feat = SHPReadObject(shp, i);
	switch (feat->nSHPType) {
	case SHPT_NULL:
	  fprintf(stderr, "Shape %d is a null feature!\n", i);
	  data[i].value[j].null = 1;
	  break;
	case SHPT_POINT:
	case SHPT_POINTZ:
	case SHPT_POINTM:
	case SHPT_MULTIPOINT:
	case SHPT_MULTIPOINTZ:
	case SHPT_MULTIPOINTM:
	case SHPT_MULTIPATCH:
	  /* Y-sort bounds */
	  data[i].value[j].u.d = feat->dfYMax;
	  break;
	case SHPT_ARC:
	case SHPT_ARCZ:
	case SHPT_ARCM:
	  data[i].value[j].u.d = shp_length(feat);
	  break;
	case SHPT_POLYGON:
	case SHPT_POLYGONZ:
	case SHPT_POLYGONM:
	  data[i].value[j].u.d = shp_area(feat);
	  break;
	default:
	  fputs("Can't sort on Shapefile feature type!\n", stderr);
	  exit(EXIT_FAILURE);
	}
	SHPDestroyObject(feat);
	break;
      case FTString:
	data[i].value[j].null = DBFIsAttributeNULL(dbf, i, fldIdx[j]);
	if (!data[i].value[j].null) {
	  data[i].value[j].u.s = dupstr(DBFReadStringAttribute(dbf, i, fldIdx[j]));
	}
	break;
      case FTInteger:
      case FTLogical:
	data[i].value[j].null = DBFIsAttributeNULL(dbf, i, fldIdx[j]);
	if (!data[i].value[j].null) {
	  data[i].value[j].u.i  = DBFReadIntegerAttribute(dbf, i, fldIdx[j]);
	}
	break;
      case FTDouble:
	data[i].value[j].null = DBFIsAttributeNULL(dbf, i, fldIdx[j]);
	if (!data[i].value[j].null) {
	  data[i].value[j].u.d = DBFReadDoubleAttribute(dbf, i, fldIdx[j]);	
	}
	break;
      }
    }
  }
  
#ifdef DEBUG
  PrintDataStruct(data);
  fputs("build_index: sorting array\n", stdout);
#endif

  qsort (data, nShapes, sizeof data[0], compare);

#ifdef DEBUG
  PrintDataStruct(data);
  fputs("build_index: returning array\n", stdout);
#endif

  return data;
}

static int compare(const void *A, const void *B) {
  const struct DataStruct *a = A;
  const struct DataStruct *b = B;
  int i;
  int result = 0;

  for (i = 0; i < nFields; i++) {
    if (a->value[i].null && b->value[i].null) {
      continue;
    }
    if (a->value[i].null && !b->value[i].null) {
      return (fldOrder[i]) ? 1 : -1;
    }
    if (!a->value[i].null && b->value[i].null) {
      return (fldOrder[i]) ? -1 : 1;
    }
    switch (fldType[i]) {
    case FIDType:
    case IntegerType:
    case LogicalType:
      if (a->value[i].u.i < b->value[i].u.i) {
	return (fldOrder[i]) ? -1 : 1;
      }
      if (a->value[i].u.i > b->value[i].u.i) {
	return (fldOrder[i]) ? 1 : -1;
      }
      break;
    case DoubleType:
    case SHPType:
      if (a->value[i].u.d < b->value[i].u.d) {
	return (fldOrder[i]) ? -1 : 1;
      }
      if (a->value[i].u.d > b->value[i].u.d) {
	return (fldOrder[i]) ? 1 : -1;
      }
      break;      
    case StringType:
      result = strcmp(a->value[i].u.s, b->value[i].u.s);
      if (result) {
	return (fldOrder[i]) ? result : -result;
      }
      break;
    default:
      fprintf(stderr, "compare: Program Error! Unhandled field type! fldType[%d] = %d\n", i, fldType[i]);
      break;
    }
  }
  return 0;
}

static double area2d_polygon (int n, double *x, double *y) {
  double area = 0;
  int i;
  for (i = 1; i < n; i++) {
    area += (x[i-1] + x[i]) * (y[i] - y[i-1]);
  }
  return area / 2.0;
}

static double shp_area (SHPObject *feat) {
  double area = 0.0;
  if (feat->nParts == 0) {
    area = area2d_polygon (feat->nVertices, feat->padfX, feat->padfY);
  }
  else {
    int part, n;
    for (part = 0; part < feat->nParts; part++) {
      if (part < feat->nParts - 1) {
	n = feat->panPartStart[part+1] - feat->panPartStart[part];
      }
      else {
	n = feat->nVertices - feat->panPartStart[part];
      }
      area += area2d_polygon (n, &(feat->padfX[feat->panPartStart[part]]),
			      &(feat->padfY[feat->panPartStart[part]]));
    }
  }
  /* our area function computes in opposite direction */
  return -area;
}

static double length2d_polyline (int n, double *x, double *y) {
  double length = 0.0;
  int i;
  for (i = 1; i < n; i++) {
    length += sqrt((x[i] - x[i-1])*(x[i] - x[i-1]) 
		   +
		   (y[i] - y[i-1])*(y[i] - y[i-1]));
  }
  return length;
}

static double shp_length (SHPObject *feat) {
  double length = 0.0;
  if (feat->nParts == 0) {
    length = length2d_polyline(feat->nVertices, feat->padfX, feat->padfY);
  }
  else {
    int part, n;
    for (part = 0; part < feat->nParts; part++) {
      if (part < feat->nParts - 1) {
	n = feat->panPartStart[part+1] - feat->panPartStart[part];
      }
      else {
	n = feat->nVertices - feat->panPartStart[part];
      }
      length += length2d_polyline (n, 
				   &(feat->padfX[feat->panPartStart[part]]),
				   &(feat->padfY[feat->panPartStart[part]]));
    }
  }
  return length;
}