#include <stdio.h>
#include "agg_basics.h"
#include "agg_rendering_buffer.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_scanline_u.h"
#include "agg_renderer_scanline.h"
#include "agg_pixfmt_rgb.h"
#include "agg_gamma_lut.h"
#include "agg_conv_dash.h"
#include "agg_conv_stroke.h"
#include "agg_span_gradient.h"
#include "agg_span_interpolator_linear.h"
#include "agg_span_gouraud_rgba.h"
#include "agg_span_allocator.h"
#include "platform/agg_platform_support.h"
#include "ctrl/agg_slider_ctrl.h"
#include "ctrl/agg_cbox_ctrl.h"


enum flip_y_e { flip_y = false };



typedef agg::gamma_lut<agg::int8u, agg::int8u, 8, 8>        gamma_lut_type;
typedef agg::pixfmt_bgr24_gamma<gamma_lut_type>             pixfmt_type;
typedef pixfmt_type::color_type                             color_type;
typedef agg::renderer_base<pixfmt_type>                     renderer_base_type;
typedef agg::renderer_scanline_aa_solid<renderer_base_type> renderer_scanline_type;
typedef agg::scanline_u8                                    scanline_type;
typedef agg::rasterizer_scanline_aa<>                       rasterizer_type;

template<class T> T min(T a, T b) { return (a < b) ? a : b; }

inline double frand(double x)
{ 
    return ((((rand() << 15) | rand()) & 0x3FFFFFFF) % 1000000) * x / 1000000.0;
}
 
 


class simple_vertex_source
{
public:
    simple_vertex_source() : m_num_vertices(0), m_count(0) 
    { 
        m_cmd[0] = agg::path_cmd_stop;
    }


    simple_vertex_source(double x1, double y1, double x2, double y2)
    {
        init(x1, y1, x2, y2);
    }


    simple_vertex_source(double x1, double y1, 
                         double x2, double y2,
                         double x3, double y3)
    {
        init(x1, y1, x2, y2, x3, y3);
    }

    void init(double x1, double y1, double x2, double y2)
    {
        m_num_vertices = 2;
        m_count = 0;
        m_x[0] = x1;
        m_y[0] = y1;
        m_x[1] = x2;
        m_y[1] = y2;
        m_cmd[0] = agg::path_cmd_move_to;
        m_cmd[1] = agg::path_cmd_line_to;
        m_cmd[2] = agg::path_cmd_stop;
    }



    void init(double x1, double y1, 
              double x2, double y2,
              double x3, double y3)
    {
        m_num_vertices = 3;
        m_count = 0;
        m_x[0] = x1;
        m_y[0] = y1;
        m_x[1] = x2;
        m_y[1] = y2;
        m_x[2] = x3;
        m_y[2] = y3;
        m_x[3] = m_y[3] = m_x[4] = m_y[4] = 0.0;
        m_cmd[0] = agg::path_cmd_move_to;
        m_cmd[1] = agg::path_cmd_line_to;
        m_cmd[2] = agg::path_cmd_line_to;
        m_cmd[3] = agg::path_cmd_end_poly | agg::path_flags_close;
        m_cmd[4] = agg::path_cmd_stop;
    }


    void rewind(unsigned)
    {
        m_count = 0;
    }

    unsigned vertex(double* x, double* y)
    {
        *x = m_x[m_count];
        *y = m_y[m_count];
        return m_cmd[m_count++];
    }

private:
    unsigned m_num_vertices;
    unsigned m_count;
    double   m_x[8];
    double   m_y[8];
    unsigned m_cmd[8];
};





