/**
 * @file intersection.c
 * @brief Set of functions to determine the intersection between two edges
 *
 * very interesting literature:
 * - http://geospatialmethods.org/spheres/GCIntersect.html
 *
 * @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.
 */

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

#include "utils.h"
#include "geometry.h"

// angle tolerance
static double const tol = 1.0e-12;

//! computes square of the lenght of the vector ab
static inline double sq_len_diff_vec(double a[], double b[]) {
  double ab[3] = {a[0]-b[0], a[1]-b[1], a[2]-b[2]};
  return ab[0] * ab[0] + ab[1] * ab[1] + ab[2] * ab[2];
}

/**
 * determines whether vector p is between vectors a and b
 * (it is assumed, that a, b, and p are in the same plane)
 */
static inline int vector_is_between (
  double a[], double b[], double p[], double sq_len_diff_ab) {

  // In case the angle between the vectors a and b is 180 degree, the angle
  // between the vectors pa and pb is exactly 90 degree (Pythagorean theorem).
  // In all other cases the angle between pa and pb must be bigger than 90
  // degree for p to be between a and b
  // from this we can deduce:
  // ||ab||^2 >= ||ap||^2 + ||bp||^2 => (Pythagorean theorem)

  // the tol is required in case p is very close to a or b
  double sq_len_diff_vec_ap = sq_len_diff_vec(a,p);
  double sq_len_diff_vec_bp = sq_len_diff_vec(b,p);
  return sq_len_diff_ab + tol >= sq_len_diff_vec_ap + sq_len_diff_vec_bp;
}

