// Copyright (c) 2017, 2020, Oracle and/or its affiliates.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0,
// as published by the Free Software Foundation.
//
// This program is also distributed with certain software (including
// but not limited to OpenSSL) that is licensed under separate terms,
// as designated in a particular file or component or in included license
// documentation.  The authors of MySQL hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have included with MySQL.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.

/// @file
///
/// This file implements the crosses functor and function.

#include <memory>  // std::unique_ptr

#include <boost/geometry.hpp>

#include "sql/dd/types/spatial_reference_system.h"  // dd::Spatial_reference_system
#include "sql/gis/crosses_functor.h"
#include "sql/gis/difference_functor.h"
#include "sql/gis/disjoint_functor.h"
#include "sql/gis/gc_utils.h"
#include "sql/gis/geometries.h"
#include "sql/gis/geometries_traits.h"
#include "sql/gis/relops.h"
#include "sql/gis/within_functor.h"
#include "sql/sql_exception_handler.h"  // handle_gis_exception

namespace bg = boost::geometry;

namespace gis {

/// Apply a Crosses functor to two geometries, which both may be geometry
/// collections, and return the booelan result of the functor applied on each
/// combination of elements in the collections.
///
/// @tparam GC Coordinate specific gometry collection type.
///
/// @param f Functor to apply.
/// @param g1 First geometry.
/// @param g2 Second geometry.
///
/// @retval true g1 crosses g2.
/// @retval false g1 doesn't cross g2.
template <typename GC>
static bool geometry_collection_apply_crosses(const Crosses &f,
                                              const Geometry *g1,
                                              const Geometry *g2) {
  if (g1->type() == Geometry_type::kGeometrycollection) {
    std::unique_ptr<Multipoint> g1_mpt;
    std::unique_ptr<Multilinestring> g1_mls;
    std::unique_ptr<Multipolygon> g1_mpy;
    split_gc(down_cast<const Geometrycollection *>(g1), &g1_mpt, &g1_mls,
             &g1_mpy);
    if (!g1_mpy->empty()) throw null_value_exception();
    gc_union(f.semi_major(), f.semi_minor(), &g1_mpt, &g1_mls, &g1_mpy);

    if (g2->type() == Geometry_type::kGeometrycollection) {
      std::unique_ptr<Multipoint> g2_mpt;
      std::unique_ptr<Multilinestring> g2_mls;
      std::unique_ptr<Multipolygon> g2_mpy;
      split_gc(down_cast<const Geometrycollection *>(g2), &g2_mpt, &g2_mls,
               &g2_mpy);
      if (!g2_mpt->empty() && g2_mls->empty() && g2_mpy->empty())
        throw null_value_exception();
      gc_union(f.semi_major(), f.semi_minor(), &g2_mpt, &g2_mls, &g2_mpy);

      // g1 and g2 must have at least one interior point in common.
      bool shared_interior = false;
      DBUG_ASSERT(g1_mpy->empty());  // Should have returned already.
      if (g1->coordinate_system() == Coordinate_system::kCartesian) {
        if (g1_mpy->empty() && !g1_mls->empty() && g2_mpy->empty() &&
            !g2_mls->empty()) {
          // Both g1 and g2 are of dimenision 1, so the common interior has to
          // be of dimension 0 for g1 and g2 to cross.
          boost::geometry::de9im::mask mask("0********");
          shared_interior = bg::relate(
              *down_cast<Cartesian_multipoint *>(g1_mpt.get()),
              *down_cast<Cartesian_multipoint *>(g2_mpt.get()), mask);
          for (std::size_t i = 0;
               i < down_cast<Cartesian_multipoint *>(g1_mpt.get())->size();
               i++) {
            auto &pt = (*down_cast<Cartesian_multipoint *>(g1_mpt.get()))[i];
            shared_interior |= bg::relate(
                pt, *down_cast<Cartesian_multilinestring *>(g2_mls.get()),
                mask);
          }
          for (std::size_t i = 0;
               i < down_cast<Cartesian_multipoint *>(g2_mpt.get())->size();
               i++) {
            auto &pt = (*down_cast<Cartesian_multipoint *>(g2_mpt.get()))[i];
            shared_interior |= bg::relate(
                pt, *down_cast<Cartesian_multilinestring *>(g1_mls.get()),
                mask);
          }
          if (bg::relate(*down_cast<Cartesian_multilinestring *>(g1_mls.get()),
                         *down_cast<Cartesian_multilinestring *>(g2_mls.get()),
                         mask)) {
            shared_interior = true;
          } else {
            boost::geometry::de9im::mask line_mask("1********");
            if (bg::relate(
                    *down_cast<Cartesian_multilinestring *>(g1_mls.get()),
                    *down_cast<Cartesian_multilinestring *>(g2_mls.get()),
                    line_mask)) {
              shared_interior = false;  // Shared interior is a line.
            }
          }
        } else {
          // Either g1 or g2 are not of dimension 1. Therefore, it's enough to
          // have some common interior, there's no requirement on the
          // dimensionality.
          boost::geometry::de9im::mask mask("T********");
          shared_interior = bg::relate(
              *down_cast<Cartesian_multipoint *>(g1_mpt.get()),
              *down_cast<Cartesian_multipoint *>(g2_mpt.get()), mask);
          for (std::size_t i = 0;
               i < down_cast<Cartesian_multipoint *>(g1_mpt.get())->size();
               i++) {
            auto &pt = (*down_cast<Cartesian_multipoint *>(g1_mpt.get()))[i];
            shared_interior |=
                bg::relate(
                    pt, *down_cast<Cartesian_multilinestring *>(g2_mls.get()),
                    mask) ||
                bg::relate(pt,
                           *down_cast<Cartesian_multipolygon *>(g2_mpy.get()),
                           mask);
          }
          for (std::size_t i = 0;
               i < down_cast<Cartesian_multipoint *>(g2_mpt.get())->size();
               i++) {
            auto &pt = (*down_cast<Cartesian_multipoint *>(g2_mpt.get()))[i];
            shared_interior |= bg::relate(
                pt, *down_cast<Cartesian_multilinestring *>(g1_mls.get()),
                mask);
          }
          shared_interior |=
              bg::relate(*down_cast<Cartesian_multilinestring *>(g1_mls.get()),
                         *down_cast<Cartesian_multilinestring *>(g2_mls.get()),
                         mask) ||
              bg::relate(*down_cast<Cartesian_multilinestring *>(g1_mls.get()),
                         *down_cast<Cartesian_multipolygon *>(g2_mpy.get()),
                         mask);
        }
      } else {
        DBUG_ASSERT(g1->coordinate_system() == Coordinate_system::kGeographic);
        if (g1_mpy->empty() && !g1_mls->empty() && g2_mpy->empty() &&
            !g2_mls->empty()) {
          // Both g1 and g2 are of dimenision 1, so the common interior has to
          // be of dimension 0 for g1 and g2 to cross.
          boost::geometry::de9im::mask mask("0********");
          shared_interior = bg::relate(
              *down_cast<Geographic_multipoint *>(g1_mpt.get()),
              *down_cast<Geographic_multipoint *>(g2_mpt.get()), mask);
          for (std::size_t i = 0;
               i < down_cast<Geographic_multipoint *>(g1_mpt.get())->size();
               i++) {
            auto &pt = (*down_cast<Geographic_multipoint *>(g1_mpt.get()))[i];
            shared_interior |= bg::relate(
                pt, *down_cast<Geographic_multilinestring *>(g2_mls.get()),
                mask);
          }
          for (std::size_t i = 0;
               i < down_cast<Geographic_multipoint *>(g2_mpt.get())->size();
               i++) {
            auto &pt = (*down_cast<Geographic_multipoint *>(g2_mpt.get()))[i];
            shared_interior |= bg::relate(
                pt, *down_cast<Geographic_multilinestring *>(g1_mls.get()),
                mask);
          }
          if (bg::relate(*down_cast<Geographic_multilinestring *>(g1_mls.get()),
                         *down_cast<Geographic_multilinestring *>(g2_mls.get()),
                         mask)) {
            shared_interior = true;
          } else {
            boost::geometry::de9im::mask line_mask("1********");
            if (bg::relate(
                    *down_cast<Geographic_multilinestring *>(g1_mls.get()),
                    *down_cast<Geographic_multilinestring *>(g2_mls.get()),
                    line_mask)) {
              shared_interior = false;  // Shared interior is a line.
            }
          }
        } else {
          // Either g1 or g2 are not of dimension 1. Therefore, it's enough to
          // have some common interior, there's no requirement on the
          // dimensionality.
          boost::geometry::de9im::mask mask("T********");
          boost::geometry::strategy::within::geographic_winding<
              Geographic_point>
              geographic_pl_pa_strategy(
                  bg::srs::spheroid<double>(f.semi_major(), f.semi_minor()));
          boost::geometry::strategy::intersection::geographic_segments<>
              geographic_ll_la_aa_strategy(
                  bg::srs::spheroid<double>(f.semi_major(), f.semi_minor()));

          shared_interior = bg::relate(
              *down_cast<Geographic_multipoint *>(g1_mpt.get()),
              *down_cast<Geographic_multipoint *>(g2_mpt.get()), mask);
          for (std::size_t i = 0;
               i < down_cast<Geographic_multipoint *>(g1_mpt.get())->size();
               i++) {
            auto &pt = (*down_cast<Geographic_multipoint *>(g1_mpt.get()))[i];
            shared_interior |=
                bg::relate(
                    pt, *down_cast<Geographic_multilinestring *>(g2_mls.get()),
                    mask, geographic_pl_pa_strategy) ||
                bg::relate(pt,
                           *down_cast<Geographic_multipolygon *>(g2_mpy.get()),
                           mask, geographic_pl_pa_strategy);
          }
          for (std::size_t i = 0;
               i < down_cast<Geographic_multipoint *>(g2_mpt.get())->size();
               i++) {
            auto &pt = (*down_cast<Geographic_multipoint *>(g2_mpt.get()))[i];
            shared_interior |= bg::relate(
                pt, *down_cast<Geographic_multilinestring *>(g1_mls.get()),
                mask, geographic_pl_pa_strategy);
          }
          shared_interior |=
              bg::relate(*down_cast<Geographic_multilinestring *>(g1_mls.get()),
                         *down_cast<Geographic_multilinestring *>(g2_mls.get()),
                         mask, geographic_ll_la_aa_strategy) ||
              bg::relate(*down_cast<Geographic_multilinestring *>(g1_mls.get()),
                         *down_cast<Geographic_multipolygon *>(g2_mpy.get()),
                         mask, geographic_ll_la_aa_strategy);
        }
      }

      if (!shared_interior) return false;

      // At least one point of g1 must be in g2's exterior.
      std::unique_ptr<Multipoint> mpt_diff;
      Difference d(f.semi_major(), f.semi_minor());
      mpt_diff.reset(down_cast<Multipoint *>(d(g1_mpt.get(), g2_mpt.get())));
      mpt_diff.reset(down_cast<Multipoint *>(d(mpt_diff.get(), g2_mls.get())));
      mpt_diff.reset(down_cast<Multipoint *>(d(mpt_diff.get(), g2_mpy.get())));
      if (mpt_diff->size() > 0) return true;
      std::unique_ptr<Multilinestring> mls_diff;
      mls_diff.reset(
          down_cast<Multilinestring *>(d(g1_mls.get(), g2_mls.get())));
      mls_diff.reset(
          down_cast<Multilinestring *>(d(mls_diff.get(), g2_mpy.get())));
      return (mls_diff->size() > 0);
    } else {
      if (g1->coordinate_system() == Coordinate_system::kCartesian) {
        Cartesian_geometrycollection gc;
        gc.push_back(*g2);
        return geometry_collection_apply_crosses<Cartesian_geometrycollection>(
            f, g1, &gc);
      } else {
        DBUG_ASSERT(g1->coordinate_system() == Coordinate_system::kGeographic);
        Geographic_geometrycollection gc;
        gc.push_back(*g2);
        return geometry_collection_apply_crosses<Geographic_geometrycollection>(
            f, g1, &gc);
      }
    }
  } else {
    if (g2->type() == Geometry_type::kGeometrycollection) {
      if (g1->coordinate_system() == Coordinate_system::kCartesian) {
        Cartesian_geometrycollection gc;
        gc.push_back(*g1);
        return geometry_collection_apply_crosses<Cartesian_geometrycollection>(
            f, &gc, g2);
      } else {
        DBUG_ASSERT(g1->coordinate_system() == Coordinate_system::kGeographic);
        Geographic_geometrycollection gc;
        gc.push_back(*g1);
        return geometry_collection_apply_crosses<Geographic_geometrycollection>(
            f, &gc, g2);
      }
    } else {
      return f(g1, g2);
    }
  }
}

Crosses::Crosses(double semi_major, double semi_minor)
    : m_semi_major(semi_major),
      m_semi_minor(semi_minor),
      m_geographic_pl_pa_strategy(
          bg::srs::spheroid<double>(semi_major, semi_minor)),
      m_geographic_ll_la_aa_strategy(
          bg::srs::spheroid<double>(semi_major, semi_minor)) {}

bool Crosses::operator()(const Geometry *g1, const Geometry *g2) const {
  return apply(*this, g1, g2);
}

bool Crosses::eval(const Geometry *g1, const Geometry *g2) const {
  // All parameter type combinations have been implemented.
  DBUG_ASSERT(false);
  throw not_implemented_exception::for_non_projected(*g1, *g2);
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Cartesian_point, *)

bool Crosses::eval(const Cartesian_point *, const Cartesian_point *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Cartesian_point *,
                   const Cartesian_linestring *) const {
  // A point may never cross another geometry.
  return false;
}

bool Crosses::eval(const Cartesian_point *, const Cartesian_polygon *) const {
  // A point may never cross another geometry.
  return false;
}

bool Crosses::eval(const Cartesian_point *g1,
                   const Cartesian_geometrycollection *g2) const {
  // Must be evaluated in case g2 contains a single point.
  return geometry_collection_apply_crosses<Cartesian_geometrycollection>(
      *this, g1, g2);
}

bool Crosses::eval(const Cartesian_point *,
                   const Cartesian_multipoint *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Cartesian_point *,
                   const Cartesian_multilinestring *) const {
  // A point may never cross another geometry.
  return false;
}

bool Crosses::eval(const Cartesian_point *,
                   const Cartesian_multipolygon *) const {
  // A point may never cross another geometry.
  return false;
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Cartesian_linestring, *)

bool Crosses::eval(const Cartesian_linestring *,
                   const Cartesian_point *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Cartesian_linestring *g1,
                   const Cartesian_linestring *g2) const {
  return bg::crosses(*g1, *g2);
}

bool Crosses::eval(const Cartesian_linestring *g1,
                   const Cartesian_polygon *g2) const {
  return bg::crosses(*g1, *g2);
}

bool Crosses::eval(const Cartesian_linestring *g1,
                   const Cartesian_geometrycollection *g2) const {
  return geometry_collection_apply_crosses<Cartesian_geometrycollection>(
      *this, g1, g2);
}

bool Crosses::eval(const Cartesian_linestring *,
                   const Cartesian_multipoint *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Cartesian_linestring *g1,
                   const Cartesian_multilinestring *g2) const {
  return bg::crosses(*g1, *g2);
}

bool Crosses::eval(const Cartesian_linestring *g1,
                   const Cartesian_multipolygon *g2) const {
  return bg::crosses(*g1, *g2);
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Cartesian_polygon, *)

bool Crosses::eval(const Cartesian_polygon *, const Geometry *) const {
  // If g1 is a 2d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Cartesian_geometrycollection, *)

bool Crosses::eval(const Cartesian_geometrycollection *g1,
                   const Geometry *g2) const {
  return geometry_collection_apply_crosses<Cartesian_geometrycollection>(
      *this, g1, g2);
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Cartesian_multipoint, *)

bool Crosses::eval(const Cartesian_multipoint *,
                   const Cartesian_point *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Cartesian_multipoint *g1,
                   const Cartesian_linestring *g2) const {
  Within within(m_semi_major, m_semi_minor);
  Disjoint disjoint(m_semi_major, m_semi_minor);
  bool found_within = false;
  bool found_disjoint = false;

  // At least one point in g1 has to be within g2, and at least one point in
  // g1
  // has to be disjoint from g2.
  for (auto &pt : *g1) {
    bool pt_disjoint = false;
    if (!found_disjoint) {
      pt_disjoint = disjoint(&pt, g2);
      found_disjoint = pt_disjoint;
    }
    if (!pt_disjoint && !found_within) {
      found_within = within(&pt, g2);
    }
    if (found_disjoint && found_within) break;
  }

  return found_disjoint && found_within;
}

bool Crosses::eval(const Cartesian_multipoint *g1,
                   const Cartesian_polygon *g2) const {
  Within within(m_semi_major, m_semi_minor);
  Disjoint disjoint(m_semi_major, m_semi_minor);
  bool found_within = false;
  bool found_disjoint = false;

  // At least one point in g1 has to be within g2, and at least one point in
  // g1
  // has to be disjoint from g2.
  for (auto &pt : *g1) {
    bool pt_disjoint = false;
    if (!found_disjoint) {
      pt_disjoint = disjoint(&pt, g2);
      found_disjoint = pt_disjoint;
    }
    if (!pt_disjoint && !found_within) {
      found_within = within(&pt, g2);
    }
    if (found_disjoint && found_within) break;
  }

  return found_disjoint && found_within;
}

bool Crosses::eval(const Cartesian_multipoint *g1,
                   const Cartesian_geometrycollection *g2) const {
  return geometry_collection_apply_crosses<Cartesian_geometrycollection>(
      *this, g1, g2);
}

bool Crosses::eval(const Cartesian_multipoint *,
                   const Cartesian_multipoint *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Cartesian_multipoint *g1,
                   const Cartesian_multilinestring *g2) const {
  Within within(m_semi_major, m_semi_minor);
  Disjoint disjoint(m_semi_major, m_semi_minor);
  bool found_within = false;
  bool found_disjoint = false;

  // At least one point in g1 has to be within g2, and at least one point in
  // g1
  // has to be disjoint from g2.
  for (auto &pt : *g1) {
    bool pt_disjoint = false;
    if (!found_disjoint) {
      pt_disjoint = disjoint(&pt, g2);
      found_disjoint = pt_disjoint;
    }
    if (!pt_disjoint && !found_within) {
      found_within = within(&pt, g2);
    }
    if (found_disjoint && found_within) break;
  }

  return found_disjoint && found_within;
}

bool Crosses::eval(const Cartesian_multipoint *g1,
                   const Cartesian_multipolygon *g2) const {
  Within within(m_semi_major, m_semi_minor);
  Disjoint disjoint(m_semi_major, m_semi_minor);
  bool found_within = false;
  bool found_disjoint = false;

  // At least one point in g1 has to be within g2, and at least one point in
  // g1
  // has to be disjoint from g2.
  for (auto &pt : *g1) {
    bool pt_disjoint = false;
    if (!found_disjoint) {
      pt_disjoint = disjoint(&pt, g2);
      found_disjoint = pt_disjoint;
    }
    if (!pt_disjoint && !found_within) {
      found_within = within(&pt, g2);
    }
    if (found_disjoint && found_within) break;
  }

  return found_disjoint && found_within;
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Cartesian_multilinestring, *)

bool Crosses::eval(const Cartesian_multilinestring *,
                   const Cartesian_point *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Cartesian_multilinestring *g1,
                   const Cartesian_linestring *g2) const {
  return bg::crosses(*g1, *g2);
}

bool Crosses::eval(const Cartesian_multilinestring *g1,
                   const Cartesian_polygon *g2) const {
  return bg::crosses(*g1, *g2);
}

bool Crosses::eval(const Cartesian_multilinestring *g1,
                   const Cartesian_geometrycollection *g2) const {
  return geometry_collection_apply_crosses<Cartesian_geometrycollection>(
      *this, g1, g2);
}

bool Crosses::eval(const Cartesian_multilinestring *,
                   const Cartesian_multipoint *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Cartesian_multilinestring *g1,
                   const Cartesian_multilinestring *g2) const {
  return bg::crosses(*g1, *g2);
}

bool Crosses::eval(const Cartesian_multilinestring *g1,
                   const Cartesian_multipolygon *g2) const {
  return bg::crosses(*g1, *g2);
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Cartesian_multipolygon, *)

bool Crosses::eval(const Cartesian_multipolygon *, const Geometry *) const {
  // If g1 is a 2d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Geographic_point, *)

bool Crosses::eval(const Geographic_point *, const Geographic_point *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Geographic_point *,
                   const Geographic_linestring *) const {
  // A point may never cross another geometry.
  return false;
}

bool Crosses::eval(const Geographic_point *, const Geographic_polygon *) const {
  // A point may never cross another geometry.
  return false;
}

bool Crosses::eval(const Geographic_point *g1,
                   const Geographic_geometrycollection *g2) const {
  // Must be evaluated in case g2 contains a single point.
  return geometry_collection_apply_crosses<Geographic_geometrycollection>(
      *this, g1, g2);
}

bool Crosses::eval(const Geographic_point *,
                   const Geographic_multipoint *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Geographic_point *,
                   const Geographic_multilinestring *) const {
  // A point may never cross another geometry.
  return false;
}

bool Crosses::eval(const Geographic_point *,
                   const Geographic_multipolygon *) const {
  // A point may never cross another geometry.
  return false;
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Geographic_linestring, *)

bool Crosses::eval(const Geographic_linestring *,
                   const Geographic_point *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Geographic_linestring *g1,
                   const Geographic_linestring *g2) const {
  return bg::crosses(*g1, *g2, m_geographic_ll_la_aa_strategy);
}

bool Crosses::eval(const Geographic_linestring *g1,
                   const Geographic_polygon *g2) const {
  return bg::crosses(*g1, *g2, m_geographic_ll_la_aa_strategy);
}

bool Crosses::eval(const Geographic_linestring *g1,
                   const Geographic_geometrycollection *g2) const {
  return geometry_collection_apply_crosses<Geographic_geometrycollection>(
      *this, g1, g2);
}

bool Crosses::eval(const Geographic_linestring *,
                   const Geographic_multipoint *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Geographic_linestring *g1,
                   const Geographic_multilinestring *g2) const {
  return bg::crosses(*g1, *g2, m_geographic_ll_la_aa_strategy);
}

bool Crosses::eval(const Geographic_linestring *g1,
                   const Geographic_multipolygon *g2) const {
  return bg::crosses(*g1, *g2, m_geographic_ll_la_aa_strategy);
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Geographic_polygon, *)

bool Crosses::eval(const Geographic_polygon *, const Geometry *) const {
  // If g1 is a 2d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Geographic_geometrycollection, *)

bool Crosses::eval(const Geographic_geometrycollection *g1,
                   const Geometry *g2) const {
  return geometry_collection_apply_crosses<Geographic_geometrycollection>(
      *this, g1, g2);
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Geographic_multipoint, *)

bool Crosses::eval(const Geographic_multipoint *,
                   const Geographic_point *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Geographic_multipoint *g1,
                   const Geographic_linestring *g2) const {
  Within within(m_semi_major, m_semi_minor);
  Disjoint disjoint(m_semi_major, m_semi_minor);
  bool found_within = false;
  bool found_disjoint = false;

  // At least one point in g1 has to be within g2, and at least one point in
  // g1
  // has to be disjoint from g2.
  for (auto &pt : *g1) {
    bool pt_disjoint = false;
    if (!found_disjoint) {
      pt_disjoint = disjoint(&pt, g2);
      found_disjoint = pt_disjoint;
    }
    if (!pt_disjoint && !found_within) {
      found_within = within(&pt, g2);
    }
    if (found_disjoint && found_within) break;
  }

  return found_disjoint && found_within;
}

bool Crosses::eval(const Geographic_multipoint *g1,
                   const Geographic_polygon *g2) const {
  Within within(m_semi_major, m_semi_minor);
  Disjoint disjoint(m_semi_major, m_semi_minor);
  bool found_within = false;
  bool found_disjoint = false;

  // At least one point in g1 has to be within g2, and at least one point in
  // g1
  // has to be disjoint from g2.
  for (auto &pt : *g1) {
    bool pt_disjoint = false;
    if (!found_disjoint) {
      pt_disjoint = disjoint(&pt, g2);
      found_disjoint = pt_disjoint;
    }
    if (!pt_disjoint && !found_within) {
      found_within = within(&pt, g2);
    }
    if (found_disjoint && found_within) break;
  }

  return found_disjoint && found_within;
}

bool Crosses::eval(const Geographic_multipoint *g1,
                   const Geographic_geometrycollection *g2) const {
  return geometry_collection_apply_crosses<Geographic_geometrycollection>(
      *this, g1, g2);
}

bool Crosses::eval(const Geographic_multipoint *,
                   const Geographic_multipoint *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Geographic_multipoint *g1,
                   const Geographic_multilinestring *g2) const {
  Within within(m_semi_major, m_semi_minor);
  Disjoint disjoint(m_semi_major, m_semi_minor);
  bool found_within = false;
  bool found_disjoint = false;

  // At least one point in g1 has to be within g2, and at least one point in
  // g1
  // has to be disjoint from g2.
  for (auto &pt : *g1) {
    bool pt_disjoint = false;
    if (!found_disjoint) {
      pt_disjoint = disjoint(&pt, g2);
      found_disjoint = pt_disjoint;
    }
    if (!pt_disjoint && !found_within) {
      found_within = within(&pt, g2);
    }
    if (found_disjoint && found_within) break;
  }

  return found_disjoint && found_within;
}

bool Crosses::eval(const Geographic_multipoint *g1,
                   const Geographic_multipolygon *g2) const {
  Within within(m_semi_major, m_semi_minor);
  Disjoint disjoint(m_semi_major, m_semi_minor);
  bool found_within = false;
  bool found_disjoint = false;

  // At least one point in g1 has to be within g2, and at least one point in
  // g1
  // has to be disjoint from g2.
  for (auto &pt : *g1) {
    bool pt_disjoint = false;
    if (!found_disjoint) {
      pt_disjoint = disjoint(&pt, g2);
      found_disjoint = pt_disjoint;
    }
    if (!pt_disjoint && !found_within) {
      found_within = within(&pt, g2);
    }
    if (found_disjoint && found_within) break;
  }

  return found_disjoint && found_within;
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Geographic_multilinestring, *)

bool Crosses::eval(const Geographic_multilinestring *,
                   const Geographic_point *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Geographic_multilinestring *g1,
                   const Geographic_linestring *g2) const {
  return bg::crosses(*g1, *g2, m_geographic_ll_la_aa_strategy);
}

bool Crosses::eval(const Geographic_multilinestring *g1,
                   const Geographic_polygon *g2) const {
  return bg::crosses(*g1, *g2, m_geographic_ll_la_aa_strategy);
}

bool Crosses::eval(const Geographic_multilinestring *g1,
                   const Geographic_geometrycollection *g2) const {
  return geometry_collection_apply_crosses<Geographic_geometrycollection>(
      *this, g1, g2);
}

bool Crosses::eval(const Geographic_multilinestring *,
                   const Geographic_multipoint *) const {
  // If g2 is a 0d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

bool Crosses::eval(const Geographic_multilinestring *g1,
                   const Geographic_multilinestring *g2) const {
  return bg::crosses(*g1, *g2, m_geographic_ll_la_aa_strategy);
}

bool Crosses::eval(const Geographic_multilinestring *g1,
                   const Geographic_multipolygon *g2) const {
  return bg::crosses(*g1, *g2, m_geographic_ll_la_aa_strategy);
}

//////////////////////////////////////////////////////////////////////////////

// crosses(Geographic_multipolygon, *)

bool Crosses::eval(const Geographic_multipolygon *, const Geometry *) const {
  // If g1 is a 2d geometry, return NULL (SQL/MM 2015, Sect. 5.1.51).
  throw null_value_exception();
}

//////////////////////////////////////////////////////////////////////////////

bool crosses(const dd::Spatial_reference_system *srs, const Geometry *g1,
             const Geometry *g2, const char *func_name, bool *crosses,
             bool *null) noexcept {
  try {
    DBUG_ASSERT(g1->coordinate_system() == g2->coordinate_system());
    DBUG_ASSERT(srs == nullptr ||
                ((srs->is_cartesian() &&
                  g1->coordinate_system() == Coordinate_system::kCartesian) ||
                 (srs->is_geographic() &&
                  g1->coordinate_system() == Coordinate_system::kGeographic)));

    if ((*null = (g1->is_empty() || g2->is_empty()))) return false;

    Crosses crosses_func(srs ? srs->semi_major_axis() : 0.0,
                         srs ? srs->semi_minor_axis() : 0.0);
    *crosses = crosses_func(g1, g2);
  } catch (const null_value_exception &) {
    *null = true;
    return false;
  } catch (...) {
    handle_gis_exception(func_name);
    return true;
  }

  return false;
}

}  // namespace gis


#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstring>
#include <getopt.h>

using namespace std;

#include "rate_model.h"
#include "tree.h"
#include "tree_reader.h"
#include "state_reconstructor_simple.h"
#include "seq_reader.h"
#include "sequence.h"
#include "seq_utils.h"
#include "utils.h"
#include "mcmc.h"
#include "log.h"

#include <armadillo>
using namespace arma;

void print_help() {
    cout << "Calculate Selection Model 0 with K and w with seqs and tree." << endl;
    cout << "Can read from stdin or file." << endl;
    cout << endl;
    cout << "Usage: pxsm2a [OPTION]... [FILE]..." << endl;
    cout << endl;
    cout << " -s, --seqf=FILE     input sequence file" << endl;
    cout << " -t, --treef=FILE    input tree file" << endl;
    cout << " -o, --outf=FILE     output sequence file, stout otherwise" << endl;
    cout << " -h, --help          display this help and exit" << endl;
    cout << " -V, --version       display version and exit" << endl;
    cout << endl;
    cout << "Report bugs to: <https://github.com/FePhyFoFum/phyx/issues>" << endl;
    cout << "phyx home page: <https://github.com/FePhyFoFum/phyx>" << endl;
}

string versionline("pxsm2a 0.1\nCopyright (C) 2014 FePhyFoFum\nLicense GPLv3\nwritten by Stephen A. Smith (blackrim)");

static struct option const long_options[] =
{
    {"seqf", required_argument, NULL, 's'},
    {"treef", required_argument, NULL, 't'},
    {"outf", required_argument, NULL, 'o'},
    {"help", no_argument, NULL, 'h'},
    {"version", no_argument, NULL, 'V'},
    {NULL, 0, NULL, 0}
};

int main(int argc, char * argv[]) {

    log_call(argc, argv);

    bool sfileset = false;
    bool tfileset = false;
    bool outfileset = false;
    char * seqf = NULL;
    char * treef = NULL;
    char * outf = NULL;
    while (1) {
        int oi = -1;
        int c = getopt_long(argc, argv, "s:t:o:hV", long_options, &oi);
        if (c == -1) {
            break;
        }
        switch(c) {
            case 's':
                sfileset = true;
                seqf = strdup(optarg);
                check_file_exists(seqf);
                break;
            case 't':
                tfileset = true;
                treef = strdup(optarg);
                check_file_exists(treef);
                break;
            case 'o':
                outfileset = true;
                outf = strdup(optarg);
                break;
            case 'h':
                print_help();
                exit(0);
            case 'V':
                cout << versionline << endl;
                exit(0);
            default:
                print_error(argv[0], (char)c);
                exit(0);
        }
    }
    istream * spios = NULL;
    istream * tpios = NULL;
    ostream * poos = NULL;
    ifstream * sfstr = NULL;
    ifstream * tfstr = NULL;
    ofstream * ofstr = NULL;

    if (sfileset == true) {
        sfstr = new ifstream(seqf);
        spios = sfstr;
    }
    if (tfileset == true) {
        tfstr = new ifstream(treef);
        tpios = tfstr;
    }
    if (tfileset == false || sfileset == false) {
        cout << "you have to set the tree and seq files" << endl;
        exit(0);
    }
    if (outfileset == true) {
        ofstr = new ofstream(outf);
        poos = ofstr;
    } else {
        poos = &cout;
    }
    //read seqs
    vector<Sequence> seqs;
    vector<Sequence> sr_seqs;
    Sequence seq;
    string retstring;
    int ft = test_seq_filetype_stream(*spios,retstring);
    while (read_next_seq_from_stream(*spios,ft,retstring,seq)) {
        (*poos) << seq.get_fasta();
        seqs.push_back(seq);
        Sequence tseq(seq.get_id(),"");
        sr_seqs.push_back(tseq);
    }
    if (ft == 2) {
        (*poos) << seq.get_fasta();
        seqs.push_back(seq);
        Sequence tseq(seq.get_id(),"");
        sr_seqs.push_back(tseq);
    }

    //read trees
    ft = test_tree_filetype_stream(*tpios, retstring);
    if (ft != 1) {
        cerr << "this really only works with newick" << endl;
        exit(0);
    }
    bool going = true;
    Tree * tree;
    if (ft == 1) {
        while (going) {
            tree = read_next_tree_from_stream_newick(*tpios, retstring, &going);
            break;
        }
    }

    map<string,string> codon_dict;
    vector<string> codon_list;
    map<string,vector<int> > codon_pos;
    populate_codon_list(&codon_list);
    populate_map_codon_dict(&codon_dict);
    populate_map_codon_indices(&codon_pos);

    mat bf(61,61);
    mat K(61,61);
    mat w(61,61);
    mat inq0(61,61);
    mat inq1(61,61);
    mat inq2(61,61);
    generate_bigpibf_K_w(&bf,&K,&w,codon_dict,codon_pos,codon_list);
    update_simple_goldman_yang_q(&inq0,1.36714,0.001,bf,K,w);
    update_simple_goldman_yang_q(&inq1,1.36714,1.0,bf,K,w);
    update_simple_goldman_yang_q(&inq2,1.36714,8.21486,bf,K,w);
//    cout << inq << endl;

    RateModel rm(61);
    rm.selection_model = 2;
    rm.set_n_qs(3);
    rm.set_Q_which(inq0,0);
    rm.set_Q_which(inq1,1);
    rm.set_Q_which(inq2,2);
    //rm.set_Q(inq);
//    cout << rm.get_Q() << endl;
    int sites = (seqs[0].get_sequence().size()/3);
    StateReconstructorSimple sr(rm,sites);
    sr.set_tree(tree);
    cout << "there are " << sites << " sites" << endl;
    sm2a_mcmc(0,10,tree,sr,rm,seqs,sr_seqs,codon_pos,bf,K,w,inq0,inq1,inq2);

    sfstr->close();
    delete spios;
    tfstr->close();
    delete tpios;

    if (outfileset) {
        ofstr->close();
        delete poos;
    }
    return EXIT_SUCCESS;
}


/*****************************************************************************
*
* Copyright (c) 2003-2020 by The University of Queensland
* http://www.uq.edu.au
*
* Primary Business: Queensland, Australia
* Licensed under the Apache License, version 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Development until 2012 by Earth Systems Science Computational Center (ESSCC)
* Development 2012-2013 by School of Earth Sciences
* Development from 2014-2017 by Centre for Geoscience Computing (GeoComp)
* Development from 2019 by School of Earth and Environmental Sciences
**
*****************************************************************************/

#include "SolverOptions.h"
#include "EsysException.h"

#include <boost/python.hpp>

#ifdef ESYS_HAVE_TRILINOS
#include <Amesos2.hpp>
#endif

namespace bp = boost::python;

template <class R>
bool convert(bp::object bpo, R& result)
{
    bool b = bp::extract<R>(bpo).check();
    if (b)
        result = bp::extract<R>(bpo);
    return b;
}

namespace escript {

SolverBuddy::SolverBuddy() :
    target(SO_TARGET_CPU),
    package(SO_DEFAULT),
    method(SO_DEFAULT),
    preconditioner(SO_PRECONDITIONER_JACOBI),
    ode_solver(SO_ODESOLVER_LINEAR_CRANK_NICOLSON),
    reordering(SO_REORDERING_DEFAULT),
    sweeps(1),
    tolerance(1e-8),
    absolute_tolerance(0.),
    inner_tolerance(0.9),
    drop_tolerance(0.0005),
    drop_storage(2.),
    iter_max(100000),
    inner_iter_max(10),
    truncation(20),
    restart(0),
    is_complex(false),
    symmetric(false),
    hermitian(false),
    verbose(false),
    adapt_inner_tolerance(true),
    accept_convergence_failure(false),
    relaxation(0.3),
    use_local_preconditioner(false),
    refinements(2),
    dim(2),
    using_default_solver_method(false)
{
    // setPackage(SO_DEFAULT);
    // setSolverMethod(SO_DEFAULT);
    resetDiagnostics(true);
}

SolverBuddy::~SolverBuddy()
{
}

std::string SolverBuddy::getSummary() const
{
    std::stringstream out;
    out << "Solver Package = " << getName(getPackage()) << std::endl
        << "Verbosity = " << isVerbose() << std::endl
        << "Accept failed convergence = " << acceptConvergenceFailure() << std::endl
        << "Relative tolerance = " << getTolerance() << std::endl
        << "Absolute tolerance = " << getAbsoluteTolerance() << std::endl
        << "Symmetric problem = " << isSymmetric() << std::endl
        << "Hermitian problem = " << isHermitian() << std::endl
        << "Maximum number of iteration steps = " << getIterMax() << std::endl
        << "Inner tolerance = " << getInnerTolerance() << std::endl
        << "Adapt innner tolerance = " << adaptInnerTolerance() << std::endl;

    if (getPackage() == SO_DEFAULT || getPackage() == SO_PACKAGE_PASO ||
            getPackage() == SO_PACKAGE_TRILINOS) {
        out << "Solver method = " << getName(getSolverMethod()) << std::endl;
        if (getSolverMethod() == SO_METHOD_GMRES) {
            out << "Truncation  = " << getTruncation() << std::endl
                << "Restart  = " << getRestart() << std::endl;
        }
        out << "Preconditioner = " << getName(getPreconditioner()) << std::endl
            << "Apply preconditioner locally = " << useLocalPreconditioner()
            << std::endl;
        switch (getPreconditioner()) {
            case SO_PRECONDITIONER_GAUSS_SEIDEL:
                out << "Number of sweeps = " << getNumSweeps() << std::endl;
                break;
            case SO_PRECONDITIONER_ILUT:
                out << "Drop tolerance = " << getDropTolerance() << std::endl;
                out << "Storage increase = " << getDropStorage() << std::endl;
                break;
            case SO_PRECONDITIONER_RILU:
                out << "Relaxation factor = " << getRelaxationFactor()
                    << std::endl;
                break;
            default:
                break;
        } // preconditioner switch
        out << "ODE solver = " << getName(getODESolver()) << std::endl;
    }
    return out.str();
}

const char* SolverBuddy::getName(int key) const
{
    switch (static_cast<SolverOptions>(key)) {
        case SO_DEFAULT: return "DEFAULT";
        case SO_TARGET_CPU: return "CPU";
        case SO_TARGET_GPU: return "GPU";

        case SO_PACKAGE_MKL: return "MKL";
        case SO_PACKAGE_PASO: return "PASO";
        case SO_PACKAGE_TRILINOS: return "TRILINOS";
        case SO_PACKAGE_UMFPACK: return "UMFPACK";
        case SO_PACKAGE_MUMPS: return "MUMPS";

        case SO_METHOD_BICGSTAB: return "BICGSTAB";
        case SO_METHOD_CGLS: return "CGLS";
        case SO_METHOD_CGS: return "CGS";
        case SO_METHOD_CHOLEVSKY: return "CHOLEVSKY";
        case SO_METHOD_CR: return "CR";
        case SO_METHOD_DIRECT: return "DIRECT";
        case SO_METHOD_DIRECT_MUMPS: return "DIRECT_MUMPS";
        case SO_METHOD_DIRECT_PARDISO: return "DIRECT_PARDISO";
        case SO_METHOD_DIRECT_SUPERLU: return "DIRECT_SUPERLU";
        case SO_METHOD_DIRECT_TRILINOS: return "DIRECT_TRILINOS";
        case SO_METHOD_GMRES: return "GMRES";
        case SO_METHOD_HRZ_LUMPING: return "HRZ_LUMPING";
        case SO_METHOD_ITERATIVE: return "ITERATIVE";
        case SO_METHOD_LSQR: return "LSQR";
        case SO_METHOD_MINRES: return "MINRES";
        case SO_METHOD_NONLINEAR_GMRES: return "NONLINEAR_GMRES";
        case SO_METHOD_PCG: return "PCG";
        case SO_METHOD_PRES20: return "PRES20";
        case SO_METHOD_ROWSUM_LUMPING: return "ROWSUM_LUMPING";
        case SO_METHOD_TFQMR: return "TFQMR";

        case SO_PRECONDITIONER_AMG: return "AMG";
        case SO_PRECONDITIONER_GAUSS_SEIDEL: return "GAUSS_SEIDEL";
        case SO_PRECONDITIONER_ILU0: return "ILU0";
        case SO_PRECONDITIONER_ILUT: return "ILUT";
        case SO_PRECONDITIONER_JACOBI: return "JACOBI";
        case SO_PRECONDITIONER_NONE: return "NO_PRECONDITIONER";
        case SO_PRECONDITIONER_REC_ILU: return "REC_ILU";
        case SO_PRECONDITIONER_RILU: return "RILU";

        case SO_ODESOLVER_BACKWARD_EULER: return "BACKWARD_EULER";
        case SO_ODESOLVER_CRANK_NICOLSON: return "CRANK_NICOLSON";
        case SO_ODESOLVER_LINEAR_CRANK_NICOLSON: return "LINEAR_CRANK_NICOLSON";

        case SO_INTERPOLATION_CLASSIC: return "CLASSIC_INTERPOLATION";
        case SO_INTERPOLATION_CLASSIC_WITH_FF_COUPLING:
            return "CLASSIC_INTERPOLATION_WITH_FF";
        case SO_INTERPOLATION_DIRECT: return "DIRECT_INTERPOLATION";

        case SO_REORDERING_DEFAULT: return "DEFAULT_REORDERING";
        case SO_REORDERING_MINIMUM_FILL_IN: return "MINIMUM_FILL_IN";
        case SO_REORDERING_NESTED_DISSECTION: return "NESTED_DISSECTION";
        case SO_REORDERING_NONE: return "NO_REORDERING";
        default:
            throw ValueError("getName() invalid option given");
    }
    return "invalid option";
}

void SolverBuddy::resetDiagnostics(bool all)
{
    num_iter = 0;
    num_level = 0;
    num_inner_iter = 0;
    time = 0.;
    set_up_time = 0.;
    net_time = 0.;
    residual_norm = 0.;
    converged = false;
    preconditioner_size = -1;
    time_step_backtracking_used = false;
    coarse_level_sparsity = 0;
    num_coarse_unknowns = 0;
    if (all) {
        cum_num_inner_iter = 0;
        cum_num_iter = 0;
        cum_time = 0.;
        cum_set_up_time = 0.;
        cum_net_time = 0.;
    }
}

void SolverBuddy::updateDiagnostics(const std::string& name, bool value)
{
    if (name == "converged") {
        converged = value;
    } else if (name == "time_step_backtracking_used") {
        time_step_backtracking_used = value;
    } else {
        throw ValueError(std::string("Unknown diagnostic: ") + name);
    }
}

void SolverBuddy::updateDiagnostics(const std::string& name, int value)
{
    if (name == "num_iter") {
        cum_num_iter += num_iter = value;
    } else if (name == "num_level") {
        num_level = value;
    } else if (name == "num_inner_iter") {
        cum_num_inner_iter += num_inner_iter = value;
    } else if (name == "num_coarse_unknowns") {
        num_coarse_unknowns = value;
    } else {
        throw ValueError(std::string("Unknown diagnostic: ") + name);
    }
}

void SolverBuddy::updateDiagnostics(const std::string& name, double value)
{
    if (name == "time") {
        cum_time += time = value;
    } else if (name == "set_up_time") {
        cum_set_up_time += set_up_time = value;
    } else if (name == "net_time") {
        cum_net_time += net_time = value;
    } else if (name == "residual_norm") {
        residual_norm = value;
    } else if (name == "coarse_level_sparsity") {
        coarse_level_sparsity = value;
    } else {
        throw ValueError(std::string("Unknown diagnostic: ") + name);
    }
}

void SolverBuddy::updateDiagnosticsPy(const std::string& name,
                                      const bp::object& value)
{
    int i=0;
    double d=0; // to keep older compilers happy
    bool b=false;
    bool ib = convert<int>(value, i);
    bool db = convert<double>(value, d);
    bool bb = convert<bool>(value, b);

    if (name == "num_iter") {
        if (!ib)
            throw ValueError("setting num_iter to non-int value");
        cum_num_iter += num_iter = i;
    } else if (name == "num_level") {
        if (!ib)
            throw ValueError("setting num_level to non-int value");
        num_level = i;
    } else if (name == "num_inner_iter") {
        if (!ib)
            throw ValueError("setting num_inner_iter to non-int value");
        cum_num_inner_iter += num_inner_iter = i;
    } else if (name == "time") {
        if (!db)
            throw ValueError("setting time to non-double value");
        cum_time += time = d;
    } else if (name == "set_up_time") {
        if (!db)
            throw ValueError("setting set_up_time to non-double value");
        cum_set_up_time += set_up_time = d;
    } else if (name == "net_time") {
        if (!db)
            throw ValueError("setting net_time to non-double value");
        cum_net_time += net_time = d;
    } else if (name == "residual_norm") {
        if (!db)
            throw ValueError("setting residual_norm to non-double value");
        residual_norm = d;
    } else if (name == "converged") {
        if (!bb)
            throw ValueError("setting converged to non-bool value");
        converged = b;
    } else if (name == "time_step_backtracking_used") {
        if (!bb)
            throw ValueError("setting time_step_backtracking_used to non-bool value");
        time_step_backtracking_used = b;
    } else if (name == "coarse_level_sparsity") {
        if (!db)
            throw ValueError("setting coarse_level_sparsity to non-double value");
        coarse_level_sparsity = d;
    } else if (name == "num_coarse_unknowns") {
        if (!ib)
            throw ValueError("setting num_coarse_unknowns to non-int value");
        num_coarse_unknowns = i;
    } else {
        throw ValueError(std::string("Unknown diagnostic: ") + name);
    }
}

double SolverBuddy::getDiagnostics(const std::string name) const
{
    if (name == "num_iter") return num_iter;
    else if (name == "cum_num_iter") return cum_num_iter;
    else if (name == "num_inner_iter") return num_inner_iter;
    else if (name == "cum_num_inner_iter") return cum_num_inner_iter;
    else if (name == "time") return time;
    else if (name == "cum_time") return cum_time;
    else if (name == "set_up_time") return set_up_time;
    else if (name == "cum_set_up_time") return cum_set_up_time;
    else if (name == "net_time") return net_time;
    else if (name == "cum_net_time") return cum_net_time;
    else if (name == "residual_norm") return residual_norm;
    else if (name == "converged") return converged;
    else if (name == "preconditioner_size") return preconditioner_size;
    else if (name == "time_step_backtracking_used")
        return  time_step_backtracking_used;
    throw ValueError(std::string("unknown diagnostic item: ") + name);
}

bool SolverBuddy::hasConverged() const
{
    return converged;
}

void SolverBuddy::setPreconditioner(int precon)
{
    SolverOptions preconditioner = static_cast<SolverOptions>(precon);
    switch(preconditioner) {
        case SO_PRECONDITIONER_AMG:
#if !defined(ESYS_HAVE_TRILINOS) && !defined(ESYS_HAVE_MUMPS)
        throw ValueError("escript was not compiled with Trilinos or MUMPS enabled");
#endif
        case SO_PRECONDITIONER_GAUSS_SEIDEL:
        case SO_PRECONDITIONER_JACOBI: // This is the default preconditioner in ifpack2
        case SO_PRECONDITIONER_ILU0:
        case SO_PRECONDITIONER_ILUT:
        case SO_PRECONDITIONER_NONE:
        case SO_PRECONDITIONER_REC_ILU:
        case SO_PRECONDITIONER_RILU:
            this->preconditioner = preconditioner;
            break;
        default:
            throw ValueError("unknown preconditioner");
    }
}

SolverOptions SolverBuddy::getPreconditioner() const
{
    return preconditioner;
}

void SolverBuddy::setSolverMethod(int method)
{
    SolverOptions meth = static_cast<SolverOptions>(method);

//     bool havePASODirect = false;
//     using_default_solver_method=false;
// #if defined(ESYS_HAVE_PASO) && (defined(ESYS_HAVE_MKL) || defined(ESYS_HAVE_UMFPACK) || defined(ESYS_HAVE_MUMPS))
//     havePASODirect = true;
// #endif

    switch(meth) {
        case SO_DEFAULT:
        case SO_METHOD_ITERATIVE:
        case SO_METHOD_BICGSTAB:
        case SO_METHOD_CGLS:
        case SO_METHOD_CGS:
        case SO_METHOD_CHOLEVSKY:
        case SO_METHOD_CR:
        case SO_METHOD_GMRES:
        case SO_METHOD_HRZ_LUMPING:
        case SO_METHOD_LSQR:
        case SO_METHOD_MINRES:
        case SO_METHOD_NONLINEAR_GMRES:
        case SO_METHOD_PCG:
        case SO_METHOD_PRES20:
        case SO_METHOD_ROWSUM_LUMPING:
        case SO_METHOD_TFQMR:
            this->method = meth;
            break;
        case SO_METHOD_DIRECT:
#if defined(ESYS_HAVE_UMFPACK) || defined(ESYS_HAVE_TRILINOS) || defined(ESYS_HAVE_MKL) || defined(ESYS_HAVE_MUMPS)
#ifdef ESYS_HAVE_TRILINOS
            // translate specific direct solver setting to generic one for PASO
            this->method = meth;
#else
            this->method = SO_METHOD_DIRECT;
#endif
            break;
#else
            throw ValueError("Cannot use DIRECT solver method, the running "
                    "escript was not compiled with a direct solver enabled");
#endif
        case SO_METHOD_DIRECT_TRILINOS:
#ifdef ESYS_HAVE_TRILINOS
            this->method = meth;
            break;
#else
            throw ValueError("escript was not compiled with Trilinos");
#endif
        case SO_METHOD_DIRECT_MUMPS:
#ifdef ESYS_HAVE_MUMPS
            this->method=meth;
#else
            throw ValueError("escript was not compiled with MUMPS");
#endif
        case SO_METHOD_DIRECT_PARDISO:
#ifdef ESYS_HAVE_TRILINOS
            if(Amesos2::query("pardiso_mkl"))
                this->method = meth;
            else
                throw ValueError("Trilinos was not compiled with MKL Pardiso");
#else
            throw ValueError("escript was not compiled with Trilinos");
#endif
        case SO_METHOD_DIRECT_SUPERLU:
#ifdef ESYS_HAVE_TRILINOS
            if(Amesos2::query("superludist") || Amesos2::query("superlu") || Amesos2::query("superlumt"))
                this->method = meth;
            else
                throw ValueError("Trilinos was not compiled with SuperLU ");
#else
            throw ValueError("escript was not compiled with Trilinos");
#endif
        default:
            throw ValueError("unknown solver method");
    }
}

SolverOptions SolverBuddy::getSolverMethod() const
{
    return method;
}

void SolverBuddy::setPackage(int package)
{
    SolverOptions pack = static_cast<SolverOptions>(package);
    switch (pack) {
        case SO_DEFAULT:
        // Default to trilinos if it is available, else use PASO
        // This should always work because escript cannot be compiled
        // unless at least one of these is available
#ifdef ESYS_HAVE_TRILINOS
            this->package = SO_PACKAGE_TRILINOS;
            setSolverMethod(getSolverMethod());
            break;
#else
            this->package = SO_PACKAGE_PASO;
            setSolverMethod(getSolverMethod());
            break;
#endif
        // Set to PASO iff escript was compiled with PASO
        case SO_PACKAGE_PASO:
#ifdef ESYS_HAVE_PASO
            this->package = SO_PACKAGE_PASO;
            setSolverMethod(getSolverMethod());
            break;
#else
            throw ValueError("escript was not compiled with PASO enabled");
#endif
        // Set to Trilinos iff escript was compiled with Trilinos
        case SO_PACKAGE_TRILINOS:
#ifdef ESYS_HAVE_TRILINOS
            this->package = SO_PACKAGE_TRILINOS;
            setSolverMethod(getSolverMethod());
            break;
#else
            throw ValueError("escript was not compiled with Trilinos enabled");
#endif
        // Set to MKL iff escript was compiled with MKL
        case SO_PACKAGE_MKL:
#ifdef ESYS_HAVE_MKL
            this->package = SO_PACKAGE_MKL;
            setSolverMethod(getSolverMethod());
            break;
#else
            throw ValueError("escript was not compiled with MKL enabled");
#endif
        // Set to Umfpack iff escript was compiled with Umfpack
        case SO_PACKAGE_UMFPACK:
#ifdef ESYS_HAVE_UMFPACK
            this->package = SO_PACKAGE_UMFPACK;
            setSolverMethod(getSolverMethod());
            break;
#else
            throw ValueError("escript was not compiled with UMFPACK enabled");
#endif
        // Set to MUMPS iff escript was compiled with MUMPS
        case SO_PACKAGE_MUMPS:
#ifdef ESYS_HAVE_MUMPS
            this->package = SO_PACKAGE_MUMPS;
            setSolverMethod(getSolverMethod());
            break;
#else
            throw ValueError("escript was not compiled with MUMPS enabled");
#endif
        default:
            throw ValueError("unknown solver package");
    }
}

SolverOptions SolverBuddy::getPackage() const
{
    return package;
}

void SolverBuddy::setReordering(int ordering)
{
    SolverOptions ord = static_cast<SolverOptions>(ordering);
    switch (ordering) {
        case SO_REORDERING_DEFAULT:
        case SO_REORDERING_MINIMUM_FILL_IN:
        case SO_REORDERING_NESTED_DISSECTION:
        case SO_REORDERING_NONE:
            reordering = ord;
            break;
        default:
            throw ValueError("unknown reordering strategy");
    }
}

SolverOptions SolverBuddy::getReordering() const
{
    return reordering;
}

void SolverBuddy::setRestart(int restart)
{
    if (restart < 0)
        throw ValueError("restart must be non-negative.");

    this->restart = restart;
}

int SolverBuddy::getRestart() const
{
    return restart;
}

int SolverBuddy::_getRestartForC() const
{
    int r = getRestart();
    if (r == 0)
        return -1;
    return r;
}

void SolverBuddy::setTruncation(int truncation)
{
    if (truncation < 1)
        throw ValueError("truncation must be positive.");
    this->truncation = truncation;
}

int SolverBuddy::getTruncation() const
{
    return truncation;
}

void SolverBuddy::setInnerIterMax(int iter_max)
{
    if (iter_max < 1)
        throw ValueError("maximum number of inner iteration must be positive.");
    inner_iter_max = iter_max;
}

int SolverBuddy::getInnerIterMax() const
{
    return inner_iter_max;
}

void SolverBuddy::setIterMax(int iter_max)
{
    if (iter_max < 1)
        throw ValueError("maximum number of iteration steps must be positive.");
    this->iter_max = iter_max;
}

int SolverBuddy::getIterMax() const
{
    return iter_max;
}

void SolverBuddy::setNumSweeps(int sweeps)
{
    if (sweeps < 1)
        throw ValueError("number of sweeps must be positive.");
    this->sweeps = sweeps;
}

int SolverBuddy::getNumSweeps() const
{
    return sweeps;
}

void SolverBuddy::setTolerance(double rtol)
{
    if (rtol < 0. || rtol > 1.)
        throw ValueError("tolerance must be between 0 and 1.");
    tolerance = rtol;
}

double SolverBuddy::getTolerance() const
{
    return tolerance;
}

void SolverBuddy::setAbsoluteTolerance(double atol)
{
    if (atol < 0.)
       throw ValueError("absolute tolerance must be non-negative.");
    absolute_tolerance = atol;
}

double SolverBuddy::getAbsoluteTolerance() const
{
    return absolute_tolerance;
}

void SolverBuddy::setInnerTolerance(double rtol)
{
    if (rtol <= 0. || rtol > 1.)
        throw ValueError("tolerance must be positive and less than or equal to 1.");
    inner_tolerance = rtol;
}

double SolverBuddy::getInnerTolerance() const
{
    return inner_tolerance;
}

void SolverBuddy::setDropTolerance(double drop_tol)
{
    if (drop_tol < 0. || drop_tol > 1.)
        throw ValueError("drop tolerance must be between 0 and 1.");
    drop_tolerance = drop_tol;
}

double SolverBuddy::getDropTolerance() const
{
    return drop_tolerance;
}

void SolverBuddy::setDropStorage(double storage)
{
    if (storage < 1.)
        throw ValueError("allowed storage increase must be greater than or equal to 1.");
    drop_storage = storage;
}

double SolverBuddy::getDropStorage() const
{
    return drop_storage;
}

void SolverBuddy::setRelaxationFactor(double factor)
{
    if (factor < 0.)
        throw ValueError("relaxation factor must be non-negative.");
    relaxation = factor;
}

double SolverBuddy::getRelaxationFactor() const
{
    return relaxation;
}

bool SolverBuddy::isComplex() const
{
    return is_complex;
}

void SolverBuddy::setComplex(bool flag)
{
    is_complex = flag;
}

bool SolverBuddy::isSymmetric() const
{
    return symmetric;
}

void SolverBuddy::setSymmetryOn()
{
    symmetric = true;
}

void SolverBuddy::setSymmetryOff()
{
    symmetric = false;
}

void SolverBuddy::setSymmetry(bool flag)
{
    if (flag)
        setSymmetryOn();
    else
        setSymmetryOff();
}

bool SolverBuddy::isHermitian() const
{
    return hermitian;
}

void SolverBuddy::setHermitianOn()
{
    hermitian = true;
}

void SolverBuddy::setHermitianOff()
{
    hermitian = false;
}

void SolverBuddy::setHermitian(bool flag)
{
    if (flag)
        setHermitianOn();
    else
        setHermitianOff();
}

bool SolverBuddy::isVerbose() const
{
    return verbose;
}

void SolverBuddy::setVerbosityOn()
{
    verbose = true;
}

void SolverBuddy::setVerbosityOff()
{
    verbose = false;
}

void SolverBuddy::setVerbosity(bool verbose)
{
    if (verbose)
        setVerbosityOn();
    else
        setVerbosityOff();
}

bool SolverBuddy::adaptInnerTolerance() const
{
    return adapt_inner_tolerance;
}

void SolverBuddy::setInnerToleranceAdaptionOn()
{
    adapt_inner_tolerance = true;
}

void SolverBuddy::setInnerToleranceAdaptionOff()
{
    adapt_inner_tolerance = false;
}

void SolverBuddy::setInnerToleranceAdaption(bool adapt)
{
    if (adapt)
        setInnerToleranceAdaptionOn();
    else
        setInnerToleranceAdaptionOff();
}

bool SolverBuddy::acceptConvergenceFailure() const
{
    return accept_convergence_failure;
}

void SolverBuddy::setAcceptanceConvergenceFailureOn()
{
    accept_convergence_failure = true;
}

void SolverBuddy::setAcceptanceConvergenceFailureOff()
{
    accept_convergence_failure = false;
}

void SolverBuddy::setAcceptanceConvergenceFailure(bool accept)
{
    if (accept)
        setAcceptanceConvergenceFailureOn();
    else
        setAcceptanceConvergenceFailureOff();
}

bool SolverBuddy::useLocalPreconditioner() const
{
    return use_local_preconditioner;
}

void SolverBuddy::setLocalPreconditionerOn()
{
    use_local_preconditioner = true;
}

void SolverBuddy::setLocalPreconditionerOff()
{
    use_local_preconditioner=false;
}

void SolverBuddy::setLocalPreconditioner(bool use)
{
    if (use)
        setLocalPreconditionerOn();
    else
        setLocalPreconditionerOff();
}

void SolverBuddy::setNumRefinements(int refinements)
{
    if (refinements < 0)
        throw ValueError("number of refinements must be non-negative.");
    this->refinements = refinements;
}

int SolverBuddy::getNumRefinements() const
{
    return refinements;
}

void SolverBuddy::setODESolver(int method)
{
    SolverOptions ode = static_cast<SolverOptions>(method);
    switch (ode) {
        case SO_ODESOLVER_BACKWARD_EULER:
        case SO_ODESOLVER_CRANK_NICOLSON:
        case SO_ODESOLVER_LINEAR_CRANK_NICOLSON:
            ode_solver = ode;
            break;
        default:
            throw ValueError("unknown ODE solver method");
    }
}

SolverOptions SolverBuddy::getODESolver() const
{
    return ode_solver;
}

void SolverBuddy::setTrilinosParameter(const std::string& name,
                                       const bp::object& value)
{
#ifdef ESYS_HAVE_TRILINOS
    trilinosParams[name] = value;
#endif
}

bp::dict SolverBuddy::getTrilinosParameters() const
{
    return trilinosParams;
}

void SolverBuddy::setDim(int dim)
{
    if (dim != 2 && dim != 3)
        throw ValueError("Dimension must be either 2 or 3.");
    this->dim = dim;
}

int SolverBuddy::getDim()
{
    return dim;
}

bool SolverBuddy::using_default_method() const
{
    return using_default_solver_method;
}

} // namespace escript


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2013, 2014, 2015.
// Modifications copyright (c) 2013-2015 Oracle and/or its affiliates.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GET_TURN_INFO_LL_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GET_TURN_INFO_LL_HPP

#include <boost/geometry/core/assert.hpp>

#include <boost/geometry/algorithms/detail/overlay/get_turn_info.hpp>
#include <boost/geometry/algorithms/detail/overlay/get_turn_info_for_endpoint.hpp>

#include <boost/geometry/util/condition.hpp>

namespace geofeatures_boost {} namespace boost = geofeatures_boost; namespace geofeatures_boost { namespace geometry {

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace overlay {

template<typename AssignPolicy>
struct get_turn_info_linear_linear
{
    static const bool handle_spikes = true;

    template
    <
        typename Point1,
        typename Point2,
        typename TurnInfo,
        typename RobustPolicy,
        typename OutputIterator
    >
    static inline OutputIterator apply(
                Point1 const& pi, Point1 const& pj, Point1 const& pk,
                Point2 const& qi, Point2 const& qj, Point2 const& qk,
                bool is_p_first, bool is_p_last,
                bool is_q_first, bool is_q_last,
                TurnInfo const& tp_model,
                RobustPolicy const& robust_policy,
                OutputIterator out)
    {
        typedef intersection_info<Point1, Point2, typename TurnInfo::point_type, RobustPolicy>
            inters_info;

        inters_info inters(pi, pj, pk, qi, qj, qk, robust_policy);

        char const method = inters.d_info().how;

        // Copy, to copy possibly extended fields
        TurnInfo tp = tp_model;

        // Select method and apply
        switch(method)
        {
            case 'a' : // collinear, "at"
            case 'f' : // collinear, "from"
            case 's' : // starts from the middle
                get_turn_info_for_endpoint<AssignPolicy, true, true>
                    ::apply(pi, pj, pk, qi, qj, qk,
                            is_p_first, is_p_last, is_q_first, is_q_last,
                            tp_model, inters, method_none, out);
                break;

            case 'd' : // disjoint: never do anything
                break;

            case 'm' :
            {
                if ( get_turn_info_for_endpoint<AssignPolicy, false, true>
                        ::apply(pi, pj, pk, qi, qj, qk,
                                is_p_first, is_p_last, is_q_first, is_q_last,
                                tp_model, inters, method_touch_interior, out) )
                {
                    // do nothing
                }
                else
                {
                    typedef touch_interior
                        <
                            TurnInfo
                        > policy;

                    // If Q (1) arrives (1)
                    if ( inters.d_info().arrival[1] == 1)
                    {
                        policy::template apply<0>(pi, pj, pk, qi, qj, qk,
                                                  tp, inters.i_info(), inters.d_info(),
                                                  inters.sides());
                    }
                    else
                    {
                        // Swap p/q
                        side_calculator
                            <
                                typename inters_info::robust_point2_type,
                                typename inters_info::robust_point1_type
                            > swapped_side_calc(inters.rqi(), inters.rqj(), inters.rqk(),
                                                inters.rpi(), inters.rpj(), inters.rpk());

                        policy::template apply<1>(qi, qj, qk, pi, pj, pk,
                                                  tp, inters.i_info(), inters.d_info(),
                                                  swapped_side_calc);
                    }

                    if ( tp.operations[0].operation == operation_blocked )
                    {
                        tp.operations[1].is_collinear = true;
                    }
                    if ( tp.operations[1].operation == operation_blocked )
                    {
                        tp.operations[0].is_collinear = true;
                    }

                    replace_method_and_operations_tm(tp.method,
                                                     tp.operations[0].operation,
                                                     tp.operations[1].operation);

                    AssignPolicy::apply(tp, pi, qi, inters);
                    *out++ = tp;
                }
            }
            break;
            case 'i' :
            {
                crosses<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                                         tp, inters.i_info(), inters.d_info());

                replace_operations_i(tp.operations[0].operation, tp.operations[1].operation);

                AssignPolicy::apply(tp, pi, qi, inters);
                *out++ = tp;
            }
            break;
            case 't' :
            {
                // Both touch (both arrive there)
                if ( get_turn_info_for_endpoint<AssignPolicy, false, true>
                        ::apply(pi, pj, pk, qi, qj, qk,
                                is_p_first, is_p_last, is_q_first, is_q_last,
                                tp_model, inters, method_touch, out) )
                {
                    // do nothing
                }
                else
                {
                    touch<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                                           tp, inters.i_info(), inters.d_info(), inters.sides());

                    // workarounds for touch<> not taking spikes into account starts here
                    // those was discovered empirically
                    // touch<> is not symmetrical!
                    // P spikes and Q spikes may produce various operations!
                    // TODO: this is not optimal solution - think about rewriting touch<>

                    if ( tp.operations[0].operation == operation_blocked
                      && tp.operations[1].operation == operation_blocked )
                    {
                        // two touching spikes on the same line
                        if ( inters.is_spike_p() && inters.is_spike_q() )
                        {
                            tp.operations[0].operation = operation_union;
                            tp.operations[1].operation = operation_union;
                        }
                        else
                        {
                            tp.operations[0].is_collinear = true;
                            tp.operations[1].is_collinear = true;
                        }
                    }
                    else if ( tp.operations[0].operation == operation_blocked )
                    {
                        // a spike on P on the same line with Q1
                        if ( inters.is_spike_p() )
                        {
                            if ( inters.sides().qk_wrt_p1() == 0 )
                            {
                                tp.operations[0].is_collinear = true;
                            }
                            else
                            {
                                tp.operations[0].operation = operation_union;
                            }
                        }
                        else
                        {
                            tp.operations[1].is_collinear = true;
                        }
                    }
                    else if ( tp.operations[1].operation == operation_blocked )
                    {
                        // a spike on Q on the same line with P1
                        if ( inters.is_spike_q() )
                        {
                            if ( inters.sides().pk_wrt_q1() == 0 )
                            {
                                tp.operations[1].is_collinear = true;
                            }
                            else
                            {
                                tp.operations[1].operation = operation_union;
                            }
                        }
                        else
                        {
                            tp.operations[0].is_collinear = true;
                        }
                    }
                    else if ( tp.operations[0].operation == operation_continue
                           && tp.operations[1].operation == operation_continue )
                    {
                        // P spike on the same line with Q2 (opposite)
                        if ( inters.sides().pk_wrt_q1() == -inters.sides().qk_wrt_q1()
                          && inters.is_spike_p() )
                        {
                            tp.operations[0].operation = operation_union;
                            tp.operations[1].operation = operation_union;
                        }
                    }
                    else if ( tp.operations[0].operation == operation_none
                           && tp.operations[1].operation == operation_none )
                    {
                        // spike not handled by touch<>
                        bool const is_p = inters.is_spike_p();
                        bool const is_q = inters.is_spike_q();

                        if ( is_p || is_q )
                        {
                            tp.operations[0].operation = operation_union;
                            tp.operations[1].operation = operation_union;

                            if ( inters.sides().pk_wrt_q2() == 0 )
                            {
                                tp.operations[0].operation = operation_continue; // will be converted to i
                                if ( is_p )
                                {
                                    tp.operations[0].is_collinear = true;
                                }
                            }

                            if ( inters.sides().qk_wrt_p2() == 0 )
                            {
                                tp.operations[1].operation = operation_continue; // will be converted to i
                                if ( is_q )
                                {
                                    tp.operations[1].is_collinear = true;
                                }
                            }
                        }
                    }

                    // workarounds for touch<> not taking spikes into account ends here

                    replace_method_and_operations_tm(tp.method,
                                                     tp.operations[0].operation,
                                                     tp.operations[1].operation);

// TODO: move this into the append_xxx and call for each turn?
                    AssignPolicy::apply(tp, pi, qi, inters);

                    if ( ! BOOST_GEOMETRY_CONDITION(handle_spikes)
                      || ! append_opposite_spikes<append_touches>(tp, inters,
                                                                  is_p_last, is_q_last,
                                                                  out) )
                    {
                        *out++ = tp;
                    }
                }
            }
            break;
            case 'e':
            {
                if ( get_turn_info_for_endpoint<AssignPolicy, true, true>
                        ::apply(pi, pj, pk, qi, qj, qk,
                                is_p_first, is_p_last, is_q_first, is_q_last,
                                tp_model, inters, method_equal, out) )
                {
                    // do nothing
                }
                else
                {
                    tp.operations[0].is_collinear = true;
                    tp.operations[1].is_collinear = true;

                    if ( ! inters.d_info().opposite )
                    {
                        // Both equal
                        // or collinear-and-ending at intersection point
                        equal<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                            tp, inters.i_info(), inters.d_info(), inters.sides());

                        operation_type spike_op
                            = ( tp.operations[0].operation != operation_continue
                             || tp.operations[1].operation != operation_continue ) ?
                                operation_union :
                                operation_continue;

                        // transform turn
                        turn_transformer_ec transformer(method_touch);
                        transformer(tp);

// TODO: move this into the append_xxx and call for each turn?
                        AssignPolicy::apply(tp, pi, qi, inters);

                        // conditionally handle spikes
                        if ( ! BOOST_GEOMETRY_CONDITION(handle_spikes)
                          || ! append_collinear_spikes(tp, inters,
                                                       is_p_last, is_q_last,
                                                       method_touch, spike_op,
                                                       out) )
                        {
                            *out++ = tp; // no spikes
                        }
                    }
                    else
                    {
                        // TODO: ignore for spikes or generate something else than opposite?

                        equal_opposite
                            <
                                TurnInfo,
                                AssignPolicy
                            >::apply(pi, qi, tp, out, inters);
                    }
                }
            }
            break;
            case 'c' :
            {
                // Collinear
                if ( get_turn_info_for_endpoint<AssignPolicy, true, true>
                        ::apply(pi, pj, pk, qi, qj, qk,
                                is_p_first, is_p_last, is_q_first, is_q_last,
                                tp_model, inters, method_collinear, out) )
                {
                    // do nothing
                }
                else
                {
                    // NOTE: this is for spikes since those are set in the turn_transformer_ec
                    tp.operations[0].is_collinear = true;
                    tp.operations[1].is_collinear = true;

                    if ( ! inters.d_info().opposite )
                    {
                        method_type method_replace = method_touch_interior;
                        operation_type spike_op = operation_continue;

                        if ( inters.d_info().arrival[0] == 0 )
                        {
                            // Collinear, but similar thus handled as equal
                            equal<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                                    tp, inters.i_info(), inters.d_info(), inters.sides());

                            method_replace = method_touch;
                            if ( tp.operations[0].operation != operation_continue
                              || tp.operations[1].operation != operation_continue )
                            {
                                spike_op = operation_union;
                            }
                        }
                        else
                        {
                            collinear<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
                                    tp, inters.i_info(), inters.d_info(), inters.sides());

                            //method_replace = method_touch_interior;
                            //spike_op = operation_continue;
                        }

                        // transform turn
                        turn_transformer_ec transformer(method_replace);
                        transformer(tp);

// TODO: move this into the append_xxx and call for each turn?
                        AssignPolicy::apply(tp, pi, qi, inters);

                        // conditionally handle spikes
                        if ( ! BOOST_GEOMETRY_CONDITION(handle_spikes)
                          || ! append_collinear_spikes(tp, inters,
                                                       is_p_last, is_q_last,
                                                       method_replace, spike_op,
                                                       out) )
                        {
                            // no spikes
                            *out++ = tp;
                        }
                    }
                    else
                    {
                        // If this always 'm' ?
                        turn_transformer_ec transformer(method_touch_interior);

                        // conditionally handle spikes
                        if ( BOOST_GEOMETRY_CONDITION(handle_spikes) )
                        {
                            append_opposite_spikes<append_collinear_opposite>(tp, inters,
                                                                              is_p_last, is_q_last,
                                                                              out);
                        }

                        // TODO: ignore for spikes?
                        //       E.g. pass is_p_valid = !is_p_last && !is_pj_spike,
                        //       the same with is_q_valid

                        collinear_opposite
                            <
                                TurnInfo,
                                AssignPolicy
                            >::apply(pi, pj, pk, qi, qj, qk,
                                tp, out, inters, inters.sides(),
                                transformer, !is_p_last, !is_q_last);
                    }
                }
            }
            break;
            case '0' :
            {
                // degenerate points
                if ( BOOST_GEOMETRY_CONDITION(AssignPolicy::include_degenerate) )
                {
                    only_convert::apply(tp, inters.i_info());

                    // if any, only one of those should be true
                    if ( is_p_first
                      && equals::equals_point_point(pi, tp.point) )
                    {
                        tp.operations[0].position = position_front;
                    }
                    else if ( is_p_last
                           && equals::equals_point_point(pj, tp.point) )
                    {
                        tp.operations[0].position = position_back;
                    }
                    else if ( is_q_first
                           && equals::equals_point_point(qi, tp.point) )
                    {
                        tp.operations[1].position = position_front;
                    }
                    else if ( is_q_last
                           && equals::equals_point_point(qj, tp.point) )
                    {
                        tp.operations[1].position = position_back;
                    }

                    AssignPolicy::apply(tp, pi, qi, inters);
                    *out++ = tp;
                }
            }
            break;
            default :
            {
#if defined(BOOST_GEOMETRY_DEBUG_ROBUSTNESS)
                std::cout << "TURN: Unknown method: " << method << std::endl;
#endif
#if ! defined(BOOST_GEOMETRY_OVERLAY_NO_THROW)
                throw turn_info_exception(method);
#endif
            }
            break;
        }

        return out;
    }

    template <typename TurnInfo,
              typename IntersectionInfo,
              typename OutIt>
    static inline bool append_collinear_spikes(TurnInfo & tp,
                                               IntersectionInfo const& inters_info,
                                               bool is_p_last, bool is_q_last,
                                               method_type method, operation_type spike_op,
                                               OutIt out)
    {
        // method == touch || touch_interior
        // both position == middle

        bool is_p_spike = tp.operations[0].operation == spike_op
                       && ! is_p_last
                       && inters_info.is_spike_p();
        bool is_q_spike = tp.operations[1].operation == spike_op
                       && ! is_q_last
                       && inters_info.is_spike_q();

        if ( is_p_spike && is_q_spike )
        {
            if ( tp.method == method_equal
              && tp.operations[0].operation == operation_continue
              && tp.operations[1].operation == operation_continue )
            {
                // treat both non-opposite collinear spikes as no-spikes
                return false;
            }

            tp.method = method;
            tp.operations[0].operation = operation_blocked;
            tp.operations[1].operation = operation_blocked;
            *out++ = tp;
            tp.operations[0].operation = operation_intersection;
            tp.operations[1].operation = operation_intersection;
            *out++ = tp;

            return true;
        }
        else if ( is_p_spike )
        {
            tp.method = method;
            tp.operations[0].operation = operation_blocked;
            tp.operations[1].operation = operation_union;
            *out++ = tp;
            tp.operations[0].operation = operation_intersection;
            //tp.operations[1].operation = operation_union;
            *out++ = tp;

            return true;
        }
        else if ( is_q_spike )
        {
            tp.method = method;
            tp.operations[0].operation = operation_union;
            tp.operations[1].operation = operation_blocked;
            *out++ = tp;
            //tp.operations[0].operation = operation_union;
            tp.operations[1].operation = operation_intersection;
            *out++ = tp;

            return true;
        }

        return false;
    }

    enum append_version { append_touches, append_collinear_opposite };

    template <append_version Version,
              typename TurnInfo,
              typename IntersectionInfo,
              typename OutIt>
    static inline bool append_opposite_spikes(TurnInfo & tp,
                                              IntersectionInfo const& inters,
                                              bool is_p_last, bool is_q_last,
                                              OutIt out)
    {
        static const bool is_version_touches = (Version == append_touches);

        bool is_p_spike = ( is_version_touches ?
                            ( tp.operations[0].operation == operation_continue
                           || tp.operations[0].operation == operation_intersection ) :
                            true )
                       && ! is_p_last
                       && inters.is_spike_p();
        bool is_q_spike = ( is_version_touches ?
                            ( tp.operations[1].operation == operation_continue
                           || tp.operations[1].operation == operation_intersection ) :
                            true )
                       && ! is_q_last
                       && inters.is_spike_q();

        bool res = false;

        if ( is_p_spike
          && ( BOOST_GEOMETRY_CONDITION(is_version_touches)
            || inters.d_info().arrival[0] == 1 ) )
        {
            if ( BOOST_GEOMETRY_CONDITION(is_version_touches) )
            {
                tp.operations[0].is_collinear = true;
                tp.operations[1].is_collinear = false;
                tp.method = method_touch;
            }
            else // Version == append_collinear_opposite
            {
                tp.operations[0].is_collinear = true;
                tp.operations[1].is_collinear = false;

                BOOST_GEOMETRY_ASSERT(inters.i_info().count > 1);

                base_turn_handler::assign_point(tp, method_touch_interior,
                                                inters.i_info(), 1);

                AssignPolicy::apply(tp, inters.pi(), inters.qi(), inters);
            }

            tp.operations[0].operation = operation_blocked;
            tp.operations[1].operation = operation_intersection;
            *out++ = tp;
            tp.operations[0].operation = operation_intersection;
            //tp.operations[1].operation = operation_intersection;
            *out++ = tp;

            res = true;
        }

        if ( is_q_spike
          && ( BOOST_GEOMETRY_CONDITION(is_version_touches)
            || inters.d_info().arrival[1] == 1 ) )
        {
            if ( BOOST_GEOMETRY_CONDITION(is_version_touches) )
            {
                tp.operations[0].is_collinear = false;
                tp.operations[1].is_collinear = true;
                tp.method = method_touch;
            }
            else // Version == append_collinear_opposite
            {
                tp.operations[0].is_collinear = false;
                tp.operations[1].is_collinear = true;

                BOOST_GEOMETRY_ASSERT(inters.i_info().count > 0);

                base_turn_handler::assign_point(tp, method_touch_interior, inters.i_info(), 0);

                AssignPolicy::apply(tp, inters.pi(), inters.qi(), inters);
            }

            tp.operations[0].operation = operation_intersection;
            tp.operations[1].operation = operation_blocked;
            *out++ = tp;
            //tp.operations[0].operation = operation_intersection;
            tp.operations[1].operation = operation_intersection;
            *out++ = tp;

            res = true;
        }

        return res;
    }

    static inline void replace_method_and_operations_tm(method_type & method,
                                                        operation_type & op0,
                                                        operation_type & op1)
    {
        if ( op0 == operation_blocked && op1 == operation_blocked )
        {
            // NOTE: probably only if methods are WRT IPs, not segments!
            method = (method == method_touch ? method_equal : method_collinear);
            op0 = operation_continue;
            op1 = operation_continue;
        }
        else
        {
            if ( op0 == operation_continue || op0 == operation_blocked )
            {
                op0 = operation_intersection;
            }
            else if ( op0 == operation_intersection )
            {
                op0 = operation_union;
            }

            if ( op1 == operation_continue || op1 == operation_blocked )
            {
                op1 = operation_intersection;
            }
            else if ( op1 == operation_intersection )
            {
                op1 = operation_union;
            }

            // spikes in 'm'
            if ( method == method_error )
            {
                method = method_touch_interior;
                op0 = operation_union;
                op1 = operation_union;
            }
        }
    }

    class turn_transformer_ec
    {
    public:
        explicit turn_transformer_ec(method_type method_t_or_m)
            : m_method(method_t_or_m)
        {}

        template <typename Turn>
        void operator()(Turn & turn) const
        {
            operation_type & op0 = turn.operations[0].operation;
            operation_type & op1 = turn.operations[1].operation;

            BOOST_GEOMETRY_ASSERT(op0 != operation_blocked || op1 != operation_blocked );

            if ( op0 == operation_blocked )
            {
                op0 = operation_intersection;
            }
            else if ( op0 == operation_intersection )
            {
                op0 = operation_union;
            }

            if ( op1 == operation_blocked )
            {
                op1 = operation_intersection;
            }
            else if ( op1 == operation_intersection )
            {
                op1 = operation_union;
            }

            if ( op0 == operation_intersection || op0 == operation_union
              || op1 == operation_intersection || op1 == operation_union )
            {
                turn.method = m_method;
            }

// TODO: is this correct?
//       it's equivalent to comparing to operation_blocked at the beginning of the function
            turn.operations[0].is_collinear = op0 != operation_intersection;
            turn.operations[1].is_collinear = op1 != operation_intersection;
        }

    private:
        method_type m_method;
    };

    static inline void replace_operations_i(operation_type & op0, operation_type & op1)
    {
        if ( op0 == operation_intersection )
        {
            op0 = operation_union;
        }

        if ( op1 == operation_intersection )
        {
            op1 = operation_union;
        }
    }
};

}} // namespace detail::overlay
#endif // DOXYGEN_NO_DETAIL

}} // namespace geofeatures_boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GET_TURN_INFO_LL_HPP