template<class Ras, class Ren, class Scanline> class dashed_line
{
public:
    dashed_line(Ras& ras, Ren& ren, Scanline& sl) : 
        m_ras(ras), m_ren(ren), m_sl(sl),
        m_src(),
        m_dash(m_src),
        m_stroke(m_src),
        m_dash_stroke(m_dash)
    {}

    void draw(double x1, double y1, double x2, double y2, 
              double line_width, double dash_length)
    {
        m_src.init(x1 + 0.5, y1 + 0.5, x2 + 0.5, y2 + 0.5);
        m_ras.reset();
        if(dash_length > 0.0)
        {
            m_dash.remove_all_dashes();
            m_dash.add_dash(dash_length, dash_length);
            m_dash_stroke.width(line_width);
            m_dash_stroke.line_cap(agg::round_cap);
            m_ras.add_path(m_dash_stroke);
        }
        else
        {
            m_stroke.width(line_width);
            m_stroke.line_cap(agg::round_cap);
            m_ras.add_path(m_stroke);
        }
        agg::render_scanlines(m_ras, m_sl, m_ren);
    }

private:
    Ras&      m_ras;
    Ren&      m_ren;
    Scanline& m_sl;
    simple_vertex_source m_src;
    agg::conv_dash<simple_vertex_source> m_dash;
    agg::conv_stroke<simple_vertex_source> m_stroke;
    agg::conv_stroke<agg::conv_dash<simple_vertex_source> > m_dash_stroke;
};



// Calculate the affine transformation matrix for the linear gradient 
// from (x1, y1) to (x2, y2). gradient_d2 is the "base" to scale the
// gradient. Here d1 must be 0.0, and d2 must equal gradient_d2.
//---------------------------------------------------------------
void calc_linear_gradient_transform(double x1, double y1, double x2, double y2, 
                                    agg::trans_affine& mtx,
                                    double gradient_d2 = 100.0)
{
    double dx = x2 - x1;
    double dy = y2 - y1;
    mtx.reset();
    mtx *= agg::trans_affine_scaling(sqrt(dx * dx + dy * dy) / gradient_d2);
    mtx *= agg::trans_affine_rotation(atan2(dy, dx));
    mtx *= agg::trans_affine_translation(x1 + 0.5, y1 + 0.5);
    mtx.invert();
}


// A simple function to form the gradient color array 
// consisting of 3 colors, "begin", "middle", "end"
//---------------------------------------------------
template<class ColorArrayT>
void fill_color_array(ColorArrayT& array, 
                      color_type begin, 
                      color_type end)
{
    unsigned i;
    for(i = 0; i < 256; ++i)
    {
        array[i] = begin.gradient(end, i / 255.0);
    }
}






class the_application : public agg::platform_support
{
    gamma_lut_type              m_gamma;
    agg::slider_ctrl<agg::rgba> m_slider_gamma;

public:
    the_application(agg::pix_format_e format, bool flip_y) :
        agg::platform_support(format, flip_y),
        m_gamma(1.0),
        m_slider_gamma(3, 3,    480-3, 8,    !flip_y)
    {
        add_ctrl(m_slider_gamma);
        m_slider_gamma.range(0.1, 3.0);
        m_slider_gamma.value(1.6);
        m_slider_gamma.label("Gamma=%4.3f");
    }


    virtual ~the_application()
    {
    }


    virtual void on_init()
    {
    }


