/**
 * @file clipping.c
 *
 * @copyright Copyright  (C)  2013 Moritz Hanke <hanke@dkrz.de>
 *                                 Rene Redler <rene.redler@mpimet.mpg.de>
 *
 * @version 1.0
 * @author Moritz Hanke <hanke@dkrz.de>
 *         Rene Redler <rene.redler@mpimet.mpg.de>
 */
/*
 * Keywords:
 * Maintainer: Moritz Hanke <hanke@dkrz.de>
 *             Rene Redler <rene.redler@mpimet.mpg.de>
 * URL: https://doc.redmine.dkrz.de/YAC/html/index.html
 *
 * This file is part of YAC.
 *
 * YAC 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 3 of the License, or
 * (at your option) any later version.
 *
 * YAC 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 YAC.  If not, see <http://www.gnu.org/licenses/gpl.txt>.
 */

#ifdef HAVE_CONFIG_H
// Get the definition of the 'restrict' keyword.
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "geometry.h"
#include "clipping.h"
#include "area.h"
#include "ensure_array_size.h"
#include "utils.h"

//#define YAC_VERBOSE_CLIPPING

static double const tol = 1.0e-12;

enum cell_type {
  LON_LAT_CELL,
  LAT_CELL,
  GREAT_CIRCLE_CELL,
  MIXED_CELL
};

enum intersect_results {
  P_ON_A = 1,
  Q_ON_A = 2,
  P_ON_B = 4,
  Q_ON_B = 8,
  SAME_PLANE = 16,
};

struct point_list_element {

  double vec_coords[3];
  enum yac_edge_type edge_type; // type of edge with next corner
  int to_be_removed;
  struct point_list_element * next;
};

struct point_list {

  struct point_list_element * start;
  struct point_list_element * free_elements;
};

/* internal helper routines for working with linked lists of points */

static void init_point_list(struct point_list * list);

static void reset_point_list(struct point_list * list);

static size_t generate_point_list(
  struct point_list * list, struct grid_cell cell);

static struct point_list_element *
get_free_point_list_element(struct point_list * list);

static size_t remove_points(struct point_list * list);
static size_t remove_zero_length_edges(struct point_list * list);

static void free_point_list(struct point_list * list);

static int get_cell_points_ordering(struct point_list * cell);

static void generate_overlap_cell(struct point_list * list,
                                  struct grid_cell * cell);

static enum cell_type get_cell_type(struct grid_cell target_cell);

/* ------------------------- */

static struct grid_cell * overlap_cell_buffer = NULL;
static size_t overlap_cell_buffer_size = 0;

static inline struct grid_cell * get_overlap_cell_buffer(size_t N) {

  // ensure that there are enough buffer cells

  if (overlap_cell_buffer_size < N) {

    size_t old_overlap_cell_buffer_size = overlap_cell_buffer_size;

    ENSURE_ARRAY_SIZE(overlap_cell_buffer, overlap_cell_buffer_size, N);

    for (; old_overlap_cell_buffer_size < overlap_cell_buffer_size;
         ++old_overlap_cell_buffer_size)
      yac_init_grid_cell(overlap_cell_buffer + old_overlap_cell_buffer_size);
  }

  return overlap_cell_buffer;
}

/* ------------------------- */

void yac_compute_overlap_areas (size_t N,
                                struct grid_cell * source_cell,
                                struct grid_cell target_cell,
                                double * partial_areas) {

  struct grid_cell * overlap_buffer = get_overlap_cell_buffer(N);

  /* Do the clipping and get the cell for the overlapping area */

  yac_cell_clipping ( N, source_cell, target_cell, overlap_buffer);

  /* Get the partial areas for the overlapping regions */

  for (size_t n = 0; n < N; n++) {
    partial_areas[n] = yac_huiliers_area (overlap_buffer[n]);
    // we cannot use pole_area because it is rather inaccurate for great circle
    // edges that are nearly circles of longitude
    //partial_areas[n] = pole_area (overlap_buffer[n]);
  }

#ifdef YAC_VERBOSE_CLIPPING
  for (size_t n = 0; n < N; n++)
    printf("overlap area : %lf\n", partial_areas[n]);
#endif
}

/* ------------------------- */

static void compute_cell_barycenter(
  struct grid_cell cell, double * barycenter) {

  barycenter[0] = 0.0;
  barycenter[1] = 0.0;
  barycenter[2] = 0.0;

  for (size_t k = 0; k < cell.num_corners; ++k) {
    barycenter[0] += cell.coordinates_xyz[k][0];
    barycenter[1] += cell.coordinates_xyz[k][1];
    barycenter[2] += cell.coordinates_xyz[k][2];
  }
  normalise_vector(barycenter);
}

