#include <math.h>
#include <stdio.h>
#include <time.h>
#include "agg_main/agg_basics.h"
#include "agg_main/agg_rendering_buffer.h"
#include "agg_main/agg_rasterizer_scanline_aa.h"
#include "agg_main/agg_rasterizer_outline.h"
#include "agg_main/agg_conv_transform.h"
#include "agg_main/agg_conv_stroke.h"
#include "agg_main/agg_scanline_p.h"
#include "agg_main/agg_renderer_scanline.h"
#include "agg_main/agg_renderer_primitives.h"
#include "agg_main/agg_rasterizer_outline.h"
#include "agg_main/agg_rasterizer_outline_aa.h"
#include "agg_main/agg_pattern_filters_rgba.h"
#include "agg_main/agg_renderer_outline_aa.h"
#include "agg_main/agg_renderer_outline_image.h"
#include "agg_ctrls/agg_slider_ctrl.h"
#include "agg_ctrls/agg_cbox_ctrl.h"
#include "agg_platform/agg_platform_support.h"

#define AGG_BGR24 
//#define AGG_RGB24
//#define AGG_BGRA32 
//#define AGG_RGBA32 
//#define AGG_ARGB32 
//#define AGG_ABGR32
//#define AGG_RGB565
//#define AGG_RGB555
#include "agg_main/pixel_formats.h"


enum flip_y_e { flip_y = true };

static const agg::int32u pixmap_chain[] = 
{
    16, 7,
    0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0xb4c29999, 0xff9a5757, 0xff9a5757, 0xff9a5757, 0xff9a5757, 0xff9a5757, 0xff9a5757, 0xb4c29999, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff,
    0x00ffffff, 0x00ffffff, 0x0cfbf9f9, 0xff9a5757, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xb4c29999, 0x00ffffff, 0x00ffffff, 0x00ffffff, 
    0x00ffffff, 0x5ae0cccc, 0xffa46767, 0xff660000, 0xff975252, 0x7ed4b8b8, 0x5ae0cccc, 0x5ae0cccc, 0x5ae0cccc, 0x5ae0cccc, 0xa8c6a0a0, 0xff7f2929, 0xff670202, 0x9ecaa6a6, 0x5ae0cccc, 0x00ffffff, 
    0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xa4c7a2a2, 0x3affff00, 0x3affff00, 0xff975151, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 
    0x00ffffff, 0x5ae0cccc, 0xffa46767, 0xff660000, 0xff954f4f, 0x7ed4b8b8, 0x5ae0cccc, 0x5ae0cccc, 0x5ae0cccc, 0x5ae0cccc, 0xa8c6a0a0, 0xff7f2929, 0xff670202, 0x9ecaa6a6, 0x5ae0cccc, 0x00ffffff, 
    0x00ffffff, 0x00ffffff, 0x0cfbf9f9, 0xff9a5757, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xff660000, 0xb4c29999, 0x00ffffff, 0x00ffffff, 0x00ffffff, 
    0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0xb4c29999, 0xff9a5757, 0xff9a5757, 0xff9a5757, 0xff9a5757, 0xff9a5757, 0xff9a5757, 0xb4c29999, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff
};



namespace agg
{
    class pattern_pixmap_argb32
    {
    public:
        typedef rgba8 color_type;

        pattern_pixmap_argb32(const int32u* pixmap) : m_pixmap(pixmap) {}

        unsigned width()  const { return m_pixmap[0]; }
        unsigned height() const { return m_pixmap[1]; }

        rgba8 pixel(int x, int y) const
        {
            int32u p = m_pixmap[y * width() + x + 2];
            return rgba8((p >> 16) & 0xFF, (p >> 8) & 0xFF, p & 0xFF, p >> 24);
        }
    private:
        const int32u* m_pixmap;
    };
}



class spiral
{
public:
    spiral(double x, double y, double r1, double r2, double step, double start_angle=0) :
        m_x(x), 
        m_y(y), 
        m_r1(r1), 
        m_r2(r2), 
        m_step(step), 
        m_start_angle(start_angle),
        m_angle(start_angle),
        m_da(agg::deg2rad(8.0)),
        m_dr(m_step / 45.0)
    {
    }

