/**
 * @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;

enum {
  p_on_a = 1,
  q_on_a = 2,
  p_on_b = 4,
  q_on_b = 8,
  circles_are_identical = 16,
};

enum {
  no_intersection = 0,
  a_between_cd = 1,
  b_between_cd = 2,
  c_between_ab = 4,
  d_between_ab = 8,
};

/**
 * 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 const a[], double const b[], double const 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 const a[], double const b[], double const 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;

   YAC_ASSERT(
      (fabs(temp) > tol),
      "ERROR(vector_is_between_lat): "
      "routine does not support zero length edges")
   /*// 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;
}

static void compute_edge_middle_point_vec(
  enum yac_edge_type edge_type, double const a[3], double const b[3],
  double middle[3]) {

  YAC_ASSERT(
    (edge_type == GREAT_CIRCLE) ||
    (edge_type == LAT_CIRCLE) ||
    (edge_type == LON_CIRCLE),
    "ERROR(compute_edge_middle_point_vec): invalide edge_type")

  if (edge_type == LAT_CIRCLE) {
    middle[0] = a[0]+b[0], middle[1] = a[1]+b[1], middle[2] = a[2];
    double temp = middle[0]*middle[0] + middle[1]*middle[1];
    double scale = sqrt((1.0 - middle[2] * middle[2])/temp);
    middle[0] *= scale;
    middle[1] *= scale;
  } else {
    middle[0] = a[0]+b[0], middle[1] = a[1]+b[1], middle[2] = a[2]+b[2];
    normalise_vector(middle);
  }
}

static int yac_identical_circles_vec(
  enum yac_edge_type edge_type, double const a[3], double const b[3],
  double const c[3], double const d[3], double p[3], double q[3],
  int intersection_type) {

  YAC_ASSERT(
    (intersection_type == no_intersection) ||
    (intersection_type == a_between_cd + b_between_cd) ||
    (intersection_type == a_between_cd + b_between_cd + c_between_ab) ||
    (intersection_type == a_between_cd + b_between_cd + d_between_ab) ||
    (intersection_type == a_between_cd + c_between_ab) ||
    (intersection_type == a_between_cd + c_between_ab + d_between_ab) ||
    (intersection_type == a_between_cd + d_between_ab) ||
    (intersection_type == b_between_cd + c_between_ab) ||
    (intersection_type == b_between_cd + c_between_ab + d_between_ab) ||
    (intersection_type == b_between_cd + d_between_ab) ||
    (intersection_type == c_between_ab + d_between_ab) ||
    (intersection_type == a_between_cd + b_between_cd +
                          c_between_ab + d_between_ab),
    "internal error")

  switch (intersection_type) {
    default:

    // edges do not intersect
    case (no_intersection):
      // use middle points of both edges as intersection points
      compute_edge_middle_point_vec(edge_type, a, b, p);
      compute_edge_middle_point_vec(edge_type, c, d, q);
      return p_on_a + q_on_b + circles_are_identical;

    // edges are identical
    case (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] = b[0], q[1] = b[1], q[2] = b[2];
      break;

    // both vertices of first edge overlap with the second edge
    case (a_between_cd + b_between_cd):
    case (a_between_cd + b_between_cd + c_between_ab):
    case (a_between_cd + b_between_cd + d_between_ab):
      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;

    // both edges of the second edge overlap with the first edge
    case (c_between_ab + d_between_ab):
    case (a_between_cd + c_between_ab + d_between_ab):
    case (b_between_cd + 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];
      break;

    // only one vertex of each edge overlaps with the other edge
    case (a_between_cd + c_between_ab):
      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 (a_between_cd + d_between_ab):
      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 (b_between_cd + c_between_ab):
      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 (b_between_cd + d_between_ab):
      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;
  }
  return p_on_a + q_on_a + p_on_b + q_on_b + circles_are_identical;
}

// computes intersection of great circle edges that are on
// the same great circle
static int yac_identical_gcxgc_vec(
  double const a[3], double const b[3], double const c[3], double const d[3],
  double p[3], double q[3]) {

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

  return
    yac_identical_circles_vec(
      GREAT_CIRCLE, a, b, c, d, p, q,
      (vector_is_between(c, d, a, sq_len_diff_cd) << 0) +
      (vector_is_between(c, d, b, sq_len_diff_cd) << 1) +
      (vector_is_between(a, b, c, sq_len_diff_ab) << 2) +
      (vector_is_between(a, b, d, sq_len_diff_ab) << 3));
}

// determines the return value for the provided intersection points p and q
static int yac_check_pq_gcxgc(
  double const a[3], double const b[3], double const c[3], double const d[3],
  double const p[3], double const q[3]) {

  // square of the Euclidean distance between the vertices of the two edges
  double sq_len_diff_ab = sq_len_diff_vec(a, b);
  double sq_len_diff_cd = sq_len_diff_vec(c, d);

  return (vector_is_between(a, b, p, sq_len_diff_ab) << 0) +
         (vector_is_between(a, b, q, sq_len_diff_ab) << 1) +
         (vector_is_between(c, d, p, sq_len_diff_cd) << 2) +
         (vector_is_between(c, d, q, sq_len_diff_cd) << 3);  
}