void yac_compute_concave_overlap_info (size_t N,
                                       struct grid_cell * source_cell,
                                       struct grid_cell target_cell,
                                       double target_node_xyz[3],
                                       double * overlap_areas,
                                       double (*overlap_barycenters)[3]) {

  struct grid_cell * overlap_buffer = get_overlap_cell_buffer(N);
  enum cell_type target_cell_type;

  if ( target_cell.num_corners > 3 )
    target_cell_type = get_cell_type (target_cell);

  if ( target_cell.num_corners < 4 || target_cell_type == LON_LAT_CELL ) {
    yac_cell_clipping ( N, source_cell, target_cell, overlap_buffer);
    for (size_t i = 0; i < N; ++i) {
      compute_cell_barycenter(overlap_buffer[i], overlap_barycenters[i]);
      overlap_areas[i] = yac_huiliers_area (overlap_buffer[i]);
    }
    return;
  }

  if ( target_node_xyz == NULL )
    yac_internal_abort_message("ERROR: missing target point coordinates "
                               "(x_coordinates == NULL || y_coordinates == NULL)",
                               __FILE__ , __LINE__);

  struct grid_cell target_partial_cell =
    {.coordinates_x   = (double[3]){-1},
     .coordinates_y   = (double[3]){-1},
     .coordinates_xyz = (double[3][3]){{-1}},
     .edge_type       = (enum yac_edge_type[3]) {GREAT_CIRCLE},
     .num_corners     = 3};

  /* Do the clipping and get the cell for the overlapping area */

  for ( size_t n = 0; n < N; n++) {
    overlap_areas[n] = 0.0;
    overlap_barycenters[n][0] = 0.0;
    overlap_barycenters[n][1] = 0.0;
    overlap_barycenters[n][2] = 0.0;
  }

  // common node point to all partial target cells
  target_partial_cell.coordinates_xyz[0][0] = target_node_xyz[0];
  target_partial_cell.coordinates_xyz[0][1] = target_node_xyz[1];
  target_partial_cell.coordinates_xyz[0][2] = target_node_xyz[2];
  XYZtoLL(target_node_xyz, target_partial_cell.coordinates_x,
          target_partial_cell.coordinates_y);

  for ( size_t num_corners = 0;
        num_corners < target_cell.num_corners; ++num_corners ) {

    size_t corner_a = num_corners;
    size_t corner_b = (num_corners+1)%target_cell.num_corners;

    // skip clipping and area calculation for degenerated triangles
    //
    // If this is not sufficient, instead we can try something like:
    //
    //     struct point_list target_list
    //     init_point_list(&target_list);
    //     generate_point_list(&target_list, target_cell);
    //     struct grid_cell temp_target_cell;
    //     generate_overlap_cell(target_list, temp_target_cell);
    //     free_point_list(&target_list);
    //
    // and use temp_target_cell for triangulation.
    //
    // Compared to the if statement below the alternative seems
    // to be quite costly.

    if ( ( ( fabs(target_cell.coordinates_xyz[corner_a][0] -
                  target_cell.coordinates_xyz[corner_b][0]) < tol ) &&
           ( fabs(target_cell.coordinates_xyz[corner_a][1] -
                  target_cell.coordinates_xyz[corner_b][1]) < tol ) &&
           ( fabs(target_cell.coordinates_xyz[corner_a][2] -
                  target_cell.coordinates_xyz[corner_b][2]) < tol ) ) ||
         ( ( fabs(target_cell.coordinates_xyz[corner_a][0] -
                  target_partial_cell.coordinates_xyz[0][0]) < tol    ) &&
           ( fabs(target_cell.coordinates_xyz[corner_a][1] -
                  target_partial_cell.coordinates_xyz[0][1]) < tol    ) &&
           ( fabs(target_cell.coordinates_xyz[corner_a][2] -
                  target_partial_cell.coordinates_xyz[0][2]) < tol    ) ) ||
         ( ( fabs(target_cell.coordinates_xyz[corner_b][0] -
                  target_partial_cell.coordinates_xyz[0][0]) < tol    ) &&
           ( fabs(target_cell.coordinates_xyz[corner_b][1] -
                  target_partial_cell.coordinates_xyz[0][1]) < tol    ) &&
           ( fabs(target_cell.coordinates_xyz[corner_b][2] -
                  target_partial_cell.coordinates_xyz[0][2]) < tol    ) ) )
       continue;

    target_partial_cell.coordinates_x[1] = target_cell.coordinates_x[corner_a];
    target_partial_cell.coordinates_y[1] = target_cell.coordinates_y[corner_a];
    target_partial_cell.coordinates_x[2] = target_cell.coordinates_x[corner_b];
    target_partial_cell.coordinates_y[2] = target_cell.coordinates_y[corner_b];

    target_partial_cell.coordinates_xyz[1][0] = target_cell.coordinates_xyz[corner_a][0];
    target_partial_cell.coordinates_xyz[1][1] = target_cell.coordinates_xyz[corner_a][1];
    target_partial_cell.coordinates_xyz[1][2] = target_cell.coordinates_xyz[corner_a][2];
    target_partial_cell.coordinates_xyz[2][0] = target_cell.coordinates_xyz[corner_b][0];
    target_partial_cell.coordinates_xyz[2][1] = target_cell.coordinates_xyz[corner_b][1];
    target_partial_cell.coordinates_xyz[2][2] = target_cell.coordinates_xyz[corner_b][2];

    yac_cell_clipping ( N, source_cell, target_partial_cell, overlap_buffer);

    /* Get the partial areas for the overlapping regions as sum over the partial target cells. */

    for (size_t n = 0; n < N; n++) {
      if (overlap_buffer[n].num_corners == 0) continue;
      double overlap_area = yac_huiliers_area (overlap_buffer[n]);
      double temp_cell_barycenter[3];
      overlap_areas[n] += overlap_area;
      compute_cell_barycenter(overlap_buffer[n], temp_cell_barycenter);
      overlap_barycenters[n][0] += overlap_area * temp_cell_barycenter[0];
      overlap_barycenters[n][1] += overlap_area * temp_cell_barycenter[1];
      overlap_barycenters[n][2] += overlap_area * temp_cell_barycenter[2];
    }
  }
  for (size_t n = 0; n < N; n++) normalise_vector(overlap_barycenters[n]);

#ifdef YAC_VERBOSE_CLIPPING
  for (size_t n = 0; n < N; n++)
    printf("overlap area %i: %lf \n", n, overlap_areas[n]);
#endif
}

static inline double dotproduct(double a[], double b[]) {

  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}

static int gc_cell_is_convex(struct grid_cell cell) {

  double const sq_angle_tol = yac_angle_tol * yac_angle_tol;
  double (* restrict coordinates_xyz)[3] = cell.coordinates_xyz;

  // for all edges (we skip the edge 0->num_corners-1; because we do not need it
  // to determine whether a polygon is convex or not
  for (size_t i = 0; i < cell.num_corners-1; ++i) {

    // compute norm vector for current edge
    double edge_norm_vec[3];
    crossproduct_d(
      coordinates_xyz[i], coordinates_xyz[i+1], edge_norm_vec);
    // (sin(edge_length))^2
    double sq_sin_edge_length = edge_norm_vec[0] * edge_norm_vec[0] +
                                edge_norm_vec[1] * edge_norm_vec[1] +
                                edge_norm_vec[2] * edge_norm_vec[2];
    // if the edge is too short -> skip edge
    if (sq_sin_edge_length < sq_angle_tol) continue;

    normalise_vector(edge_norm_vec);

    size_t same_side_count = 0;

    // for the corners of the triangle
    // (from corner index 0 to start of the edge)
    for (size_t j = 0; j < i; ++j)
      same_side_count += dotproduct(edge_norm_vec, coordinates_xyz[j]) > 0.0;

    // for the remaining corners
    for (size_t j = i + 2; j < cell.num_corners; ++j)
      same_side_count += dotproduct(edge_norm_vec, coordinates_xyz[j]) > 0.0;

    // if not all corners are on the same side of the current edge -> concave
    if ((same_side_count != 0) &&
        (same_side_count != cell.num_corners - 2)) return 0;
  }

  // the cell is convex
  return 1;
}

/* ------------------------- */

