// Copyright (c) 2004-2008 Max-Planck-Institute Saarbruecken (Germany).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1.1/Arrangement_on_surface_2/include/CGAL/Curved_kernel_via_analysis_2/gfx/Subdivision_2.h $
// $Id: include/CGAL/Curved_kernel_via_analysis_2/gfx/Subdivision_2.h 08b27d3db14 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s)     : Pavel Emeliyanenko <asm@mpi-sb.mpg.de>
//
// ============================================================================

/*!\file CGAL/Curved_kernel_via_analysis_2/gfx//Subdivision_2.h
 * \brief definition of Subdivision_2<>
 * 2D space subdivision for rasterization of planar curves
 */

#ifndef CGAL_CKVA_SUBDIVISION_2_H
#define CGAL_CKVA_SUBDIVISION_2_H

#include <CGAL/license/Arrangement_on_surface_2.h>


#warning this file is considered obsolete

#include <vector>
#include <CGAL/Polynomial.h>
#include <CGAL/Interval_nt.h>

#include <CGAL/Curved_kernel_via_analysis_2/gfx/Curve_renderer_internals.h>

namespace CGAL {

namespace internal {

template <class NT>
struct Range_templ
{
        Range_templ() { }
        Range_templ(const NT& l_, const NT& u_) : lower(l_), upper(u_) { }
        NT lower, upper;
};

} // namespace internal

/*!\brief
 * The class template \c Subdivision_2 and its associate functions.
 *
 * The class implements a space method to plot algebraic curves, we use Affine
 * Arithmetic with recursive derivative information
 */
template <class NT_, class Algebraic_curve_2_>
class Subdivision_2
{
public:
        //! \name public typedefs
    //!@{
        //! this instance's first template argument
        typedef NT_ NT;
        //! this instance's second template argument
        typedef Algebraic_curve_2_ Algebraic_curve_2;
        //! specialized integer number type
        typedef typename SoX::Curve_renderer_traits<NT>::Integer Integer;
        //! instance of univariate polynomial
        typedef CGAL::Polynomial<NT> Poly_1;
        //! instance of bivariate polynomial
        typedef CGAL::Polynomial<Poly_1> Poly_2;
        //! rational number type instance
        typedef typename Algebraic_curve_2::Rational Rational;
        //! instance of input bivariate polynomial
        typedef typename Algebraic_curve_2::Poly_d Poly_input_2;
        //! instance of input univariate polynomuial
        typedef typename Poly_input_2::NT Poly_input_1;
        //! instance of coefficient number type
        typedef typename Poly_input_1::NT Coeff;
        //! container used to store coefficient sequence
        typedef typename Poly_1::Vector Vector_1;
        //! container's const iterator (random access)
        typedef typename Poly_1::const_iterator const_iterator_1;
        //! container used to store univariate polynomials sequence
        typedef typename Poly_2::Vector Vector_2;
        //! container's const iterator (random access)
        typedef typename Poly_2::const_iterator const_iterator_2;
        //@}
private:
        //! \name private typedefs
    //@{

