#include <stdio.h>
#include "agg_main/agg_rendering_buffer.h"
#include "agg_main/agg_renderer_base.h"
#include "agg_main/agg_rasterizer_scanline_aa.h"
#include "agg_main/agg_scanline_u.h"
#include "agg_main/agg_renderer_scanline.h"
#include "agg_main/agg_rounded_rect.h"
#include "agg_main/agg_pixfmt_rgba.h"
#include "agg_main/agg_span_allocator.h"
#include "agg_main/agg_span_gradient.h"
#include "agg_main/agg_gsv_text.h"
#include "agg_main/agg_span_interpolator_linear.h"
#include "agg_platform/agg_platform_support.h"
#include "agg_ctrls/agg_slider_ctrl.h"
#include "agg_ctrls/agg_rbox_ctrl.h"


enum flip_y_e { flip_y = true };

typedef agg::rgba8 color;
typedef agg::order_bgra order;
typedef agg::int32u pixel_type;
typedef agg::rendering_buffer rbuf_type;
#define pix_format agg::pix_format_bgra32

typedef agg::blender_rgba<color, order> prim_blender_type; 
typedef agg::pixfmt_alpha_blend_rgba<prim_blender_type, rbuf_type> prim_pixfmt_type;
typedef agg::renderer_base<prim_pixfmt_type> prim_ren_base_type;

void force_comp_op_link()
{
    // For unknown reason Digital Mars C++ doesn't want to link these 
    // functions if they are not specified explicitly. 
    agg::int8u p[4] = {0};
    agg::comp_op_rgba_contrast   <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_darken     <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_lighten    <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_color_dodge<color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_color_burn <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_hard_light <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_soft_light <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_difference <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_exclusion  <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_src_atop   <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_dst_atop   <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_xor        <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_plus       <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_minus      <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_multiply   <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_screen     <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_overlay    <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_src        <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_dst        <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_src_over   <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_dst_over   <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_src_in     <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_dst_in     <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_src_out    <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_dst_out    <color, order>::blend_pix(p,0,0,0,0,0);
    agg::comp_op_rgba_clear      <color, order>::blend_pix(p,0,0,0,0,0);
}

namespace agg
{

    //========================================================================
    template<> struct gradient_linear_color<color>
    {
        typedef color color_type;
        enum base_scale_e { base_shift = color_type::base_shift };

        gradient_linear_color() {}
        gradient_linear_color(const color_type& c1, const color_type& c2) :
            m_c1(c1), m_c2(c2) {}

        static unsigned size() { return 256; }
        color_type operator [] (unsigned v) const 
        {
            color_type c;
            typedef color_type::value_type value_type;
            v <<= base_shift - 8;
            c.r = (value_type)((((m_c2.r - m_c1.r) * v) + (m_c1.r << base_shift)) >> base_shift);
            c.g = (value_type)((((m_c2.g - m_c1.g) * v) + (m_c1.g << base_shift)) >> base_shift);
            c.b = (value_type)((((m_c2.b - m_c1.b) * v) + (m_c1.b << base_shift)) >> base_shift);
            c.a = (value_type)((((m_c2.a - m_c1.a) * v) + (m_c1.a << base_shift)) >> base_shift);
            return c;
        }

        void colors(const color_type& c1, const color_type& c2)
        {
            m_c1 = c1;
            m_c2 = c2;
        }

        color_type m_c1;
        color_type m_c2;
    };

}




agg::trans_affine gradient_affine(double x1, double y1, double x2, double y2, 
                                  double gradient_d2 = 100.0)
{
    agg::trans_affine mtx;
    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, y1);
    mtx.invert();
    return mtx;
}



template<class RenBase> 
void circle(RenBase& rbase, color c1, color c2, 
            double x1, double y1, double x2, double y2,
            double shadow_alpha)
{
    typedef RenBase renderer_base_type;
    typedef agg::gradient_x gradient_func_type;
    typedef agg::gradient_linear_color<color> color_func_type;
    typedef agg::span_interpolator_linear<> interpolator_type;
    typedef agg::span_allocator<color> span_allocator_type;
    typedef agg::span_gradient<color, 
                               interpolator_type, 
                               gradient_func_type, 
                               color_func_type> span_gradient_type;

    gradient_func_type  gradient_func;                   // The gradient function
    agg::trans_affine   gradient_mtx = gradient_affine(x1, y1, x2, y2, 100);
    interpolator_type   span_interpolator(gradient_mtx); // Span interpolator
    span_allocator_type span_allocator;                  // Span Allocator
    color_func_type     color_func(c1, c2);
    span_gradient_type  span_gradient(span_interpolator, 
                                      gradient_func, 
                                      color_func, 
                                      0, 100);
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_u8 sl;

    double r = agg::calc_distance(x1, y1, x2, y2) / 2;
    agg::ellipse ell((x1+x2)/2+5, (y1+y2)/2-3, r, r, 100);

    ras.add_path(ell);
    agg::render_scanlines_aa_solid(ras, sl, rbase, 
                                   agg::rgba(0.6, 0.6, 0.6, 0.7*shadow_alpha));

    ell.init((x1+x2)/2, (y1+y2)/2, r, r, 100);
    ras.add_path(ell);
    agg::render_scanlines_aa(ras, sl, rbase, span_allocator, span_gradient);
}



