/* vim: set expandtab shiftwidth=2 softtabstop=2 tw=70: */

#include <Rcpp.h>
using namespace Rcpp;
#include <math.h>

// #define DEBUG

// Cross-reference work:
// 1. update ../src/registerDynamicSymbol.c with an item for this
// 2. main code should use the autogenerated wrapper in ../R/RcppExports.R

// [[Rcpp::export]]
List mapAssemblePolygons(NumericVector lon, NumericVector lat,
                         NumericMatrix z) {
  int nlat = lat.size();
  int nlon = lon.size();
  if (nlon < 1) {
    Rcpp::stop("must have at least 2 longitudes");
  }
  if (nlat < 1) {
    Rcpp::stop("must have at least 2 latitudes");
  }
  int nrow = z.nrow();
  int ncol = z.ncol();
  if (nlat != ncol) {
    Rcpp::stop("mismatch; length(lat)=%d must equal nrow(z)=%d", nlat, ncol);
  }
  if (nlon != nrow) {
    Rcpp::stop("mismatch; length(lon)=%d must equal ncol(z)=%d", nlon, nrow);
  }
  int n = nlon * nlat;
  NumericVector polylon(5 * n);
  NumericVector polylat(5 * n);
  NumericMatrix polyz(nlon, nlat);
  double latstep = 0.5 * fabs(lat[1] - lat[0]);
  double lonstep = 0.5 * fabs(lon[1] - lon[0]);
#ifdef DEBUG
  Rprintf("nlon: %d, nlat: %d, latstep: %f, lonstep: %f\n", nlon, nlat, latstep,
          lonstep);
#endif
  int k = 0, l = 0; // indices for points and polygons
  for (int j = 0; j < ncol; j++) {
    for (int i = 0; i < nrow; i++) {
#ifdef DEBUG
      if (j == 0 && i < 3) {
        Rprintf("i: %d, j: %d, lon: %.4f, lat:%.4f, k: %d\n", i, j, lon[i],
                lat[j], k);
      }
#endif
      // Lower left
      polylon[k] = lon[i] - lonstep;
      polylat[k++] = lat[j] - latstep;
      // Upper left
      polylon[k] = lon[i] - lonstep;
      polylat[k++] = lat[j] + latstep;
      // Upper right
      polylon[k] = lon[i] + lonstep;
      polylat[k++] = lat[j] + latstep;
      // Lower right
      polylon[k] = lon[i] + lonstep;
      polylat[k++] = lat[j] - latstep;
      // end
      polylon[k] = NA_REAL;
      polylat[k++] = NA_REAL;
      polyz[l++] = z(i, j);
#ifdef DEBUG
      if (j == 0 && i < 3) {
        for (int kk = k - 5; kk < k - 1; kk++) {
          Rprintf("k: %d, lon: %.4f, lat:%.4f\n", kk, polylon[kk], polylat[kk]);
        }
      }
#endif
    }
    if (k > 5 * n) {
      Rcpp::stop("coding error (assigned insufficient memory); k: %d,  5*n: %d",
                 k, 5 * n);
    }
  }
  if (k != 5 * n) {
    Rcpp::stop("coding error (assigned surplus memory); k: %d,  5*n: %d", k,
               5 * n);
  }
  return (List::create(Named("longitude") = polylon,
                       Named("latitude") = polylat, Named("z") = polyz));
}

// [[Rcpp::export]]
List mapCheckPolygons(NumericVector x, NumericVector y, NumericVector z,
                      NumericVector xokspan,
                      NumericVector usr) // returns new x vector
{
  int nusr = usr.size();
  if (nusr != 4)
    Rcpp::stop("'usr' must hold 4 values, but it holds %d", nusr);
  int nx = x.size();
  if (nx < 2) {
    Rcpp::stop("must have at least two x values, but have %d", nx);
  }
  int ny = y.size();
  if (ny < 2) {
    Rcpp::stop("must have at least two y values, but have %d", ny);
  }
  int nz = z.size();
  if (nz < 1) {
    Rcpp::stop("must have at least one z value, but have %d", nz);
  }
  int nxokspan = xokspan.size();
  if (nxokspan != 1) {
    Rcpp::stop("okspan should be of length 1, but it is of length %d",
               nxokspan);
  }
  int npoly = nx / 5;
  LogicalVector okPoint(nx);
  LogicalVector clippedPoint(nx);
  LogicalVector okPolygon(npoly);
  LogicalVector clippedPolygon(npoly);
  // Initialize (not be needed if below catches all cases)
  for (int ipoly = 0; ipoly < npoly; ipoly++) {
    okPolygon[ipoly] = 1;
    clippedPolygon[ipoly] = 0;
  }
  for (int ix = 0; ix < nx; ix++) {
    okPoint[ix] = 1;
    clippedPoint[ix] = 0;
  }
  // x1 x2 x3 x4 NA x1 x2 x3 x4 NA ...
  double dxPermitted = 0.0 + abs(xokspan[0]);
#ifdef DEBUG
  int count = 0, ncount = 100000;
#endif
  for (int ipoly = 0; ipoly < npoly; ipoly++) {
    int start = 5 * ipoly;
    // Check for bad polygons, in three phases.
    // 1. Find polygons that have some NA values for vertices
#ifdef DEBUG
    if (ipoly < 3)
      Rprintf("start: %d; okPoint= %d %d ...\n", start, okPoint[start],
              okPoint[start + 1]);
#endif
    for (int j = 0; j < 4; j++) { // skip 5th point which is surely NA
      // Check for x or y being NA
      if (ISNA(x[start + j]) || ISNA(y[start + j])) {
#ifdef DEBUG
        if (count++ < ncount) { // FIXME: remove when working
          Rprintf("(1.) x or y is NA -- ipoly: %d, j: %d, span: %f (limit to "
                  "span: %f)\n",
                  ipoly, j, fabs(x[start + j] - x[start + j - 1]), dxPermitted);
        }
#endif
        for (int k = 0; k < 5; k++) {
          okPoint[start + k] = 0;
        }
        okPolygon[ipoly] = 0;
        break;
      }
    }
    // 2. Find polygons with all vertices outside the plot region
    double xmin = x[start], xmax = x[start], ymin = y[start], ymax = y[start];
    for (int j = 1; j < 4; j++) {
      if (x[start + j] < xmin)
        xmin = x[start + j];
      if (y[start + j] < ymin)
        ymin = y[start + j];
      if (x[start + j] > xmax)
        xmax = x[start + j];
      if (y[start + j] > ymax)
        ymax = y[start + j];
    }
    if (xmax < usr[0] || usr[1] < xmin || ymax < usr[2] || usr[3] < ymin) {
#ifdef DEBUG
      if (count < ncount) {
        count++;
        Rprintf("clipping points %d to %d\n", start, start + 4);
      }
#endif
      for (int k = 0; k < 5; k++) {
        clippedPoint[start + k] = 1;
      }
      clippedPolygon[ipoly] = 1;
    }
    // 3. Find polygons with excessive x range (an error in projection)
    for (int j = 1; j < 4; j++) { // skip 5th point which is surely NA
      if (dxPermitted < fabs(x[start + j] - x[start + j - 1])) {
#ifdef DEBUG
        if (count++ < ncount) { // FIXME: remove when working
          Rprintf("(3.) ipoly: %d, j: %d, span: %f (limit to span: %f)\n",
                  ipoly, j, fabs(x[start + j] - x[start + j - 1]), dxPermitted);
        }
#endif
        for (int k = 0; k < 5; k++) {
          okPoint[start + k] = 0;
        }
        okPolygon[ipoly] = 0;
        break;
      }
    }
  }
  return (List::create(Named("okPoint") = okPoint,
                       Named("clippedPoint") = clippedPoint,
                       Named("okPolygon") = okPolygon,
                       Named("clippedPolygon") = clippedPolygon));
} // mapCheckPolygons

