/* -*- coding: utf-8;mode:c++;c-file-style:"stroustrup" -*- */
/*
  Licensed under the terms of the BSD 3-Clause
  (see plotpy/__init__.py for details)
*/
#ifndef __POINTS_HPP__
#define __POINTS_HPP__

#include "traits.hpp"

/*
  This file defines several classes of 2D points
  which are used by other algorithms
*/

class Point
{
public:
    typedef double real;
    typedef num_trait<real> trait;
    Point() : _ix(0), _iy(0), _x(0.0), _y(0.0) {}
    real x() const { return _x; }
    real y() const { return _y; }
    int ix() const { return _ix; }
    int iy() const { return _iy; }

    void setx(real x)
    {
        _x = x;
        _ix = trait::toint(x);
    }
    void sety(real y)
    {
        _y = y;
        _iy = trait::toint(y);
    }
    void set(real x, real y)
    {
        setx(x);
        sety(y);
    }
    void copy(const Point &p)
    {
        _ix = p._ix;
        _iy = p._iy;
        _x = p._x;
        _y = p._y;
    }
    Point &operator=(const Point &p)
    {
        copy(p);
        return *this;
    }

protected:
    int _ix, _iy;
    real _x, _y;
};

/* A point that keep track of it's coordinates
   as double and integers
*/
class Point2DRectilinear : public Point
{
public:
    Point2DRectilinear() : _insidex(true), _insidey(true) {}
    bool inside() const { return _insidex && _insidey; }
    void testx(int _m, int _M)
    {
        if (_ix < _m || _ix >= _M)
        {
            _insidex = false;
        }
        else
        {
            _insidex = true;
        }
    }
    void testy(int _m, int _M)
    {
        if (_iy < _m || _iy >= _M)
        {
            _insidey = false;
        }
        else
        {
            _insidey = true;
        }
    }
    void copy(const Point2DRectilinear &p)
    {
        Point::copy(p);
        _insidex = p._insidex;
        _insidey = p._insidey;
    }
    Point2DRectilinear &operator=(const Point2DRectilinear &p)
    {
        copy(p);
        return *this;
    }

protected:
    bool _insidex, _insidey;
};

/* A special transformation operation that transforms
   i,j (int) coordinates with a translation and scale
*/
class ScaleTransform
{
public:
    typedef Point2DRectilinear point;
    typedef point::real real;

    ScaleTransform(int _nx, int _ny,
                   real _x0, real _y0,
                   real _dx, real _dy) : nx(_nx), ny(_ny), x0(_x0), y0(_y0), dx(_dx), dy(_dy) {}

    void set(point &p, int x, int y) const
    {
        p.set(x0 + x * dx, y0 + y * dy);
        p.testx(0, nx);
        p.testy(0, ny);
    }
    void incx(point &p, real k = 1) const
    {
        p.setx(p.x() + k * dx);
        p.testx(0, nx);
    }
    void incy(point &p, real k = 1) const
    {
        p.sety(p.y() + k * dy);
        p.testy(0, ny);
    }

public:
    int nx, ny;
    real x0, y0;
    real dx, dy;
};

class Point2D : public Point
{
public:
    Point2D() : _inside(true) {}
    bool inside() const { return _inside; }
    void copy(const Point2D &p)
    {
        Point::copy(p);
        _inside = p._inside;
    }
    Point2D &operator=(const Point2D &p)
    {
        copy(p);
        return *this;
    }

    void test(int _xm, int _xM, int _ym, int _yM)
    {
        if ((_ix < _xm) || (_ix >= _xM) || (_iy < _ym) || (_iy >= _yM))
        {
            _inside = false;
        }
        else
        {
            _inside = true;
        }
    }
    bool _inside;
};

class LinearTransform
{
public:
    typedef Point2D point;
    typedef point::real real;