/** \example test_gcxgc.c
 * This contains examples on how to use \ref yac_gcxgc_vec
 */
/**
 * computes the intersection points of two great circles \n
 * based on http://www.geoclub.de/viewtopic.php?f=54&t=29689
 * @param[in]  a first point of edge a the is on a great circle
 * @param[in]  b second point of edge a the is on a great circle
 * @param[in]  c first point of edge b the is on a great circle
 * @param[in]  d second point of edge b the is on a great circle
 * @param[out] p intersection point
 * @param[out] q intersection point
 * @return  0 if the intersection points are neither on edge a or b \n
 *          -1 if an error occurred \n
 *          1st bit will be set if p is on edge a \n
 *          2nd bit will be set if q is on edge a \n
 *          3rd bit will be set if p is on edge b \n
 *          4th bit will be set if q is on edge b
 *          5th bit will be set if both great circles are identically
 * @remarks both edges need to have length of at least yac_angle_tol
 */
static int yac_gcxgc_vec(
  double const a[3], double const b[3], double const c[3], double const d[3],
  double p[3], double q[3]) {

  long double cross_ab[3], cross_cd[3], cross_abxcd[3];

  // compute p and q
  // p' = (a X b) X (c X d)
  // p  = p' / length(p')
  // q = -p
  long double const a_[3] = {a[0], a[1], a[2]};
  long double const b_[3] = {b[0], b[1], b[2]};
  long double const c_[3] = {c[0], c[1], c[2]};
  long double const 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];

  YAC_ASSERT((sq_sin_ab_ > 0.0) && (sq_sin_cd_ > 0.0), "internal error");
  long double sq_length_abxcd = cross_abxcd[0] * cross_abxcd[0] +
                                cross_abxcd[1] * cross_abxcd[1] +
                                cross_abxcd[2] * cross_abxcd[2];
  double sq_sin_abxcd = (double)(sq_length_abxcd / (sq_sin_ab_ * sq_sin_cd_));

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

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

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

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

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

/** \example test_latcxlatc.c
 * This contains examples on \ref yac_latcxlatc_vec
 */
/** \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
 * @remarks both edges need to have length of at least yac_angle_tol
 **/
static int yac_latcxlatc_vec(
  double const a[3], double const b[3], double const c[3], double const 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;
  else
    return
      yac_identical_circles_vec(
        LAT_CIRCLE, a, b, c, d, p, q,
        (vector_is_between_lat(c, d, a) << 0) +
        (vector_is_between_lat(c, d, b) << 1) +
        (vector_is_between_lat(a, b, c) << 2) +
        (vector_is_between_lat(a, b, d) << 3));
}

static void compute_lon_abs_norm_cross(
  double const a[3], double const b[3], double abs_norm_cross[2]) {

  double const * ref_point = ((fabs(a[2]) > fabs(b[2]))?b:a);

  double scale = 1.0 / sqrt(ref_point[0]*ref_point[0] +
                            ref_point[1]*ref_point[1]);
  abs_norm_cross[0] = ref_point[1] * scale;
  abs_norm_cross[1] = ref_point[0] * scale;

  double max_abs_val =
    (fabs(abs_norm_cross[0]) > fabs(abs_norm_cross[1]))?
      (abs_norm_cross[0]):(abs_norm_cross[1]);
  if (max_abs_val < 0) {
    abs_norm_cross[0] = -abs_norm_cross[0];
    abs_norm_cross[1] = -abs_norm_cross[1];
  }
}

/** \example test_loncxlonc.c
 * This contains examples on \ref yac_loncxlonc_vec
 */
/** \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
 * @remarks both edges need to have length of at least yac_angle_tol
 **/
static int yac_loncxlonc_vec(
  double const a[3], double const b[3], double const c[3], double const d[3],
  double p[3], double q[3]) {

  double abs_norm_cross_ab[2], abs_norm_cross_cd[2];
  compute_lon_abs_norm_cross(a, b, abs_norm_cross_ab);
  compute_lon_abs_norm_cross(c, d, abs_norm_cross_cd);

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

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

  } else {

    // the intersection points of the two circles of longitude are the poles
    p[0] = 0, p[1] = 0; p[2] = 1;
    q[0] = 0, q[1] = 0; q[2] = -1;

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

// determines the return value for the provided intersection points p and q
static int yac_check_pq_gcxlatc(
  double const a[3], double const b[3], double const c[3], double const d[3],
  double const p[3], double const q[3]) {

  // square of the Euclidean distance between the vertices of the two edges
  double sq_len_diff_ab = sq_len_diff_vec(a, b);

  return
    (vector_is_between(a, b, p, sq_len_diff_ab) << 0) +
    (vector_is_between(a, b, q, sq_len_diff_ab) << 1) +
    (vector_is_between_lat(c, d, p)             << 2) +
    (vector_is_between_lat(c, d, q)             << 3);
}

/** \example test_loncxlatc.c
 * This contains examples on \ref yac_loncxlatc_vec
 */
/** \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
 * @remarks both edges need to have length of at least yac_angle_tol
 **/
static int yac_loncxlatc_vec (
  double const a[3], double const b[3], double const c[3], double const d[3],
  double p[3], double q[3]) {

  // 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
  YAC_ASSERT((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),
             "edge is not a circle of longitude")

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

  // compute the two intersection points
  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];
  }

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