// [[Rcpp::export]]
List mapClipXy(NumericVector x, NumericVector y, NumericVector usr) {
  // Eliminate any coastline segments that lie wholly outside par("usr")
  // returns list with new x and y vectors
  int nusr = usr.size();
  if (nusr != 4) {
    Rcpp::stop("'usr' must hold 4 values, not %d", nusr);
  }
  int xlen = x.size();
  int ylen = y.size();
  if (xlen != ylen) {
    Rcpp::stop(
        "'x' and 'y' must be of same length, but their lengths are %d and %d",
        xlen, ylen);
  }
  if (xlen < 2) {
    Rcpp::stop("must have at least two 'x' and 'y' pairs");
  }
  // xb and yb are growable buffers; we copy to xc and yc near the end.
  int clen = xlen + 100; // the 100 may save reallocs
  double *xbp = (double *)R_Calloc((size_t)clen, double);
  double *ybp = (double *)R_Calloc((size_t)clen, double);
#ifdef DEBUG
  double distMIN = 10e6; // FIXME: temporary to find problem in Greenland
  // Find chunks, and copy any with 1 or more datum in the usr window.
  Rprintf("usrp=%.0f %.0f %.0f %.0f\n", usrp[0], usrp[1], usrp[2], usrp[3]);
  double danSmall = 1e10;
#endif
  // flush any NA at the start
  int i = 0;
  while (i < xlen && ISNA(x[i])) {
    i++;
  }
  int istart = i, iend = -1;
  int j = 0;
  for (; i < xlen; i++) {
    if (ISNA(x[i])) {
      iend = i - 1;
      while (i < xlen && ISNA(x[i])) {
        i++;
      }
      // Save the polygon, if it intersects par("usr")
      for (int ii = istart; ii <= iend; ii++) {
        if (usr[0] <= x[ii] && x[ii] <= usr[1] && usr[2] <= y[ii] &&
            y[ii] <= usr[3]) {
#ifdef DEBUG
          Rprintf("istart=%d iend=%d intersects par('usr')\n", istart, iend);
#endif
          for (int k = istart; k <= iend; k++) {
            xbp[j] = x[k];
            ybp[j] = y[k];
            if (j > (clen - 2)) {
              /*Rprintf("INCREASE storage from %d to %d [a]\n", clen, (int)(100
               * + clen));*/
              clen += 100;
              xbp = (double *)R_Realloc(xbp, clen, double);
              ybp = (double *)R_Realloc(ybp, clen, double);
            }
            j++;
          }
#ifdef DEBUG
          Rprintf("  copied to output buffer, so now j=%d\n", j);
#endif
          break;
        }
      }
      istart = i;
      xbp[j] = NA_REAL;
      ybp[j] = NA_REAL;
      if (j > (clen - 2)) {
        /*Rprintf("INCREASE storage from %d to %d [a]\n", clen, (int)(100
         * + clen));*/
        clen += 100;
        xbp = (double *)R_Realloc(xbp, clen, double);
        ybp = (double *)R_Realloc(ybp, clen, double);
      }
      j++;
    }
  }
  // copy to a return value
  NumericVector xc(j);
  NumericVector yc(j);
  for (int jj = 0; jj < j; jj++) {
    xc[jj] = xbp[jj];
    yc[jj] = ybp[jj];
  }
  // I think the allocated space is cleared up automatically
  return (List::create(Named("x") = xc, Named("y") = yc));
} // mapClipXy