void yac_compute_concave_overlap_areas (size_t N,
                                        struct grid_cell * source_cell,
                                        struct grid_cell target_cell,
                                        double target_node_xyz[3],
                                        double * partial_areas) {
  enum cell_type target_cell_type;

  if ( target_cell.num_corners > 3 )
    target_cell_type = get_cell_type (target_cell);

  if (( target_cell.num_corners < 4 || target_cell_type == LON_LAT_CELL ) ||
      ((target_cell_type == GREAT_CIRCLE_CELL) &&
       gc_cell_is_convex(target_cell))) {
    yac_compute_overlap_areas (N, source_cell, target_cell, partial_areas);
    return;
  }

  if ( target_node_xyz == NULL )
    yac_internal_abort_message("ERROR: missing target point coordinates "
                               "(x_coordinates == NULL || y_coordinates == NULL)",
                               __FILE__ , __LINE__);

  struct grid_cell target_partial_cell =
    {.coordinates_x   = (double[3]){-1},
     .coordinates_y   = (double[3]){-1},
     .coordinates_xyz = (double[3][3]){{-1}},
     .edge_type       = (enum yac_edge_type[3]) {GREAT_CIRCLE},
     .num_corners     = 3};

  struct grid_cell * overlap_buffer = get_overlap_cell_buffer(N);

  /* Do the clipping and get the cell for the overlapping area */

  for ( size_t n = 0; n < N; n++) partial_areas[n] = 0.0;

  // common node point to all partial target cells
  target_partial_cell.coordinates_xyz[0][0] = target_node_xyz[0];
  target_partial_cell.coordinates_xyz[0][1] = target_node_xyz[1];
  target_partial_cell.coordinates_xyz[0][2] = target_node_xyz[2];
  XYZtoLL(target_node_xyz, target_partial_cell.coordinates_x,
          target_partial_cell.coordinates_y);

  for ( size_t num_corners = 0;
        num_corners < target_cell.num_corners; ++num_corners ) {

    size_t corner_a = num_corners;
    size_t corner_b = (num_corners+1)%target_cell.num_corners;

    // skip clipping and area calculation for degenerated triangles
    //
    // If this is not sufficient, instead we can try something like:
    //
    //     struct point_list target_list
    //     init_point_list(&target_list);
    //     generate_point_list(&target_list, target_cell);
    //     struct grid_cell temp_target_cell;
    //     generate_overlap_cell(target_list, temp_target_cell);
    //     free_point_list(&target_list);
    //
    // and use temp_target_cell for triangulation.
    //
    // Compared to the if statement below the alternative seems
    // to be quite costly.

    if ( ( ( fabs(target_cell.coordinates_xyz[corner_a][0] -
                  target_cell.coordinates_xyz[corner_b][0]) < tol ) &&
           ( fabs(target_cell.coordinates_xyz[corner_a][1] -
                  target_cell.coordinates_xyz[corner_b][1]) < tol ) &&
           ( fabs(target_cell.coordinates_xyz[corner_a][2] -
                  target_cell.coordinates_xyz[corner_b][2]) < tol ) ) ||
         ( ( fabs(target_cell.coordinates_xyz[corner_a][0] -
                  target_partial_cell.coordinates_xyz[0][0]) < tol    ) &&
           ( fabs(target_cell.coordinates_xyz[corner_a][1] -
                  target_partial_cell.coordinates_xyz[0][1]) < tol    ) &&
           ( fabs(target_cell.coordinates_xyz[corner_a][2] -
                  target_partial_cell.coordinates_xyz[0][2]) < tol    ) ) ||
         ( ( fabs(target_cell.coordinates_xyz[corner_b][0] -
                  target_partial_cell.coordinates_xyz[0][0]) < tol    ) &&
           ( fabs(target_cell.coordinates_xyz[corner_b][1] -
                  target_partial_cell.coordinates_xyz[0][1]) < tol    ) &&
           ( fabs(target_cell.coordinates_xyz[corner_b][2] -
                  target_partial_cell.coordinates_xyz[0][2]) < tol    ) ) )
       continue;

    target_partial_cell.coordinates_x[1] = target_cell.coordinates_x[corner_a];
    target_partial_cell.coordinates_y[1] = target_cell.coordinates_y[corner_a];
    target_partial_cell.coordinates_x[2] = target_cell.coordinates_x[corner_b];
    target_partial_cell.coordinates_y[2] = target_cell.coordinates_y[corner_b];

    target_partial_cell.coordinates_xyz[1][0] = target_cell.coordinates_xyz[corner_a][0];
    target_partial_cell.coordinates_xyz[1][1] = target_cell.coordinates_xyz[corner_a][1];
    target_partial_cell.coordinates_xyz[1][2] = target_cell.coordinates_xyz[corner_a][2];
    target_partial_cell.coordinates_xyz[2][0] = target_cell.coordinates_xyz[corner_b][0];
    target_partial_cell.coordinates_xyz[2][1] = target_cell.coordinates_xyz[corner_b][1];
    target_partial_cell.coordinates_xyz[2][2] = target_cell.coordinates_xyz[corner_b][2];

    yac_cell_clipping ( N, source_cell, target_partial_cell, overlap_buffer);

    /* Get the partial areas for the overlapping regions as sum over the partial target cells. */

    for (size_t n = 0; n < N; n++) {
      partial_areas[n] += yac_huiliers_area (overlap_buffer[n]);
      // we cannot use pole_area because it is rather inaccurate for great circle
      // edges that are nearly circles of longitude
      //partial_areas[n] = pole_area (overlap_buffer[n]);
    }
  }

#ifdef YAC_VERBOSE_CLIPPING
  for (size_t n = 0; n < N; n++)
    printf("overlap area %i: %lf \n", n, partial_areas[n]);
#endif
}

/* ------------------------- */

static void compute_norm_vector(double a[], double b[], double norm[]) {

  crossproduct_ld(a, b, norm);

  if ((fabs(norm[0]) < tol) &&
      (fabs(norm[1]) < tol) &&
      (fabs(norm[2]) < tol))
    yac_internal_abort_message("ERROR: a and b are identical -> no norm vector\n",
                               __FILE__, __LINE__);

  double scale = 1.0 / sqrt(norm[0] * norm[0] + norm[1] * norm[1] + norm[2] * norm[2]);

  norm[0] *= scale;
  norm[1] *= scale;
  norm[2] *= scale;
}

static void compute_lat_circle_z_value(double a[], double b[], double z[]) {

  double temp[3];

  crossproduct_ld(a, b, temp);

  z[0] = 0;
  z[1] = 0;

  if (temp[2] > 0)
    z[2] = 1.0 - a[2];
  else
    z[2] = -1.0 - a[2];
}

/**
 * Determines whether a given point is on a hemisphere that is defined by a plane
 * through the middle of the sphere.\n
 * The plane is defined by its norm vector.
 * @param[in] point point to be checked
 * @param[in] norm_vec norm vector of the plane dividing the sphere
 * @returns  0 if the point is not inside the hemisphere
 *           1 if the point is inside the hemisphere
 *           2 if the point is in the plane
 */
static int is_inside_gc(double point[], double norm_vec[]) {

  // the product is defined as follows
  // a * b = |a| * |b| * cos(alpha)
  // where alpha is the angle between a and b

  double dot = dotproduct(point, norm_vec);

  // if the point is on the line
  if (fabs(dot) <= yac_angle_tol * 1e-3)
    return 2;

  return dot < 0;
}

static int is_inside_latc(double point[], double z) {

  double temp = fabs(point[2] + z);

  if (fabs(1.0 - temp) <= yac_angle_tol * 1e-3) return 2;
  else return temp < 1.0;
}