    void rewind(unsigned) 
    { 
        m_angle = m_start_angle; 
        m_curr_r = m_r1; 
        m_start = true; 
    }

    unsigned vertex(double* x, double* y)
    {
        if(m_curr_r > m_r2) return agg::path_cmd_stop;

        *x = m_x + cos(m_angle) * m_curr_r;
        *y = m_y + sin(m_angle) * m_curr_r;
        m_curr_r += m_dr;
        m_angle += m_da;
        if(m_start) 
        {
            m_start = false;
            return agg::path_cmd_move_to;
        }
        return agg::path_cmd_line_to;
    }

private:
    double m_x;
    double m_y;
    double m_r1;
    double m_r2;
    double m_step;
    double m_start_angle;

    double m_angle;
    double m_curr_r;
    double m_da;
    double m_dr;
    bool   m_start;
};


struct roundoff
{
    void transform(double* x, double* y) const
    { 
        *x = floor(*x);
        *y = floor(*y);
    }
};


class the_application : public agg::platform_support
{
    agg::slider_ctrl<agg::rgba8> m_step;
    agg::slider_ctrl<agg::rgba8> m_width;
    agg::cbox_ctrl<agg::rgba8>   m_test;
    agg::cbox_ctrl<agg::rgba8>   m_rotate;
    agg::cbox_ctrl<agg::rgba8>   m_accurate_joins;
    agg::cbox_ctrl<agg::rgba8>   m_scale_pattern;
    double m_start_angle;


public:
    typedef agg::renderer_base<pixfmt> renderer_base;
    typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_aa;
    typedef agg::renderer_primitives<renderer_base> renderer_prim;
    typedef agg::rasterizer_outline<renderer_prim> rasterizer_outline;
    typedef agg::rasterizer_scanline_aa<> rasterizer_scanline;
    typedef agg::scanline_p8 scanline;
    typedef agg::renderer_outline_aa<renderer_base> renderer_oaa;
    typedef agg::pattern_filter_bilinear_rgba8 pattern_filter;
    typedef agg::line_image_pattern_pow2<pattern_filter> image_pattern;
    typedef agg::renderer_outline_image<renderer_base, image_pattern> renderer_img;
    typedef agg::rasterizer_outline_aa<renderer_oaa> rasterizer_outline_aa;
    typedef agg::rasterizer_outline_aa<renderer_img> rasterizer_outline_img;
    


    the_application(agg::pix_format_e format, bool flip_y) :
        agg::platform_support(format, flip_y),
        m_step(10.0, 10.0 + 4.0, 150.0, 10.0 + 8.0 + 4.0, !flip_y),
        m_width(150.0 + 10.0, 10.0 + 4.0, 400 - 10.0, 10.0 + 8.0 + 4.0, !flip_y),
        m_test(10.0, 10.0 + 4.0 + 16.0,    "Test Performance", !flip_y),
        m_rotate(130 + 10.0, 10.0 + 4.0 + 16.0,    "Rotate", !flip_y),
        m_accurate_joins(200 + 10.0, 10.0 + 4.0 + 16.0, "Accurate Joins", !flip_y),
        m_scale_pattern(310 + 10.0, 10.0 + 4.0 + 16.0, "Scale Pattern", !flip_y),
        m_start_angle(0.0)
    {
        add_ctrl(m_step);
        m_step.range(0.0, 2.0);
        m_step.value(0.1);
        m_step.label("Step=%1.2f");
        m_step.no_transform();

        add_ctrl(m_width);
        m_width.range(0.0, 7.0);
        m_width.value(3.0);
        m_width.label("Width=%1.2f");
        m_width.no_transform();

        add_ctrl(m_test);
        m_test.text_size(9.0, 7.0);
        m_test.no_transform();

        add_ctrl(m_rotate);
        m_rotate.text_size(9.0, 7.0);
        m_rotate.no_transform();

        add_ctrl(m_accurate_joins);
        m_accurate_joins.text_size(9.0, 7.0);
        m_accurate_joins.no_transform();

        add_ctrl(m_scale_pattern);
        m_scale_pattern.text_size(9.0, 7.0);
        m_scale_pattern.no_transform();
        m_scale_pattern.status(true);
    }



