#include "Application.h"
#include "Window.h"
#include "Painter.h"
#include "SignalSlot.h"
#include "Widget.h"
#include "CommonWidgets.h"
#include <iostream>
#include <iomanip>
#include <climits>
#include <fstream>
#include <cctype>
#include <cstdarg>

USING_NAMESPACE


void drawGrid(Painter& p, const int x1, const int y1, const int gridSize, const int gridDim, const uint32_t* buf)
{
  if (buf)
    for (int i = 0; i < gridDim; i++)
      for (int j = 0; j < gridDim; j++)
        if (buf[j*gridDim+i])
        {
          p.setBrush(~buf[j*gridDim+i] | 0xFF000000);
          p.drawRectangle(x1 + i * gridSize, y1 + j * gridSize, gridSize, gridSize);
        }

  for (int i = 0; i <= gridDim; i++)
    p.drawLine(x1 + i * gridSize, y1, x1 + i * gridSize, y1 + gridSize * gridDim);
  for (int i = 0; i <= gridDim; i++)
    p.drawLine(x1, y1 + i * gridSize, x1 + gridSize * gridDim, y1 + gridSize * i); 
}


// Internal font rendering APIs
//extern "C"
BEGIN_NAMESPACE
  extern bool drawAAText;
  extern bool g_enableKerning;
END_NAMESPACE


class NamedCombo : public VBox
{
  Property<String> m_label;
public:
  NamedCombo(Widget* parent, const char* name)
    : VBox(parent)
  {
    new Label(this, m_label);
    m_label = name;
    m_combo = new ComboBox(this, m_value);
  }
  void addItem(const char* name)
  {
    m_combo->addItem(name);
  }
  ComboBox*     m_combo;
  Property<int> m_value;
};


template <typename T>
class PropertyDelegate : public Property<T>
{
  Property<T> m_value;
  T& m_backingValue;
public:
  PropertyDelegate(T& a_backingValue)
    : m_backingValue(a_backingValue)
  {
    m_value = a_backingValue;
    connect(m_value.valueChanged, this, &PropertyDelegate<T>::replicate);
  }
  void replicate(T newValue)
  {
    m_backingValue = newValue;
  }
};


template <class T>
class PropertyWrapper : public AbstractProperty<T>
{
public:
	//PropertyWrapper() {}
	PropertyWrapper(T& val) : m_value(val) {}
	PropertyWrapper(const PropertyWrapper<T>& other) : m_value(other.m_value) {}

	Property<T>& operator=(T const & a_newValue)
	{
		AbstractProperty<T>::setValue(a_newValue);
		return *this;
	}

protected:
	T const & get() const { return m_value; }
	void set(T a_newValue) { m_value = a_newValue; }

private:
	T& m_value;
};