    virtual void on_draw()
    {
        pixfmt_type pixf(rbuf_window(), m_gamma);
        renderer_base_type ren_base(pixf);
        renderer_scanline_type ren_sl(ren_base);
        scanline_type sl;
        rasterizer_type ras;

        ren_base.clear(agg::rgba(0,0,0));


        // gamma correction
        //ras.gamma(agg::gamma_power());
        m_gamma.gamma(m_slider_gamma.value());

        int i;

        // radial line test
        //-------------------------
        dashed_line<rasterizer_type, 
                    renderer_scanline_type, 
                    scanline_type> dash(ras, ren_sl, sl);

        double cx = width() / 2.0;
        double cy = height() / 2.0;

        ren_sl.color(agg::rgba(1.0, 1.0, 1.0, 0.2));
        for(i = 180; i > 0; i--) 
        {
            double n = 2.0 * agg::pi * i / 180.0;
            dash.draw(cx + min(cx, cy) * sin(n), cy + min(cx, cy) * cos(n),
                      cx, cy, 
                      1.0, (i < 90) ? i : 0.0);
        }


        typedef agg::gradient_x gradient_func_type;
        typedef agg::span_interpolator_linear<> interpolator_type;
        typedef agg::span_allocator<color_type> span_allocator_type;
        typedef agg::pod_auto_array<color_type, 256> color_array_type;
        typedef agg::span_gradient<color_type, 
                                   interpolator_type, 
                                   gradient_func_type, 
                                   color_array_type> span_gradient_type;

        typedef agg::renderer_scanline_aa<renderer_base_type, 
                                          span_allocator_type,
                                          span_gradient_type> renderer_gradient_type;

        gradient_func_type  gradient_func;                   // The gradient function
        agg::trans_affine   gradient_mtx;                    // Affine transformer
        interpolator_type   span_interpolator(gradient_mtx); // Span interpolator
        span_allocator_type span_allocator;                  // Span Allocator
        color_array_type    gradient_colors;                 // The gradient colors
        span_gradient_type  span_gradient(span_interpolator, 
                                          gradient_func, 
                                          gradient_colors, 
                                          0, 100);

        renderer_gradient_type ren_gradient(ren_base, span_allocator, span_gradient);

        dashed_line<rasterizer_type, 
                    renderer_gradient_type, 
                    scanline_type> dash_gradient(ras, ren_gradient, sl);

        double x1, y1, x2, y2;

        for(i = 1; i <= 20; i++)
        {
            ren_sl.color(agg::rgba(1,1,1));

            // integral point sizes 1..20
            //----------------
            agg::ellipse ell;
            
            ell.init(20 + i * (i + 1) + 0.5, 
                     20.5, 
                     i / 2.0, 
                     i / 2.0, 
                     8 + i);
            ras.reset();
            ras.add_path(ell);
            agg::render_scanlines(ras, sl, ren_sl);
            

            // fractional point sizes 0..2
            //----------------
            ell.init(18 + i * 4 + 0.5, 33 + 0.5, 
                     i/20.0, i/20.0, 
                     8);
            ras.reset();
            ras.add_path(ell);
            agg::render_scanlines(ras, sl, ren_sl);


            // fractional point positioning
            //---------------
            ell.init(18 + i * 4 + (i-1) / 10.0 + 0.5, 
                     27 + (i - 1) / 10.0 + 0.5, 
                     0.5, 0.5, 8);
            ras.reset();
            ras.add_path(ell);
            agg::render_scanlines(ras, sl, ren_sl);


            // integral line widths 1..20
            //----------------
            fill_color_array(gradient_colors, 
                             agg::rgba(1,1,1), 
                             agg::rgba(i % 2, (i % 3) * 0.5, (i % 5) * 0.25));

            x1 = 20 + i* (i + 1);
            y1 = 40.5;
            x2 = 20 + i * (i + 1) + (i - 1) * 4;
            y2 = 100.5;
            calc_linear_gradient_transform(x1, y1, x2, y2, gradient_mtx);
            dash_gradient.draw(x1, y1, x2, y2, i, 0);


            fill_color_array(gradient_colors, 
                             agg::rgba(1,0,0), 
                             agg::rgba(0,0,1));

            // fractional line lengths H (red/blue)
            //----------------
            x1 = 17.5 + i * 4;
            y1 = 107;
            x2 = 17.5 + i * 4 + i/6.66666667;
            y2 = 107;
            calc_linear_gradient_transform(x1, y1, x2, y2, gradient_mtx);
            dash_gradient.draw(x1, y1, x2, y2, 1.0, 0);


            // fractional line lengths V (red/blue)
            //---------------
            x1 = 18 + i * 4;
            y1 = 112.5;
            x2 = 18 + i * 4;
            y2 = 112.5 + i / 6.66666667;
            calc_linear_gradient_transform(x1, y1, x2, y2, gradient_mtx);
            dash_gradient.draw(x1, y1, x2, y2, 1.0, 0);

            // fractional line positioning (red)
            //---------------
            fill_color_array(gradient_colors, 
                             agg::rgba(1,0,0), 
                             agg::rgba(1,1,1));
            x1 = 21.5;
            y1 = 120 + (i - 1) * 3.1;
            x2 = 52.5;
            y2 = 120 + (i - 1) * 3.1;
            calc_linear_gradient_transform(x1, y1, x2, y2, gradient_mtx);
            dash_gradient.draw(x1, y1, x2, y2, 1.0, 0);


            // fractional line width 2..0 (green)
            fill_color_array(gradient_colors, 
                             agg::rgba(0,1,0), 
                             agg::rgba(1,1,1));
            x1 = 52.5;
            y1 = 118 + i * 3;
            x2 = 83.5;
            y2 = 118 + i * 3;
            calc_linear_gradient_transform(x1, y1, x2, y2, gradient_mtx);
            dash_gradient.draw(x1, y1, x2, y2, 2.0 - (i - 1) / 10.0, 0);

            // stippled fractional width 2..0 (blue)
            fill_color_array(gradient_colors, 
                             agg::rgba(0,0,1), 
                             agg::rgba(1,1,1));
            x1 = 83.5;
            y1 = 119 + i * 3;
            x2 = 114.5;
            y2 = 119 + i * 3;
            calc_linear_gradient_transform(x1, y1, x2, y2, gradient_mtx);
            dash_gradient.draw(x1, y1, x2, y2, 2.0 - (i - 1) / 10.0, 3.0);


            ren_sl.color(agg::rgba(1,1,1));
            if(i <= 10)
            {
                // integral line width, horz aligned (mipmap test)
                //-------------------
                dash.draw(125.5, 119.5 + (i + 2) * (i / 2.0),
                          135.5, 119.5 + (i + 2) * (i / 2.0),
                          i, 0.0);
            }

            // fractional line width 0..2, 1 px H
            //-----------------
            dash.draw(17.5 + i * 4, 192, 18.5 + i * 4, 192, i / 10.0, 0);

            // fractional line positioning, 1 px H
            //-----------------
            dash.draw(17.5 + i * 4 + (i - 1) / 10.0, 186, 
                      18.5 + i * 4 + (i - 1) / 10.0, 186,
                      1.0, 0);
        }


        // Triangles
        //---------------
        for (i = 1; i <= 13; i++) 
        {
            fill_color_array(gradient_colors, 
                             agg::rgba(1,1,1), 
                             agg::rgba(i % 2, (i % 3) * 0.5, (i % 5) * 0.25));
            calc_linear_gradient_transform(width()  - 150, 
                                           height() - 20 - i * (i + 1.5),
                                           width()  - 20,  
                                           height() - 20 - i * (i + 1),
                                           gradient_mtx);
            ras.reset();
            ras.move_to_d(width() - 150, height() - 20 - i * (i + 1.5));
            ras.line_to_d(width() - 20,  height() - 20 - i * (i + 1));
            ras.line_to_d(width() - 20,  height() - 20 - i * (i + 2));
            agg::render_scanlines(ras, sl, ren_gradient);
        }


        // Reset AA Gamma and render the controls
        ras.gamma(agg::gamma_power(1.0));
        agg::render_ctrl(ras, sl, ren_base, m_slider_gamma);
    }