    void draw_aliased_pix_accuracy(rasterizer_outline& ras, renderer_prim& prim)
    {
        spiral s1(width()/5, height()/4+50, 5, 70, 8, m_start_angle);
        roundoff rn;
        agg::conv_transform<spiral, roundoff> trans(s1, rn);
        prim.line_color(agg::rgba(0.4, 0.3, 0.1));
        ras.add_path(trans);
    }

    void draw_aliased_subpix_accuracy(rasterizer_outline& ras, renderer_prim& prim)
    {
        spiral s2(width()/2, height()/4+50, 5, 70, 8, m_start_angle);
        prim.line_color(agg::rgba(0.4, 0.3, 0.1));
        ras.add_path(s2);
    }

    void draw_anti_aliased_outline(rasterizer_outline_aa& ras, renderer_oaa& ren)
    {
        spiral s3(width()/5, height() - height()/4 + 20, 5, 70, 8, m_start_angle);
        ren.color(agg::rgba(0.4, 0.3, 0.1));
        ras.add_path(s3);
    }
    
    void draw_anti_aliased_scanline(rasterizer_scanline& ras, scanline& sl, renderer_aa& ren)
    {
        spiral s4(width()/2, height() - height()/4 + 20, 5, 70, 8, m_start_angle);
        agg::conv_stroke<spiral> stroke(s4);
        stroke.width(m_width.value());
        stroke.line_cap(agg::round_cap);
        ren.color(agg::rgba(0.4, 0.3, 0.1));
        ras.add_path(stroke);
        agg::render_scanlines(ras, sl, ren);
    }

    void draw_anti_aliased_outline_img(rasterizer_outline_img& ras, renderer_img& ren)
    {
        spiral s5(width() - width()/5, height() - height()/4 + 20, 5, 70, 8, m_start_angle);
        ras.add_path(s5);
    }
    


    void text(rasterizer_scanline& ras, 
              scanline& sl,
              renderer_aa& ren, 
              double x, double y, const char* txt)
    {
        agg::gsv_text t;
        t.size(8);
        t.text(txt);
        t.start_point(x, y);
        agg::conv_stroke<agg::gsv_text> stroke(t);
        stroke.width(0.7);
        ras.add_path(stroke);
        ren.color(agg::rgba(0,0,0));
        agg::render_scanlines(ras, sl, ren);
    }