template<class RenBase> 
void dst_shape(RenBase& rbase, color c1, color c2, 
               double x1, double y1, double x2, double y2)
{
    typedef RenBase renderer_base_type;
    typedef agg::gradient_x gradient_func_type;
    typedef agg::gradient_linear_color<color> color_func_type;
    typedef agg::span_interpolator_linear<> interpolator_type;
    typedef agg::span_allocator<color> span_allocator_type;
    typedef agg::span_gradient<color, 
                               interpolator_type, 
                               gradient_func_type, 
                               color_func_type> span_gradient_type;

    gradient_func_type  gradient_func;                   // The gradient function
    agg::trans_affine   gradient_mtx = gradient_affine(x1, y1, x2, y2, 100);
    interpolator_type   span_interpolator(gradient_mtx); // Span interpolator
    span_allocator_type span_allocator;                  // Span Allocator
    color_func_type     color_func(c1, c2);
    span_gradient_type  span_gradient(span_interpolator, 
                                      gradient_func, 
                                      color_func, 
                                      0, 100);
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_u8 sl;

    agg::rounded_rect shape(x1, y1, x2, y2, 40);
//    agg::ellipse shape((x1+x2)/2, (y1+y2)/2, fabs(x2-x1)/2, fabs(y2-y1)/2, 100);

    ras.add_path(shape);
    agg::render_scanlines_aa(ras, sl, rbase, span_allocator, span_gradient);
}







class the_application : public agg::platform_support
{
    agg::slider_ctrl<color>    m_alpha_src;
    agg::slider_ctrl<color>    m_alpha_dst;
    agg::rbox_ctrl<agg::rgba8> m_comp_op;

public:
    the_application(agg::pix_format_e format, bool flip_y) :
        agg::platform_support(format, flip_y),
        m_alpha_src(5, 5,    400, 11,    !flip_y),
        m_alpha_dst(5, 5+15, 400, 11+15, !flip_y),
        m_comp_op(420, 5.0, 420+170.0, 395.0, !flip_y)
    {
        m_alpha_src.label("Src Alpha=%.2f");
        m_alpha_src.value(1.0);
        add_ctrl(m_alpha_src);

        m_alpha_dst.label("Dst Alpha=%.2f");
        m_alpha_dst.value(0.75);
        add_ctrl(m_alpha_dst);

        m_comp_op.text_size(7);
        m_comp_op.add_item("clear");
        m_comp_op.add_item("src");
        m_comp_op.add_item("dst");
        m_comp_op.add_item("src-over");
        m_comp_op.add_item("dst-over");
        m_comp_op.add_item("src-in");
        m_comp_op.add_item("dst-in");
        m_comp_op.add_item("src-out");
        m_comp_op.add_item("dst-out");
        m_comp_op.add_item("src-atop");
        m_comp_op.add_item("dst-atop");
        m_comp_op.add_item("xor");
        m_comp_op.add_item("plus");
        m_comp_op.add_item("minus");
        m_comp_op.add_item("multiply");
        m_comp_op.add_item("screen");
        m_comp_op.add_item("overlay");
        m_comp_op.add_item("darken");
        m_comp_op.add_item("lighten");
        m_comp_op.add_item("color-dodge");
        m_comp_op.add_item("color-burn");
        m_comp_op.add_item("hard-light");
        m_comp_op.add_item("soft-light");
        m_comp_op.add_item("difference");
        m_comp_op.add_item("exclusion");
        m_comp_op.add_item("contrast");
        m_comp_op.cur_item(3);
        add_ctrl(m_comp_op);
    }

    virtual ~the_application()
    {
    }

    virtual void on_init()
    {
    }