        //! conversion from the basic number type to integers
        typename SoX::Curve_renderer_traits<NT>::To_integer to_integer;
        //! conversion from \c Integer type to built-in integer
        typename SoX::Curve_renderer_traits<NT>::To_machine_int to_int;
    //! conversion from \c Rational type to used number type
        typename SoX::Curve_renderer_traits<NT>::From_rational from_rat;
        //! makes the result exact after inexact operation (applicable only for
        //! exact number types
        typename SoX::Curve_renderer_traits<NT>::Make_exact make_exact;
        //! an interval
        typedef Intern::Range_templ<NT> Range;
        //! affine form instance
        typedef SoX::Affine_form<NT> Affine_form;
        //@}
public:
        //! \name Constructors
    //@{
        //! default constructor
        Subdivision_2() : initialized(false), polynomial_set(false) {}
        //@}
public:
        //! \name public methods
    //@{
        //! specifies drawing window and pixel resolution
        void setup(const double& x_min_,const double& y_min_,
                                const double& x_max_,const double& y_max_,
                                int res_w_, int res_h_)
        {
                x_min = static_cast<NT>(x_min_);
                x_max = static_cast<NT>(x_max_);
                y_min = static_cast<NT>(y_min_);
                y_max = static_cast<NT>(y_max_);
                res_w = res_w_;
                res_h = res_h_;
                if(x_min >= x_max||y_min >= y_max||res_w < 5||res_h < 5||res_w > 1024||
                        res_h > 1024) {
                        std::cout << "Incorrect setup parameters" << std::endl;
                        initialized = false;
                        return;
                }
                pixel_w = (x_max - x_min) / res_w;
                pixel_h = (y_max - y_min) / res_h;
                make_exact(pixel_w);
                make_exact(pixel_h);
                initialized = true;
        }
        //! sets up the underlying polynomial
        void set_polynomial(const Poly_input_2& poly)
        {
                input_poly = poly;
                precompute();
        }
        //! returns the underlying polynomial
        Poly_input_2 get_polynomial() const
        {
                return input_poly;
        }
        //! \brief returns drawing window boundaries
        void get_window(double& x_min_, double& y_min_, double& x_max_,
                double& y_max_) const
        {
                x_min_ = to_double(x_min);
                x_max_ = to_double(x_max);
                y_min_ = to_double(y_min);
                y_max_ = to_double(y_max);
        }
        //! \brief returns pixel resolution
        void get_resolution(int& res_w_, int& res_h_) const
        {
                res_w_ = res_w;
                res_h_ = res_h;
        }
        //! \brief the main rendering procedure
        void draw(QPainter *painter_);
        //@}
private:
        //! \name Private methods
    //@{
        //! precomputes polynomials and derivative coefficients
        void precompute();
        //! \brief switches to another cache instance depending on the
        //! supporting curve of a segment
                //! \brief evaluates the ith derivative at certain x
        //!
        //! \c cache_it - an intetator pointing to the end of an array of
        //! polynomial coefficients, \c der_it - an iterator for derivative
        //! coefficients
        NT evaluate_der(const_iterator_1 der_it, const_iterator_1 begin,
                        const_iterator_1 cache_it, const NT& x)
        {
                NT val((*cache_it--) * (*der_it));
                while((der_it--)!=begin)
                        val = val * x + (*cache_it--) * (*der_it);
                return val;
        }
        //! evaluates a function at a certain x
        NT evaluate(const Poly_1& poly, const NT& x)
        {
                const_iterator_1 it = poly.end() - 1, begin = poly.begin();
                NT val(*it);
                while((it--)!=begin)
                        val = val * x + (*it);
                return val;
        }
        //! \brief computes the ranges of univariate polynomial values over an interval
        void get_range_1(int var, const NT& lower, const NT& upper,
                const Poly_1& poly, NT& l, NT& h);
        //! Affine Arithmetic with Recursive Derivative information
        //! for univariate case
        void get_range_AARD_1(int var, const NT& lower, const NT& upper,
                const Poly_1& poly, NT& l, NT& h);
        //! \brief recursive Taylor, bivariate case
        //!
        //! returns a range of polynomial values as Affine_form
        void get_range_RT_2(const NT& x_low,  const NT& x_high, const NT& y_low,
                const NT& y_high, int depth, int index, Affine_form& res);
        //! checks a rectangular area with 2D range analysis: either discrads it,
        //! subdivides further or draws a pixel
        void quad_tree(const NT& x_low, const NT& x_high, const NT& y_low,
                const NT& y_high);
        //! \brief recursive quad tree subdivision
        void subdivide(const NT& x_low, const NT& x_high, const NT& y_low,
                const NT& y_high);
public:
        //! destructor
        ~Subdivision_2()
        {

        }
        //@}
private:
        //! \name Private properties
    //@{
        NT x_min, x_max, y_min, y_max; //! drawing window boundaries
        int res_w, res_h;                           //! pixel resolution
        NT pixel_w, pixel_h;                    //! pixel dimensions w.r.t. resolution
        Poly_2 coeffs_x, coeffs_y; //! f(x(y)) / f(y(x))
        Poly_input_2 input_poly;
        Vector_2 der_x, der_y;  //! derivative coefficients df/dx (df/dy)
        std::vector<Poly_2> mixed_derivatives;
        int max_deg; //! the maximal degree w.r.t. x and y
        bool initialized, polynomial_set;
        QPainter *painter;
        //@}
}; // class Subdivision_2<>

//! \brief main rasterization procedure
template <class NT, class Algebraic_curve_2_>
void Subdivision_2<NT, Algebraic_curve_2_>::draw(QPainter *painter_)
{
        if(!initialized||!polynomial_set||painter_==nullptr)
                return;
        painter = painter_;
        //std::cout << " P(x(y)): " << coeffs_x << std::endl;
        //std::cout << " P(y(x)): " << coeffs_y << std::endl;
        std::cout << "resolution: " << res_w << " x " << res_h << std::endl;
        //std::cout << "box: [" << x_min << "; " << y_min << "]x[" << x_max << "; "
                // <<        y_max << "]" << std::endl;

        //quad_tree(x_min/15, x_max/15, y_min/15, y_max/15);
        quad_tree(x_min, x_max, y_min, y_max);
        //std::cout << "exit normal" << std::endl;
}

//! \brief checks a rectangular area with 2D range analysis: either discrads it,
//! subdivides further or draws a pixel
template <class NT_, class Algebraic_curve_2_>
void Subdivision_2<NT_, Algebraic_curve_2_>::quad_tree(const NT& x_low,
          const NT& x_high, const NT& y_low, const NT& y_high)
{
        Affine_form res;
        NT lower, upper;
        get_range_RT_2(x_low, x_high, y_low, y_high, 0, 0, res);
        res.convert(lower, upper);
        if(lower*upper > 0)
                return;
        if(x_high - x_low <= pixel_w&&y_high - y_low <= pixel_h) {
                NT x = (x_low), y = (y_low);
                int pix_x = static_cast<int>(CGAL::to_double((x - x_min) / pixel_w)),
                    pix_y = static_cast<int>(CGAL::to_double((y - y_min) / pixel_h));
                painter->drawPoint(pix_x, res_h - pix_y);
                //painter->drawEllipse(pix_x-2,res_h-pix_y-2,4,4);
        }
        else
                subdivide(x_low, x_high, y_low, y_high);
}

//! \brief recursive quad tree subdivision
template <class NT_, class Algebraic_curve_2_>
void Subdivision_2<NT_, Algebraic_curve_2_>::subdivide(const NT& x_low,
          const NT& x_high, const NT& y_low, const NT& y_high)
{
        NT x_mid = (x_low + x_high)/2, y_mid = (y_low + y_high)/2;
        quad_tree(x_low,x_mid,y_low,y_mid);
        quad_tree(x_low,x_mid,y_mid,y_high);
        quad_tree(x_mid,x_high,y_mid,y_high);
        quad_tree(x_mid,x_high,y_low,y_mid);
}

//! \brief recursive Taylor, bivariate case
template <class NT_, class Algebraic_curve_2_>
void Subdivision_2<NT_, Algebraic_curve_2_>::get_range_RT_2(
        const NT& x_low, const NT& x_high, const NT& y_low, const NT& y_high,
                int depth, int index, Affine_form& res)
{
        //std::cout << "range for [" << x_low << "; " << y_low << "]x[" <<
        //x_high <<
                //"; " << y_high << "]: (" << low << "; " << high << ")" << std::endl;
        typename std::vector<Poly_2>::const_iterator der_it =
                mixed_derivatives.begin() + index;
        if((*der_it).degree()==0) {
                NT c = (*der_it).lcoeff().lcoeff();
                res = Affine_form(c,c);
                return;
        } else if((*der_it).degree()==1) {
                Poly_1 p_x_low = NiX::substitute_x((*der_it), x_low),
                           p_x_high = NiX::substitute_x((*der_it), x_high);
                NT eval_1 = p_x_low.evaluate(y_low),
                   eval_2 = p_x_low.evaluate(y_high),
                   eval_3 = p_x_high.evaluate(y_low),
                   eval_4 = p_x_high.evaluate(y_high);
                NT min1 = eval_1, max1 = eval_2;
                if(min1 > eval_2) {
                        min1 = eval_2;
                        max1 = eval_1;
                }
                NT min2 = eval_3, max2 = eval_4;
                if(min2 > eval_4) {
                        min2 = eval_4;
                        max2 = eval_3;
                }
                if(min1 > min2)
                        min1 = min2;
                if(max1 < max2)
                        max1 = max2;
                res = Affine_form(min1,max1);
                return;
        }

        if(depth >= 4)
        {
        NT l, h;
        std::vector<Affine_form> x_forms;
        const_iterator_2 it_2 = (*der_it).begin();
        while(it_2 != (*der_it).end())
        {
                // it_2 - a poly in x_range
                if((*it_2).is_zero())
                        l = h = 0;
                else
                        get_range_1(X_RANGE, x_low, x_high, *it_2, l, h);
                x_forms.push_back(Affine_form(l, h));
                it_2++;
        }
        typename std::vector<Affine_form>::const_iterator it =
                x_forms.end()-1;
        Affine_form y_form(y_low, y_high);
        res = Affine_form(*it);
        while((it--) != x_forms.begin())
                res = res * y_form + (*it);
        //res.convert(lower, upper);
        return;
        }

        NT x0 = (x_low+x_high)/2, y0 = (y_low+y_high)/2, x1 = (x_high-x_low)/2,
                y1 = (y_high-y_low)/2;
        Affine_form one1(-1,1), one2(-1,1), one3(-1,1),
                        zero1(0,1), zero2(0,1), fxx, fxy, fyy;
        NT eval_f = NiX::substitute_xy(*der_it, x0, y0),
           eval_fx = NiX::substitute_xy(*(der_it+depth+1), x0, y0)*x1,
           eval_fy = NiX::substitute_xy(*(der_it+depth+2), x0, y0)*y1;
        res = eval_f + eval_fx*one1 + eval_fy*one2;

        int idx = index+2*depth+3;
        if(idx < (int)mixed_derivatives.size()) {
                get_range_RT_2(x_low, x_high, y_low, y_high, depth+2, idx,   fxx);
                get_range_RT_2(x_low, x_high, y_low, y_high, depth+2, idx+1, fxy);
                get_range_RT_2(x_low, x_high, y_low, y_high, depth+2, idx+2, fyy);
                res = res + ((x1*x1/2*zero1)*fxx) + ((y1*y1/2*zero2)*fyy) +
                        ((x1*y1*one3)*fxy);
        }

        //res.convert(lower, upper);
        //std::cout << "range for depth = " << depth << " index = " << index <<
                //" [" << lower << "; " << upper << "]" << std::endl;
}

//! \brief computes the range of polynomial values \c f([lower,upper]) using
//! Affine Arithmetic with Recursive Derivative information
//!
//! \c var = \c X_RANGE: \c y is fixed - polynomial is given in \c x
//! coordinates, otherwise: \c x is fixed - polynomial is given in \c y
//! coordinates
template <class NT_, class Algebraic_curve_2_>
void Subdivision_2<NT_, Algebraic_curve_2_>::get_range_AARD_1(int var,
        const NT& l_, const NT& r_, const Poly_1& poly,  NT& l1, NT& h1)
{
        Vector_2 *der = &der_y;
        if(var == X_RANGE)
                der = &der_x;
        NT low, up, l(l_), r(r_);
        const_iterator_2 der_it_2 = der->end()-1;
        const_iterator_1 der_it, cache_it, begin;
        if(poly.degree()==0) {
                l1 = h1 = poly.lcoeff();
                return;
        }
        low = up = poly.lcoeff() * (*der_it_2).lcoeff();
        if(l > r) {
                l = r_;
                r = l_;
        }
        Affine_form f(l,r);
        /*while((der_it_2--)!=der->begin()) {
                // iterate through derivative coefficients
                der_it = (*der_it_2).end()-1;
                begin = (*der_it_2).begin();
                cache_it = poly.end()-1; // iterate through precomputed y-values
                // if a derivative does not straddle zero we can
                // calculate the exact boundaries for f(x)
                if(low * up > 0) {
                        v1 = v2 = (*cache_it--) * (*der_it);
                        // calculate the ith derivative at xa and xb
                        while((der_it--)!=begin) {
                                v1 = v1 * l + (*cache_it) * (*der_it);
                                v2 = v2 * r + (*cache_it--) * (*der_it);
                        }
                        if(low < 0 && up < 0) {
                                v = v1;
                                v1 = v2;
                                v2 = v;
                        }
                        low = v1;
                        up = v2;
                } else { // use affine arithmetic to compute bounds
                        Affine_form<NT>        range(((*cache_it--) * (*der_it)));
                        // calculate the ith derivative using affine arithmetic
                        while((der_it--)!=begin)
                                range = (*cache_it--) * (*der_it) + (range * f);
                        range.convert(low,up);
                }
        }
        if(low * up >= 0) {
                Gfx_OUT << "+ " << std::endl;
                v1 = evaluate(poly, l);
                v2 = evaluate(poly, r);
                //Gfx_OUT << "v1 = " << v1 << " v2 = " << v2 << std::endl;
                if(low < 0 && up < 0) {
                        v = v1;
                        v1 = v2;
                        v2 = v;
                }
                low = v1;
                up = v2;
        } else */{ // use affine arithmetic to compute bounds
                cache_it = poly.end()-1;
                begin = poly.begin();
                Affine_form res(*cache_it);
                while((cache_it--)!=begin)
                        res = (*cache_it) + (res * f);
                res.convert(low,up);
        }
        l1 = low;
        h1 = up;
//std::cout << "AARD bounds: [" << low << "; " << up << "]" << std::endl;
}

//! \brief returns whether a polynomial has zero at a given interval,
//! since we are not interested in concrete values
//!
//! flag \t der_check forces to calculate boundaries for the first
//! derivative instead of function itself. Caching is used if
//! appropriate
template <class NT_, class Algebraic_curve_2_>
void Subdivision_2<NT_, Algebraic_curve_2_>::get_range_1(int var,
        const NT& lower, const NT& upper, const Poly_1& poly, NT& l, NT& h)
{
        //std::cout << "range for: [" << lower << "; " << upper << "] poly: " <<
                //poly << std::endl;
        get_range_AARD_1(var, lower, upper, poly, l, h);
}

//! precomputes some initial data, necessary for subsequent computations
template <class NT_, class Algebraic_curve_2_>
void Subdivision_2<NT_, Algebraic_curve_2_>::precompute()
{
        Intern::Max_coeff<Coeff> max_coeff;
        Coeff mx1 = max_coeff(input_poly);
        Intern::Transform<Poly_2, Coeff, Rational,
                typename SoX::Curve_renderer_traits<NT>::From_rational,
                typename SoX::Curve_renderer_traits<NT>::Make_exact> tr;
        tr.max = Rational(mx1);
        coeffs_y = tr(input_poly);
        // x - outer variable, y - inner
        coeffs_x = transpose_bivariate_polynomial(coeffs_y);
        int degree_x = coeffs_x.degree(),
                degree_y = coeffs_y.degree();
        der_x.clear();
        der_y.clear();
        int i, j;
        max_deg = degree_x;
        if(degree_y > max_deg)
                max_deg = degree_y;
        NT *X = new NT[max_deg];
        NT det(1.0);
        std::cout << "start" << std::endl;
        for(i = 0; i < degree_x; i++) {
                if(i != 0)
                        det = X[0];
                for(j = 1; j <= degree_x - i; j++)
                        if(i == 0)
                                X[j-1] = j;
                        else {
                                X[j-1] = X[j] * j / det; // divide by the lowest coefficient ?
                                make_exact(X[j-1]);
                        }
                der_x.push_back(Poly_1(X,(X + degree_x - i)));
        }
        for(i = 0; i < degree_y; i++) {
                if(i != 0)
                        det = X[0];
                for(j = 1; j <= degree_y - i; j++)
                        if(i == 0)
                                X[j-1] = j; // divide by the lowest coefficient ?
                        else {
                                X[j-1] = X[j] * j / det;
                                make_exact(X[j-1]);
                        }
                der_y.push_back(Poly_1(X,(X + degree_y - i)));
        }
        delete []X;
        mixed_derivatives.clear();
        // f, fx, fy, fxx, fxy, fyy, fxxx, fxxy, fxyy, fyyy, ...
        mixed_derivatives.push_back(coeffs_y);
        int idx = 0;
        for(i = 1; i <= max_deg; i++)
        {
                mixed_derivatives.push_back(NiX::diff_x(mixed_derivatives[idx]));
                for(j = 0; j < i; j++)
// compute fx^(i-j)y^(j), i.e. (i-j) times derivate by x; and j times
// derivate by y
                {
                        Poly_2 p = mixed_derivatives[idx+j];
                        p.diff();
                        mixed_derivatives.push_back(p);
                }
                idx += i;
        }
        std::cout << "finished" << std::endl;
        polynomial_set = true;
        /*typename std::vector<Poly_2>::const_iterator der_it =
                mixed_derivatives.end()-1;
        for(i = max_deg; i >= 0; i--)
        {
                std::cout << i << "th mixed derivatives: " << std::endl;
                for(j = 0; j < i+1; j++, der_it--)
                        std::cout << *der_it << std::endl;
        }*/
}

} //namespace CGAL

#endif // CGAL_CKVA_SUBDIVISION_2_H