static int is_inside(double point[], double help_vec[],
                     enum yac_edge_type edge_type,
                     int cell_points_ordering) {

  int ret_val = 0;

  switch (edge_type) {

    case (LON_CIRCLE) :
    case (GREAT_CIRCLE) :
      ret_val = is_inside_gc(point, help_vec);
      break;
    case (LAT_CIRCLE) :
      ret_val = is_inside_latc(point, help_vec[2]);
      break;
    default:
      yac_internal_abort_message("invalid edge type\n", __FILE__, __LINE__);
  };

  if (ret_val == 2) return 2;
  else return ret_val ^ cell_points_ordering;
}

static enum cell_type get_cell_type(struct grid_cell target_cell) {

  size_t count_lat_edges = 0, count_great_circle_edges = 0;

   if ((target_cell.num_corners == 4) &&
       ((target_cell.edge_type[0] == LAT_CIRCLE &&
         target_cell.edge_type[1] == LON_CIRCLE &&
         target_cell.edge_type[2] == LAT_CIRCLE &&
         target_cell.edge_type[3] == LON_CIRCLE) ||
        (target_cell.edge_type[0] == LON_CIRCLE &&
         target_cell.edge_type[1] == LAT_CIRCLE &&
         target_cell.edge_type[2] == LON_CIRCLE &&
         target_cell.edge_type[3] == LAT_CIRCLE)))
      return LON_LAT_CELL;
   else
      for (size_t i = 0; i < target_cell.num_corners; ++i)
         if (target_cell.edge_type[i] == LON_CIRCLE ||
             target_cell.edge_type[i] == GREAT_CIRCLE)
            count_great_circle_edges++;
         else
            count_lat_edges++;

   if (count_lat_edges && count_great_circle_edges)
      return MIXED_CELL;
   else if (count_lat_edges)
      return LAT_CELL;
   else
      return GREAT_CIRCLE_CELL;
}

/**
 * cell clipping using Sutherland-Hodgman algorithm;
 */
