//----------------------------------------------------------------------------
// Anti-Grain Geometry (AGG) - Version 2.5
// A high quality rendering engine for C++
// Copyright (C) 2002-2006 Maxim Shemanarev
// Contact: mcseem@antigrain.com
//          mcseemagg@yahoo.com
//          http://antigrain.com
// 
// AGG is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// AGG 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 for more details.
// 
// You should have received a copy of the GNU General Public License
// along with AGG; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
// MA 02110-1301, USA.
//----------------------------------------------------------------------------

#ifndef AGG_RASTERIZER_SL_CLIP_INCLUDED
#define AGG_RASTERIZER_SL_CLIP_INCLUDED

#include "agg_clip_liang_barsky.h"

namespace agg
{
    //--------------------------------------------------------poly_max_coord_e
    enum poly_max_coord_e
    {
        poly_max_coord = (1 << 30) - 1 //----poly_max_coord
    };
    
    //------------------------------------------------------------ras_conv_int
    struct ras_conv_int
    {
        typedef int coord_type;
        static AGG_INLINE int mul_div(double a, double b, double c)
        {
            return iround(a * b / c);
        }
        static int xi(int v) { return v; }
        static int yi(int v) { return v; }
        static int upscale(double v) { return iround(v * poly_subpixel_scale); }
        static int downscale(int v)  { return v; }
    };

    //--------------------------------------------------------ras_conv_int_sat
    struct ras_conv_int_sat
    {
        typedef int coord_type;
        static AGG_INLINE int mul_div(double a, double b, double c)
        {
            return saturation<poly_max_coord>::iround(a * b / c);
        }
        static int xi(int v) { return v; }
        static int yi(int v) { return v; }
        static int upscale(double v) 
        { 
            return saturation<poly_max_coord>::iround(v * poly_subpixel_scale); 
        }
        static int downscale(int v) { return v; }
    };

    //---------------------------------------------------------ras_conv_int_3x
    struct ras_conv_int_3x
    {
        typedef int coord_type;
        static AGG_INLINE int mul_div(double a, double b, double c)
        {
            return iround(a * b / c);
        }
        static int xi(int v) { return v * 3; }
        static int yi(int v) { return v; }
        static int upscale(double v) { return iround(v * poly_subpixel_scale); }
        static int downscale(int v)  { return v; }
    };

    //-----------------------------------------------------------ras_conv_dbl
    struct ras_conv_dbl
    {
        typedef double coord_type;
        static AGG_INLINE double mul_div(double a, double b, double c)
        {
            return a * b / c;
        }
        static int xi(double v) { return iround(v * poly_subpixel_scale); }
        static int yi(double v) { return iround(v * poly_subpixel_scale); }
        static double upscale(double v) { return v; }
        static double downscale(int v)  { return v / double(poly_subpixel_scale); }
    };

    //--------------------------------------------------------ras_conv_dbl_3x
    struct ras_conv_dbl_3x
    {
        typedef double coord_type;
        static AGG_INLINE double mul_div(double a, double b, double c)
        {
            return a * b / c;
        }
        static int xi(double v) { return iround(v * poly_subpixel_scale * 3); }
        static int yi(double v) { return iround(v * poly_subpixel_scale); }
        static double upscale(double v) { return v; }
        static double downscale(int v)  { return v / double(poly_subpixel_scale); }
    };





