#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "agg_rendering_buffer.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_scanline_p.h"
#include "agg_renderer_scanline.h"
#include "agg_conv_transform.h"
#include "agg_conv_stroke.h"
#include "agg_bspline.h"
#include "agg_ellipse.h"
#include "agg_gsv_text.h"
#include "ctrl/agg_slider_ctrl.h"
#include "ctrl/agg_scale_ctrl.h"
#include "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_BGR96
//#define AGG_BGRA128
//#define AGG_RGB565
//#define AGG_RGB555
#include "pixel_formats.h"

enum flip_y_e { flip_y = true };
enum default_num_points_e { default_num_points = 20000 };

enum start_size_e
{
    start_width  = 400,
    start_height = 400
};


enum atom_color_e
{
    atom_color_general = 0,
    atom_color_N       = 1,
    atom_color_O       = 2,
    atom_color_S       = 3,
    atom_color_P       = 4,
    atom_color_halogen = 5,
    end_of_atom_colors
};



struct atom_type
{
    double   x;
    double   y;
    char     label[4];
    int      charge;
    unsigned color_idx;
};

struct bond_type
{
    unsigned idx1;
    unsigned idx2;
    double   x1;
    double   y1;
    double   x2;
    double   y2;
    unsigned order;
    int      stereo;
    int      topology;
};


class molecule
{
public:
    ~molecule();
    molecule();

    bool read(FILE* fd);

    unsigned num_atoms() const { return m_num_atoms; }
    unsigned num_bonds() const { return m_num_bonds; }

    const atom_type& atom(unsigned idx) const { return m_atoms[idx]; }
    const bond_type& bond(unsigned idx) const { return m_bonds[idx]; }

    double average_bond_len() const { return m_avr_len; }

    const char* name() const { return m_name; }

    static int    get_int(const char* buf, int pos, int len);
    static double get_dbl(const char* buf, int pos, int len);
    static char*  get_str(char* dst, const char* buf, int pos, int len);

private:
    atom_type* m_atoms;
    unsigned   m_num_atoms;
    bond_type* m_bonds;
    unsigned   m_num_bonds;
    char       m_name[128];
    double     m_avr_len;
};




molecule::~molecule()
{
    delete [] m_bonds;
    delete [] m_atoms;
}




molecule::molecule() :
    m_atoms(0),
    m_num_atoms(0),
    m_bonds(0),
    m_num_bonds(0),
    m_avr_len(0)
{
    m_name[0] = 0;
}


int molecule::get_int(const char* buf, int pos, int len)
{
    char tmp[32];
    return atoi(get_str(tmp, buf, pos, len));
}

double molecule::get_dbl(const char* buf, int pos, int len)
{
    char tmp[32];
    return atof(get_str(tmp, buf, pos, len));
}

char* molecule::get_str(char* dst, const char* buf, int pos, int len)
{
    --pos;
    int buf_len = strlen(buf);
    buf += pos;
    while(pos + len < buf_len && isspace(*buf)) 
    {
        ++buf;
        --len;
    }
    if(len >= 0)
    {
        if(len > 0) memcpy(dst, buf, len);
        dst[len] = 0;
    }
    
    char* ts = dst;
    while(*ts && !isspace(*ts)) ts++;
    *ts = 0;
    return dst;
}


unsigned trim_cr_lf(char* buf)
{
    unsigned len = strlen(buf);

    // Trim "\n\r" at the beginning 
    unsigned pos = 0;
    while(len && (buf[0] == '\n' || buf[0] == '\r'))
    {
        --len;
        ++pos;
    }
    if(pos) strcpy(buf, buf + pos);

    // Trim "\n\r" at the end 
    while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) --len;
    buf[len] = 0;
    return len;
}