    virtual void on_draw()
    {
        pixfmt pf(rbuf_window());
        renderer_base ren_base(pf);
        renderer_aa ren_aa(ren_base);
        renderer_prim ren_prim(ren_base);
        rasterizer_scanline ras_aa;
        scanline sl;
        rasterizer_outline ras_al(ren_prim);
        agg::line_profile_aa prof;
        prof.width(m_width.value());
        renderer_oaa ren_oaa(ren_base, prof);
        rasterizer_outline_aa ras_oaa(ren_oaa);
        ras_oaa.line_join(m_accurate_joins.status() ? 
                              agg::outline_miter_accurate_join :
                              agg::outline_round_join); 
        ras_oaa.round_cap(true);

        pattern_filter filter;
        agg::pattern_pixmap_argb32 src(pixmap_chain);
        agg::line_image_scale<agg::pattern_pixmap_argb32> src_scaled(src, m_width.value());
        image_pattern pattern(filter);
        if(m_scale_pattern.status())
        {
            pattern.create(src_scaled);
        }
        else
        {
            pattern.create(src);
        }
        renderer_img ren_img(ren_base, pattern);
        if(m_scale_pattern.status())
        {
            ren_img.scale_x(m_width.value() / src.height());
        }
        rasterizer_outline_img ras_img(ren_img);

        ren_base.clear(agg::rgba(1.0, 1.0, 0.95));

        draw_aliased_pix_accuracy(ras_al, ren_prim);
        draw_aliased_subpix_accuracy(ras_al, ren_prim);
        draw_anti_aliased_outline(ras_oaa, ren_oaa);
        draw_anti_aliased_scanline(ras_aa, sl, ren_aa);
        draw_anti_aliased_outline_img(ras_img, ren_img);

        text(ras_aa, sl, ren_aa, 50, 80, "Bresenham lines,\n\nregular accuracy");
        text(ras_aa, sl, ren_aa, width()/2-50, 80, "Bresenham lines,\n\nsubpixel accuracy");
        text(ras_aa, sl, ren_aa, 50, height()/2+50, "Anti-aliased lines");
        text(ras_aa, sl, ren_aa, width()/2-50, height()/2+50, "Scanline rasterizer");
        text(ras_aa, sl, ren_aa, width() - width()/5 - 50, height()/2+50, "Arbitrary Image Pattern");


        agg::render_ctrl(ras_aa, sl, ren_base, m_step);
        agg::render_ctrl(ras_aa, sl, ren_base, m_width);
        agg::render_ctrl(ras_aa, sl, ren_base, m_test);
        agg::render_ctrl(ras_aa, sl, ren_base, m_rotate);
        agg::render_ctrl(ras_aa, sl, ren_base, m_accurate_joins);
        agg::render_ctrl(ras_aa, sl, ren_base, m_scale_pattern);



/*
// An example of using anti-aliased outline rasterizer. 
// Uncomment it to see the result
//

// Includes:
//#include "agg_pixfmt_rgb.h"           // or another
//#include "agg_renderer_outline_aa.h"
//#include "agg_rasterizer_outline_aa.h"

typedef agg::renderer_base<agg::pixfmt_bgr24> base_ren_type;
typedef agg::renderer_outline_aa<base_ren_type> renderer_type;
typedef agg::rasterizer_outline_aa<renderer_type> rasterizer_type;

double width = 5.0;
//-- create with specifying width
//-- min_width=1.0, smoother_width=1.0
//agg::line_profile_aa(width, agg::gamma_none()); 

//-- create uninitialized and set parameters
agg::line_profile_aa profile;
profile.gamma(agg::gamma_power(1.2));  //optional
profile.min_width(0.75);               //optional
profile.smoother_width(3.0);           //optional
profile.width(width);                  //mandatory!

agg::pixfmt_bgr24 pixf(rbuf_window()); //or another
base_ren_type base_ren(pixf);
renderer_type ren(base_ren, profile);
ren.color(agg::rgba8(0,0,0));          //mandatory!
rasterizer_type ras(ren);
ras.round_cap(true);                   //optional
ras.accurate_join(true);               //optional

//-- move_to/line_to interface
ras.move_to_d(100, 100);
ras.line_to_d(150, 200);
ras.render(false);     //false means "don't close 
                       //the polygon", i.e. polyline

//-- add_path interface
//-- doesn't require invoking render()
//ras.add_path(some_path);
*/




/*
// An example of using image pattern outline rasterizer
// Uncomment it to see the result
//

// Includes:
//#include "agg_pixfmt_rgb.h"           // or another
//#include "agg_pattern_filters_rgba.h"  // for all rgba-8-bit color formats
//#include "agg_renderer_outline_image.h"
//#include "agg_rasterizer_outline_aa.h"


agg::pattern_filter_bilinear_rgba8 fltr;          // Filtering functor

agg::pattern_pixmap_argb32 patt_src(pixmap_chain); // Source. Must have an interface:
                                                   // width() const
                                                   // height() const
                                                   // pixel(int x, int y) const
                                                   // Any agg::renderer_base<> or derived
                                                   // is good for the use as a source.

// agg::line_image_pattern is the main container for the patterns. It creates
// a copy of the patterns extended according to the needs of the filter.
// agg::line_image_pattern can operate with arbitrary image width, but if the 
// width of the pattern is power of 2, it's better to use the modified
// version agg::line_image_pattern_pow2 because it works about 15-25 percent
// faster than agg::line_image_pattern (because of using simple masking instead 
// of expensive '%' operation). 
typedef agg::line_image_pattern_pow2<agg::pattern_filter_bilinear_rgba8> pattern_type;

typedef agg::renderer_base<agg::pixfmt_bgr24> base_ren_type;
typedef agg::renderer_outline_image<base_ren_type, pattern_type> renderer_type;
typedef agg::rasterizer_outline_aa<renderer_type>                rasterizer_type;

//-- Create with specifying the source
pattern_type patt(fltr, src);   

//-- Create uninitialized and set the source
//pattern_type patt(fltr);        
//patt.create(src);

agg::pixfmt_bgr24 pixf(rbuf_window()); //or another
base_ren_type base_ren(pixf);
renderer_type ren(base_ren, patt);
//ren.scale_x(1.3);                    // Optional
rasterizer_type ras(ren);

//-- move_to/line_to interface
ras.move_to_d(100, 150);
ras.line_to_d(0, 0);
ras.line_to_d(300, 200);
//ras.line_to_d(10, 10);
ras.render(false);     //false means "don't close 
                       //the polygon", i.e. polyline

//-- add_path interface
//-- doesn't require invoking render()
//ras.add_path(some_path);
*/

    }