    //------------------------------------------------------rasterizer_sl_clip
    template<class Conv> class rasterizer_sl_clip
    {
    public:
        typedef Conv                      conv_type;
        typedef typename Conv::coord_type coord_type;
        typedef rect_base<coord_type>     rect_type;

        //--------------------------------------------------------------------
        rasterizer_sl_clip() :  
            m_clip_box(0,0,0,0),
            m_x1(0),
            m_y1(0),
            m_f1(0),
            m_clipping(false) 
        {}

        //--------------------------------------------------------------------
        void reset_clipping()
        {
            m_clipping = false;
        }

        //--------------------------------------------------------------------
        void clip_box(coord_type x1, coord_type y1, coord_type x2, coord_type y2)
        {
            m_clip_box = rect_type(x1, y1, x2, y2);
            m_clip_box.normalize();
            m_clipping = true;
        }

        //--------------------------------------------------------------------
        void move_to(coord_type x1, coord_type y1)
        {
            m_x1 = x1;
            m_y1 = y1;
            if(m_clipping) m_f1 = clipping_flags(x1, y1, m_clip_box);
        }

    private:
        //------------------------------------------------------------------------
        template<class Rasterizer>
        AGG_INLINE void line_clip_y(Rasterizer& ras,
                                    coord_type x1, coord_type y1, 
                                    coord_type x2, coord_type y2, 
                                    unsigned   f1, unsigned   f2) const
        {
            f1 &= 10;
            f2 &= 10;
            if((f1 | f2) == 0)
            {
                // Fully visible
                ras.line(Conv::xi(x1), Conv::yi(y1), Conv::xi(x2), Conv::yi(y2)); 
            }
            else
            {
                if(f1 == f2)
                {
                    // Invisible by Y
                    return;
                }

                coord_type tx1 = x1;
                coord_type ty1 = y1;
                coord_type tx2 = x2;
                coord_type ty2 = y2;

                if(f1 & 8) // y1 < clip.y1
                {
                    tx1 = x1 + Conv::mul_div(m_clip_box.y1-y1, x2-x1, y2-y1);
                    ty1 = m_clip_box.y1;
                }

                if(f1 & 2) // y1 > clip.y2
                {
                    tx1 = x1 + Conv::mul_div(m_clip_box.y2-y1, x2-x1, y2-y1);
                    ty1 = m_clip_box.y2;
                }

                if(f2 & 8) // y2 < clip.y1
                {
                    tx2 = x1 + Conv::mul_div(m_clip_box.y1-y1, x2-x1, y2-y1);
                    ty2 = m_clip_box.y1;
                }

                if(f2 & 2) // y2 > clip.y2
                {
                    tx2 = x1 + Conv::mul_div(m_clip_box.y2-y1, x2-x1, y2-y1);
                    ty2 = m_clip_box.y2;
                }
                ras.line(Conv::xi(tx1), Conv::yi(ty1), 
                         Conv::xi(tx2), Conv::yi(ty2)); 
            }
        }


    public:
        //--------------------------------------------------------------------
        template<class Rasterizer>
        void line_to(Rasterizer& ras, coord_type x2, coord_type y2)
        {
            if(m_clipping)
            {
                unsigned f2 = clipping_flags(x2, y2, m_clip_box);

                if((m_f1 & 10) == (f2 & 10) && (m_f1 & 10) != 0)
                {
                    // Invisible by Y
                    m_x1 = x2;
                    m_y1 = y2;
                    m_f1 = f2;
                    return;
                }

                coord_type x1 = m_x1;
                coord_type y1 = m_y1;
                unsigned   f1 = m_f1;
                coord_type y3, y4;
                unsigned   f3, f4;

                switch(((f1 & 5) << 1) | (f2 & 5))
                {
                case 0: // Visible by X
                    line_clip_y(ras, x1, y1, x2, y2, f1, f2);
                    break;

                case 1: // x2 > clip.x2
                    y3 = y1 + Conv::mul_div(m_clip_box.x2-x1, y2-y1, x2-x1);
                    f3 = clipping_flags_y(y3, m_clip_box);
                    line_clip_y(ras, x1, y1, m_clip_box.x2, y3, f1, f3);
                    line_clip_y(ras, m_clip_box.x2, y3, m_clip_box.x2, y2, f3, f2);
                    break;

                case 2: // x1 > clip.x2
                    y3 = y1 + Conv::mul_div(m_clip_box.x2-x1, y2-y1, x2-x1);
                    f3 = clipping_flags_y(y3, m_clip_box);
                    line_clip_y(ras, m_clip_box.x2, y1, m_clip_box.x2, y3, f1, f3);
                    line_clip_y(ras, m_clip_box.x2, y3, x2, y2, f3, f2);
                    break;

                case 3: // x1 > clip.x2 && x2 > clip.x2
                    line_clip_y(ras, m_clip_box.x2, y1, m_clip_box.x2, y2, f1, f2);
                    break;

                case 4: // x2 < clip.x1
                    y3 = y1 + Conv::mul_div(m_clip_box.x1-x1, y2-y1, x2-x1);
                    f3 = clipping_flags_y(y3, m_clip_box);
                    line_clip_y(ras, x1, y1, m_clip_box.x1, y3, f1, f3);
                    line_clip_y(ras, m_clip_box.x1, y3, m_clip_box.x1, y2, f3, f2);
                    break;

                case 6: // x1 > clip.x2 && x2 < clip.x1
                    y3 = y1 + Conv::mul_div(m_clip_box.x2-x1, y2-y1, x2-x1);
                    y4 = y1 + Conv::mul_div(m_clip_box.x1-x1, y2-y1, x2-x1);
                    f3 = clipping_flags_y(y3, m_clip_box);
                    f4 = clipping_flags_y(y4, m_clip_box);
                    line_clip_y(ras, m_clip_box.x2, y1, m_clip_box.x2, y3, f1, f3);
                    line_clip_y(ras, m_clip_box.x2, y3, m_clip_box.x1, y4, f3, f4);
                    line_clip_y(ras, m_clip_box.x1, y4, m_clip_box.x1, y2, f4, f2);
                    break;

                case 8: // x1 < clip.x1
                    y3 = y1 + Conv::mul_div(m_clip_box.x1-x1, y2-y1, x2-x1);
                    f3 = clipping_flags_y(y3, m_clip_box);
                    line_clip_y(ras, m_clip_box.x1, y1, m_clip_box.x1, y3, f1, f3);
                    line_clip_y(ras, m_clip_box.x1, y3, x2, y2, f3, f2);
                    break;

                case 9:  // x1 < clip.x1 && x2 > clip.x2
                    y3 = y1 + Conv::mul_div(m_clip_box.x1-x1, y2-y1, x2-x1);
                    y4 = y1 + Conv::mul_div(m_clip_box.x2-x1, y2-y1, x2-x1);
                    f3 = clipping_flags_y(y3, m_clip_box);
                    f4 = clipping_flags_y(y4, m_clip_box);
                    line_clip_y(ras, m_clip_box.x1, y1, m_clip_box.x1, y3, f1, f3);
                    line_clip_y(ras, m_clip_box.x1, y3, m_clip_box.x2, y4, f3, f4);
                    line_clip_y(ras, m_clip_box.x2, y4, m_clip_box.x2, y2, f4, f2);
                    break;

                case 12: // x1 < clip.x1 && x2 < clip.x1
                    line_clip_y(ras, m_clip_box.x1, y1, m_clip_box.x1, y2, f1, f2);
                    break;
                }
                m_f1 = f2;
            }
            else
            {
                ras.line(Conv::xi(m_x1), Conv::yi(m_y1), 
                         Conv::xi(x2),   Conv::yi(y2)); 
            }
            m_x1 = x2;
            m_y1 = y2;
        }


    private:
        rect_type        m_clip_box;
        coord_type       m_x1;
        coord_type       m_y1;
        unsigned         m_f1;
        bool             m_clipping;
    };