static int vector_is_between_lat (double a[], double b[], double p[]) {

/* determines whether p is between a and b
   (a, b, p have the same latitude)*/

/*  I. a_0 * alpha + b_0 * beta = p_0
   II. a_1 * alpha + b_1 * beta = p_1

   if alpha > 0 and beta > 0 -> p is between a and b */

   if (fabs(fabs(a[2]) - 1.0) < tol) return 1;

   double fabs_a[2] = {fabs(a[0]), fabs(a[1])};
   int flag = fabs_a[0] > fabs_a[1];
   double a_0 = a[flag], a_1 = a[flag^1];
   double b_0 = b[flag], b_1 = b[flag^1];
   double p_0 = p[flag], p_1 = p[flag^1];

   double temp = b_0 - (b_1 * a_0) / a_1;

   // if a and b are nearly identical
   if (fabs(temp) < tol) return (fabs(a_0 - p_0) < tol) &&
                                (fabs(a_1 - p_1) < tol);

   double beta = (p_0 - (p_1 * a_0) / a_1) / temp;

   if (beta < -tol) return 0;

   double alpha = (p_1 - b_1 * beta) / a_1;

   return alpha > -tol;
}

 int yac_gcxgc_vec (double a[3], double b[3], double c[3], double d[3],
                    double p[3], double q[3]) {

   double sq_sin_ab, sq_sin_cd, sq_sin_abxcd = 0;
   long double cross_ab[3], cross_cd[3], cross_abxcd[3];

   // p' = (a X b) X (c X d)
   // p  = p' / length(p')
   {
      long double a_[3] = {a[0], a[1], a[2]};
      long double b_[3] = {b[0], b[1], b[2]};
      long double c_[3] = {c[0], c[1], c[2]};
      long double d_[3] = {d[0], d[1], d[2]};
      long double sq_sin_ab_, sq_sin_cd_;

      cross_ab[0] = a_[1] * b_[2] - a_[2] * b_[1];
      cross_ab[1] = a_[2] * b_[0] - a_[0] * b_[2];
      cross_ab[2] = a_[0] * b_[1] - a_[1] * b_[0];
      cross_cd[0] = c_[1] * d_[2] - c_[2] * d_[1];
      cross_cd[1] = c_[2] * d_[0] - c_[0] * d_[2];
      cross_cd[2] = c_[0] * d_[1] - c_[1] * d_[0];
      cross_abxcd[0] = cross_ab[1] * cross_cd[2] - cross_ab[2] * cross_cd[1];
      cross_abxcd[1] = cross_ab[2] * cross_cd[0] - cross_ab[0] * cross_cd[2];
      cross_abxcd[2] = cross_ab[0] * cross_cd[1] - cross_ab[1] * cross_cd[0];
      sq_sin_ab_ = cross_ab[0] * cross_ab[0] +
                   cross_ab[1] * cross_ab[1] +
                   cross_ab[2] * cross_ab[2];
      sq_sin_cd_ = cross_cd[0] * cross_cd[0] +
                   cross_cd[1] * cross_cd[1] +
                   cross_cd[2] * cross_cd[2];
      sq_sin_ab = (double)sq_sin_ab_;
      sq_sin_cd = (double)sq_sin_cd_;
      if ((sq_sin_ab > 0.0) && (sq_sin_cd > 0.0)) {
         long double sq_length_abxcd = cross_abxcd[0] * cross_abxcd[0] +
                                       cross_abxcd[1] * cross_abxcd[1] +
                                       cross_abxcd[2] * cross_abxcd[2];
         sq_sin_abxcd = (double)(sq_length_abxcd / (sq_sin_ab_ * sq_sin_cd_));
         if (sq_length_abxcd > 0.0) {
            long double scale = 1.0 / sqrtl(sq_length_abxcd);
            p[0] = (double)(cross_abxcd[0] * scale);
            p[1] = (double)(cross_abxcd[1] * scale);
            p[2] = (double)(cross_abxcd[2] * scale);
            q[0] = -p[0], q[1] = -p[1], q[2] = -p[2];
         }
      }
   }

  // if edge ab and edge cd have at least a length of yac_angle_tol
  if ((sq_sin_ab > yac_angle_tol * yac_angle_tol) &&
      (sq_sin_cd > yac_angle_tol * yac_angle_tol)) {

    // if both edges are on different great circles
    if (sq_sin_abxcd > yac_angle_tol * yac_angle_tol) {

      double sq_len_diff_ab = sq_len_diff_vec(a, b);
      double sq_len_diff_cd = sq_len_diff_vec(c, d);

      int ret = 0;
      ret |= vector_is_between(a, b, p, sq_len_diff_ab) << 0;
      ret |= vector_is_between(a, b, q, sq_len_diff_ab) << 1;
      ret |= vector_is_between(c, d, p, sq_len_diff_cd) << 2;
      ret |= vector_is_between(c, d, q, sq_len_diff_cd) << 3;
      return ret;

    // both edges are on the same great circle
    } else {

      double sq_len_diff_ab = sq_len_diff_vec(a, b);
      double sq_len_diff_cd = sq_len_diff_vec(c, d);

      int a_between_cd = vector_is_between(c, d, a, sq_len_diff_cd) << 0;
      int b_between_cd = vector_is_between(c, d, b, sq_len_diff_cd) << 1;
      int c_between_ab = vector_is_between(a, b, c, sq_len_diff_ab) << 2;
      int d_between_ab = vector_is_between(a, b, d, sq_len_diff_ab) << 3;

      switch (a_between_cd + b_between_cd + c_between_ab + d_between_ab) {

        case (0):
          p[0] = a[0], p[1] = a[1], p[2] = a[2];
          q[0] = b[0], q[1] = b[1], q[2] = b[2];
          return (1 << 0) + (1 << 1) + (1 << 4);
        case (1+2):
        case (1+2+4):
        case (1+2+8):
          p[0] = a[0], p[1] = a[1], p[2] = a[2];
          q[0] = b[0], q[1] = b[1], q[2] = b[2];
          break;
        case (1+4):
        case (1+4+8):
          p[0] = a[0], p[1] = a[1], p[2] = a[2];
          q[0] = c[0], q[1] = c[1], q[2] = c[2];
          break;
        case (1+8):
          p[0] = a[0], p[1] = a[1], p[2] = a[2];
          q[0] = d[0], q[1] = d[1], q[2] = d[2];
          break;
        case (2+4):
        case (2+4+8):
          p[0] = b[0], p[1] = b[1], p[2] = b[2];
          q[0] = c[0], q[1] = c[1], q[2] = c[2];
          break;
        case (2+8):
          p[0] = b[0], p[1] = b[1], p[2] = b[2];
          q[0] = d[0], q[1] = d[1], q[2] = d[2];
          break;
        case (4+8):
          p[0] = c[0], p[1] = c[1], p[2] = c[2];
          q[0] = d[0], q[1] = d[1], q[2] = d[2];
          break;
        case (1+2+4+8):
          p[0] = a[0], p[1] = a[1], p[2] = a[2];
          q[0] = a[0], q[1] = a[1], q[2] = a[2];
          break;
        default:
          yac_internal_abort_message("internal error", __FILE__, __LINE__);
      }
      return (1 << 0) + (1 << 1) + (1 << 2) + (1 << 3) + (1 << 4);
    }

  // if points a and b are nearly in the same location
  } if (sq_sin_ab <= yac_angle_tol * yac_angle_tol) {

    p[0] = a[0], p[1] = a[1], p[2] = a[2];
    q[0] = -a[0], q[1] = -a[1], q[2] = -a[2];

    // if points c and d are nearly in the same location
    if (sq_sin_cd <= yac_angle_tol * yac_angle_tol) {

      double cross_ac[3];
      crossproduct_ld(a, c, cross_ac);
      double sq_sin_ac = cross_ac[0] * cross_ac[0] +
                         cross_ac[1] * cross_ac[1] +
                         cross_ac[2] * cross_ac[2];

      // if points are identically or have an angle of 180°
      if (sq_sin_ac <= yac_angle_tol * yac_angle_tol) {

         double cos_ac = a[0]*c[0] + a[1]*c[1] + a[2]*c[2];
         return (1 + ((cos_ac > 0)?(1 << 2):(1 << 3)));

      } else return -1;

      // edge cd has at least a length of yac_angle_tol
    } else {

      double sin_a_cd = fabs(((double)(cross_cd[0]) * a[0] +
                              (double)(cross_cd[1]) * a[1] +
                              (double)(cross_cd[2]) * a[2]) / sqrt(sq_sin_cd));

      // if ab is on the plane of cd
      if (sin_a_cd <= yac_angle_tol) {

        int result = 1;
        double sq_len_diff_cd = sq_len_diff_vec(c, d);

        if (vector_is_between(c, d, a, sq_len_diff_cd))
           result |= 1 << 2;
        double tvec[] = {-a[0], -a[1], -a[2]};
        if (vector_is_between(c, d, tvec, sq_len_diff_cd))
           result |= 1 << 3;

        return result;
      }

      return -1;
    }

  // edge ab has at least a length of yac_angle_tol and points c and d are in
  // the same location
  } else {

    p[0] = c[0], p[1] = c[1], p[2] = c[2];
    q[0] = -c[0], q[1] = -c[1], q[2] = -c[2];

    // dot product is most accurate for angles around half PI
    // (for angles very close to half PI: alpha = fabs(acos(alpha))
    double sin_c_ad = fabs(((double)(cross_ab[0]) * c[0] +
                            (double)(cross_ab[1]) * c[1] +
                            (double)(cross_ab[2]) * c[2]) / sqrt(sq_sin_ab));

    // if cd is on the plane of ab
    if (sin_c_ad <= yac_angle_tol) {

      int result = 4;
      double sq_len_diff_ab = sq_len_diff_vec(a, b);

      if (vector_is_between(a, b, c, sq_len_diff_ab))
        result |= 1 << 0;
      double tvec[] = {-c[0], -c[1], -c[2]};
      if (vector_is_between(a, b, tvec, sq_len_diff_ab))
        result |= 1 << 1;

      return result;
    }

    return -1;
  }
}

