/**
 * @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://dkrz-sw.gitlab-pages.dkrz.de/yac/
 *
 * This file is part of YAC.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are  permitted provided that the following conditions are
 * met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the DKRZ GmbH nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#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 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 yac_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 inline int edge_has_zero_length(double * a, double * b) {
  return compare_angles(get_vector_angle_2(a, b), SIN_COS_LOW_TOL) <= 0;
}

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 yac_cell_type target_cell_type;

  if (overlap_barycenters != NULL)
    for (size_t i = 0; i < N; ++i)
      for (int j = 0; j < 3; ++j)
        overlap_barycenters[i][j] = 0.0;

  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) {
      if (overlap_buffer[i].num_corners > 1) {
        if (overlap_barycenters == NULL)
          overlap_areas[i] = yac_huiliers_area (overlap_buffer[i]);
        else {
          overlap_areas[i] =
            yac_huiliers_area_info(overlap_buffer[i], overlap_barycenters[i]);
          normalise_vector(overlap_barycenters[i]);
        }
      } else {
        overlap_areas[i] = 0.0;
      }
    }
    return;
  }

  YAC_ASSERT(
    target_node_xyz != NULL,
    "ERROR(yac_compute_concave_overlap_info): "
    "missing target cell centers (required for "
    "cells with more than 3 corners; lon-lat cells are an exception")

  struct grid_cell target_partial_cell =
    {.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;

  // 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];

  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 (edge_has_zero_length(target_cell.coordinates_xyz[corner_a],
                             target_cell.coordinates_xyz[corner_b]))
       continue;

    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;

      if (overlap_barycenters == NULL)
        overlap_areas[n] += yac_huiliers_area (overlap_buffer[n]);
      else
        overlap_areas[n] +=
          yac_huiliers_area_info (overlap_buffer[n], overlap_barycenters[n]);
    }
  }
  if (overlap_barycenters != NULL)
    for (size_t n = 0; n < N; n++)
      if (overlap_areas[n] > 0.0)
        normalise_vector(overlap_barycenters[n]);

#ifdef YAC_VERBOSE_CLIPPING
  for (size_t n = 0; n < N; n++)
    printf("overlap area %zu: %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];
}

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

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) {

  yac_compute_concave_overlap_info(
    N, source_cell, target_cell, target_node_xyz, partial_areas, NULL);
}

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

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

  crossproduct_ld(a, b, norm);

  YAC_ASSERT(
    (fabs(norm[0]) >= tol) || (fabs(norm[1]) >= tol) || (fabs(norm[2]) >= tol),
    "ERROR: a and b are identical -> no norm vector")

  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_lat_circle(double z, double ref_z, int north_is_out) {

  double diff_z = z - ref_z;

  if (fabs(diff_z) <= yac_angle_tol * 1e-3) return 2;
  else return (diff_z > 0.0) ^ north_is_out;
}

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_ABORT("invalid edge type")
  };

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

static enum yac_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 == 1) && (count_great_circle_edges == 2)) {
     size_t i;
     for (i = 0; i < 3; ++i)
       if (target_cell.edge_type[i] == LAT_CIRCLE) break;
     size_t pol_index = (3 + i - 1) % 3;
     if (fabs(fabs(target_cell.coordinates_xyz[pol_index][2])-1.0) <
         yac_angle_low_tol)
        return LON_LAT_CELL;
     else
        return MIXED_CELL;

   } 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
            YAC_ASSERT_F(
              !one_intersect_expected,
              "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])

            // 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_ABORT("invalid edge type")
          };

          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_ABORT(
            "ERROR: one source edge vertex is on the target edge, therefore we "
            "should not have two intersections.")
        }

      // 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_ABORT("ERROR: unhandled intersection case")
        }
      }

      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);
  }
}

/**
 * cell clipping along a single latitude circle using
 * Sutherland-Hodgman algorithm;
 */
