#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>
#include "agg_rendering_buffer.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_scanline_u.h"
#include "agg_renderer_scanline.h"
#include "agg_span_allocator.h"
#include "agg_span_gradient.h"
#include "agg_gradient_lut.h"
#include "agg_gamma_lut.h"
#include "agg_span_interpolator_linear.h"
#include "ctrl/agg_slider_ctrl.h"
#include "platform/agg_platform_support.h"

#define AGG_BGR24
#include "pixel_formats.h"

enum { flip_y = true };


class the_application : public agg::platform_support
{
    typedef agg::renderer_base<pixfmt> renderer_base;
    typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_solid;
    typedef agg::gamma_lut<agg::int8u, agg::int8u> gamma_lut_type;
    typedef agg::gradient_radial_focus gradient_func_type;
    typedef agg::gradient_reflect_adaptor<gradient_func_type> gradient_adaptor_type;
    typedef agg::gradient_lut<agg::color_interpolator<agg::srgba8>, 1024> color_func_type;
    typedef agg::span_interpolator_linear<> interpolator_type;
    typedef agg::span_allocator<color_type> span_allocator_type;
    typedef agg::span_gradient<color_type, 
                               interpolator_type, 
                               gradient_adaptor_type, 
                               color_func_type> span_gradient_type;
    
    agg::slider_ctrl<color_type>    m_gamma;

    agg::scanline_u8                m_scanline;
    agg::rasterizer_scanline_aa<>   m_rasterizer;
    span_allocator_type             m_alloc;
    color_func_type                 m_gradient_lut;
    gamma_lut_type                  m_gamma_lut;

    double m_mouse_x, m_mouse_y;
    double m_old_gamma;


public:
    the_application(agg::pix_format_e format, bool flip_y) :
        agg::platform_support(format, flip_y),
        m_gamma(5.0, 5.0, 340.0, 12.0, !flip_y),
        m_mouse_x(200), m_mouse_y(200)
    {
        m_gamma.range(0.5, 2.5);
        m_gamma.value(1.0);
        m_gamma.label("Gamma = %.3f");
        add_ctrl(m_gamma);
        m_gamma.no_transform();

        m_gamma_lut.gamma(m_gamma.value());
        m_old_gamma = m_gamma.value();

        build_gradient_lut();
    }

    virtual void on_init()
    {
        m_mouse_y = initial_height() / 2;
        m_mouse_x = initial_width() / 2;
    }

    void build_gradient_lut()
    {
        m_gradient_lut.remove_all();

        m_gradient_lut.add_color(0.0, agg::rgba8_gamma_dir(agg::srgba8(0, 255, 0),   m_gamma_lut));
        m_gradient_lut.add_color(0.2, agg::rgba8_gamma_dir(agg::srgba8(120, 0, 0),   m_gamma_lut));
        m_gradient_lut.add_color(0.7, agg::rgba8_gamma_dir(agg::srgba8(120, 120, 0), m_gamma_lut));
        m_gradient_lut.add_color(1.0, agg::rgba8_gamma_dir(agg::srgba8(0, 0, 255),   m_gamma_lut));

        //m_gradient_lut.add_color(0.0, agg::srgba8::from_wavelength(380, m_gamma.value()));
        //m_gradient_lut.add_color(0.1, agg::srgba8::from_wavelength(420, m_gamma.value()));
        //m_gradient_lut.add_color(0.2, agg::srgba8::from_wavelength(460, m_gamma.value()));
        //m_gradient_lut.add_color(0.3, agg::srgba8::from_wavelength(500, m_gamma.value()));
        //m_gradient_lut.add_color(0.4, agg::srgba8::from_wavelength(540, m_gamma.value()));
        //m_gradient_lut.add_color(0.5, agg::srgba8::from_wavelength(580, m_gamma.value()));
        //m_gradient_lut.add_color(0.6, agg::srgba8::from_wavelength(620, m_gamma.value()));
        //m_gradient_lut.add_color(0.7, agg::srgba8::from_wavelength(660, m_gamma.value()));
        //m_gradient_lut.add_color(0.8, agg::srgba8::from_wavelength(700, m_gamma.value()));
        //m_gradient_lut.add_color(0.9, agg::srgba8::from_wavelength(740, m_gamma.value()));
        //m_gradient_lut.add_color(1.0, agg::srgba8::from_wavelength(780, m_gamma.value()));

        m_gradient_lut.build_lut();
    }