/** \brief compute the intersection point two circles of latitude
 *
 * compute the intersection points of two circle of latitude
 * if p and q are != NULL they contain the intersection points
 * the return value is:
 *      - 0 if the intersection points are neither between (a and b) or (c and d)
 *      - -1 if an error occurred
 *      - 1st bit will be set if p is between a and b
 *      - 2nd bit will be set if q is between a and b
 *      - 3rd bit will be set if p is between c and d
 *      - 4th bit will be set if q is between c and d
 *      - 5th bit will be set if both edges are on the same circle of latitude
 **/
int yac_latcxlatc_vec (double a[3], double b[3], double c[3], double d[3],
                       double p[3], double q[3]) {

   // two circles of latitude can only intersect if they are on the same latitude
   if (fabs(a[2] - c[2]) > tol)
      return -1;

   int result = 16;

   int a_between_cd, b_between_cd, c_between_ab, d_between_ab;

   a_between_cd = vector_is_between_lat(c, d, a);
   b_between_cd = vector_is_between_lat(c, d, b);
   c_between_ab = vector_is_between_lat(a, b, c);
   d_between_ab = vector_is_between_lat(a, b, d);

   if (a_between_cd && b_between_cd && c_between_ab && d_between_ab) {

      p[0] = a[0], p[1] = a[1], p[2] = a[2];
      q[0] = a[0], q[1] = a[1], q[2] = a[2];

      result |= 1 + 4;

   } else if (a_between_cd) {

      p[0] = a[0], p[1] = a[1], p[2] = a[2];

      result |= 1 + 2 + 4 + 8;

      if (b_between_cd) q[0] = b[0], q[1] = b[1], q[2] = b[2];
      else if (c_between_ab) q[0] = c[0], q[1] = c[1], q[2] = c[2];
      else if (d_between_ab) q[0] = d[0], q[1] = d[1], q[2] = d[2];
      else yac_internal_abort_message("internal error", __FILE__, __LINE__);

   } else if (b_between_cd) {

      p[0] = b[0], p[1] = b[1], p[2] = b[2];

      result |= 1 + 2 + 4 + 8;

      if (c_between_ab) q[0] = c[0], q[1] = c[1], q[2] = c[2];
      else if (d_between_ab) q[0] = d[0], q[1] = d[1], q[2] = d[2];
      else yac_internal_abort_message("internal error", __FILE__, __LINE__);

   } else if (c_between_ab && d_between_ab) {

      p[0] = c[0], p[1] = c[1], p[2] = c[2];
      q[0] = d[0], q[1] = d[1], q[2] = d[2];

      result |= 1 + 2 + 4 + 8;

   } else {

      p[0] = a[0], p[1] = a[1], p[2] = a[2];
      q[0] = b[0], q[1] = b[1], q[2] = b[2];

      result |= 1 + 2;
   }

   return result;
}

