//----------------------------------------------------------------------------
// Anti-Grain Geometry - Version 2.2
// Copyright (C) 2002-2004 Maxim Shemanarev (http://www.antigrain.com)
//
// 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
//----------------------------------------------------------------------------

#ifndef AGG_PATH_STORAGE_INCLUDED
#define AGG_PATH_STORAGE_INCLUDED

#include "agg_basics.h"

namespace agg
{

    //------------------------------------------------------------path_storage
    // A container to store vertices with their flags. 
    // A path consists of a number of contours separated with "move_to" 
    // commands. The path storage can keep and maintain more than one
    // path. 
    // To navigate to the beginning of a particular path, use rewind(path_id);
    // Where path_id is what start_new_path() returns. So, when you call
    // start_new_path() you need to store its return value somewhere else
    // to navigate to the path afterwards.
    //
    // See Implementation: agg_path_storage.cpp 
    // See also: vertex_source concept
    //------------------------------------------------------------------------
    class path_storage
    {
        // Allocation parameters
        enum
        {
            block_shift = 8,
            block_size  = 1 << block_shift,
            block_mask  = block_size - 1,
            block_pool  = 256
        };

    public:

        //--------------------------------------------------------------------
        class const_iterator
        {
            void vertex()
            {
                if(m_vertex_idx < m_path->total_vertices()) 
                {
                    m_vertex.cmd = m_path->vertex(m_vertex_idx, &m_vertex.x, &m_vertex.y);
                }
                else
                {
                    m_vertex.cmd = path_cmd_stop;
                    m_vertex.x = m_vertex.y = 0.0;
                }
            }

        public:
            const_iterator() {}
            const_iterator(unsigned cmd) { m_vertex.cmd = cmd; }
            const_iterator(const const_iterator& i) : 
                m_path(i.m_path),
                m_vertex_idx(i.m_vertex_idx),
                m_vertex(i.m_vertex) 
            {
            }

            const_iterator(const path_storage& p, unsigned id) : 
                m_path(&p),
                m_vertex_idx(id)
            {
                vertex();
            }

            const_iterator& operator++() 
            {
                ++m_vertex_idx;
                vertex();
                return *this;
            }

            const vertex_type& operator*() const { return m_vertex; }
            const vertex_type* operator->() const { return &m_vertex; }

            bool operator != (const const_iterator& i) 
            { 
                return m_vertex.cmd != i.m_vertex.cmd; 
            }

        private:
            const path_storage* m_path;
            unsigned            m_vertex_idx;
            vertex_type         m_vertex;
        };

        ~path_storage();
        path_storage();
        path_storage(const path_storage& ps);

        void remove_all();

        unsigned last_vertex(double* x, double* y) const;
        unsigned prev_vertex(double* x, double* y) const;

        void rel_to_abs(double* x, double* y) const;

        void move_to(double x, double y);
        void move_rel(double dx, double dy);

        void line_to(double x, double y);
        void line_rel(double dx, double dy);

        void arc_to(double rx, double ry,
                    double angle,
                    bool large_arc_flag,
                    bool sweep_flag,
                    double x, double y);

        void arc_rel(double rx, double ry,
                     double angle,
                     bool large_arc_flag,
                     bool sweep_flag,
                     double dx, double dy);

        void curve3(double x_ctrl, double y_ctrl, 
                    double x_to,   double y_to);

        void curve3_rel(double dx_ctrl, double dy_ctrl, 
                        double dx_to,   double dy_to);

        void curve3(double x_to, double y_to);

        void curve3_rel(double dx_to, double dy_to);

        void curve4(double x_ctrl1, double y_ctrl1, 
                    double x_ctrl2, double y_ctrl2, 
                    double x_to,    double y_to);

        void curve4_rel(double dx_ctrl1, double dy_ctrl1, 
                        double dx_ctrl2, double dy_ctrl2, 
                        double dx_to,    double dy_to);

        void curve4(double x_ctrl2, double y_ctrl2, 
                    double x_to,    double y_to);

        void curve4_rel(double x_ctrl2, double y_ctrl2, 
                        double x_to,    double y_to);


        void end_poly(unsigned flags = path_flags_close);

        void close_polygon(unsigned flags = path_flags_none)
        {
            end_poly(path_flags_close | flags);
        }

        void add_poly(const double* vertices, unsigned num, 
                      bool solid_path = false,
                      unsigned end_flags = path_flags_none);

        void add_vertices(const double* vertices, unsigned num)
        {
            add_poly(vertices, num, path_flags_none);
        }

        template<class VertexSource> 
        void add_path(VertexSource& vs, 
                      unsigned path_id = 0, 
                      bool solid_path = true)
        {
            double x, y;
            unsigned cmd;
            vs.rewind(path_id);
            while(!is_stop(cmd = vs.vertex(&x, &y)))
            {
                if(is_move_to(cmd) && solid_path && m_total_vertices) 
                {
                    cmd = path_cmd_line_to;
                }
                add_vertex(x, y, cmd);
            }
        }

        unsigned start_new_path();

        void copy_from(const path_storage& ps);
        const path_storage& operator = (const path_storage& ps)
        {
            copy_from(ps);
            return *this;
        }