    void render_scene(rbuf_type& rbuf, prim_pixfmt_type& pixf)
    {
        typedef agg::comp_op_adaptor_rgba<color, order> blender_type;
        typedef agg::pixfmt_custom_blend_rgba<blender_type, rbuf_type> pixfmt_type;
        typedef agg::renderer_base<pixfmt_type> renderer_type;

        pixfmt_type ren_pixf(rbuf);
        renderer_type renderer(ren_pixf);

        agg::renderer_base<prim_pixfmt_type> rb(pixf);

        rb.blend_from(prim_pixfmt_type(rbuf_img(1)), 
                      0, 250, 180, 
                      unsigned(m_alpha_src.value() * 255));

        circle(rb, 
               agg::rgba8(0xFD, 0xF0, 0x6F, unsigned(m_alpha_src.value() * 255)), 
               agg::rgba8(0xFE, 0x9F, 0x34, unsigned(m_alpha_src.value() * 255)),
               70*3, 100+24*3, 37*3, 100+79*3,
               m_alpha_src.value());

        ren_pixf.comp_op(m_comp_op.cur_item());

        if(m_comp_op.cur_item() == 25) // Contrast
        {
            double v = m_alpha_dst.value();
            dst_shape(renderer, 
                      agg::rgba(v, v, v), 
                      agg::rgba(v, v, v),
                      300+50, 100+24*3, 107+50, 100+79*3);
        }
        else
        {
            dst_shape(renderer, 
                      agg::rgba8(0x7F, 0xC1, 0xFF, unsigned(m_alpha_dst.value() * 255)), 
                      agg::rgba8(0x05, 0x00, 0x5F, unsigned(m_alpha_dst.value() * 255)),
                      300+50, 100+24*3, 107+50, 100+79*3);
        }
    }


    virtual void on_draw()
    {
        prim_pixfmt_type pixf(rbuf_window());
        prim_ren_base_type rb(pixf);
        rb.clear(agg::rgba8(255, 255, 255));

        unsigned y;
        for(y = 0; y < rb.height(); y += 8)
        {
            unsigned x;
            for(x = ((y >> 3) & 1) << 3; x < rb.width(); x += 16)
            {
                rb.copy_bar(x, y, x+7, y+7, agg::rgba8(0xdf, 0xdf, 0xdf));
            }
        }

        create_img(0, rbuf_window().width(), rbuf_window().height()); // agg_platform_support functionality

        prim_pixfmt_type pixf2(rbuf_img(0));
        prim_ren_base_type rb2(pixf2);
        rb2.clear(agg::rgba8(0,0,0,0));

        typedef agg::blender_rgba_pre<color, order> blender_type_pre; 
        typedef agg::pixfmt_alpha_blend_rgba<blender_type_pre, rbuf_type, pixel_type> pixfmt_pre;
        typedef agg::renderer_base<pixfmt_pre> ren_base_pre;

        pixfmt_pre pixf_pre(rbuf_window());
        ren_base_pre rb_pre(pixf_pre);


        start_timer();
        render_scene(rbuf_img(0), pixf2);
        double tm = elapsed_time();

        rb_pre.blend_from(pixf2);

        agg::rasterizer_scanline_aa<> ras;
        agg::scanline_u8 sl;
        agg::renderer_scanline_aa_solid<prim_ren_base_type> ren(rb);

        char buf[64]; 
        agg::gsv_text t;
        t.size(10.0);

        agg::conv_stroke<agg::gsv_text> pt(t);
        pt.width(1.5);

        sprintf(buf, "%3.2f ms", tm);
        t.start_point(10.0, 35.0);
        t.text(buf);

        ras.add_path(pt);
        ren.color(agg::rgba(0,0,0));
        agg::render_scanlines(ras, sl, ren);

        agg::render_ctrl_rs(ras, sl, ren, m_alpha_src);
        agg::render_ctrl_rs(ras, sl, ren, m_alpha_dst);
        agg::render_ctrl_rs(ras, sl, ren, m_comp_op);
    }


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

    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[])
{
    force_comp_op_link();
    the_application app(pix_format, flip_y);
    app.caption("AGG Example. Compositing Modes");

    const char* img_name = "compositing";
    if(argc >= 2) img_name = argv[1];
    if(!app.load_img(1, img_name)) 
    {
        char buf[256];
        if(strcmp(img_name, "compositing") == 0)
        {
            sprintf(buf, "File not found: %s%s. Download http://www.antigrain.com/%s%s\n"
                         "or copy it from another directory if available.",
                    img_name, app.img_ext(), img_name, app.img_ext());
        }
        else
        {
            sprintf(buf, "File not found: %s%s", img_name, app.img_ext());
        }
        app.message(buf);
        return 1;
    }


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