/** \brief compute the intersection point two circles of longitude
 *
 * compute the intersection points of two circle of longitude
 * if p and q are != NULL they contain the intersection points
 * the return value is:
 *      - 0 if the intersection points are neither between (a and b) or (c and d)
 *      - -1 if an error occurred
 *      - 1st bit will be set if p is between a and b
 *      - 2nd bit will be set if q is between a and b
 *      - 3rd bit will be set if p is between c and d
 *      - 4th bit will be set if q is between c and d
 *      - 5th bit will be set if both edges are on the same circle of longitude
 **/
int yac_loncxlonc_vec (double a[3], double b[3], double c[3], double d[3],
                       double p[3], double q[3]) {

   int ret_value = 0;

   double cross_ab[3], cross_cd[3];

   crossproduct_ld(a, b, cross_ab);
   crossproduct_ld(c, d, cross_cd);

   double abs_norm_cross_ab[2], abs_norm_cross_cd[2];

   // abs_norm_cross_ab[2] = 0;
   // abs_norm_cross_cd[2] = 0;

   int edge_is_pole_point = 0;

   double fabs_a_2 = fabs(a[2]);
   double fabs_b_2 = fabs(b[2]);
   double * ref_point = (fabs_a_2 > fabs_b_2)?b:a;
   // if both points are at the pole
   if (fabs(ref_point[2]) > 1.0-tol) {
      abs_norm_cross_ab[0] = 1;
      abs_norm_cross_ab[1] = 0;
      edge_is_pole_point = 1;
   } else {
      double scale = 1.0 / sqrt(ref_point[0]*ref_point[0] +
                                ref_point[1]*ref_point[1]);
      abs_norm_cross_ab[0] = ref_point[1] * scale;
      abs_norm_cross_ab[1] = ref_point[0] * scale;
      double fabs_abs_norm_cross_ab[2] = {fabs(abs_norm_cross_ab[0]),
                                          fabs(abs_norm_cross_ab[1])};
      double max_abs_val = (fabs_abs_norm_cross_ab[0] >
                            fabs_abs_norm_cross_ab[1])?(abs_norm_cross_ab[0]):
                                                        (abs_norm_cross_ab[1]);
      if (max_abs_val < 0) {
         abs_norm_cross_ab[0] *= -1.0;
         abs_norm_cross_ab[1] *= -1.0;
      }
   }

   double fabs_c_2 = fabs(c[2]);
   double fabs_d_2 = fabs(d[2]);
   ref_point = (fabs_c_2 > fabs_d_2)?d:c;
   // if both points are at the pole
   if (fabs(ref_point[2]) > 1.0-tol) {
      abs_norm_cross_cd[0] = 0;
      abs_norm_cross_cd[1] = 1;
      edge_is_pole_point = 1;
   } else {
      double scale = 1.0 / sqrt(ref_point[0]*ref_point[0] +
                                ref_point[1]*ref_point[1]);
      abs_norm_cross_cd[0] = ref_point[1] * scale;
      abs_norm_cross_cd[1] = ref_point[0] * scale;
      double fabs_abs_norm_cross_cd[2] = {fabs(abs_norm_cross_cd[0]),
                                          fabs(abs_norm_cross_cd[1])};
      double max_abs_val =
        (fabs_abs_norm_cross_cd[0] > fabs_abs_norm_cross_cd[1])?
        (abs_norm_cross_cd[0]):(abs_norm_cross_cd[1]);
      if (max_abs_val < 0) {
         abs_norm_cross_cd[0] *= -1.0;
         abs_norm_cross_cd[1] *= -1.0;
      }
   }

   // if both edges are on the same circle of longitude
   if (fabs(abs_norm_cross_ab[0] - abs_norm_cross_cd[0]) < tol &&
       fabs(abs_norm_cross_ab[1] - abs_norm_cross_cd[1]) < tol) {

      if (!edge_is_pole_point)
         ret_value |= 16;

      int a_between_cd, b_between_cd, c_between_ab, d_between_ab;
      double sq_len_diff_ab = sq_len_diff_vec(a, b);
      double sq_len_diff_cd = sq_len_diff_vec(c, d);

      a_between_cd = vector_is_between(c, d, a, sq_len_diff_cd) << 0;
      b_between_cd = vector_is_between(c, d, b, sq_len_diff_cd) << 1;
      c_between_ab = vector_is_between(a, b, c, sq_len_diff_ab) << 2;
      d_between_ab = vector_is_between(a, b, d, sq_len_diff_ab) << 3;

      switch (a_between_cd + b_between_cd + c_between_ab + d_between_ab) {

         case (0):
            p[0] = 0, p[1] = 0, p[2] = 1;
            q[0] = 0, q[1] = 0, q[2] = -1;
            break;
         case (1+2):
         case (1+2+4):
         case (1+2+8):
            p[0] = a[0], p[1] = a[1], p[2] = a[2];
            q[0] = b[0], q[1] = b[1], q[2] = b[2];
            break;
         case (1+4):
         case (1+4+8):
            p[0] = a[0], p[1] = a[1], p[2] = a[2];
            q[0] = c[0], q[1] = c[1], q[2] = c[2];
            break;
         case (1+8):
            p[0] = a[0], p[1] = a[1], p[2] = a[2];
            q[0] = d[0], q[1] = d[1], q[2] = d[2];
            break;
         case (2+4):
         case (2+4+8):
            p[0] = b[0], p[1] = b[1], p[2] = b[2];
            q[0] = c[0], q[1] = c[1], q[2] = c[2];
            break;
         case (2+8):
            p[0] = b[0], p[1] = b[1], p[2] = b[2];
            q[0] = d[0], q[1] = d[1], q[2] = d[2];
            break;
         case (4+8):
            p[0] = c[0], p[1] = c[1], p[2] = c[2];
            q[0] = d[0], q[1] = d[1], q[2] = d[2];
            break;
         case (1+2+4+8):
            p[0] = a[0], p[1] = a[1], p[2] = a[2];
            q[0] = a[0], q[1] = a[1], q[2] = a[2];
            break;
         default:
            yac_internal_abort_message("internal error", __FILE__, __LINE__);
      }

   } else {

      p[0] = 0, p[1] = 0; p[2] = 1;
      q[0] = 0, q[1] = 0; q[2] = -1;
   }

   double sq_len_diff_ab = sq_len_diff_vec(a, b);
   double sq_len_diff_cd = sq_len_diff_vec(c, d);

   if (ret_value & 16) {
      if (vector_is_between(a, b, p, sq_len_diff_ab)) ret_value |= 1;
      if (vector_is_between(a, b, q, sq_len_diff_ab)) ret_value |= 2;
      if (vector_is_between(c, d, p, sq_len_diff_cd)) ret_value |= 4;
      if (vector_is_between(c, d, q, sq_len_diff_cd)) ret_value |= 8;
   } else {
      if (vector_is_between(a, b, p, sq_len_diff_ab)) ret_value |= 1;
      else if (vector_is_between(a, b, q, sq_len_diff_ab)) ret_value |= 2;
      if (vector_is_between(c, d, p, sq_len_diff_cd)) ret_value |= 4;
      else if (vector_is_between(c, d, q, sq_len_diff_cd)) ret_value |= 8;
   }

   return ret_value;
}