    LinearTransform(int _nx, int _ny,
                    real _x0, real _y0,
                    real _xx, real _xy,
                    real _yx, real _yy) : nx(_nx), ny(_ny),
                                          x0(_x0), y0(_y0),
                                          xx(_xx), xy(_xy),
                                          yx(_yx), yy(_yy)
    {
    }
    void set(point &p, int x, int y) const
    {
        p.set(x0 + x * xx + y * xy,
              y0 + x * yx + y * yy);
        p.test(0, nx, 0, ny);
    }
    void incx(point &p, real k = 1) const
    {
        p.set(p.x() + k * xx, p.y() + k * yx);
        p.test(0, nx, 0, ny);
    }
    void incy(point &p, real k = 1) const
    {
        p.set(p.x() + k * xy, p.y() + k * yy);
        p.test(0, nx, 0, ny);
    }

public:
    int nx, ny;
    real x0, y0;
    real xx, xy, yx, yy;
};

template <class axis_type>
class Point2DAxis : public Point
{
public:
    Point2DAxis() : _insidex(true), _insidey(true) {}
    bool inside() const { return _insidex && _insidey; }

    void set(const axis_type &ax, real x,
             const axis_type &ay, real y)
    {
        setx(ax, x);
        sety(ay, y);
    }
    void setx(const axis_type &ax, real x)
    {
        _ix = -1;
        _x = x;
        while (_ix < ax.ni - 1 && ax.value(_ix + 1) < x)
        {
            ++_ix;
        }
    }
    void sety(const axis_type &ay, real y)
    {
        _iy = -1;
        _y = y;
        while (_iy < ay.ni - 1 && ay.value(_iy + 1) < y)
        {
            ++_iy;
        }
    }
    void incx(const axis_type &ax, real dx)
    {
        _x += dx;
        if (dx < 0)
        {
            while (_ix >= 0 && ax.value(_ix) >= _x)
            {
                --_ix;
            }
        }
        else
        {
            while (_ix < ax.ni - 1 && ax.value(_ix + 1) < _x)
            {
                ++_ix;
            }
        }
    }
    void incy(const axis_type &ay, real dy)
    {
        _y += dy;
        if (dy < 0)
        {
            while (_iy >= 0 && ay.value(_iy) >= _y)
            {
                --_iy;
            }
        }
        else
        {
            while (_iy < ay.ni - 1 && ay.value(_iy + 1) < _y)
            {
                ++_iy;
            }
        }
    }
    void copy(const Point2DAxis<axis_type> &p)
    {
        Point::copy(p);
        _insidex = p._insidex;
        _insidey = p._insidey;
    }
    Point2DAxis<axis_type> &operator=(const Point2DAxis<axis_type> &p)
    {
        copy(p);
        return *this;
    }

    bool _insidex, _insidey;
};

template <class axis_type>
class XYTransform
{
public:
    typedef Point2DAxis<axis_type> point;
    typedef typename point::real real;

    XYTransform(int _nx, int _ny,
                const axis_type &_ax,
                const axis_type &_ay,
                real _x0, real _y0,
                real _dx, real _dy) : nx(_nx), ny(_ny),
                                      ax(_ax), ay(_ay),
                                      x0(_x0), y0(_y0),
                                      dx(_dx), dy(_dy) {}

    void testx(point &p) const
    {
        if (p.ix() < 0 || p.ix() >= nx)
        {
            p._insidex = false;
        }
        else
        {
            p._insidex = true;
        }
    }
    void testy(point &p) const
    {
        if (p.iy() < 0 || p.iy() >= ny)
        {
            p._insidey = false;
        }
        else
        {
            p._insidey = true;
        }
    }
    void set(point &p, int x, int y) const
    {
        p.set(ax, x0 + x * dx, ay, y0 + y * dy);
        testx(p);
        testy(p);
    }
    void incx(point &p, real k = 1) const
    {
        p.incx(ax, k * dx);
        testx(p);
    }
    void incy(point &p, real k = 1) const
    {
        p.incy(ay, k * dy);
        testy(p);
    }

public:
    int nx, ny;
    const axis_type &ax;
    const axis_type &ay;
    real x0, y0;
    real dx, dy;
};

#endif