static size_t point_list_lat_clipping (
  struct point_list * cell, size_t num_corners,
  double lat_bound, int north_is_out) {

  if (cell->start == NULL) return 0;

  // if the latitude circle actually is a pole
  if (fabs(lat_bound - 1.0) < yac_angle_tol) {

    // if the outside pole and the latitude bound not are the same
    if ((lat_bound > 0.0) ^ north_is_out) {
      reset_point_list(cell);
      return 0;
    } else {
      return num_corners;
    }
  }

  struct point_list_element * prev_point = cell->start;
  struct point_list_element * curr_point = prev_point->next;

  int prev_is_inside, curr_is_inside, last_curr_is_inside;

  prev_is_inside =
    is_inside_lat_circle(
      prev_point->vec_coords[2], lat_bound, north_is_out);
  last_curr_is_inside = prev_is_inside;

  double lon_bound = sqrt(1.0 - lat_bound * lat_bound);
  double dummy_lat_edge_coords[2][3] =
    {{lon_bound, 0.0, lat_bound}, {0.0, lon_bound, lat_bound}};

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

    if (j != num_corners - 1)
      curr_is_inside =
        is_inside_lat_circle(
          curr_point->vec_coords[2], lat_bound, north_is_out);
    else
      curr_is_inside = last_curr_is_inside;

    double * cell_edge_coords[2] =
      {prev_point->vec_coords, curr_point->vec_coords};

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

    // One intersection is possible, if either the previous or the current
    // point is inside and the respective other one is outside.
    // Otherwise, two intersections are possible unless both cell points
    // are on the plane of the latitude circle.
    int one_intersect_expected = (curr_is_inside + prev_is_inside == 1);
    int two_intersect_possible =
      !one_intersect_expected && (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(
          GREAT_CIRCLE, cell_edge_coords[0], cell_edge_coords[1],
          LAT_CIRCLE, dummy_lat_edge_coords[0], dummy_lat_edge_coords[1],
          intersection[0], intersection[1]);

      // if yac_intersect_lat_circle_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 both edge vertices on different "
                                  "sides of the cutting plane.\n"
                                  "edge (%lf %lf %lf; %lf %lf %lf)\n"
                                  "lat %lf\n"
                                  "yac_intersect_vec return value %d\n"
                                  "p %lf %lf %lf\n"
                                  "q %lf %lf %lf\n",
                    cell_edge_coords[0][0], cell_edge_coords[0][1],
                    cell_edge_coords[0][2], cell_edge_coords[1][0],
                    cell_edge_coords[1][1], cell_edge_coords[1][2],
                    lat_bound, 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 cell 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 cell edge vertex and
              // the two intersection points
              struct sin_cos_angle angle_a =
                get_vector_angle_2(cell_edge_coords[0], intersection[0]);
              struct sin_cos_angle angle_b =
                get_vector_angle_2(cell_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 cell 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 cell edge vertices is on the target edge
          // => one of the two intersection points must be more or less
          //    identical to a vertex of the cell edge
          } else {

            int on_edge_idx = (curr_is_inside == 2);

            struct sin_cos_angle angles[2] =
              {get_vector_angle_2(cell_edge_coords[on_edge_idx],
                                  intersection[0]),
               get_vector_angle_2(cell_edge_coords[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 cell
            // 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 cell edge
        } else if (intersect & (P_ON_A | Q_ON_A)) {

          // if one of the two cell 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
            // cell edge vertex, which is on the target edge -> we do not
            // need this intersection point
            num_intersections = 0;

          } else {

            // if q is on the cell 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 cell edge vertices closer to the cutting edge
        temp_a = fabs(1.0 - fabs(cell_edge_coords[0][2] + lat_bound));
        temp_b = fabs(1.0 - fabs(cell_edge_coords[1][2] + lat_bound));

        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 cell edge vertices to the cutting edge
    // (prev_is_inside and curr_is_inside)

    // if the previous cell edge vertex is outside -> dump it after cutting
    // is finished
    prev_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 cell point list in the
      // current edge
      struct point_list_element * intersect_point =
        get_free_point_list_element(cell);
      prev_point->next = intersect_point;
      intersect_point->next = curr_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)?LAT_CIRCLE:GREAT_CIRCLE;

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

      prev_point->edge_type = LAT_CIRCLE;

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

      // if the current cell edge vertex is outside
      if (curr_is_inside == 0) prev_point->edge_type = LAT_CIRCLE;

    // if we have two intersections
    } else if (num_intersections == 2) {

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

      // add two points between the current cell edge vertices
      prev_point->next = intersect_points[0];
      intersect_points[0]->next = intersect_points[1];
      intersect_points[1]->next = curr_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 = GREAT_CIRCLE;
        intersect_points[1]->edge_type = LAT_CIRCLE;

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

    // if we have one intersection even though the two cell edge vertices
    // are not on opposite sides of the latitude circle
    } 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 cell point list in the
        // current edge
        struct point_list_element * intersect_point =
          get_free_point_list_element(cell);
        prev_point->next = intersect_point;
        intersect_point->next = curr_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 = LAT_CIRCLE;

      // if one of the two cell 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 cell point list in the
        // current edge
        struct point_list_element * intersect_point =
          get_free_point_list_element(cell);
        prev_point->next = intersect_point;
        intersect_point->next = curr_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 cell edge vertex is on the latitude circle
        if (prev_is_inside == 2) {
          // if the current cell edge vertex in on the outside
          if (curr_is_inside == 0) {
            prev_point->edge_type = GREAT_CIRCLE;
            intersect_point->edge_type = LAT_CIRCLE;
          } else {
            prev_point->edge_type = LAT_CIRCLE;
            intersect_point->edge_type = GREAT_CIRCLE;
          }
        // if the current cell edge vertex is on the latitude circle
        } else {
          // if the previous cell edge vertex in on the outside
          if (prev_is_inside == 0) {
            prev_point->edge_type = LAT_CIRCLE;
            intersect_point->edge_type = GREAT_CIRCLE;
          } else {
            prev_point->edge_type = GREAT_CIRCLE;
            intersect_point->edge_type = LAT_CIRCLE;
          }
        }

      // 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_point = curr_point;
    curr_point = curr_point->next;
    prev_is_inside = curr_is_inside;

  } // for all edges of the cell

  // remove all points that are to be deleted
  return remove_points(cell);
}

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 yac_cell_type tgt_cell_type = get_cell_type(target_cell);

  YAC_ASSERT(
   tgt_cell_type != MIXED_CELL,
   "invalid target cell type (cell contains edges consisting "
   "of great circles and circles of latitude)")

  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 *)xmalloc(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_ABORT("invalid edge type")
    };
    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 yac_cell_type src_cell_type = get_cell_type(source_cell[n]);

    YAC_ASSERT(
      src_cell_type != MIXED_CELL,
      "invalid source cell type (cell contains edges consisting "
      "of great circles and circles of latitude)")

    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_z = fabs(target_cell.coordinates_xyz[0][2]);
    double fabs_src_coordinate_z = fabs(source_cell[n].coordinates_xyz[0][2]);

    // 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_z > fabs_src_coordinate_z))) {

      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_ABORT("invalid edge type")
        };
        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);
}