/** \brief compute the intersection point of a meridian and a parallel
 *
 * compute the intersection points of a circle of longitude (defined by a and b)
 * and a circle of latitude (defined by c and d)
 * if p and q are != NULL they contain the intersection points
 * the return value is:
 *      - 0 if the intersection points are neither between (a and b) or (c and d)
 *      - -1 if an error occurred
 *      - 1st bit will be set if p is between a and b
 *      - 2nd bit will be set if q is between a and b
 *      - 3rd bit will be set if p is between c and d
 *      - 4th bit will be set if q is between c and d
 **/
int yac_loncxlatc_vec (double a[3], double b[3], double c[3], double d[3],
                       double p[3], double q[3]) {

   unsigned ret_value;

   ret_value = 0;

   // this test is not very accurate but should catch the most obvious cases
   // the accuracy of this test is not allowed to be higher than the one in then
   // routine is_inside_gc
   if ((fabs(a[0] * b[1] - a[1] * b[0]) > 1e-7) &&
       (fabs(fabs(a[2]) - 1.0) > tol) &&
       (fabs(fabs(b[2]) - 1.0) > tol)) {

      yac_internal_abort_message("edge is not a circle of longitude", __FILE__, __LINE__);
   }

   unsigned ab_goes_across_pole =
      ((fabs(a[0]) < yac_angle_tol) && (fabs(a[1]) < yac_angle_tol)) ||
      ((fabs(b[0]) < yac_angle_tol) && (fabs(b[1]) < yac_angle_tol)) ||
      (((a[0] > 0.0) ^ (b[0] > 0.0)) && ((a[1] > 0.0) ^ (b[1] > 0.0)));
   unsigned cd_is_on_pole =
      (fabs(c[0]) < yac_angle_tol) && (fabs(c[1]) < yac_angle_tol);
   unsigned ab_is_point =
      compare_angles(get_vector_angle_2(a, b), SIN_COS_TOL) <= 0;
   unsigned cd_is_point =
      compare_angles(get_vector_angle_2(c, d), SIN_COS_TOL) <= 0;

   if (cd_is_on_pole) {

      if (((ab_goes_across_pole) && (fabs(a[2] - c[2]) < 1.0)) ||
          ((fabs(a[2] - c[2]) < tol)) || (fabs(b[2] - c[2]) < tol))
         ret_value |= 1;

      if (p != NULL)
         p[0] = c[0], p[1] = c[1], p[2] = c[2];
      if (q != NULL)
         q[0] = c[0], q[1] = c[1], q[2] = c[2];

      ret_value |= 4;

   } else if (ab_goes_across_pole && ab_is_point) {

      if (p != NULL)
         p[0] = c[0], p[1] = c[1], p[2] = c[2];
      if (q != NULL)
         q[0] = -p[0], q[1] = -p[1], q[2] = p[2];

      ret_value |= 4;

   } else {

      /*
      // the cos is too inaccurate close to the equator
      {
         if (fabs(a[2]) < fabs(b[2])) {

            double scale = cos(c[2]) / cos(a[2]);

            if (p != NULL)
               p[0] = a[0] * scale, p[1] = a[1] * scale, p[2] = c[2];
            if (q != NULL)
               q[0] = -a[0] * scale, q[1] = -a[1] * scale, q[2] = c[2];
         } else {d

            double scale = cos(c[2]) / cos(b[2]);

            if (p != NULL)
               p[0] = b[0] * scale, p[1] = b[1] * scale, p[2] = c[2];
            if (q != NULL)
               q[0] = -b[0] * scale, q[1] = -b[1] * scale, q[2] = c[2];
         }
      }
      */
      {

        double tmp_scale_a = a[0] * a[0] + a[1] * a[1];
        double tmp_scale_b = b[0] * b[0] + b[1] * b[1];

         if (tmp_scale_a > tmp_scale_b) {

            double scale = sqrt((1.0 - c[2] * c[2])/tmp_scale_a);

            if (p != NULL)
               p[0] = a[0] * scale, p[1] = a[1] * scale, p[2] = c[2];
            if (q != NULL)
               q[0] = -a[0] * scale, q[1] = -a[1] * scale, q[2] = c[2];

         } else {

            double scale = sqrt((1.0 - c[2] * c[2])/tmp_scale_b);

            if (p != NULL)
               p[0] = b[0] * scale, p[1] = b[1] * scale, p[2] = c[2];
            if (q != NULL)
               q[0] = -b[0] * scale, q[1] = -b[1] * scale, q[2] = c[2];
         }
      }



      if (cd_is_point) {

         if (fabs(c[0]-p[0]) < tol &&
             fabs(c[1]-p[1]) < tol &&
             fabs(c[2]-p[2]) < tol) ret_value |= 1 << 2;
         if (fabs(c[0]-q[0]) < tol &&
             fabs(c[1]-q[1]) < tol &&
             fabs(c[2]-q[2]) < tol) ret_value |= 1 << 3;

      } else {

         if (vector_is_between_lat(c, d, p))
            ret_value |= 1 << 2;
         if (vector_is_between_lat(c, d, q))
            ret_value |= 1 << 3;
      }

      if (ab_is_point) {
         if (fabs(a[0]-p[0]) < tol &&
             fabs(a[1]-p[1]) < tol &&
             fabs(a[2]-p[2]) < tol) ret_value |= 1 << 0;
         if (fabs(a[0]-q[0]) < tol &&
             fabs(a[1]-q[1]) < tol &&
             fabs(a[2]-q[2]) < tol) ret_value |= 1 << 1;
      } else {

         double sq_len_diff_ab = sq_len_diff_vec(a, b);

         if (vector_is_between(a, b, p, sq_len_diff_ab)) ret_value |= 1 << 0;
         if (vector_is_between(a, b, q, sq_len_diff_ab)) ret_value |= 1 << 1;
      }
   }

   return ret_value;
}