    virtual void on_idle()
    {
        m_start_angle += agg::deg2rad(m_step.value());
        if(m_start_angle > agg::deg2rad(360.0)) m_start_angle -= agg::deg2rad(360.0);
        force_redraw();
    }

    virtual void on_ctrl_change()
    {
        wait_mode(!m_rotate.status());

        if(m_test.status())
        {
            on_draw();
            update_window();

            pixfmt pf(rbuf_window());
            renderer_base ren_base(pf);
            renderer_aa ren_aa(ren_base);
            renderer_prim ren_prim(ren_base);
            rasterizer_scanline ras_aa;
            scanline sl;
            rasterizer_outline ras_al(ren_prim);
            agg::line_profile_aa prof;
            prof.width(m_width.value());
            renderer_oaa ren_oaa(ren_base, prof);
            rasterizer_outline_aa ras_oaa(ren_oaa);
            ras_oaa.line_join(m_accurate_joins.status() ? 
                                  agg::outline_miter_accurate_join :
                                  agg::outline_round_join); 
            ras_oaa.round_cap(true);

            pattern_filter filter;
            agg::pattern_pixmap_argb32 src(pixmap_chain);
            agg::line_image_scale<agg::pattern_pixmap_argb32> src_scaled(src, m_width.value());
            image_pattern pattern(filter);
            if(m_scale_pattern.status())
            {
                pattern.create(src_scaled);
            }
            else
            {
                pattern.create(src);
            }
            renderer_img ren_img(ren_base, pattern);
            if(m_scale_pattern.status())
            {
                ren_img.scale_x(src.height() / m_width.value());
            }
            rasterizer_outline_img ras_img(ren_img);

            unsigned i;

            start_timer();
            for(i = 0; i < 200; i++)
            {
                draw_aliased_subpix_accuracy(ras_al, ren_prim);
                m_start_angle += agg::deg2rad(m_step.value());
            }
            double t2 = elapsed_time();

            start_timer();
            for(i = 0; i < 200; i++)
            {
                draw_anti_aliased_outline(ras_oaa, ren_oaa);
                m_start_angle += agg::deg2rad(m_step.value());
            }
            double t3 = elapsed_time();

            start_timer();
            for(i = 0; i < 200; i++)
            {
                draw_anti_aliased_scanline(ras_aa, sl, ren_aa);
                m_start_angle += agg::deg2rad(m_step.value());
            }
            double t4 = elapsed_time();

            start_timer();
            for(i = 0; i < 200; i++)
            {
                draw_anti_aliased_outline_img(ras_img, ren_img);
                m_start_angle += agg::deg2rad(m_step.value());
            }
            double t5 = elapsed_time();

            m_test.status(false);
            force_redraw();
            char buf[256];
            sprintf(buf, "Aliased=%1.2fms, Anti-Aliased=%1.2fms, Scanline=%1.2fms, Image-Pattern=%1.2fms", 
                          t2, t3, t4, t5);
            message(buf);
        }
    }
};


int agg_main(int argc, char* argv[])
{
    the_application app(pix_format, flip_y);
    app.caption("AGG Example. Line Join");

    if(app.init(500, 450, 0))
    {
        return app.run();
    }

    return 1;
}