/*
MFCD00191150
  Mt7.00  09020210442D

 23 23  0  0  1  0  0  0  0  0999 V2000
   -2.6793   -0.2552    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   . . .
  1  2  1  0  0  0  0
   . . .
M  END
> <MDLNUMBER>
MFCD00191150

$$$$
*/
bool molecule::read(FILE* fd)
{
    char buf[512];
    unsigned len;
    if(!fgets(buf, 510, fd)) return false;
    len = trim_cr_lf(buf);
    if(len > 128) len = 128;

    if(len) memcpy(m_name, buf, len);
    m_name[len] = 0;

    if(!fgets(buf, 510, fd)) return false;
    if(!fgets(buf, 510, fd)) return false;
    if(!fgets(buf, 510, fd)) return false;
    trim_cr_lf(buf);
    m_num_atoms = get_int(buf, 1, 3);
    m_num_bonds = get_int(buf, 4, 3);
    if(m_num_atoms == 0 || m_num_bonds == 0) return false;
    m_atoms = new atom_type[m_num_atoms];
    m_bonds = new bond_type[m_num_bonds];
    memset(m_atoms, 0, sizeof(atom_type) * m_num_atoms);
    memset(m_bonds, 0, sizeof(bond_type) * m_num_bonds);

    unsigned i;
    for(i = 0; i < m_num_atoms; i++)
    {
        if(!fgets(buf, 510, fd)) return false;
        trim_cr_lf(buf);
/*
   -2.6793   -0.2552    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
*/
        m_atoms[i].x = get_dbl(buf, 1,  10);
        m_atoms[i].y = get_dbl(buf, 11, 10);
        get_str(m_atoms[i].label, buf, 32, 3);
        m_atoms[i].charge = get_int(buf, 39, 1);
        if(m_atoms[i].charge) m_atoms[i].charge = 4 - m_atoms[i].charge;

        if(strcmp(m_atoms[i].label, "N") == 0) m_atoms[i].color_idx = atom_color_N;
        if(strcmp(m_atoms[i].label, "O") == 0) m_atoms[i].color_idx = atom_color_O;
        if(strcmp(m_atoms[i].label, "S") == 0) m_atoms[i].color_idx = atom_color_S;
        if(strcmp(m_atoms[i].label, "P") == 0) m_atoms[i].color_idx = atom_color_P;
        if(strcmp(m_atoms[i].label, "F") == 0 ||
           strcmp(m_atoms[i].label, "Cl") == 0 || 
           strcmp(m_atoms[i].label, "Br") == 0 || 
           strcmp(m_atoms[i].label, "I")  == 0) m_atoms[i].color_idx = atom_color_halogen;
    }

    m_avr_len = 0.0;
    for(i = 0; i < m_num_bonds; i++)
    {
        if(!fgets(buf, 510, fd)) return false;
        trim_cr_lf(buf);
/*
  1  2  1  0  0  0  0
*/
        m_bonds[i].idx1 = get_int(buf, 1, 3) - 1;
        m_bonds[i].idx2 = get_int(buf, 4, 3) - 1;

        if(m_bonds[i].idx1 >= m_num_atoms || m_bonds[i].idx2 >= m_num_atoms) return false;

        m_bonds[i].x1 = m_atoms[m_bonds[i].idx1].x;
        m_bonds[i].y1 = m_atoms[m_bonds[i].idx1].y;
        m_bonds[i].x2 = m_atoms[m_bonds[i].idx2].x;
        m_bonds[i].y2 = m_atoms[m_bonds[i].idx2].y;
        m_bonds[i].order    = get_int(buf, 7,  3);
        m_bonds[i].stereo   = get_int(buf, 10, 3);
        m_bonds[i].topology = get_int(buf, 13, 3);

        m_avr_len += sqrt((m_bonds[i].x1 - m_bonds[i].x2) * (m_bonds[i].x1 - m_bonds[i].x2) + 
                          (m_bonds[i].y1 - m_bonds[i].y2) * (m_bonds[i].y1 - m_bonds[i].y2));
    }
    m_avr_len /= double(m_num_bonds);

    while(fgets(buf, 510, fd))
    {
        trim_cr_lf(buf);
        if(buf[0] == '$') return true;
    }

    return false;
}