/** \brief compute the intersection of a great circle with the parallel
 *
 *  compute the intersection points of a great circle (defined by a and b)
 * and a circle of latitude (defined by c and d)
 * if p and q are != NULL they contain the intersection points
 * the return value is:
 *    - 0 if the intersection points are neither between (a and b) or (c and d)
 *    - -1 if the two circles do not intersect or an error occurred
 *    - 1st bit will be set if p is between a and b
 *    - 2nd bit will be set if q is between a and b
 *    - 3rd bit will be set if p is between c and d
 *    - 4th bit will be set if q is between c and d
 *    - 5th bit will be set if both circles are identically
 * \remarks if -1 is returned neither p or q is set
 * \remarks if the two circles only have one intersection point,
 *          p and q will be identically, but only the p bits will be set
 **/

#if defined __INTEL_COMPILER
#pragma intel optimization_level 0
#elif defined _CRAYC
#pragma _CRI noopt
#endif
int yac_gcxlatc_vec(double a[3], double b[3], double c[3], double d[3],
                    double p[3], double q[3]) {

   unsigned result = 0;

   double cross_ab[3], scale;

   crossproduct_ld(a, b, cross_ab);
   scale = sqrt(cross_ab[0]*cross_ab[0]+
                cross_ab[1]*cross_ab[1]+
                cross_ab[2]*cross_ab[2]);

   double fabs_a_2 = fabs(a[2]);
   double fabs_b_2 = fabs(b[2]);

   // if the great circle is the equator
   if (fabs_a_2 < tol && fabs_b_2 < tol) {

      return yac_latcxlatc_vec(a, b, c, d, p, q);

   // if the great circle is  a circle of longitude
   } else if (scale < tol || fabs(cross_ab[2]/scale) < tol ||
              fabs(fabs_a_2-1.0) < 1e-13 ||
              fabs(fabs_b_2-1.0) < 1e-13) {

      return yac_loncxlatc_vec(a, b, c, d, p, q);
   }

   double t[3], s[3];

   if (fabs_a_2 > fabs_b_2) {

      scale = c[2] / a[2];

      t[0] = scale * a[0];
      t[1] = scale * a[1];

   } else {

      scale = c[2] / b[2];

      t[0] = scale * b[0];
      t[1] = scale * b[1];
   }

   t[2] = c[2];

   s[2] = 0;

   if (fabs_a_2 < tol)
      s[0] = a[0], s[1] = a[1];
   else if (fabs_b_2 < tol)
      s[0] = b[0], s[1] = b[1];
   else if (fabs_a_2 > fabs_b_2) {
      scale = b[2] / a[2];
      s[0] = b[0] - scale * a[0];
      s[1] = b[1] - scale * a[1];
   } else {
      scale = a[2] / b[2];
      s[0] = a[0] - scale * b[0];
      s[1] = a[1] - scale * b[1];
   }

   {
      // the intersection of the planes of both circles is defined by:
      // x = t + n * s

      // x_0^2 + x_1^2 + x_2^2 = 1
      // x_2 = c_2

      double a_ = s[0] * s[0] + s[1] * s[1];
      double b_ = 2.0 * (t[0] * s[0] + t[1] * s[1]);
      double c_ = t[0] * t[0] + t[1] * t[1] + c[2] * c[2] - 1.0;

      if (!(fabs(a_) > 0.0))
         yac_internal_abort_message("internal error", __FILE__, __LINE__);

      double temp = b_ * b_ - 4.0 * a_ * c_;

      // no intersection possible
      if (temp < 0.0) {
        if (temp < -tol)
          return -1;
        else
          temp = 0;
      }

      double n[2];

      n[0] = - (b_ + sqrt(temp)) / (2.0 * a_);
      n[1] = - (b_ - sqrt(temp)) / (2.0 * a_);

      p[0] = t[0] + n[0] * s[0];
      p[1] = t[1] + n[0] * s[1];
      p[2] = t[2] + n[0] * s[2];

      double sq_len_diff_ab = sq_len_diff_vec(a, b);

      if (vector_is_between(a, b, p, sq_len_diff_ab)) result |= 1;
      if (vector_is_between_lat(c, d, p)) result |= 4;

      if (fabs(n[0] - n[1]) >= tol) {

         q[0] = t[0] + n[1] * s[0];
         q[1] = t[1] + n[1] * s[1];
         q[2] = t[2] + n[1] * s[2];

         if (vector_is_between(a, b, q, sq_len_diff_ab)) result |= 2;
         if (vector_is_between_lat(c, d, q)) result |= 8;
      } else
         q[0] = p[0], q[1] = p[1], q[2] = p[2];
   }

   return result;
}
#if defined _CRAYC
#pragma _CRI opt
#endif