    virtual void on_mouse_button_down(int x, int y, unsigned flags)
    {
        srand(123);
        pixfmt_type pixf(rbuf_window(), m_gamma);
        renderer_base_type ren_base(pixf);
        renderer_scanline_type ren_sl(ren_base);
        scanline_type sl;
        rasterizer_type ras;

        ren_base.clear(agg::rgba(0,0,0));

        int i;

        double w = width();
        double h = height();

        agg::ellipse ell;

        start_timer();
        for(i = 0; i < 20000; i++)
        {
            double r = frand(20.0) + 1.0;
            ell.init(frand(w), frand(h), r/2, r/2, int(r) + 10);
            ras.reset();
            ras.add_path(ell);
            agg::render_scanlines(ras, sl, ren_sl);
            ren_sl.color(agg::rgba(frand(1.0), frand(1.0), frand(1.0), 0.5+frand(0.5)));
        }
        double t1 = elapsed_time();

        typedef agg::gradient_x gradient_func_type;
        typedef agg::span_interpolator_linear<> interpolator_type;
        typedef agg::span_allocator<color_type> span_allocator_type;
        typedef agg::pod_auto_array<color_type, 256> color_array_type;
        typedef agg::span_gradient<color_type, 
                                   interpolator_type, 
                                   gradient_func_type, 
                                   color_array_type> span_gradient_type;
        typedef agg::renderer_scanline_aa<renderer_base_type, 
                                          span_allocator_type,
                                          span_gradient_type> renderer_gradient_type;

        gradient_func_type  gradient_func;                   // The gradient function
        agg::trans_affine   gradient_mtx;                    // Affine transformer
        interpolator_type   span_interpolator(gradient_mtx); // Span interpolator
        span_allocator_type span_allocator;                  // Span Allocator
        color_array_type    gradient_colors;
        span_gradient_type  span_gradient(span_interpolator, 
                                          gradient_func, 
                                          gradient_colors, 
                                          0, 100);
        renderer_gradient_type ren_gradient(ren_base, 
                                            span_allocator, 
                                            span_gradient);
        dashed_line<rasterizer_type, 
                    renderer_gradient_type, 
                    scanline_type> dash_gradient(ras, ren_gradient, sl);

        double x1, y1, x2, y2, x3, y3;

        start_timer();
        for(i = 0; i < 2000; i++)
        {
			x1 = frand(w); 
            y1 = frand(h);
            x2 = x1 + frand(w * 0.5) - w * 0.25;
            y2 = y1 + frand(h * 0.5) - h * 0.25;

            fill_color_array(gradient_colors, 
                             agg::rgba(frand(1.0), frand(1.0), frand(1.0), 0.5+frand(0.5)), 
                             agg::rgba(frand(1.0), frand(1.0), frand(1.0), frand(1.0)));
            calc_linear_gradient_transform(x1, y1, x2, y2, gradient_mtx);
            dash_gradient.draw(x1, y1, x2, y2, 10.0, 0);
        }
        double t2 = elapsed_time();



        typedef agg::span_gouraud_rgba<color_type> gouraud_span_gen_type;
        typedef agg::renderer_scanline_aa<renderer_base_type, 
                                          span_allocator_type,
                                          gouraud_span_gen_type> renderer_gouraud_type;
        
        gouraud_span_gen_type span_gouraud;
        renderer_gouraud_type ren_gouraud(ren_base, span_allocator, span_gouraud);


        start_timer();
        for(i = 0; i < 2000; i++)
        {
			x1 = frand(w); 
            y1 = frand(h);
            x2 = x1 + frand(w * 0.4) - w * 0.2;
            y2 = y1 + frand(h * 0.4) - h * 0.2;
            x3 = x1 + frand(w * 0.4) - w * 0.2;
            y3 = y1 + frand(h * 0.4) - h * 0.2;

            span_gouraud.colors(agg::rgba(frand(1.0), frand(1.0), frand(1.0), 0.5+frand(0.5)),
                                agg::rgba(frand(1.0), frand(1.0), frand(1.0), frand(1.0)),
                                agg::rgba(frand(1.0), frand(1.0), frand(1.0), frand(1.0)));
            span_gouraud.triangle(x1, y1, x2, y2, x3, y3, 0.0);
            ras.add_path(span_gouraud);
            agg::render_scanlines(ras, sl, ren_gouraud);
        }

        double t3 = elapsed_time();

        char buf[256];
        sprintf(buf, "Points=%.2fK/sec, Lines=%.2fK/sec, Triangles=%.2fK/sec", 20000.0/t1, 2000.0/t2, 2000.0/t3);
        message(buf);
        update_window();
    }




    virtual void on_mouse_move(int x, int y, unsigned flags)
    {
    }

    virtual void on_mouse_button_up(int x, int y, unsigned flags)
    {
    }
};


int agg_main(int argc, char* argv[])
{
    the_application app(agg::pix_format_bgr24, flip_y);
    app.caption("AGG Example. Anti-Aliasing Test");

    if(app.init(480, 350, agg::window_resize))
    {
        return app.run();
    }
    return 1;
}


