// g++ test_layout.cpp -g -o test_layout `pkg-config --cflags --libs gtk+-2.0` `xft-config --cflags --libs`

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <Xft/Xft.h>
#include <assert.h>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

static const int screen_resolution = 96;
static const int printer_resolution = 3000;
static const float mm_per_inch = 25.4;
static const int x_off = 10;
static const int y_off = 10;
static float cursor_pos = x_off;

// double buffer pixmap for the drawing area
static GdkPixmap* pixmap = 0;
static GdkGC* gc = 0;
static GdkGC* white_gc = 0;

// Buffer of characters
typedef vector<FcChar32> TextBuffer;
static TextBuffer text_buffer;

// Xft related structures
static XftDraw* xft_draw = 0;
static XftFont* font = 0;
static XftColor color;

void
destroy_cb (GtkWidget *widget, gpointer   data)
{
    gtk_main_quit ();
}

inline static int
inches_to_pixels (float inches)
{
    return (int) (inches * 96 + 0.5);
}

class FaceGetter
{
public:
    FaceGetter(XftFont* font) : face_(XftLockFace(font)), font_(font) {}
    ~FaceGetter() { XftUnlockFace(font_); }

    FT_Face getFace() { return face_; }
    
private:
    FaceGetter(const FaceGetter&);
    FaceGetter& operator= (const FaceGetter&);
    
    FT_Face face_;
    XftFont* font_;
};

// that kinda of sucks.  I'm calculating everything in the drawing handler,
// when all that can be calculated before...
static void
draw_text()
{
    float dx;
    const size_t nb_chars = text_buffer.size();
    vector<XftCharSpec> chars(nb_chars);
    XftCharSpec char_spec;
    XGlyphInfo extents;
    FaceGetter face_getter(font);
    FT_Face face = face_getter.getFace();
    FT_UInt glyph_index;
    int error;
    
    cursor_pos = x_off;
    
    for (size_t i = 0; i < nb_chars; ++i)
    {
        FT_Fixed linearHoriAdvance;
        
        char_spec.ucs4 = text_buffer[i];
        char_spec.x = (short) (cursor_pos + 0.5);
        char_spec.y = y_off + 20;
        chars.push_back(char_spec);

        glyph_index = FT_Get_Char_Index(face, char_spec.ucs4);
        error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
        if (error)
        {
            cout << "Error loading glyph" << endl;
            continue;
        }

        linearHoriAdvance = face->glyph->linearHoriAdvance;

        dx = ((float) linearHoriAdvance) / (1 << 16);
        cursor_pos += dx;
    }

    XftDrawCharSpec (xft_draw, &color, font, &chars[0], chars.size());
    XftDrawString32 (xft_draw, &color, font, x_off, y_off + 36, &text_buffer[0], text_buffer.size());
}

static void
draw_page_borders ()
{
    /* the weight and height of a A4 page (210 x 297 mm) */
    static const float width_inches = 8.268;
    static const float height_inches = 11.693;
    int x0 = x_off;
    int y0 = y_off;
    /* the -1 here is just because X sucks */
    int xf = x_off + inches_to_pixels (width_inches) - 1;
    int yf = y_off + inches_to_pixels (height_inches) - 1;
    
    assert (pixmap);
    assert (gc);
    
    gdk_draw_rectangle (pixmap, gc, 0, x0, y0, xf, yf);
}

static void
draw ()
{
    int width;
    int height;
    
    assert (pixmap);
    assert (gc);

    gdk_drawable_get_size (pixmap, &width, &height);
    gdk_draw_rectangle (pixmap, white_gc, TRUE, 0, 0, width, height);
    
    draw_page_borders ();
    draw_text ();
}

static gboolean
key_press_event (GtkWidget* widget, GdkEventKey* event, gpointer user_data)
{
    FcChar32 unichar;

    unichar = gdk_keyval_to_unicode (event->keyval);
    if (unichar)
    {
        text_buffer.push_back (unichar);
        draw ();
        gtk_widget_queue_draw_area (widget, 0, 0, widget->allocation.width, widget->allocation.height);
    }

    return TRUE;
}

/* Create a new backing pixmap of the appropriate size */
static gint
configure_event (GtkWidget* widget, GdkEventConfigure* event)
{
    if (pixmap)
        gdk_pixmap_unref(pixmap);

    gc = widget->style->black_gc;
    white_gc = widget->style->white_gc;
    
    pixmap = gdk_pixmap_new(widget->window,
                            widget->allocation.width,
                            widget->allocation.height,
                            -1);

    if (xft_draw)
        XftDrawChange(xft_draw, GDK_WINDOW_XWINDOW (pixmap));
    else
        xft_draw = XftDrawCreate (GDK_DISPLAY (), GDK_WINDOW_XWINDOW (pixmap),
                                  GDK_VISUAL_XVISUAL (gdk_drawable_get_visual (pixmap)),
                                  GDK_COLORMAP_XCOLORMAP (gdk_drawable_get_colormap (pixmap)));

    draw ();

    return TRUE;
}

static gint
expose_event (GtkWidget* widget, GdkEventExpose* event)
{
    gdk_draw_pixmap(widget->window,
                    widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
                    pixmap,
                    event->area.x, event->area.y,
                    event->area.x, event->area.y,
                    event->area.width, event->area.height);

    return FALSE;
}

int
main(int argc, char* argv[])
{
    GtkDrawingArea* da;
    GtkWindow* main_window;
    GdkWindow* win;
    
    gtk_init (&argc, &argv);
    main_window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));

    g_signal_connect (G_OBJECT (main_window), "destroy",
                      G_CALLBACK (destroy_cb), NULL);
 
    da = GTK_DRAWING_AREA (gtk_drawing_area_new ());
    win = GTK_WIDGET (da)->window;
    
    GdkColormap* colormap = gdk_rgb_get_cmap();
    GdkColor c;
    c.red = 0;
    c.green = 0;
    c.blue = 0;
    
    gdk_color_alloc(colormap, &c);

    color.color.red = c.red;
    color.color.green = c.green;
    color.color.blue = c.blue;
    color.color.alpha = 0xffff;
    color.pixel = c.pixel;
    
    font = XftFontOpenName(GDK_DISPLAY (), DefaultScreen (GDK_DISPLAY ()), "Times-12");
    
    gtk_container_add(GTK_CONTAINER (main_window), GTK_WIDGET (da));

    g_signal_connect (G_OBJECT (da), "configure_event",
                      G_CALLBACK (configure_event), NULL);
    g_signal_connect (G_OBJECT (da), "expose_event",
                      G_CALLBACK (expose_event), NULL);
    g_signal_connect (G_OBJECT (main_window), "key_press_event",
                      G_CALLBACK (key_press_event), NULL);
    
    gtk_widget_show_all (GTK_WIDGET (main_window));
    
    gtk_main ();
    return 0;
}