    virtual void on_draw()
    {
        pixfmt pixf(rbuf_window());
        renderer_base rb(pixf);
        renderer_solid rs(rb);
        rb.clear(agg::rgba(1, 1, 1));

        // When Gamma changes rebuild the gamma and gradient LUTs 
        //------------------
        if(m_old_gamma != m_gamma.value())
        {
            m_gamma_lut.gamma(m_gamma.value());
            build_gradient_lut();
            m_old_gamma = m_gamma.value();
        }


        // Gradient center. All gradient functions assume the 
        // center being in the origin (0,0) and you can't 
        // change it. But you can apply arbitrary transformations
        // to the gradient (see below).
        //------------------
        double cx = initial_width()  / 2;
        double cy = initial_height() / 2;
        double r = 100;

        // Focal center. Defined in the gradient coordinates, 
        // that is, with respect to the origin (0,0)
        //------------------
        double fx = m_mouse_x - cx;
        double fy = m_mouse_y - cy;

        gradient_func_type    gradient_func(r, fx, fy);
        gradient_adaptor_type gradient_adaptor(gradient_func);
        agg::trans_affine     gradient_mtx;
        
        // Making the affine matrix. Move to (cx,cy), 
        // apply the resizing transformations and invert
        // the matrix. Gradients and images always assume the
        // inverse transformations.
        //------------------
        gradient_mtx.translate(cx, cy);
        gradient_mtx *= trans_affine_resizing();
        gradient_mtx.invert();

        interpolator_type     span_interpolator(gradient_mtx);
        span_gradient_type    span_gradient(span_interpolator, 
                                          gradient_adaptor, 
                                          m_gradient_lut, 
                                          0, r);

        // Form the simple rectangle 
        //------------------
        m_rasterizer.reset();
        m_rasterizer.move_to_d(0,0);
        m_rasterizer.line_to_d(width(), 0);
        m_rasterizer.line_to_d(width(), height());
        m_rasterizer.line_to_d(0, height());

        // Render the gradient to the whole screen and measure the time
        //------------------
        start_timer();
        agg::render_scanlines_aa(m_rasterizer, m_scanline, rb, m_alloc, span_gradient);
        double tm = elapsed_time();

        // Draw the transformed circle that shows the gradient boundary
        //------------------
        agg::ellipse e(cx, cy, r, r);
        agg::conv_stroke<agg::ellipse> estr(e);
        agg::conv_transform<
            agg::conv_stroke<
                agg::ellipse> > etrans(estr, trans_affine_resizing());

        m_rasterizer.add_path(etrans);
        agg::render_scanlines_aa_solid(m_rasterizer, m_scanline, rb, agg::rgba(1,1,1));

        // Show the gradient time
        //------------------
        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);
        m_rasterizer.add_path(pt);
        agg::render_scanlines_aa_solid(m_rasterizer, m_scanline, rb, agg::rgba(0,0,0));

        // Show the controls
        //------------------
        agg::render_ctrl(m_rasterizer, m_scanline, rb, m_gamma);

        // Apply the inverse gamma to the whole buffer 
        // (transform the colors to the perceptually uniform space)
        //------------------
        pixf.apply_gamma_inv(m_gamma_lut);
    }




    virtual void on_mouse_move(int x, int y, unsigned flags)
    {
        if(flags & agg::mouse_left)
        {
            m_mouse_x = x;
            m_mouse_y = y;
            trans_affine_resizing().inverse_transform(&m_mouse_x, &m_mouse_y);
            force_redraw();
        }
    }


    virtual void on_mouse_button_down(int x, int y, unsigned flags)
    {
        if(flags & agg::mouse_left)
        {
            m_mouse_x = x;
            m_mouse_y = y;
            trans_affine_resizing().inverse_transform(&m_mouse_x, &m_mouse_y);
            force_redraw();
        }
    }







};











int agg_main(int argc, char* argv[])
{
    the_application app(pix_format, flip_y);
    app.caption("AGG Example. PDF linear and radial gradients");

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