int yac_intersect_vec (enum yac_edge_type edge_type_a, double a[3], double b[3],
                       enum yac_edge_type edge_type_b, double c[3], double d[3],
                       double p[3], double q[3]) {

   int switch_edges;

   switch_edges = 0;

   int (*intersect_func)(double *, double *, double *, double *, double *, double *);

   // if both edges are on circles of latitude
   if (edge_type_a == LAT_CIRCLE &&
       edge_type_b == LAT_CIRCLE) {

      intersect_func = yac_latcxlatc_vec;

   // if both edges are on circle of longitude
   } else if (edge_type_a == LON_CIRCLE &&
              edge_type_b == LON_CIRCLE) {

      intersect_func = yac_loncxlonc_vec;

   // if both edges are on great circles
   } else if ((edge_type_a == GREAT_CIRCLE &&
               edge_type_b == GREAT_CIRCLE) ||
              (edge_type_a == LON_CIRCLE   &&
               edge_type_b == GREAT_CIRCLE) ||
              (edge_type_a == GREAT_CIRCLE &&
               edge_type_b == LON_CIRCLE)) {

      intersect_func = yac_gcxgc_vec;

   // if one edge a is on a great circle and edge b on a circle of latitude
   } else if (edge_type_a == GREAT_CIRCLE &&
              edge_type_b == LAT_CIRCLE) {

      intersect_func = yac_gcxlatc_vec;

   // if one edge a is on a circle of latitude and edge b on a great circle
   } else if (edge_type_a == LAT_CIRCLE &&
              edge_type_b == GREAT_CIRCLE ) {

      switch_edges = 1;
      intersect_func = yac_gcxlatc_vec;

   // if one edge a is on a circle of longitude and edge b on a circle of latitude
   } else if (edge_type_a == LON_CIRCLE &&
              edge_type_b == LAT_CIRCLE) {

      intersect_func = yac_loncxlatc_vec;

   // if one edge a is on a circle of latitude and edge b on a circle of longitude
   } else if (edge_type_a == LAT_CIRCLE &&
              edge_type_b == LON_CIRCLE ) {

      switch_edges = 1;
      intersect_func = yac_loncxlatc_vec;

   } else {

      yac_internal_abort_message ( "ERROR: unknown edge type.", __FILE__, __LINE__ );
      exit(EXIT_FAILURE);
   }

   int ret_value;

   // compute the intersection between both circles
   if (switch_edges) ret_value = intersect_func(c, d, a, b, p, q);
   else              ret_value = intersect_func(a, b, c, d, p, q);

   if (switch_edges)
      ret_value = (ret_value & (~(1 + 2 + 4 + 8))) +
                  ((ret_value & (1 + 2)) << 2) +
                  ((ret_value & (4 + 8)) >> 2);

   return ret_value;
}