namespace agg
{
    class line
    {
    public:
        line() :
            m_x1(0.0), m_y1(0.0), m_x2(1.0), m_y2(0.0), m_thickness(0.1)
        {
        }

        line(double x1, double y1, double x2, double y2, double thickness) :
            m_x1(x1), m_y1(y1), m_x2(x2), m_y2(y2), m_thickness(thickness)
        {
        }

        void init(double x1, double y1, double x2, double y2)
        {
            m_x1 = x1;
            m_y1 = y1;
            m_x2 = x2;
            m_y2 = y2;
        }

        void thickness(double th)
        {
            m_thickness = th;
        }

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

    private:
        double   m_x1;
        double   m_y1;
        double   m_x2;
        double   m_y2;
        double   m_dx;
        double   m_dy;
        double   m_thickness;
        unsigned m_vertex;
    };



    inline void line::rewind(unsigned)
    {
        calc_orthogonal(m_thickness*0.5, m_x1, m_y1, m_x2, m_y2, &m_dx, &m_dy);
        m_vertex = 0;
    }



    inline unsigned line::vertex(double* x, double* y)
    {
        switch(m_vertex)
        {
        case 0:
            *x = m_x1 - m_dx;
            *y = m_y1 - m_dy;
            m_vertex++;
            return path_cmd_move_to;

        case 1:
            *x = m_x2 - m_dx;
            *y = m_y2 - m_dy;
            m_vertex++;
            return path_cmd_line_to;

        case 2:
            *x = m_x2 + m_dx;
            *y = m_y2 + m_dy;
            m_vertex++;
            return path_cmd_line_to;

        case 3:
            *x = m_x1 + m_dx;
            *y = m_y1 + m_dy;
            m_vertex++;
            return path_cmd_line_to;
        }
        return path_cmd_stop;
    }



    class solid_wedge
    {
    public:
        solid_wedge() :
            m_x1(0.0), m_y1(0.0), m_x2(1.0), m_y2(0.0), m_thickness(0.1)
        {
        }

        solid_wedge(double x1, double y1, double x2, double y2, double thickness) :
            m_x1(x1), m_y1(y1), m_x2(x2), m_y2(y2), m_thickness(thickness)
        {
        }

        void init(double x1, double y1, double x2, double y2)
        {
            m_x1 = x1;
            m_y1 = y1;
            m_x2 = x2;
            m_y2 = y2;
        }

        void thickness(double th)
        {
            m_thickness = th;
        }

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

    private:
        double   m_x1;
        double   m_y1;
        double   m_x2;
        double   m_y2;
        double   m_dx;
        double   m_dy;
        double   m_thickness;
        unsigned m_vertex;
    };



    inline void solid_wedge::rewind(unsigned)
    {
        calc_orthogonal(m_thickness*2.0, m_x1, m_y1, m_x2, m_y2, &m_dx, &m_dy);
        m_vertex = 0;
    }



    inline unsigned solid_wedge::vertex(double* x, double* y)
    {
        switch(m_vertex)
        {
        case 0:
            *x = m_x1;
            *y = m_y1;
            m_vertex++;
            return path_cmd_move_to;

        case 1:
            *x = m_x2 - m_dx;
            *y = m_y2 - m_dy;
            m_vertex++;
            return path_cmd_line_to;

        case 2:
            *x = m_x2 + m_dx;
            *y = m_y2 + m_dy;
            m_vertex++;
            return path_cmd_line_to;
        }
        return path_cmd_stop;
    }









    class dashed_wedge
    {
    public:
        dashed_wedge() :
            m_x1(0.0), m_y1(0.0), m_x2(1.0), m_y2(0.0), 
            m_thickness(0.1),
            m_num_dashes(8)
        {
        }

        dashed_wedge(double x1, double y1, double x2, double y2, 
                     double thickness, unsigned num_dashes=8) :
            m_x1(x2), m_y1(y2), m_x2(x1), m_y2(y1), 
            m_thickness(thickness),
            m_num_dashes(num_dashes)
        {
        }

