/*
    Inside/Outside polygon filter

    Copyright (C) 2002 Robert Lipe, robertlipe@usa.net

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA

 */
#include "defs.h"
#include "filterdefs.h"

#if FILTERS_ENABLED
#define MYNAME "Polygon filter"

static char *polyfileopt = NULL;
static char *exclopt = NULL;

/*
 * This test for insideness is essentially an odd/even test.  The
 * traditional (simple) implementation of the odd/even test counts
 * intersections along a test ray, and if it should happen to exactly hit
 * a vertex of the polygon, it throws away the result and fires a different
 * test ray.  Since we're potentially testing hundreds of test points against
 * a polygon with hundreds of sides, this seemed both wasteful and difficult
 * to coordinate, so instead I added extra state to try to figure out what we
 * would have seen if we had just missed the vertex.  The result is the
 * hodgepodge of "limbo" states explained below.  On the credit side of the
 * ledger, though, the tests for intersection are vastly simplified by always
 * having a horizontal test ray.
 *
 * The general structure of this filter is: we loop over the points in the
 * polygon.  For each point, we update the state of each of the waypoints in
 * the test set.  Thus, the state of every waypoint is indeterminate until
 * the end of the outer loop, at which point the state of every waypoint
 * should theoretically be completely determined.
 *
 * The bits following this comment encode the current state of the test point
 * as we go around the polygon.  OUTSIDE clearly isn't a bit; it's just here
 * for completeness.  If it's not INSIDE, and it's not something else, it's
 * clearly OUTSIDE.
 *
 * INSIDE is self-explanatory.  What it means is that the last time we were
 * certain of our state, we were inside of the polygon.
 *
 * LIMBO encodes a bit more state information, to handle the case where our
 * test ray (a horizontal line) has intersected one of the vertices of the
 * polygon exactly.  If the two lines that meet at that vertex are on
 * opposite sides of the test ray, it was an intersection.  Otherwise, it just
 * grazed a local minimum or maximum and so counted as either zero or two
 * intersections - not a change in state.  Horizontal lines encountered
 * while in limbo don't change the limbo state.  All other lines do.
 * The rest of the bits talk about how we got into limbo, and thus what to do
 * when we get out of limbo.
 *
 * LIMBO_UP means that the last line segment we saw going into limbo was
 * headed upward.  When we see another non-horizontal line segment, whether
 * we flip the INSIDE bit or not depends on whether it also goes upward.  If
 * it does, we flip the INSIDE bit.  Otherwise, we just clear our limbo state
 * and go on as if nothing had happened.
 *
 * LIMBO_BEGIN means that the very first vertex in the polygon put us into a
 * limbo state.  We won't be able to resolve that limbo state until we get to
 * the end of the cycle, and it can actually coexist with another more local
 * limbo state.  The next two bits talk about the beginning limbo state in
 * more detail.
 *
 * BEGIN_UP means that the first non-horizontal line segment we encountered
 * while in a LIMBO_BEGIN state went upward.  As with LIMBO_UP, this is used
 * to determine the final disposition of the limbo state when we get back
 * around to the other end of the cycle.
 *
 * BEGIN_HOR is fairly temporary.  It says that we've encountered one or more
 * horizontal line segments at the beginning of the cycle, so we haven't yet
 * been able to resolve the state of BEGIN_UP.  It's a way of propagating the
 * "firstness" forward until we can make a decision, without propagating it
 * for every test point.
 *
 * UP is used to remember which way we were going in case we encounter a
 * limbo state.
 *
 *  --  RLP
 */

#define OUTSIDE     0
#define INSIDE      1
#define LIMBO       2
#define LIMBO_UP    4
#define LIMBO_BEGIN 8
#define BEGIN_UP    16
#define BEGIN_HOR   32
#define UP          64

typedef struct {
  unsigned short state;
  unsigned short override;
} extra_data;

static
arglist_t polygon_args[] = {
  {
    "file", &polyfileopt,  "File containing vertices of polygon",
    NULL, ARGTYPE_FILE | ARGTYPE_REQUIRED, ARG_NOMINMAX
  },
  {
    "exclude", &exclopt, "Exclude points inside the polygon",
    NULL, ARGTYPE_BOOL, ARG_NOMINMAX
  },
  ARG_TERMINATOR
};