static void yac_lon_lat_cell_lat_clipping(
  struct grid_cell * cell, double z_upper_bound, double z_lower_bound,
  struct grid_cell * overlap_buffer) {

  double cell_upper_bound = cell->coordinates_xyz[0][2];
  double cell_lower_bound = cell->coordinates_xyz[2][2];

  int upper_idx[2], lower_idx[2];

  if (cell_upper_bound < cell_lower_bound) {
    double temp = cell_upper_bound;
    cell_upper_bound = cell_lower_bound;
    cell_lower_bound = temp;
    if (cell->edge_type[0] == LAT_CIRCLE) {
      upper_idx[0] = 2;
      upper_idx[1] = 3;
      lower_idx[0] = 1;
      lower_idx[1] = 0;
    } else {
      upper_idx[0] = 2;
      upper_idx[1] = 1;
      lower_idx[0] = 3;
      lower_idx[1] = 0;
    }
  } else {
    if (cell->edge_type[0] == LAT_CIRCLE) {
      upper_idx[0] = 0;
      upper_idx[1] = 1;
      lower_idx[0] = 3;
      lower_idx[1] = 2;
    } else {
      upper_idx[0] = 0;
      upper_idx[1] = 3;
      lower_idx[0] = 1;
      lower_idx[1] = 2;
    }
  }

  // if z_upper_bound and z_lower_bound are identical or
  // if cell does not overlap with latitude band
  if ((z_upper_bound == z_lower_bound) ||
      (cell_upper_bound <= z_lower_bound) ||
      (cell_lower_bound >= z_upper_bound)) {
    overlap_buffer->num_corners = 0;
    return;
  }

  if (overlap_buffer->array_size < 4) {
    overlap_buffer->coordinates_xyz =
      xmalloc(4 * sizeof(*(overlap_buffer->coordinates_xyz)));
    overlap_buffer->edge_type =
      xmalloc(4 * sizeof(*(overlap_buffer->edge_type)));
    overlap_buffer->array_size = 4;
  }

  memcpy(overlap_buffer->coordinates_xyz, cell->coordinates_xyz,
         4 * sizeof(*(cell->coordinates_xyz)));
  memcpy(overlap_buffer->edge_type, cell->edge_type,
         4 * sizeof(*(cell->edge_type)));
  overlap_buffer->num_corners = 4;


  double tmp_scale;
  double * p[2];
  if (fabs(cell_lower_bound) < fabs(cell_upper_bound)) {
    p[0] = cell->coordinates_xyz[lower_idx[0]];
    p[1] = cell->coordinates_xyz[lower_idx[1]];
    tmp_scale = cell->coordinates_xyz[lower_idx[0]][0] *
                cell->coordinates_xyz[lower_idx[0]][0] +
                cell->coordinates_xyz[lower_idx[0]][1] *
                cell->coordinates_xyz[lower_idx[0]][1];
  } else {
    p[0] = cell->coordinates_xyz[upper_idx[0]];
    p[1] = cell->coordinates_xyz[upper_idx[1]];
    tmp_scale = cell->coordinates_xyz[upper_idx[0]][0] *
                cell->coordinates_xyz[upper_idx[0]][0] +
                cell->coordinates_xyz[upper_idx[0]][1] *
                cell->coordinates_xyz[upper_idx[0]][1];
  }

  // if upper bound overlaps with cell
  if ((z_upper_bound < cell_upper_bound) &&
      (z_upper_bound > cell_lower_bound)) {

    double scale = sqrt((1.0 - z_upper_bound * z_upper_bound) / tmp_scale);

    overlap_buffer->coordinates_xyz[upper_idx[0]][0] = p[0][0] * scale;
    overlap_buffer->coordinates_xyz[upper_idx[0]][1] = p[0][1] * scale;
    overlap_buffer->coordinates_xyz[upper_idx[0]][2] = z_upper_bound;
    overlap_buffer->coordinates_xyz[upper_idx[1]][0] = p[1][0] * scale;
    overlap_buffer->coordinates_xyz[upper_idx[1]][1] = p[1][1] * scale;
    overlap_buffer->coordinates_xyz[upper_idx[1]][2] = z_upper_bound;
  }

  // if lower bound overlaps with cell
  if ((z_lower_bound < cell_upper_bound) &&
      (z_lower_bound > cell_lower_bound)) {

    double scale = sqrt((1.0 - z_lower_bound * z_lower_bound) / tmp_scale);

    overlap_buffer->coordinates_xyz[lower_idx[0]][0] = p[0][0] * scale;
    overlap_buffer->coordinates_xyz[lower_idx[0]][1] = p[0][1] * scale;
    overlap_buffer->coordinates_xyz[lower_idx[0]][2] = z_lower_bound;
    overlap_buffer->coordinates_xyz[lower_idx[1]][0] = p[1][0] * scale;
    overlap_buffer->coordinates_xyz[lower_idx[1]][1] = p[1][1] * scale;
    overlap_buffer->coordinates_xyz[lower_idx[1]][2] = z_lower_bound;
  }
}