        void init(double x1, double y1, double x2, double y2)
        {
            m_x1 = x2;
            m_y1 = y2;
            m_x2 = x1;
            m_y2 = y1;
        }

        void num_dashes(unsigned nd)
        {
            m_num_dashes = nd;
        }

        void thickness(double th)
        {
            m_thickness = th;
        }

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

    private:
        double   m_x1;
        double   m_y1;
        double   m_x2;
        double   m_y2;
        double   m_xt2;
        double   m_yt2;
        double   m_xt3;
        double   m_yt3;
        double   m_xd[4];
        double   m_yd[4];
        double   m_thickness;
        unsigned m_num_dashes;
        unsigned m_vertex;
    };



    void dashed_wedge::rewind(unsigned)
    {
        double dx;
        double dy;
        calc_orthogonal(m_thickness*2.0, m_x1, m_y1, m_x2, m_y2, &dx, &dy);
        m_xt2 = m_x2 - dx;
        m_yt2 = m_y2 - dy;
        m_xt3 = m_x2 + dx;
        m_yt3 = m_y2 + dy;
        m_vertex = 0;
    }


    unsigned dashed_wedge::vertex(double* x, double* y)
    {
        if(m_vertex < m_num_dashes * 4)
        {
            if((m_vertex % 4) == 0)
            {
                double k1 = double(m_vertex / 4) / double(m_num_dashes);
                double k2 = k1 + 0.4 / double(m_num_dashes);

                m_xd[0] = m_x1 + (m_xt2 - m_x1) * k1;
                m_yd[0] = m_y1 + (m_yt2 - m_y1) * k1;
                m_xd[1] = m_x1 + (m_xt2 - m_x1) * k2;
                m_yd[1] = m_y1 + (m_yt2 - m_y1) * k2;
                m_xd[2] = m_x1 + (m_xt3 - m_x1) * k2;
                m_yd[2] = m_y1 + (m_yt3 - m_y1) * k2;
                m_xd[3] = m_x1 + (m_xt3 - m_x1) * k1;
                m_yd[3] = m_y1 + (m_yt3 - m_y1) * k1;
                *x = m_xd[0];
                *y = m_yd[0];
                m_vertex++;
                return path_cmd_move_to;
            }
            else
            {
                *x = m_xd[m_vertex % 4];
                *y = m_yd[m_vertex % 4];
                m_vertex++;
                return path_cmd_line_to;
            }
        }
        return path_cmd_stop;
    }











}






class bond_vertex_generator
{
    enum bond_style_e
    {
        bond_single,
        bond_wedged_solid,
        bond_wedged_dashed,
        bond_double,
        bond_double_left,
        bond_double_right,
        bond_triple
    };

public:
    bond_vertex_generator(const bond_type& bond, double thickness) :
        m_bond(bond),
        m_thickness(thickness),
        m_style(bond_single)
    {
        if(bond.order == 1)
        {
            if(bond.stereo == 1) m_style = bond_wedged_solid;
            if(bond.stereo == 6) m_style = bond_wedged_dashed;
        }
        if(bond.order == 2)
        {
            m_style = bond_double;
            if(bond.topology == 1) m_style = bond_double_left;
            if(bond.topology == 2) m_style = bond_double_right;
        }
        if(bond.order == 3) m_style = bond_triple;
        m_line1.thickness(thickness);
        m_line2.thickness(thickness);
        m_line3.thickness(thickness);
        m_solid_wedge.thickness(thickness);
        m_dashed_wedge.thickness(thickness);
    }