static void point_list_clipping (struct point_list * source_list, int source_ordering,
                                 struct point_list target_list, int target_ordering,
                                 size_t nct, double * tgt_edge_norm_vec) {

  struct {
    double * edge_norm_vec;
    struct point_list_element * point;
  } tgt_points[nct];

  // to avoid some problems that can occur close the the pole, we process the
  // target lat-circle edges at the end
  {

    size_t count = 0;

    struct point_list_element * curr_tgt_point = target_list.start;

    for (size_t i = 0; i < nct; ++i, curr_tgt_point = curr_tgt_point->next) {

      if (curr_tgt_point->edge_type == LAT_CIRCLE) continue;

      tgt_points[count].edge_norm_vec = tgt_edge_norm_vec + i * 3;
      tgt_points[count++].point = curr_tgt_point;
    }

    if (count != nct) {
      for (size_t i = 0; i < nct; ++i, curr_tgt_point = curr_tgt_point->next) {

        if (curr_tgt_point->edge_type != LAT_CIRCLE) continue;

        tgt_points[count].edge_norm_vec = tgt_edge_norm_vec + i * 3;
        tgt_points[count++].point = curr_tgt_point;
      }
    }
  }

  // count the number of edges in the source cell
  size_t ncs;
  if (source_list->start != NULL) {
    ncs = 1;
    for (struct point_list_element * curr = source_list->start->next,
                                   * end = source_list->start;
         curr != end; curr = curr->next, ++ncs);
  } else {
    ncs = 0;
  }

  // for all edges of the target cell
  for (size_t i = 0; (i < nct) && (ncs > 1); ++i) {

    struct point_list_element * prev_src_point = source_list->start;
    struct point_list_element * curr_src_point = prev_src_point->next;

    double * tgt_edge_coords[2] = {tgt_points[i].point->vec_coords,
                                   tgt_points[i].point->next->vec_coords};
    enum yac_edge_type tgt_edge_type = tgt_points[i].point->edge_type;
    double * curr_tgt_edge_norm_vec = tgt_points[i].edge_norm_vec;

    int prev_is_inside, curr_is_inside, last_curr_is_inside;

    prev_is_inside = is_inside(prev_src_point->vec_coords,
                               tgt_points[i].edge_norm_vec,
                               tgt_points[i].point->edge_type, target_ordering);
    last_curr_is_inside = prev_is_inside;

    // for all edges of the source cell
    for (size_t j = 0; j < ncs; ++j) {

      if (j != ncs - 1)
        curr_is_inside = is_inside(curr_src_point->vec_coords,
                                   curr_tgt_edge_norm_vec, tgt_edge_type,
                                   target_ordering);
      else
        curr_is_inside = last_curr_is_inside;

      double * src_edge_coords[2] = {prev_src_point->vec_coords,
                                     curr_src_point->vec_coords};
      enum yac_edge_type src_edge_type = prev_src_point->edge_type;

      double intersection[2][3];
      int num_intersections = 0;

      // One intersection is possible, if either the previous or the current
      // source point is inside and the respective other one is outside.
      // Two intersections are possible, if one edge is a circle of latitude,
      // while this other is not. Additionally, this is not possible if one of
      // the two source points is inside while the other is not or if both
      // source points are on the plane of the target edge.
      int one_intersect_expected = (curr_is_inside + prev_is_inside == 1);
      int two_intersect_possible =
        ((src_edge_type == LAT_CIRCLE) ^ (tgt_edge_type == LAT_CIRCLE)) &&
        (curr_is_inside + prev_is_inside != 1) &&
        (curr_is_inside + prev_is_inside != 4);

      // if we need to check for intersections
      if (one_intersect_expected || two_intersect_possible) {

        // get intersection points
        int intersect =
          yac_intersect_vec(
            src_edge_type, src_edge_coords[0], src_edge_coords[1],
            tgt_edge_type, tgt_edge_coords[0], tgt_edge_coords[1],
            intersection[0], intersection[1]);

        // if yac_intersect_vec found something -> determine the number of
        // intersections and to some processing on them if necessary.
        if (intersect != -1) {

          // if both edges are in the same plane
          if (intersect & SAME_PLANE) {

            prev_is_inside = 2;
            curr_is_inside = 2;
            num_intersections = 0;

            one_intersect_expected = 0;

            // if this is the first iteration
            if (j == 0) last_curr_is_inside = 2;

          // if there are two intersections on the edge 
          } else if ((intersect & P_ON_A) && (intersect & Q_ON_A)) {

            // if only one was expected
            if (one_intersect_expected) {
              char error_string[1024];
              sprintf(error_string, "ERROR: two intersections found, even "
                                    "though no circle of latitude involed and "
                                    "both edge vertices on different sides of "
                                    "the cutting plane.\n"
                                    "edge A %lf %lf %lf (edge type %d)\n"
                                    "edge B %lf %lf %lf (edge type %d)\n"
                                    "yac_intersect_vec return value %d\n"
                                    "p %lf %lf %lf\n"
                                    "q %lf %lf %lf\n",
                      src_edge_coords[0][0], src_edge_coords[0][1],
                      src_edge_coords[0][2], (int)src_edge_type,
                      tgt_edge_coords[0][0], tgt_edge_coords[0][1],
                      tgt_edge_coords[0][2], (int)tgt_edge_type, intersect,
                      intersection[0][0], intersection[0][1],
                      intersection[0][2], intersection[1][0],
                      intersection[1][1], intersection[1][2]);
              yac_internal_abort_message(error_string, __FILE__, __LINE__);
            }

            // if both source edge vertices are on the same side of the
            // target edge
            if (curr_is_inside == prev_is_inside) {

              // if the two intersection points are basically the same point
              if (compare_angles(
                   get_vector_angle_2(intersection[0],
                                      intersection[1]), SIN_COS_TOL) <= 0) {

                num_intersections = 1;

              } else {

                // compute the angle between the previous source edge vertex and
                // the two intersection points
                struct sin_cos_angle angle_a =
                  get_vector_angle_2(src_edge_coords[0], intersection[0]);
                struct sin_cos_angle angle_b =
                  get_vector_angle_2(src_edge_coords[0], intersection[1]);
                int compare_angles_ret_value = compare_angles(angle_a, angle_b);

                // if the two angles are identical, we assume that both points
                // are more or less identical to each other
                if (compare_angles_ret_value == 0) {

                  num_intersections = 1;

                } else {

                  // if the first intersection point is further away from the
                  // previous source edge vertex than the second one ->
                  // switch them
                  if (compare_angles_ret_value > 0) {

                    double temp_intersection[3];
                    temp_intersection[0] = intersection[1][0];
                    temp_intersection[1] = intersection[1][1];
                    temp_intersection[2] = intersection[1][2];
                    intersection[1][0] = intersection[0][0];
                    intersection[1][1] = intersection[0][1];
                    intersection[1][2] = intersection[0][2];
                    intersection[0][0] = temp_intersection[0];
                    intersection[0][1] = temp_intersection[1];
                    intersection[0][2] = temp_intersection[2];
                  }

                  num_intersections = 2;
                }
              }

            // one of the two source edge vertices is on the target edge
            // => one of the two intersection points must be more or less
            //    identical to a vertex of the source edge
            } else {

              int src_on_edge_idx = (curr_is_inside == 2);

              struct sin_cos_angle angles[2] =
                {get_vector_angle_2(src_edge_coords[src_on_edge_idx],
                                    intersection[0]),
                 get_vector_angle_2(src_edge_coords[src_on_edge_idx],
                                    intersection[1])};
              int compare_angles_ret_value[2] =
                {compare_angles(angles[0], SIN_COS_TOL),
                 compare_angles(angles[1], SIN_COS_TOL)};

              // if both intersection points are nearly identical to the src
              // edge vertex
              if ((compare_angles_ret_value[0] <= 0) &&
                  (compare_angles_ret_value[1] <= 0)) {

                num_intersections = 0;

              } else {

                num_intersections = 1;

                // if the second intersection points is closer than the first
                if (compare_angles(angles[0], angles[1]) < 0) {
                  intersection[0][0] = intersection[1][0];
                  intersection[0][1] = intersection[1][1];
                  intersection[0][2] = intersection[1][2];
                }
              }
            }

          // if one of the intersection points is on the source edge
          } else if (intersect & (P_ON_A | Q_ON_A)) {

            // if one of the two source edge vertices is on the target edge
            if ((curr_is_inside == 2) || (prev_is_inside == 2)) {

              // we assume that the intersection point is the respective
              // source edge vertex, which is on the target edge -> we do not
              // need this intersection point
              num_intersections = 0;

            } else {

              // if q is on the source edge
              if (intersect & Q_ON_A) {
                intersection[0][0] = intersection[1][0];
                intersection[0][1] = intersection[1][1];
                intersection[0][2] = intersection[1][2];
              }

              num_intersections = 1;
            }
          }
        }

        // If an intersection was expected but none was found, the cutting edge
        // was most propably to close to an edge vertex. We can now assume that
        // the respective vertex is directly on the cutting edge.
        if (one_intersect_expected && (num_intersections == 0)) {

          double temp_a, temp_b;

          // which of the two source edge vertices closer to the cutting edge

          switch (tgt_points[i].point->edge_type) {

            case (LON_CIRCLE) :
            case (GREAT_CIRCLE) :
              temp_a = fabs(dotproduct(src_edge_coords[0],
                                       curr_tgt_edge_norm_vec));
              temp_b = fabs(dotproduct(src_edge_coords[1],
                                       curr_tgt_edge_norm_vec));
              break;
            case (LAT_CIRCLE) :
              temp_a = fabs(1.0 - fabs(src_edge_coords[0][2] +
                                       curr_tgt_edge_norm_vec[2]));
              temp_b = fabs(1.0 - fabs(src_edge_coords[1][2] +
                                       curr_tgt_edge_norm_vec[2]));
              break;
            default:
              yac_internal_abort_message("invalid edge type\n", __FILE__, __LINE__);
              return;
          };

          if (temp_a < temp_b) prev_is_inside = 2;
          else curr_is_inside = 2;

          one_intersect_expected = 0;

          // if this is the first iteration
          if (j == 0) last_curr_is_inside = prev_is_inside;
        }
      }

      // here we know the number of intersections and their location and we
      // know the relation of the source edge vertices to the cutting edge
      // (prev_is_inside and curr_is_inside)

      // if the previous source edge vertex is outside -> dump it after cutting
      // is finished
      prev_src_point->to_be_removed = prev_is_inside == 0;

      // the easiest case is that we expected one intersection and got one
      if (one_intersect_expected) {

        // insert an intersection point in the source point list in the
        // current edge
        struct point_list_element * intersect_point =
          get_free_point_list_element(source_list);
        prev_src_point->next = intersect_point;
        intersect_point->next = curr_src_point;

        intersect_point->vec_coords[0] = intersection[0][0];
        intersect_point->vec_coords[1] = intersection[0][1];
        intersect_point->vec_coords[2] = intersection[0][2];

        intersect_point->edge_type =
          (prev_is_inside)?tgt_edge_type:src_edge_type;

      // if the cutting edge goes through both of the two source edge vertices
      } else if ((prev_is_inside == 2) && (curr_is_inside == 2)) {

        // if one of the two edges is a circle of latitude while the other is
        // not
        if ((src_edge_type == LAT_CIRCLE) ^ (tgt_edge_type == LAT_CIRCLE)) {

          double cross_src_z = (double)((long double)src_edge_coords[0][0] *
                                        (long double)src_edge_coords[1][1] -
                                        (long double)src_edge_coords[0][1] *
                                        (long double)src_edge_coords[1][0]);
          double cross_tgt_z = (double)((long double)tgt_edge_coords[0][0] *
                                        (long double)tgt_edge_coords[1][1] -
                                        (long double)tgt_edge_coords[0][1] *
                                        (long double)tgt_edge_coords[1][0]);

          int same_ordering = source_ordering == target_ordering;
          int same_direction = (cross_src_z > 0) == (cross_tgt_z > 0);

          // if source and target cell have the same ordering and both
          // edges have the same direction or if both cells have different
          // ordering and the edges have different directions, then we might
          // have to change the edge type of the source edge
          if (same_ordering == same_direction) {

            // well...it works...do not ask  ;-)
            // ((edge is on south hemisphere) XOR (direction of source edge) XOR
            //  (ordering of source cell))
            if ((curr_src_point->vec_coords[2] > 0) ^
                (cross_src_z < 0) ^ source_ordering)
              prev_src_point->edge_type = LAT_CIRCLE;
            else
              prev_src_point->edge_type = GREAT_CIRCLE;
          }
        } else {
          // nothing to be done here
        }

      // if we have no intersection, but the previous source point edge vector
      // is on the target edge
      } else if ((num_intersections == 0) && (prev_is_inside == 2)) {

        // if the current source edge vertex is outside
        if (curr_is_inside == 0) prev_src_point->edge_type = tgt_edge_type;

      // if we have two intersections (only happens if one of the two edges is
      // a circle of latitude while the other is not
      } else if (num_intersections == 2) {

        struct point_list_element * intersect_points[2] =
          {get_free_point_list_element(source_list),
           get_free_point_list_element(source_list)};

        // add two points between the current source edge vertices
        prev_src_point->next = intersect_points[0];
        intersect_points[0]->next = intersect_points[1];
        intersect_points[1]->next = curr_src_point;

        intersect_points[0]->vec_coords[0] = intersection[0][0];
        intersect_points[0]->vec_coords[1] = intersection[0][1];
        intersect_points[0]->vec_coords[2] = intersection[0][2];
        intersect_points[1]->vec_coords[0] = intersection[1][0];
        intersect_points[1]->vec_coords[1] = intersection[1][1];
        intersect_points[1]->vec_coords[2] = intersection[1][2];

        // if a and b are outside
        if ((prev_is_inside == 0) && (curr_is_inside == 0)) {
          intersect_points[0]->edge_type = src_edge_type;
          intersect_points[1]->edge_type = tgt_edge_type;

        // if a and b are inside
        } else if ((prev_is_inside == 1) && (curr_is_inside == 1)) {
          intersect_points[0]->edge_type = tgt_edge_type;
          intersect_points[1]->edge_type = src_edge_type;
        } else {
          yac_internal_abort_message(
            "ERROR: one source edge vertex is on the target edge, therefore we "
            "should not have two intersections.", __FILE__, __LINE__);
        }

      // if we have one intersection even though the two source edge vertices
      // are not on opposite sides of the target edge
      } else if (two_intersect_possible && (num_intersections == 1)) {

        // if  both points are outside -> circle of latitude and greate circle
        // touch at a single point
        if ((prev_is_inside == 0) && (curr_is_inside == 0)) {

          // insert an intersection point in the source point list in the
          // current edge
          struct point_list_element * intersect_point =
            get_free_point_list_element(source_list);
          prev_src_point->next = intersect_point;
          intersect_point->next = curr_src_point;

          intersect_point->vec_coords[0] = intersection[0][0];
          intersect_point->vec_coords[1] = intersection[0][1];
          intersect_point->vec_coords[2] = intersection[0][2];

          intersect_point->edge_type = tgt_edge_type;

        // if one of the two source edge vertices is on the edge while the other
        // is either inside or outside
        } else if ((prev_is_inside == 2) || (curr_is_inside == 2)) {


          // insert an intersection point in the source point list in the
          // current edge
          struct point_list_element * intersect_point =
            get_free_point_list_element(source_list);
          prev_src_point->next = intersect_point;
          intersect_point->next = curr_src_point;

          intersect_point->vec_coords[0] = intersection[0][0];
          intersect_point->vec_coords[1] = intersection[0][1];
          intersect_point->vec_coords[2] = intersection[0][2];

          // if the previous source edge vertex is on the target edge
          if (prev_is_inside == 2) {
            // if the current source edge vertex in on the outside
            if (curr_is_inside == 0) {
              prev_src_point->edge_type = src_edge_type;
              intersect_point->edge_type = tgt_edge_type;
            } else {
              prev_src_point->edge_type = tgt_edge_type;
              intersect_point->edge_type = src_edge_type;
            }
          // if the current source edge vertex is on the target edge
          } else {
            // if the previous source edge vertex in on the outside
            if (prev_is_inside == 0) {
              prev_src_point->edge_type = tgt_edge_type;
              intersect_point->edge_type = src_edge_type;
            } else {
              prev_src_point->edge_type = src_edge_type;
              intersect_point->edge_type = tgt_edge_type;
            }
          }

        // if both source edge vertices are inside
        } else if ((curr_is_inside == 1) && (prev_is_inside == 1)) {
          // nothing to be done here
        } else {
          yac_internal_abort_message(
            "ERROR: unhandled intersection case", __FILE__, __LINE__);
        }
      }

      prev_src_point = curr_src_point;
      curr_src_point = curr_src_point->next;
      prev_is_inside = curr_is_inside;

    } // for all source edges

    // remove all points that are to be deleted
    ncs = remove_points(source_list);
  }
}