/** \example test_gcxlatc.c
 * This contains examples on gcxlatc_vec.
 */
/** \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)
 * (both circles need to have a length of at least yac_angle_tol)
 * 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
 * @remarks both edges need to have length of at least yac_angle_tol
 **/

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

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

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

  // if the great circle is  a circle of longitude
  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;

    YAC_ASSERT(fabs(a_) > 0.0, "internal error")

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

    // no intersection possible
    if (temp < -tol) return -1;
    temp = MAX(temp, 0.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];

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

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

#if defined _CRAYC
#pragma _CRI opt
#endif

static int yac_pxp_vec(
  double const a[3], double const b[3], double p[3], double q[3]) {

  // if both points are nearly identical
  if (points_are_identically(a, b)) {

    p[0] = a[0], p[1] = a[1], p[2] = a[2];
    q[0] = -a[0], q[1] = -a[1], q[2] = -a[2];
    return p_on_a + p_on_b;
  } else {
    return -1;
  }
}

static int yac_pxgc_vec(
  double const point[3], double const a[3], double const b[3],
  double p[3], double q[3]) {

  // compute norm vector for the plane of ab
  double norm_ab[3];
  crossproduct_ld(a, b, norm_ab);
  normalise_vector(norm_ab);

  // compute the sin of the angle between the point and the plane of ab
  double sin_point_ab =
    fabs(norm_ab[0]*point[0] + norm_ab[1]*point[1] + norm_ab[2]*point[2]);

  // if the point is on the plane of ab
  if (sin_point_ab < yac_angle_tol) {

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

    int ret_value = p_on_a;
    double sq_len_diff_ab = sq_len_diff_vec(a, b);

    if      (vector_is_between(a, b, p, sq_len_diff_ab)) ret_value |= p_on_b;
    else if (vector_is_between(a, b, q, sq_len_diff_ab)) ret_value |= q_on_b;

    return ret_value;

  } else {
    return -1;
  }
}

static int yac_pxlat_vec(
  double const point[3], double const a[3], double const b[3],
  double p[3], double q[3]) {

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

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

  int ret_value = p_on_a;

  if      (vector_is_between_lat(a, b, p)) ret_value |= p_on_b;
  else if (vector_is_between_lat(a, b, q)) ret_value |= q_on_b;

  return ret_value;
}

static inline int adjust_ret_value(int ret_value) {
  return (ret_value & (~(1 + 2 + 4 + 8))) +
         ((ret_value & (1 + 2)) << 2) +
         ((ret_value & (4 + 8)) >> 2);
}

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

  YAC_ASSERT(
    ((edge_type_a == LON_CIRCLE) ||
     (edge_type_a == LAT_CIRCLE) ||
     (edge_type_a == GREAT_CIRCLE)) &&
    ((edge_type_b == LON_CIRCLE) ||
     (edge_type_b == LAT_CIRCLE) ||
     (edge_type_b == GREAT_CIRCLE)), "ERROR: unknown edge type.")
    

  int edge_a_is_point = points_are_identically(a, b);
  int edge_b_is_point = points_are_identically(c, d);

  // if both edges are points
  if (edge_a_is_point && edge_b_is_point) {

    return yac_pxp_vec(a, c, p, q);

  // if one edge is a point
  } else if (edge_a_is_point || edge_b_is_point) {

    enum yac_edge_type edge_type[2] = {edge_type_a, edge_type_b};
    double const * edge[2][2] = {{a,b}, {c,d}};
    double const * point[2] = {c,a};

    int ret_value;
    switch (edge_type[edge_a_is_point]) {
      case (LAT_CIRCLE):
        ret_value =
          yac_pxlat_vec(
            point[edge_a_is_point], 
            edge[edge_a_is_point][0], edge[edge_a_is_point][1], p, q);
        break;
      default:
      case (LON_CIRCLE):
      case (GREAT_CIRCLE):
        ret_value =
          yac_pxgc_vec(
            point[edge_a_is_point], 
            edge[edge_a_is_point][0], edge[edge_a_is_point][1], p, q);
        break;
    }

    if (edge_a_is_point) return ret_value;
    else                 return adjust_ret_value(ret_value);

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

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

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

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

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

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

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

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

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

    return adjust_ret_value(yac_gcxlatc_vec(c, d, a, b, p, q));

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

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

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

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