    void rewind(unsigned)
    {
        double dx, dy, dx1, dy1, dx2, dy2;

        switch(m_style)
        {
        case bond_wedged_solid:
            m_solid_wedge.init(m_bond.x1, m_bond.y1, m_bond.x2, m_bond.y2);
            m_solid_wedge.rewind(0);
            break;

        case bond_wedged_dashed:
            m_dashed_wedge.init(m_bond.x1, m_bond.y1, m_bond.x2, m_bond.y2);
            m_dashed_wedge.rewind(0);
            break;

        case bond_double:
        case bond_double_left:
        case bond_double_right:
            agg::calc_orthogonal(m_thickness, 
                                 m_bond.x1, m_bond.y1, 
                                 m_bond.x2, m_bond.y2, 
                                 &dx, &dy);
            dx1 = dy1 = 0;

            // To Do: ring perception and the proper drawing 
            // of the double bonds in the aromatic rings.
            //if(m_style == bond_double)
            {
                dx1 = dx2 = dx;
                dy1 = dy2 = dy;
            }
/*
            else if(m_style == bond_double_left)
            {
                dx2 = dx * 2.0;
                dy2 = dy * 2.0;
            }
            else
            {
                dx2 = -dx * 2.0;
                dy2 = -dy * 2.0;
            }
*/

            m_line1.init(m_bond.x1 - dx1, 
                         m_bond.y1 - dy1, 
                         m_bond.x2 - dx1, 
                         m_bond.y2 - dy1);
            m_line1.rewind(0);

            m_line2.init(m_bond.x1 + dx2, 
                         m_bond.y1 + dy2, 
                         m_bond.x2 + dx2, 
                         m_bond.y2 + dy2);
            m_line2.rewind(0);
            m_status = 0;
            break;

        case bond_triple:
            // To Do: triple bonds drawing.

        default:
            m_line1.init(m_bond.x1, m_bond.y1, m_bond.x2, m_bond.y2);
            m_line1.rewind(0);
            break;
        }
    }


    unsigned vertex(double* x, double* y)
    {
        unsigned flag = agg::path_cmd_stop;
        switch(m_style)
        {
            case bond_wedged_solid:
                return m_solid_wedge.vertex(x, y);

            case bond_wedged_dashed:
                return m_dashed_wedge.vertex(x, y);

            case bond_double_left:
            case bond_double_right:
            case bond_double:
                if(m_status == 0)
                {
                    flag = m_line1.vertex(x, y);
                    if(flag == agg::path_cmd_stop)
                    {
                        m_status = 1;
                    }
                }

                if(m_status == 1)
                {
                    flag = m_line2.vertex(x, y);
                }
                return flag;

            case bond_triple:
            case bond_single:
                break;
        }
        return m_line1.vertex(x, y);
    }



private:
    bond_vertex_generator(const bond_vertex_generator&);
    const bond_vertex_generator& operator = (const bond_vertex_generator&);

    const bond_type& m_bond;
    double m_thickness;
    bond_style_e m_style;
    agg::line m_line1;
    agg::line m_line2;
    agg::line m_line3;
    agg::solid_wedge m_solid_wedge;
    agg::dashed_wedge m_dashed_wedge;
    unsigned m_status;
};











class the_application : public agg::platform_support
{   
    molecule*                    m_molecules;
    unsigned                     m_num_molecules;
    unsigned                     m_cur_molecule;
    agg::slider_ctrl<color_type> m_thickness;
    agg::slider_ctrl<color_type> m_text_size;
    double     m_pdx;
    double     m_pdy;
    double     m_center_x;
    double     m_center_y;
    double     m_scale;
    double     m_prev_scale;
    double     m_angle;
    double     m_prev_angle;
    bool       m_mouse_move;
    agg::srgba8 m_atom_colors[end_of_atom_colors];


public:
    virtual ~the_application()
    {
        delete [] m_molecules;
    }

