//----------------------------------------------------------------------------
// Anti-Grain Geometry - Version 2.3
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
// Copyright (c) 2008 Rene Rebe <rene@exactcode.de> [arc parser code]
// Copyright (c) 2017 John Horigan <john@glyphic.com> [align_subpath()]
//
// Permission to copy, use, modify, sell and distribute this software 
// is granted provided this copyright notice appears in all copies. 
// This software is provided "as is" without express or implied
// warranty, and with no claim as to its suitability for any purpose.
//
//----------------------------------------------------------------------------
// Contact: mcseem@antigrain.com
//          mcseemagg@yahoo.com
//          http://www.antigrain.com
//----------------------------------------------------------------------------
//
// SVG path renderer.
//
//----------------------------------------------------------------------------
#ifndef AGG_SVG_PATH_RENDERER_INCLUDED
#define AGG_SVG_PATH_RENDERER_INCLUDED

#include "agg_path_storage.h"
#include "agg_conv_transform.h"
#include "agg_conv_stroke.h"
#include "agg_conv_contour.h"
#include "agg_conv_curve.h"
#include "agg_color_rgba.h"
#include "agg_renderer_scanline.h"
#include "agg_bounding_rect.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_svg_path_tokenizer.h"

namespace agg
{
namespace svg
{
    template<class VertexSource> class conv_count
    {
    public:
        conv_count(VertexSource& vs) : m_source(&vs), m_count(0) {}

        void count(unsigned n) { m_count = n; }
        unsigned count() const { return m_count; }

        void rewind(unsigned path_id) { m_source->rewind(path_id); }
        unsigned vertex(double* x, double* y) 
        { 
            ++m_count; 
            return m_source->vertex(x, y); 
        }

    private:
        VertexSource* m_source;
        unsigned m_count;
    };




    //============================================================================
    // Basic path attributes
    struct path_attributes
    {
        unsigned     index;
        rgba8        fill_color;
        rgba8        stroke_color;
        bool         fill_flag;
        bool         stroke_flag;
        bool         even_odd_flag;
        line_join_e  line_join;
        line_cap_e   line_cap;
        double       miter_limit;
        double       stroke_width;
        trans_affine transform;

        // Empty constructor
        path_attributes() :
            index(0),
            fill_color(rgba(0,0,0)),
            stroke_color(rgba(0,0,0)),
            fill_flag(true),
            stroke_flag(false),
            even_odd_flag(false),
            line_join(miter_join),
            line_cap(butt_cap),
            miter_limit(4.0),
            stroke_width(1.0),
            transform()
        {
        }

        // Copy constructor
        path_attributes(const path_attributes& attr) :
            index(attr.index),
            fill_color(attr.fill_color),
            stroke_color(attr.stroke_color),
            fill_flag(attr.fill_flag),
            stroke_flag(attr.stroke_flag),
            even_odd_flag(attr.even_odd_flag),
            line_join(attr.line_join),
            line_cap(attr.line_cap),
            miter_limit(attr.miter_limit),
            stroke_width(attr.stroke_width),
            transform(attr.transform)
        {
        }

        // Copy constructor with new index value
        path_attributes(const path_attributes& attr, unsigned idx) :
            index(idx),
            fill_color(attr.fill_color),
            stroke_color(attr.stroke_color),
            fill_flag(attr.fill_flag),
            stroke_flag(attr.stroke_flag),
            even_odd_flag(attr.even_odd_flag),
            line_join(attr.line_join),
            line_cap(attr.line_cap),
            miter_limit(attr.miter_limit),
            stroke_width(attr.stroke_width),
            transform(attr.transform)
        {
        }
    };


    //============================================================================
    // Path container and renderer. 
    class path_renderer
    {
    public:
        typedef pod_bvector<path_attributes>   attr_storage;

        typedef conv_curve<path_storage>       curved;
        typedef conv_count<curved>             curved_count;

        typedef conv_stroke<curved_count>      curved_stroked;
        typedef conv_transform<curved_stroked> curved_stroked_trans;

        typedef conv_transform<curved_count>   curved_trans;
        typedef conv_contour<curved_trans>     curved_trans_contour;

        path_renderer();

        void remove_all();

        // Use these functions as follows:
        // begin_path() when the XML tag <path> comes ("start_element" handler)
        // parse_path() on "d=" tag attribute
        // end_path() when parsing of the entire tag is done.
        void begin_path();
        void parse_path(path_tokenizer& tok);
        void end_path();