static void copy_point_list(struct point_list in, struct point_list * out) {

  reset_point_list(out);

  struct point_list_element * curr = in.start;

  if (curr == NULL) return;

  struct point_list_element * new_point_list = get_free_point_list_element(out);
  out->start = new_point_list;
  *new_point_list = *curr;
  curr = curr->next;

  do {

    new_point_list->next = get_free_point_list_element(out);
    new_point_list = new_point_list->next;
    *new_point_list = *curr;
    curr = curr->next;

  } while (curr != in.start);

  new_point_list->next = out->start;
}

void yac_cell_clipping (size_t N,
                        struct grid_cell * source_cell,
                        struct grid_cell target_cell,
                        struct grid_cell * overlap_buffer) {

  size_t ncs;               /* number of vertices of source cell */
  size_t nct;               /* number of vertices of target cell */

  struct point_list target_list, source_list, temp_list;

  int target_ordering; /* ordering of target cell corners */
  int source_ordering; /* ordering of source cell corners */


  enum cell_type tgt_cell_type = get_cell_type(target_cell);

  if (tgt_cell_type == MIXED_CELL)
    yac_internal_abort_message("invalid target cell type (cell contains edges consisting "
                               "of great circles and circles of latitude)\n",
                               __FILE__, __LINE__);

  init_point_list(&temp_list);

  // generate point list for target cell (clip cell)
  init_point_list(&target_list);
  nct = generate_point_list(&target_list, target_cell);

  // if there is no target cell (e.g. if all edges of target cell have a length
  // of zero)
  if (nct == 0) {
    free_point_list(&target_list);
    for (size_t i = 0; i < N; ++i) overlap_buffer[i].num_corners = 0;
    return;
  }

  // compute target direction
  target_ordering = get_cell_points_ordering(&target_list);

  // if all corners of the target cell are on the same great circle
  if (target_ordering == -1) {
    free_point_list(&target_list);
    for (size_t n = 0; n < N; n++ ) overlap_buffer[n].num_corners = 0;
    return;
  }

  struct point_list_element * prev_tgt_point = target_list.start;
  struct point_list_element * curr_tgt_point = target_list.start->next;

  /* norm vector for temporary target edge plane */
  double * norm_vec = (double *)malloc(3 * nct * sizeof(*norm_vec));

  // compute norm vectors for all edges
  // or for lat circle edges a special z value
  for (size_t i = 0; i < nct; ++i) {

    switch (prev_tgt_point->edge_type) {

      case (LON_CIRCLE) :
      case (GREAT_CIRCLE) :
        compute_norm_vector(prev_tgt_point->vec_coords, curr_tgt_point->vec_coords,
                            norm_vec + 3 * i);
        break;
      case (LAT_CIRCLE):
        compute_lat_circle_z_value(prev_tgt_point->vec_coords, curr_tgt_point->vec_coords,
                            norm_vec + 3 * i);
        break;
      default:
        yac_internal_abort_message("invalid edge type\n", __FILE__, __LINE__);
    };
    prev_tgt_point = curr_tgt_point;
    curr_tgt_point = curr_tgt_point->next;
  }

  init_point_list(&source_list);

  // for all source cells
  for (size_t n = 0; n < N; n++ ) {

    overlap_buffer[n].num_corners = 0;

    enum cell_type src_cell_type = get_cell_type(source_cell[n]);

    if (src_cell_type == MIXED_CELL)
      yac_internal_abort_message("invalid source cell type (cell contains edges consisting "
                                 "of great circles and circles of latitude)\n",
                                 __FILE__, __LINE__);

    if (source_cell[n].num_corners < 2)
      continue;

    // generate point list for current source list
    ncs = generate_point_list(&source_list, source_cell[n]);

    // compute source direction
    source_ordering = get_cell_points_ordering(&source_list);

    // if all corners of the source cell are on the same great circle
    if (source_ordering == -1) continue;

    struct point_list * overlap;
    double fabs_tgt_coordinate_y = fabs(target_cell.coordinates_y[0]);
    double fabs_src_coordinate_y = fabs(source_cell[n].coordinates_y[0]);

    // in this case the source an target cell are both LAT_CELL's than the
    // bigger one has to be the target cell
    // a similar problem occurs when the target cell is a LAT_CELL and the
    // source is a GREAT_CIRCLE_CELL which overlaps with the pole that is also
    // include in the target cell
    if (((tgt_cell_type == LAT_CELL) && (src_cell_type == GREAT_CIRCLE_CELL)) ||
        ((tgt_cell_type == LAT_CELL) && (src_cell_type == LAT_CELL) &&
         (fabs_tgt_coordinate_y > fabs_src_coordinate_y))) {

      copy_point_list(target_list, &temp_list);

      double temp_norm_vec[3*ncs];
      struct point_list_element * src_point = source_list.start;


      for (size_t i = 0; i < ncs; ++i) {
        switch (src_point->edge_type) {

          case (LON_CIRCLE) :
          case (GREAT_CIRCLE) :
            compute_norm_vector(src_point->vec_coords,
                                src_point->next->vec_coords,
                                temp_norm_vec + 3 * i);
            break;
          case (LAT_CIRCLE):
            compute_lat_circle_z_value(src_point->vec_coords,
                                       src_point->next->vec_coords,
                                       temp_norm_vec + 3 * i);
            break;
          default:
            yac_internal_abort_message("invalid edge type\n", __FILE__, __LINE__);
        };
        src_point = src_point->next;
      }

      point_list_clipping(&temp_list, target_ordering,
                          source_list, source_ordering, ncs, temp_norm_vec);

      overlap = &temp_list;

    } else {

      point_list_clipping(&source_list, source_ordering,
                          target_list, target_ordering, nct, norm_vec);

      overlap = &source_list;
    }

    if (overlap->start != NULL)
      generate_overlap_cell(overlap, overlap_buffer + n);
  }

  free(norm_vec);
  free_point_list(&source_list);
  free_point_list(&target_list);
  free_point_list(&temp_list);
}