static void polytest(double lat1, double lon1,
                     double lat2, double lon2,
                     double wlat, double wlon,
                     unsigned short *state, int first, int last)
{

  if (lat1 == wlat) {
    if (lat2 < wlat) {
      /* going down */
      if (*state & LIMBO) {
        if (*state & LIMBO_UP) {
          *state = *state ^ INSIDE;
        }
        *state = *state & ~LIMBO &~LIMBO_UP;
      } else if (*state & LIMBO_BEGIN) {
        if (*state & BEGIN_HOR) {
          *state = *state & ~BEGIN_HOR;
        } else if (last) {
          if (*state & BEGIN_UP) {
            *state = *state ^ INSIDE;
          }
          *state = *state & ~LIMBO_BEGIN & ~BEGIN_UP;
        }
      } else if (first && (lon1 > wlon)) {
        *state |= LIMBO_BEGIN;
      }
    } else if (lat2 == wlat) {
      if (first & (lon1 > wlon || lon2 > wlon)) {
        *state |= LIMBO_BEGIN | BEGIN_HOR;
      } else if (last && (*state & LIMBO_BEGIN) && (*state & LIMBO)) {
        if ((!!(*state & LIMBO_UP)) != (!!(*state & BEGIN_UP))) {
          *state = *state ^ INSIDE;
        }
        *state = *state & ~LIMBO & ~LIMBO_UP &
                 ~LIMBO_BEGIN & ~BEGIN_UP;
      } else if (*state & LIMBO) {
        /* do nothing */
      } else {
        if (lon1 <= wlon && lon2 > wlon) {
          if (*state & UP) {
            *state &= ~UP;
            *state |= LIMBO_UP;
          }
          *state = *state | LIMBO;
        }
      }
    } else {
      /* going up */
      if (*state & LIMBO) {
        if (!(*state & LIMBO_UP)) {
          *state = *state ^ INSIDE;
        }
        *state = *state & ~LIMBO & ~LIMBO_UP;
      } else if (*state & LIMBO_BEGIN) {
        if (*state & BEGIN_HOR) {
          *state &= ~BEGIN_HOR;
          *state |= BEGIN_UP;
        } else if (last) {
          if (!(*state & BEGIN_UP)) {
            *state = *state ^ INSIDE;
          }
          *state = *state & ~LIMBO_BEGIN & ~BEGIN_UP;
        }
      } else if (first && (lon1 > wlon)) {
        *state |= LIMBO_BEGIN | BEGIN_UP;
      }
    }
    *state = *state & ~UP;
  } else if (lat2 == wlat) {
    if (lat1 < wlat) {
      if (last) {
        if (*state & BEGIN_UP) {
          *state = *state ^ INSIDE;
        }
        *state = *state & ~LIMBO_BEGIN & ~BEGIN_UP;
      } else if (lon2 > wlon) {
        *state |= LIMBO;
      }
    }
    /* no case for lat1==wlat; that's above */
    else {
      if (last) {
        if (!(*state & BEGIN_UP)) {
          *state = *state ^ INSIDE;
        }
        *state = *state & ~LIMBO_BEGIN & ~BEGIN_UP;
      } else if (lon2 > wlon) {
        *state |= LIMBO | LIMBO_UP;
      } else {
        *state |= UP;
      }
    }
  } else {
    if ((lat1 > wlat && lat2 < wlat) ||
        (lat1 < wlat && lat2 > wlat)) {
      /* we only care if the lines might intersect */
      if (lon1 > wlon && lon2 > wlon) {
        *state = *state ^ INSIDE;
      } else if (!(lon1 <= wlon && lon2 <= wlon)) {
        /* we're inside the bbox of a diagonal line.  math time. */
        double loni = lon1+(lon2-lon1)/(lat2-lat1)*(wlat-lat1);
        if (loni > wlon) {
          *state = *state ^ INSIDE;
        }
      }
    }
  }

}

#define BADVAL 999999

void
polygon_process(void)
{
  queue * elem, * tmp;
  waypoint * waypointp;
  extra_data *ed;
  double lat1, lon1, lat2, lon2;
  double olat, olon;
  int fileline = 0;
  int first = 1;
  int last = 0;
  char *line;
  gbfile *file_in;

  file_in = gbfopen(polyfileopt, "r", MYNAME);

  olat = olon = lat1 = lon1 = lat2 = lon2 = BADVAL;
  while ((line = gbfgetstr(file_in))) {
    char *pound = NULL;
    int argsfound = 0;

    fileline++;

    pound = strchr(line, '#');
    if (pound) {
      *pound = '\0';
    }

    lat2 = lon2 = BADVAL;
    argsfound = sscanf(line, "%lf %lf", &lat2, &lon2);

    if (argsfound != 2 && strspn(line, " \t\n") < strlen(line)) {
      warning(MYNAME
              ": Warning: Polygon file contains unusable vertex on line %d.\n",
              fileline);
    } else if (lat1 != BADVAL && lon1 != BADVAL &&
               lat2 != BADVAL && lon2 != BADVAL) {
      QUEUE_FOR_EACH(&waypt_head, elem, tmp) {

        waypointp = (waypoint *)elem;
        if (waypointp->extra_data) {
          ed = (extra_data *) waypointp->extra_data;
        } else {
          ed = (extra_data *) xcalloc(1, sizeof(*ed));
          ed->state = OUTSIDE;
          ed->override = 0;
          waypointp->extra_data = (extra_data *) ed;
        }
        if (lat2 == waypointp->latitude &&
            lon2 == waypointp->longitude) {
          ed->override = 1;
        }
        if (olat != BADVAL && olon != BADVAL &&
            olat == lat2 && olon == lon2) {
          last = 1;
        }
        polytest(lat1, lon1, lat2, lon2,
                 waypointp->latitude,
                 waypointp->longitude,
                 &ed->state, first, last);
        first = 0;
        last = 0;
      }
    }
    if (olat != BADVAL && olon != BADVAL &&
        olat == lat2 && olon == lon2) {
      olat = BADVAL;
      olon = BADVAL;
      lat1 = BADVAL;
      lon1 = BADVAL;
      first = 1;
    } else if (lat1 == BADVAL || lon1 == BADVAL) {
      olat = lat2;
      olon = lon2;
      lat1 = lat2;
      lon1 = lon2;
    } else {
      lat1 = lat2;
      lon1 = lon2;
    }
  }
  gbfclose(file_in);

  QUEUE_FOR_EACH(&waypt_head, elem, tmp) {
    waypoint *wp = (waypoint *) elem;
    ed = (extra_data *) wp->extra_data;
    wp->extra_data = NULL;
    if (ed) {
      if (ed->override) {
        ed->state = INSIDE;
      }
      if (((ed->state & INSIDE) == OUTSIDE) == (exclopt == NULL)) {
        waypt_del(wp);
        waypt_free(wp);
      }
      xfree(ed);
    }
  }
}

void
polygon_init(const char *args)
{
  /* do nothing */
}

void
polygon_deinit(void)
{
  /* do nothing */
}

filter_vecs_t polygon_vecs = {
  polygon_init,
  polygon_process,
  polygon_deinit,
  NULL,
  polygon_args
};
#endif // FILTERS_ENABLED