    the_application(agg::pix_format_e format, bool flip_y, const char* fname) :
        agg::platform_support(format, flip_y),
        m_molecules(new molecule [100]),
        m_num_molecules(0),
        m_cur_molecule(0),
        m_thickness(5, 5,  start_width-5, 12),
        m_text_size(5, 20, start_width-5, 27),
        m_pdx(0.0),
        m_pdy(0.0),
        m_center_x(start_width / 2),
        m_center_y(start_height / 2),
        m_scale(1.0),
        m_prev_scale(1.0),
        m_angle(0.0),
        m_prev_angle(0.0),
        m_mouse_move(false)
    {
        m_thickness.label("Thickness=%3.2f");
        m_text_size.label("Label Size=%3.2f");
        add_ctrl(m_thickness);
        add_ctrl(m_text_size);

        FILE* fd = fopen(full_file_name(fname), "r");
        if(fd)
        {
            unsigned i;
            for(i = 0; i < 100; i++)
            {
                if(!m_molecules[m_num_molecules].read(fd)) break;
                m_num_molecules++;
            }
            fclose(fd);
        }
        else
        {
            char buf[256];
            sprintf(buf, "File not found: '%s'. Download http://www.antigrain.com/%s\n",
                    fname, fname);
            message(buf);
        }
        memset(m_atom_colors, 0, sizeof(agg::srgba8) * end_of_atom_colors);
        m_atom_colors[atom_color_general] = agg::srgba8(0,0,0);
        m_atom_colors[atom_color_N]       = agg::srgba8(0,0,120);
        m_atom_colors[atom_color_O]       = agg::srgba8(200,0,0);
        m_atom_colors[atom_color_S]       = agg::srgba8(120,120,0);
        m_atom_colors[atom_color_P]       = agg::srgba8(80,50,0);
        m_atom_colors[atom_color_halogen] = agg::srgba8(0,200,0);
    }


    virtual void on_init()
    {
    }


    virtual void on_draw()
    {
        double width = initial_width();
        double height = initial_height();

        agg::rasterizer_scanline_aa<> ras;
        agg::scanline_p8 sl;

        typedef agg::renderer_base<pixfmt> renderer_base;
        typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_solid;

        pixfmt pixf(rbuf_window());
        renderer_base rb(pixf);
        renderer_solid rs(rb);

        ras.clip_box(0, 0, rb.width(), rb.height());

        rb.clear(agg::rgba(1,1,1));

        const molecule& mol = m_molecules[m_cur_molecule];
        unsigned i;
        double min_x =  1e100;
        double max_x = -1e100;
        double min_y =  1e100;
        double max_y = -1e100;

        for(i = 0; i < mol.num_atoms(); i++)
        {
            if(mol.atom(i).x < min_x) min_x = mol.atom(i).x;
            if(mol.atom(i).y < min_y) min_y = mol.atom(i).y;
            if(mol.atom(i).x > max_x) max_x = mol.atom(i).x;
            if(mol.atom(i).y > max_y) max_y = mol.atom(i).y;
        }

        agg::trans_affine mtx;

        mtx *= agg::trans_affine_translation(-(max_x + min_x) * 0.5, -(max_y + min_y) * 0.5);
        
        double scale = width  / (max_x - min_x);
        double t = height / (max_y - min_y);
        if(scale > t) scale = t;
        
        double text_size = mol.average_bond_len() * m_text_size.value() / 4.0;
        double thickness = mol.average_bond_len() / 
                           sqrt(m_scale < 0.0001 ? 0.0001 : m_scale) /
                           8.0;
        
        mtx *= agg::trans_affine_scaling(scale*0.80, scale*0.80);
        mtx *= agg::trans_affine_rotation(m_angle);
        mtx *= agg::trans_affine_scaling(m_scale, m_scale);
        mtx *= agg::trans_affine_translation(m_center_x, m_center_y);
        mtx *= trans_affine_resizing();

        rs.color(agg::rgba(0,0,0));
        for(i = 0; i < mol.num_bonds(); i++)
        {
            bond_vertex_generator bond(mol.bond(i), 
                m_thickness.value() * thickness);
            agg::conv_transform<bond_vertex_generator> tr(bond, mtx);
            ras.add_path(tr);
            agg::render_scanlines(ras, sl, rs);
        }


        agg::ellipse ell;
        agg::conv_transform<agg::ellipse> tr(ell, mtx);
        for(i = 0; i < mol.num_atoms(); i++)
        {
            if(strcmp(mol.atom(i).label, "C") != 0)
            {
                ell.init(mol.atom(i).x, 
                         mol.atom(i).y, 
                         text_size * 2.5,
                         text_size * 2.5,
                         20);
                ras.add_path(tr);
                rs.color(agg::rgba(1.0, 1.0, 1.0));
                agg::render_scanlines(ras, sl, rs);
            }
        }


        text_size *= 3.0;


        agg::gsv_text label;
        agg::conv_stroke<agg::gsv_text> ls(label);
        agg::conv_transform<agg::conv_stroke<agg::gsv_text> > lo(ls, mtx);
        ls.line_join(agg::round_join);
        ls.line_cap(agg::round_cap);
        ls.approximation_scale(mtx.scale());
        for(i = 0; i < mol.num_atoms(); i++)
        {
            if(strcmp(mol.atom(i).label, "C") != 0)
            {
                ls.width(m_thickness.value() * thickness);
                label.text(mol.atom(i).label);
                label.start_point(mol.atom(i).x - text_size/2, mol.atom(i).y - text_size/2);
                label.size(text_size);
                ras.add_path(lo);
                rs.color(m_atom_colors[mol.atom(i).color_idx]);
                agg::render_scanlines(ras, sl, rs);
            }
        }


        ls.approximation_scale(1.0);
        agg::conv_transform<agg::conv_stroke<agg::gsv_text> > name(ls, trans_affine_resizing());
        ls.width(1.5);
        label.text(mol.name());
        label.size(10.0);
        label.start_point(10.0, start_height - 20.0);
        ras.reset();
        ras.add_path(name);
        rs.color(agg::rgba(0,0,0));
        agg::render_scanlines(ras, sl, rs);

        
        
        agg::render_ctrl(ras, sl, rb, m_thickness);
        agg::render_ctrl(ras, sl, rb, m_text_size);
    }