    //---------------------------------------------------rasterizer_sl_no_clip
    class rasterizer_sl_no_clip
    {
    public:
        typedef ras_conv_int conv_type;
        typedef int          coord_type;

        rasterizer_sl_no_clip() : m_x1(0), m_y1(0) {}

        void reset_clipping() {}
        void clip_box(coord_type x1, coord_type y1, coord_type x2, coord_type y2) {}
        void move_to(coord_type x1, coord_type y1) { m_x1 = x1; m_y1 = y1; }

        template<class Rasterizer>
        void line_to(Rasterizer& ras, coord_type x2, coord_type y2) 
        { 
            ras.line(m_x1, m_y1, x2, y2); 
            m_x1 = x2; 
            m_y1 = y2;
        }

    private:
        int m_x1, m_y1;
    };


    //                                         -----rasterizer_sl_clip_int
    //                                         -----rasterizer_sl_clip_int_sat
    //                                         -----rasterizer_sl_clip_int_3x
    //                                         -----rasterizer_sl_clip_dbl
    //                                         -----rasterizer_sl_clip_dbl_3x
    //------------------------------------------------------------------------
    typedef rasterizer_sl_clip<ras_conv_int>     rasterizer_sl_clip_int;
    typedef rasterizer_sl_clip<ras_conv_int_sat> rasterizer_sl_clip_int_sat;
    typedef rasterizer_sl_clip<ras_conv_int_3x>  rasterizer_sl_clip_int_3x;
    typedef rasterizer_sl_clip<ras_conv_dbl>     rasterizer_sl_clip_dbl;
    typedef rasterizer_sl_clip<ras_conv_dbl_3x>  rasterizer_sl_clip_dbl_3x;


}

#endif