        // The following functions are essentially a "reflection" of
        // the respective SVG path commands.
        void move_to(double x, double y, bool rel=false);   // M, m
        void line_to(double x,  double y, bool rel=false);  // L, l
        void hline_to(double x, bool rel=false);            // H, h
        void vline_to(double y, bool rel=false);            // V, v
        void curve3(double x1, double y1,                   // Q, q
                    double x,  double y, bool rel=false);
        void curve3(double x, double y, bool rel=false);    // T, t
        void curve4(double x1, double y1,                   // C, c
                    double x2, double y2, 
                    double x,  double y, bool rel=false);
        void curve4(double x2, double y2,                   // S, s
                    double x,  double y, bool rel=false);
        void arc(double rx, double ry, double angle,        // A, a
                 bool large_arc_flag, bool sweep_flag, double x, double y,
                 bool rel=false);
        void close_subpath();                               // Z, z
        void align_subpath(unsigned start_idx);

//        template<class VertexSource> 
//        void add_path(VertexSource& vs, 
//                      unsigned path_id = 0, 
//                      bool solid_path = true)
//        {
//            m_storage.add_path(vs, path_id, solid_path);
//        }


        unsigned vertex_count() const { return m_curved_count.count(); }
        

        // Call these functions on <g> tag (start_element, end_element respectively)
        void push_attr();
        void pop_attr();

        // Attribute setting functions.
        void fill(const rgba8& f);
        void stroke(const rgba8& s);
        void even_odd(bool flag);
        void stroke_width(double w);
        void fill_none();
        void stroke_none();
        void fill_opacity(double op);
        void stroke_opacity(double op);
        void line_join(line_join_e join);
        void line_cap(line_cap_e cap);
        void miter_limit(double ml);
        trans_affine& transform();

        // Make all polygons CCW-oriented
        void arrange_orientations()
        {
            m_storage.arrange_orientations_all_paths(path_flags_ccw);
        }

        // Expand all polygons 
        void expand(double value)
        {
            m_curved_trans_contour.width(value);
        }

        unsigned operator [](unsigned idx)
        {
            m_transform = m_attr_storage[idx].transform;
            return m_attr_storage[idx].index;
        }

        void bounding_rect(double* x1, double* y1, double* x2, double* y2)
        {
            agg::conv_transform<agg::path_storage> trans(m_storage, m_transform);
            agg::bounding_rect(trans, *this, 0, m_attr_storage.size(), x1, y1, x2, y2);
        }

        // Rendering. One can specify two additional parameters: 
        // trans_affine and opacity. They can be used to transform the whole
        // image and/or to make it translucent.
        template<class Rasterizer, class Scanline, class Renderer> 
        void render(Rasterizer& ras, 
                    Scanline& sl,
                    Renderer& ren, 
                    const trans_affine& mtx, 
                    const rect_i& cb,
                    double opacity=1.0)
        {
            unsigned i;

            ras.clip_box(cb.x1, cb.y1, cb.x2, cb.y2);
            m_curved_count.count(0);

            for(i = 0; i < m_attr_storage.size(); i++)
            {
                const path_attributes& attr = m_attr_storage[i];
                m_transform = attr.transform;
                m_transform *= mtx;
                double scl = m_transform.scale();
                //m_curved.approximation_method(curve_inc);
                m_curved.approximation_scale(scl);
                m_curved.angle_tolerance(0.0);

                rgba8 color;

                if(attr.fill_flag)
                {
                    ras.reset();
                    ras.filling_rule(attr.even_odd_flag ? fill_even_odd : fill_non_zero);
                    if(fabs(m_curved_trans_contour.width()) < 0.0001)
                    {
                        ras.add_path(m_curved_trans, attr.index);
                    }
                    else
                    {
                        m_curved_trans_contour.miter_limit(attr.miter_limit);
                        ras.add_path(m_curved_trans_contour, attr.index);
                    }

                    color = attr.fill_color;
                    color.opacity(color.opacity() * opacity);
                    ren.color(color);
                    agg::render_scanlines(ras, sl, ren);
                }

                if(attr.stroke_flag)
                {
                    m_curved_stroked.width(attr.stroke_width);
                    //m_curved_stroked.line_join((attr.line_join == miter_join) ? miter_join_round : attr.line_join);
                    m_curved_stroked.line_join(attr.line_join);
                    m_curved_stroked.line_cap(attr.line_cap);
                    m_curved_stroked.miter_limit(attr.miter_limit);
                    m_curved_stroked.inner_join(inner_round);
                    m_curved_stroked.approximation_scale(scl);

                    // If the *visual* line width is considerable we 
                    // turn on processing of curve cusps.
                    //---------------------
                    if(attr.stroke_width * scl > 1.0)
                    {
                        m_curved.angle_tolerance(0.2);
                    }
                    ras.reset();
                    ras.filling_rule(fill_non_zero);
                    ras.add_path(m_curved_stroked_trans, attr.index);
                    color = attr.stroke_color;
                    color.opacity(color.opacity() * opacity);
                    ren.color(color);
                    agg::render_scanlines(ras, sl, ren);
                }
            }
        }

    private:
        path_attributes& cur_attr();

        path_storage   m_storage;
        attr_storage   m_attr_storage;
        attr_storage   m_attr_stack;
        trans_affine   m_transform;

        curved                       m_curved;
        curved_count                 m_curved_count;

        curved_stroked               m_curved_stroked;
        curved_stroked_trans         m_curved_stroked_trans;

        curved_trans                 m_curved_trans;
        curved_trans_contour         m_curved_trans_contour;
    };

}
}

#endif