    virtual void on_idle()
    {
        m_angle += agg::deg2rad(0.1);
        force_redraw();
    }






    virtual void on_mouse_button_down(int x, int y, unsigned flags)
    {
        m_mouse_move = true;
        double x2 = x;
        double y2 = y;
        trans_affine_resizing().inverse_transform(&x2, &y2);

        m_pdx = m_center_x - x2;
        m_pdy = m_center_y - y2;
        m_prev_scale = m_scale;
        m_prev_angle = m_angle + agg::pi;
        force_redraw();
    }





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

    virtual void on_mouse_move(int x, int y, unsigned flags)
    {
        double x2 = x;
        double y2 = y;
        trans_affine_resizing().inverse_transform(&x2, &y2);

        if(m_mouse_move && (flags & agg::mouse_left) != 0)
        {
            double dx = x2 - m_center_x;
            double dy = y2 - m_center_y;
            m_scale = m_prev_scale * 
                      sqrt(dx * dx + dy * dy) / 
                      sqrt(m_pdx * m_pdx + m_pdy * m_pdy);

            m_angle = m_prev_angle + atan2(dy, dx) - atan2(m_pdy, m_pdx);
            force_redraw();
        }

        if(m_mouse_move && (flags & agg::mouse_right) != 0)
        {
            m_center_x = x2 + m_pdx;
            m_center_y = y2 + m_pdy;
            force_redraw();
        }

    }







    virtual void on_key(int, int, unsigned key, unsigned)
    {
        switch(key)
        {
        case agg::key_left:
        case agg::key_up:
        case agg::key_page_up:
            if(m_cur_molecule) --m_cur_molecule;
            force_redraw();
            break;

        case agg::key_right:
        case agg::key_down:
        case agg::key_page_down:
            if(m_cur_molecule < m_num_molecules - 1) ++m_cur_molecule;
            force_redraw();
            break;

        case ' ':
            wait_mode(!wait_mode());
            break;
        }
    }

};





int agg_main(int argc, char* argv[])
{
    const char* fname = "1.sdf";
    if(argc > 1)
    {
        fname = argv[1];
    }


    the_application app(pix_format, flip_y, fname);
    app.caption("AGG - A Simple SDF Molecular Viewer");

    if(app.init(start_width, start_height, agg::window_resize))
    {
        return app.run();
    }

    return 1;
}