class TestWindow : public Widget
{
  //Property<bool> m_drawAA;
  PropertyWrapper<bool> m_drawAA;
  PropertyWrapper<bool> m_enableKerning;
  Property<bool> m_drawFill;
  Property<bool> m_drawDebugOutline;
  Property<int> m_sliderValue;
  Property<String> m_testText;
  NamedCombo*      m_drawMode;
public:
  TestWindow(Widget* a_parent, const char* a_name)
    : Widget(a_parent, a_name)
    , m_drawAA(drawAAText)
    , m_enableKerning(g_enableKerning)
  {
    Widget* vbox = new VBox(this);
    new CheckBox(vbox, m_drawFill, "fill");
    new CheckBox(vbox, m_drawDebugOutline, "debug-outline");
    new CheckBox(vbox, m_drawAA, "Draw anti-alias");
    new CheckBox(vbox, m_enableKerning, "Enable kerning");

    m_drawMode = new NamedCombo(vbox, "Draw mode:");
    m_drawMode->addItem("Filled");
    m_drawMode->addItem("Outline");
    m_drawMode->addItem("OutlineBlend");
    m_drawMode->addItem("Stage0");
    m_drawMode->addItem("Stage1");
    m_drawMode->addItem("Stage2");
    m_drawMode->addItem("Stage3");
    m_drawMode->addItem("Stage4");
    m_drawMode->addItem("Stage5");

    NamedCombo *fontFamily = new NamedCombo(vbox, "Font family:");
    fontFamily->addItem("Filled");
    fontFamily->addItem("Outline");
    fontFamily->addItem("OutlineBlend");
    fontFamily->addItem("Stage0");
    fontFamily->addItem("Stage1");
    fontFamily->addItem("Stage2");
    fontFamily->addItem("Stage3");

    new LineEdit(vbox, m_testText);
    new Slider(vbox, m_sliderValue);
    m_sliderValue = 32000;
    connect(m_drawAA.valueChanged, this, &TestWindow::toggleAA);
    connect(m_enableKerning.valueChanged, this, &TestWindow::redraw);
    connect(m_drawFill.valueChanged, this, &TestWindow::redraw);
    connect(m_drawMode->m_value.valueChanged, this, &TestWindow::redraw);
  
//    m_drawAA = drawAAText;
  }
  void redraw(bool)
  {
    repaint();
  }
  void toggleAA(bool a_on)
  {
//    drawAAText = a_on;
    repaint();
  }
  void paintEvent(PaintEvent& a_event)
  {
    const int w = 80;
    const int h = 80;
    uint32_t *buf = new uint32_t[w*h];
    memset(buf, 0, w*h*sizeof(uint32_t));
    Pixmap pix((unsigned char*)buf, w, h, sizeof(uint32_t));
    Painter p1(&pix);
    //p1.setFontFamily("Segoe UI");//  "Arial Unicode");
    
    int fontSize = m_sliderValue() * 24 / 32000;
    //p1.setPen(0x000000);
    
    p1.setFontSize(fontSize);

    int yoff = 20;//fontSize / 2;
    p1.drawText(0, yoff/2, m_testText(), m_drawMode->m_value());

    const int x1 = 150;
    const int y1 = 150;
    const int gridSize = 5;
    const int gridDim = w;
    int scale = (fontSize) ? 1024 / (2 * fontSize) : 1;

    Painter p(this);
    FontMetrics fm = p.fontMetrics();

    GlyphMetrics gm = p.glyphMetrics(m_testText()[0]);
    
    int boundsY1 = fontSize * 2 + yoff + 9 - gm.m_minY / scale;
    int boundsY2 = fontSize * 2 + yoff + 9 - gm.m_maxY / scale;
    int boundsX1 = (gm.m_leftSideBearing + gm.m_minX) / scale;
    int boundsX2 = (gm.m_leftSideBearing + gm.m_maxX) / scale;
    
    //printf("ascent: %i\n", fm.m_ascent);
    //printf("descent: %i\n", fm.m_descent);
    //printf("lineGap: %i\n", fm.m_lineGap);

    // draw font baseline
    p.setPen(0xF01010);
    int baseLine = yoff + 9 + fontSize * 2;
    p.drawLine(x1, y1+2+gridSize*baseLine, x1+gridSize*w-1, y1+2+gridSize*baseLine);
    
    p.setPen(0x10F010);
    int descentLine = baseLine - (fm.m_descent / scale);
    p.drawLine(x1, y1+2+gridSize*descentLine, x1+gridSize*w-1, y1+2+gridSize*descentLine);
    int bottomLine = descentLine + (fm.m_lineGap / scale);
    p.drawLine(x1, y1+2+gridSize*bottomLine, x1+gridSize*w-1, y1+2+gridSize*bottomLine);
    int ascentLine = baseLine - (fm.m_ascent / scale);
    p.drawLine(x1, y1+2+gridSize*ascentLine, x1+gridSize*w-1, y1+2+gridSize*ascentLine);
    
    p.setPen(0x1010F0);
    int xHeightLine = baseLine - (fm.m_xHeight / scale);
    p.drawLine(x1, y1+2+gridSize*xHeightLine, x1+gridSize*w-1, y1+2+gridSize*xHeightLine);
    int capHeightLine = baseLine - (fm.m_capHeight / scale);
    p.drawLine(x1, y1+2+gridSize*capHeightLine, x1+gridSize*w-1, y1+2+gridSize*capHeightLine);

    p.setPen(0x000000);
//    p.drawText(0, 0, "Blah");
//    p.drawLine(0, 0, 1000, 1000);

    drawGrid(p, x1, y1, gridSize, gridDim, buf);
    
    p.setPen(0xFF00FF);
    p.drawLine(x1 + boundsX1*gridSize+2, y1+2+boundsY1*gridSize, x1 + boundsX1*gridSize+2, y1+2+boundsY2*gridSize);
    p.drawLine(x1 + boundsX2*gridSize+2, y1+2+boundsY1*gridSize, x1 + boundsX2*gridSize+2, y1+2+boundsY2*gridSize);
    p.drawLine(x1 + boundsX1*gridSize, y1+2+boundsY1*gridSize, x1 + boundsX2*gridSize, y1+2+boundsY1*gridSize);
    p.drawLine(x1 + boundsX1*gridSize, y1+2+boundsY2*gridSize, x1 + boundsX2*gridSize, y1+2+boundsY2*gridSize);

    delete[] buf;
  }
};



int main(int argc, char* argv[])
{
  Application app;
  TestWindow testWin(0, "Test Window");
  testWin.setNewSize(800, 600);
  return app.exec();
}