        unsigned total_vertices() const { return m_total_vertices; }
        unsigned vertex(unsigned idx, double* x, double* y) const
        {
            unsigned nb = idx >> block_shift;
            const double* pv = m_coord_blocks[nb] + ((idx & block_mask) << 1);
            *x = *pv++;
            *y = *pv;
            return m_cmd_blocks[nb][idx & block_mask];
        }
        unsigned command(unsigned idx) const
        {
            return m_cmd_blocks[idx >> block_shift][idx & block_mask];
        }

        void     rewind(unsigned path_id);
        unsigned vertex(double* x, double* y);

        const_iterator begin(unsigned id) const { return const_iterator(*this, id); }
        const_iterator begin()            const { return const_iterator(*this, 0); }
        const_iterator end()              const { return const_iterator(path_cmd_stop); }

        // Arrange the orientation of all the polygons. After calling this
        // method all the polygons will have the same orientation
        // determined by the new_orientation flag, i.e., 
        // path_flags_cw or path_flags_ccw
        unsigned arrange_orientations(unsigned path_id, path_flags_e new_orientation);
        void arrange_orientations_all_paths(path_flags_e new_orientation);

        // Flip all the vertices horizontally or vertically
        void flip_x(double x1, double x2);
        void flip_y(double y1, double y2);
        
        // This function adds a vertex with its flags directly. Since there's no 
        // checking for errors, keeping proper path integrity is the responsibility
        // of the caller. It can be said the function is "not very public". 
        void add_vertex(double x, double y, unsigned cmd);

        // Allows you to modify vertex coordinates. The caller must know 
        // the index of the vertex. 
        void modify_vertex(unsigned idx, double x, double y)
        {
            double* pv = m_coord_blocks[idx >> block_shift] + ((idx & block_mask) << 1);
            *pv++ = x;
            *pv   = y;
        }

        // Allows you to modify vertex command. The caller must know 
        // the index of the vertex. 
        void modify_command(unsigned idx, unsigned cmd)
        {
            m_cmd_blocks[idx >> block_shift][idx & block_mask] = (unsigned char)cmd;
        }


    private:
        void allocate_block(unsigned nb);
        unsigned char* storage_ptrs(double** xy_ptr);
        unsigned perceive_polygon_orientation(unsigned idx, 
                                              double xs, double ys,
                                              unsigned* orientation);
        void reverse_polygon(unsigned start, unsigned end);

    private:
        unsigned        m_total_vertices;
        unsigned        m_total_blocks;
        unsigned        m_max_blocks;
        double**        m_coord_blocks;
        unsigned char** m_cmd_blocks;
        unsigned        m_iterator;
    };


    //------------------------------------------------------------------------
    inline unsigned path_storage::vertex(double* x, double* y)
    {
        if(m_iterator >= m_total_vertices) return path_cmd_stop;
        return vertex(m_iterator++, x, y);
    }

    //------------------------------------------------------------------------
    inline unsigned path_storage::prev_vertex(double* x, double* y) const
    {
        if(m_total_vertices > 1)
        {
            return vertex(m_total_vertices - 2, x, y);
        }
        return path_cmd_stop;
    }

    //------------------------------------------------------------------------
    inline unsigned path_storage::last_vertex(double* x, double* y) const
    {
        if(m_total_vertices)
        {
            return vertex(m_total_vertices - 1, x, y);
        }
        return path_cmd_stop;
    }

    //------------------------------------------------------------------------
    inline void path_storage::rel_to_abs(double* x, double* y) const
    {
        if(m_total_vertices)
        {
            double x2;
            double y2;
            if(is_vertex(vertex(m_total_vertices - 1, &x2, &y2)))
            {
                *x += x2;
                *y += y2;
            }
        }
    }

    //------------------------------------------------------------------------
    inline unsigned char* path_storage::storage_ptrs(double** xy_ptr)
    {
        unsigned nb = m_total_vertices >> block_shift;
        if(nb >= m_total_blocks)
        {
            allocate_block(nb);
        }
        *xy_ptr = m_coord_blocks[nb] + ((m_total_vertices & block_mask) << 1);
        return m_cmd_blocks[nb] + (m_total_vertices & block_mask);
    }


    //------------------------------------------------------------------------
    inline void path_storage::add_vertex(double x, double y, unsigned cmd)
    {
        double* coord_ptr = 0;
        unsigned char* cmd_ptr = storage_ptrs(&coord_ptr);
        *cmd_ptr = (unsigned char)cmd;
        *coord_ptr++ = x;
        *coord_ptr   = y;
        m_total_vertices++;
    }

    //------------------------------------------------------------------------
    inline void path_storage::move_to(double x, double y)
    {
        add_vertex(x, y, path_cmd_move_to);
    }

    //------------------------------------------------------------------------
    inline void path_storage::move_rel(double dx, double dy)
    {
        rel_to_abs(&dx, &dy);
        add_vertex(dx, dy, path_cmd_move_to);
    }

    //------------------------------------------------------------------------
    inline void path_storage::line_to(double x, double y)
    {
        add_vertex(x, y, path_cmd_line_to);
    }

    //------------------------------------------------------------------------
    inline void path_storage::line_rel(double dx, double dy)
    {
        rel_to_abs(&dx, &dy);
        add_vertex(dx, dy, path_cmd_line_to);
    }
}



#endif
