#include <Rcpp.h>
// Copyright (c) 1999
// Utrecht University (The Netherlands),
// ETH Zurich (Switzerland),
// INRIA Sophia-Antipolis (France),
// Max-Planck-Institute Saarbruecken (Germany),
// and Tel-Aviv University (Israel).  All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL: https://github.com/CGAL/cgal/blob/v6.0.1/Kernel_23/include/CGAL/Line_2.h $
// $Id: include/CGAL/Line_2.h 50cfbde3b84 $
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Andreas Fabri

#ifndef CGAL_LINE_2_H
#define CGAL_LINE_2_H

#include <CGAL/assertions.h>
#include <CGAL/Kernel/Return_base_tag.h>
#include <CGAL/Dimension.h>
#include <CGAL/IO/io.h>
#include <CGAL/Kernel/mpl.h>

#include <type_traits>

namespace CGAL {

template <class R_>
class Line_2 : public R_::Kernel_base::Line_2
{
  typedef typename R_::RT                    RT;
  typedef typename R_::FT                    FT;
  typedef typename R_::Point_2               Point_2;
  typedef typename R_::Segment_2             Segment_2;
  typedef typename R_::Ray_2                 Ray_2;
  typedef typename R_::Vector_2              Vector_2;
  typedef typename R_::Direction_2           Direction_2;
  typedef typename R_::Aff_transformation_2  Aff_transformation_2;
  typedef typename R_::Kernel_base::Line_2   RLine_2;

  typedef Line_2                             Self;
  static_assert(std::is_same<Self, typename R_::Line_2>::value);

public:

  typedef Dimension_tag<2>  Ambient_dimension;
  typedef Dimension_tag<1>  Feature_dimension;

  typedef RLine_2 Rep;

  const Rep& rep() const
  {
    return *this;
  }

  Rep& rep()
  {
    return *this;
  }

  typedef  R_   R;

  Line_2() {}

  Line_2(const RLine_2& l)  // conversion impl -> interface class
    : RLine_2(l) {}

  Line_2(RLine_2&& l)
    : RLine_2(std::move(l)) {}

  Line_2(const Point_2 &p, const Point_2 &q)
    : RLine_2(typename R::Construct_line_2()(Return_base_tag(), p,q)) {}

  Line_2(const RT &a, const RT &b, const RT &c)
    : RLine_2(typename R::Construct_line_2()(Return_base_tag(), a,b,c)) {}

  explicit Line_2(const Segment_2& s)
    : RLine_2(typename R::Construct_line_2()(Return_base_tag(), s)) {}

  explicit Line_2(const Ray_2& r)
    : RLine_2(typename R::Construct_line_2()(Return_base_tag(), r)) {}

  Line_2(const Point_2 &p, const Direction_2 &d)
    : RLine_2(typename R::Construct_line_2()(Return_base_tag(), p,d)) {}

  Line_2(const Point_2 &p, const Vector_2 &v)
    : RLine_2(typename R::Construct_line_2()(Return_base_tag(), p,v)) {}


  decltype(auto) a() const
  {
    return R().compute_a_2_object()(*this);
  }

  decltype(auto) b() const
  {
    return R().compute_b_2_object()(*this);
  }

  decltype(auto) c() const
  {
    return R().compute_c_2_object()(*this);
  }

  Line_2
  transform(const Aff_transformation_2 &t) const
  {
    return Line_2(t.transform(point(0)),
                  t.transform(direction()));
  }

  Line_2
  opposite() const
  {
    return R().construct_opposite_line_2_object()(*this);
  }

  Direction_2
  direction() const
  {
    return R().construct_direction_2_object()(*this);
  }

  Vector_2
  to_vector() const
  {
    return R().construct_vector_2_object()(*this);
  }

  Line_2
  perpendicular(const Point_2 &p) const
  {
    return R().construct_perpendicular_line_2_object()(*this,p);
  }

  Point_2
  projection(const Point_2& p) const
  {
    return R().construct_projected_point_2_object()(*this,p);
  }

  typename R::Boolean
  is_horizontal() const
  {
    return R().is_horizontal_2_object()(*this);
  }

  typename R::Boolean
  is_vertical() const
  {
    return R().is_vertical_2_object()(*this);
  }

  typename R::Boolean
  is_degenerate() const
  { return R().is_degenerate_2_object()(*this); }

  typename R::Oriented_side
  oriented_side(const Point_2 &p) const
  {
    return R().oriented_side_2_object()(*this,p);
  }

  typename R::Boolean
  has_on_boundary(const Point_2 &p) const
  {
    return oriented_side(p) == ON_ORIENTED_BOUNDARY;
  }

  typename R::Boolean
  has_on_positive_side(const Point_2 &p) const
  {
    return oriented_side(p) == ON_POSITIVE_SIDE;
  }

  typename R::Boolean
  has_on_negative_side(const Point_2 &p) const
  {
    return oriented_side(p) == ON_NEGATIVE_SIDE;
  }

  typename R::Boolean
  has_on(const Point_2 &p) const
  {
    return has_on_boundary(p);
  }

  FT
  x_at_y(const FT &y) const
  {
    return R().compute_x_at_y_2_object()(*this, y);
  }

  FT
  y_at_x(const FT &y) const
  {
    return R().compute_y_at_x_2_object()(*this, y);
  }

  Point_2
  point() const
  {
    return R().construct_point_2_object()(*this);
  }

  Point_2
  point(const int i) const
  {
    return R().construct_point_2_object()(*this, RT(i));
  }

  Point_2
  point(const typename First_if_different<RT, int, 1>::Type& i) const
  {
    return R().construct_point_2_object()(*this,i);
  }

  Point_2
  point(const typename First_if_different<
                const typename First_if_different<FT, RT, 2>::Type, int>::Type& i) const
  {
    return R().construct_point_2_object()(*this,i);
  }

  typename R::Boolean
  operator==(const Line_2 &l) const
  {
    return R().equal_2_object()(*this, l);
  }

  typename R::Boolean
  operator!=(const Line_2 &l) const
  {
    return !(*this == l);
  }

};


template <class R >
std::ostream&
insert(std::ostream& os, const Line_2<R>& l)
{
    switch(IO::get_mode(os)) {
    case IO::ASCII :
        return os << l.a() << ' ' << l.b() << ' ' << l.c();
    case IO::BINARY :
        write(os, l.a());
        write(os, l.b());
        write(os, l.c());
        return os;
    default:
        return os << "Line_2(" << l.a()
                  << ", " << l.b() << ", " << l.c() <<')';
    }
}

template < class R >
std::ostream &
operator<<(std::ostream &os, const Line_2<R> &l)
{
  return insert(os, l);
}


template <class R >
std::istream&
extract(std::istream& is, Line_2<R>& l)
{
  typename R::RT a(0), b(0), c(0);
    switch(IO::get_mode(is)) {
    case IO::ASCII :
        is >> IO::iformat(a) >> IO::iformat(b) >> IO::iformat(c);
        break;
    case IO::BINARY :
        read(is, a);
        read(is, b);
        read(is, c);
        break;
    default:
        is.setstate(std::ios::failbit);
        Rcpp::Rcerr << "" << std::endl;
        Rcpp::Rcerr << "Stream must be in ASCII or binary mode" << std::endl;
        break;
    }
    if (is)
        l = Line_2<R>(a, b, c);
    return is;
}


template < class R >
std::istream &
operator>>(std::istream &is, Line_2<R> &l)
{
  return extract(is, l);
}

} //namespace CGAL

#endif  // CGAL_LINE_2_H