/* ---------------------------------------------------- */

void yac_correct_weights ( size_t nSourceCells, double * weight ) {

  static const size_t maxIter = 10; // number of iterations to get better accuracy of the weights

  double weight_diff;
#ifdef YAC_VERBOSE_CLIPPING
  double weight_sum;
#endif

  for ( size_t iter = 1; iter < maxIter; iter++ ) {

    weight_diff = 1.0;

    for ( size_t n = 0; n < nSourceCells; n++ ) weight_diff -= weight[n];

#ifdef YAC_VERBOSE_CLIPPING
    for ( size_t n = 0; n < nSourceCells; n++ ) weight_sum += weight[n];

    printf ("weight sum is %.15f \n", weight_sum);
    printf ("weights are  ");
    for (size_t i = 0; i < nSourceCells; ++i)
      printf (" %.15f", weight[i]);
    printf("\n");
#endif

    if ( fabs(weight_diff) < tol ) break;

    for ( size_t n = 0; n < nSourceCells; n++ )
      weight[n] += weight[n] * weight_diff;
  }
#ifdef YAC_VERBOSE_CLIPPING
  if ( fabs(weight_diff) > tol )
    printf ("weight sum is %.15f \n", weight_sum);
#endif
}

/* ---------------------------------------------------- */

static int get_cell_points_ordering(struct point_list * cell) {

  if ((cell->start == NULL) || (cell->start == cell->start->next))
    yac_internal_abort_message("ERROR: invalid cell\n", __FILE__, __LINE__);

  double norm_vec[3];
  struct point_list_element * curr = cell->start;

  compute_norm_vector(curr->vec_coords, curr->next->vec_coords, norm_vec);

  curr = curr->next;

  if (curr->next == cell->start)
    yac_internal_abort_message("ERROR: invalid cell\n", __FILE__, __LINE__);

  do {
    curr = curr->next;

    double dot = dotproduct(curr->vec_coords, norm_vec);

    if (fabs(dot) > tol)
      return dot > 0;

  } while (curr != cell->start);

  return -1;
}

static void init_point_list(struct point_list * list) {

  list->start = NULL;
  list->free_elements = NULL;
}

static void reset_point_list(struct point_list * list) {

  if (list->start != NULL) {

    struct point_list_element * temp = list->start->next;
    list->start->next = list->free_elements;
    list->free_elements = temp;

    list->start = NULL;
  }
}