static double get_closest_pole(struct point_list * cell_list) {

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

  if (curr == NULL) return 1.0;

  double max_z = 0.0;

  do {

    double curr_z = curr->vec_coords[2];
    if (fabs(curr_z) > fabs(max_z)) max_z = curr_z;

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

  return (max_z > 0.0)?1.0:-1.0;
}

/*this routine is potentially being used in the CDO*/
void yac_cell_lat_clipping (size_t N,
                            struct grid_cell * cells,
                            double lat_bounds[2], // lat in rad
                            struct grid_cell * overlap_buffer) {

  struct point_list cell_list;

  init_point_list(&cell_list);

  double upper_bound, lower_bound;
  if (lat_bounds[0] > lat_bounds[1]) {
    upper_bound = sin(lat_bounds[0]);
    lower_bound = sin(lat_bounds[1]);
  } else {
    upper_bound = sin(lat_bounds[1]);
    lower_bound = sin(lat_bounds[0]);
  }

  // if both bounds are at the same pole
  if ((fabs(-1.0 - upper_bound) < yac_angle_tol) ||
      (fabs(1.0 - lower_bound) < yac_angle_tol)) {

    for (size_t n = 0; n < N; ++n)
      overlap_buffer[n].num_corners = 0;
    return;
  }

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

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

    overlap_buffer[n].num_corners = 0;

    enum yac_cell_type src_cell_type = get_cell_type(cells[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 (src_cell_type == LON_LAT_CELL) {

      yac_lon_lat_cell_lat_clipping(
        cells + n, upper_bound, lower_bound, overlap_buffer + n);

    } else {

      // generate point list for current source list
      size_t num_corners = generate_point_list(&cell_list, cells[n]);

      // if the cell contains a pole, we need to add this pole
      double pole = get_closest_pole(&cell_list);
      if (yac_point_in_cell(
            (double[3]){0.0, 0.0, pole}, cells[n])) {

        int flag = 0;

        // if the cell contains the north pole
        if (pole > 0.0) {
          double bound =
            (fabs(upper_bound - 1.0) > yac_angle_tol)?upper_bound:lower_bound;

          for (size_t i = 0; i < num_corners; ++i) {
            flag |= (bound < cells[n].coordinates_xyz[i][2]);
          }
        } else {
          double bound =
            (fabs(lower_bound + 1.0) > yac_angle_tol)?lower_bound:upper_bound;

          for (size_t i = 0; i < num_corners; ++i) {
            flag |= (bound > cells[n].coordinates_xyz[i][2]);
          }
        }

        if (!flag) {

          yac_internal_abort_message(
            "ERROR(yac_cell_lat_clipping): Latitude bounds are within a cell "
            "covering a pole, this is not supported. Increased grid resolution "
            "or widen lat bounds may help.", __FILE__, __LINE__);
        }

      }

      num_corners =
        point_list_lat_clipping(
          &cell_list, num_corners, upper_bound, 1);
      point_list_lat_clipping(&cell_list, num_corners, lower_bound, 0);

      remove_zero_length_edges(&cell_list);

      if (cell_list.start != NULL)
        generate_overlap_cell(&cell_list, overlap_buffer + n);
    }
  }

  free_point_list(&cell_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) {

  YAC_ASSERT(
    (cell->start != NULL) && (cell->start != cell->start->next),
    "ERROR: invalid cell")

  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;

  YAC_ASSERT(curr->next != cell->start, "ERROR: invalid cell")

  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 &&
        edge_has_zero_length(curr->vec_coords, curr->next->vec_coords)) {
      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 *)xmalloc(1 * sizeof(*element));

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

    element = (struct point_list_element *)xmalloc(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_xyz);
    free(cell->edge_type);
    cell->coordinates_xyz = (double(*)[3])xmalloc(num_edges * sizeof(*cell->coordinates_xyz));
    cell->edge_type = (enum yac_edge_type *)xmalloc(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) {

    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;
  }
}