static size_t remove_points(struct point_list * list) {

  struct point_list_element * curr = list->start;
  struct point_list_element * start = list->start;
  struct point_list_element * prev = NULL;

  if (curr == NULL) return 0;

  // find the first point that is not to be removed
  while(curr->to_be_removed) {
    prev = curr;
    curr = curr->next;
    if (curr == start) break;
  };

  // there is no point to remain
  if (curr->to_be_removed) {
    reset_point_list(list);
    return 0;
  }

  list->start = curr;
  start = curr;
  size_t num_remaining_points = 1;

  prev = curr;
  curr = curr->next;

  while (curr != start) {

    if (curr->to_be_removed) {
      prev->next = curr->next;
      curr->next = list->free_elements;
      list->free_elements = curr;
      curr = prev;
    } else {
      num_remaining_points++;
    }

    prev = curr;
    curr = curr->next;

  } while (curr != start);

  if (list->start == list->start->next) {

    list->start->next = list->free_elements;
    list->free_elements = list->start;
    list->start = NULL;
    num_remaining_points = 0;
  }

  return num_remaining_points;
}

//! returns number of edges/corners
static size_t remove_zero_length_edges(struct point_list * list) {

  struct point_list_element * curr = list->start;
  struct point_list_element * start = list->start;

  if (curr == NULL) return 0;

  if (curr->next == curr) {
    reset_point_list(list);
    return 0;
  }

  do {

    // if both points are nearly identical (angle between them is very small)
    if (!curr->to_be_removed &&
        (compare_angles(
           get_vector_angle_2(
              curr->vec_coords, curr->next->vec_coords),
           SIN_COS_LOW_TOL) <= 0)) {
      curr->to_be_removed = 1;
    } else if (curr->edge_type == LAT_CIRCLE && curr->next->edge_type == LAT_CIRCLE) {
        double temp_a = atan2(curr->vec_coords[1], curr->vec_coords[0]);
        double temp_b = atan2(curr->next->next->vec_coords[1], curr->next->next->vec_coords[0]);
        if (fabs(get_angle(temp_a, temp_b)) < M_PI_2)
          curr->next->to_be_removed = 1;
    }

    curr = curr->next;
  } while (curr != start);

  return remove_points(list);
}

static size_t generate_point_list(struct point_list * list,
                                  struct grid_cell cell) {

  reset_point_list(list);

  if (cell.num_corners == 0) return 0;

  struct point_list_element * curr = get_free_point_list_element(list);

  list->start = curr;
  curr->vec_coords[0] = cell.coordinates_xyz[0][0];
  curr->vec_coords[1] = cell.coordinates_xyz[0][1];
  curr->vec_coords[2] = cell.coordinates_xyz[0][2];

  for (size_t i = 1; i < cell.num_corners; ++i) {

    curr->next = get_free_point_list_element(list);
    curr->edge_type = cell.edge_type[i - 1];
    curr = curr->next;

    curr->vec_coords[0] = cell.coordinates_xyz[i][0];
    curr->vec_coords[1] = cell.coordinates_xyz[i][1];
    curr->vec_coords[2] = cell.coordinates_xyz[i][2];
    curr->edge_type = cell.edge_type[i];
  }

  curr->next = list->start;

  return remove_zero_length_edges(list);
}

static struct point_list_element *
get_free_point_list_element(struct point_list * list) {

  struct point_list_element * element;

  if (list->free_elements == NULL) {

    for (int i = 0; i < 7; ++i) {

      element = (struct point_list_element *)malloc(1 * sizeof(*element));

      element->next = list->free_elements;
      list->free_elements = element;
    }

    element = (struct point_list_element *)malloc(1 * sizeof(*element));

  } else {

    element = list->free_elements;
    list->free_elements = list->free_elements->next;
  }

  element->next = NULL;
  element->to_be_removed = 0;

  return element;
}

static void free_point_list(struct point_list * list) {

  struct point_list_element * element;

  if (list->start != NULL) {

    struct point_list_element * temp = list->free_elements;
    list->free_elements = list->start->next;
    list->start->next = temp;
  }

  while (list->free_elements != NULL) {

    element = list->free_elements;
    list->free_elements = element->next;
    free(element);
  }

  list->start = NULL;
  list->free_elements = NULL;
}

static int is_empty_gc_cell(struct point_list * list, size_t num_edges) {

#define NORM_TOL (1e-6)

  if ((num_edges == 2) &&
      !((list->start->edge_type == LAT_CIRCLE) ^
        (list->start->next->edge_type == LAT_CIRCLE)))
    return 1;

  struct point_list_element * curr = list->start;

  for (size_t i = 0; i < num_edges; ++i) {

    if (curr->edge_type == LAT_CIRCLE) return 0;
    curr = curr->next;
  }

  double ref_norm[3];

  compute_norm_vector(curr->vec_coords, curr->next->vec_coords, ref_norm);

  for (size_t i = 0; i < num_edges-1; ++i) {

    curr = curr->next;

    double norm[3];

    compute_norm_vector(curr->vec_coords, curr->next->vec_coords, norm);

    if (((fabs(ref_norm[0] - norm[0]) > NORM_TOL) ||
         (fabs(ref_norm[1] - norm[1]) > NORM_TOL) ||
         (fabs(ref_norm[2] - norm[2]) > NORM_TOL)) &&
        ((fabs(ref_norm[0] + norm[0]) > NORM_TOL) ||
         (fabs(ref_norm[1] + norm[1]) > NORM_TOL) ||
         (fabs(ref_norm[2] + norm[2]) > NORM_TOL)))
      return 0;
  }

  return 1;
#undef NORM_TOL
}

static void generate_overlap_cell(struct point_list * list,
                                  struct grid_cell * cell) {

  //! \todo test whether all points of the cell are on a single
  //!       great circle --> empty cell

  size_t num_edges = remove_zero_length_edges(list);

  if ((num_edges < 2) ||
      is_empty_gc_cell(list, num_edges)){

    reset_point_list(list);
    cell->num_corners = 0;
    return;
  }

  if (num_edges > cell->array_size) {
    free(cell->coordinates_x);
    free(cell->coordinates_y);
    free(cell->coordinates_xyz);
    free(cell->edge_type);
    cell->coordinates_x = (double *)malloc(num_edges * sizeof(*cell->coordinates_x));
    cell->coordinates_y = (double *)malloc(num_edges * sizeof(*cell->coordinates_y));
    cell->coordinates_xyz = (double(*)[3])malloc(num_edges * sizeof(*cell->coordinates_xyz));
    cell->edge_type = (enum yac_edge_type *)malloc(num_edges * sizeof(*cell->edge_type));
    cell->array_size = num_edges;
  }
  cell->num_corners = num_edges;

  struct point_list_element * curr = list->start;

  for (size_t i = 0; i < num_edges; ++i) {


    XYZtoLL(curr->vec_coords, cell->coordinates_x+i, cell->coordinates_y+i);
    cell->coordinates_xyz[i][0] = curr->vec_coords[0];
    cell->coordinates_xyz[i][1] = curr->vec_coords[1];
    cell->coordinates_xyz[i][2] = curr->vec_coords[2];
    cell->edge_type[i] = curr->edge_type;

    curr = curr->next;
  }
}

