#include <cmath>
#include "Maths.h"

struct vec2f
{
  float x, y;
};

vec2f operator*(const vec2f& v, float c)
{
  return (vec2f){ v.x*c, v.y*c };
}

vec2f operator*(float c, const vec2f& v)
{
  return (vec2f){ v.x*c, v.y*c };
}

vec2f operator+(const vec2f& v1, const vec2f& v2)
{
  return (vec2f){ v1.x+v2.x, v1.y+v2.y };
}

// Generalized spline function
// a_tension       -1.0 -> 1.0     Round -> Tight
// a_bias          -1.0 -> 1.0     Pre shoot -> Post shoot
// a_continuity    -1.0 -> 1.0     Box corners -> Inverted corners
vec2f kochanekBartelsSpline(const std::vector<vec2f>& a_points, float t, float a_tension, float a_bias, float a_continuity)
{
  int pnts = a_points.size();
  if (!pnts)
    return (vec2f){ 0.0f, 0.0f };
  t = std::max(0.0f, std::min(1.0f, t)); // clamp t to be between 0->1
  int intervals = pnts - 1; // the intervals are the segments between each point 
  int n = std::max(0, std::min(pnts-1, int(intervals * t))); // figure out which interval t is between
  t = t * intervals - n; // the new t value is the ratio between the points at n and n+1
  float tmp1 = 0.5f * (1.0f - a_tension);
  float A = tmp1 * (1.0f + a_continuity) * (1.0f + a_bias); // A,B,C,D are the parameter factors
  float B = tmp1 * (1.0f - a_continuity) * (1.0f - a_bias);
  float C = tmp1 * (1.0f - a_continuity) * (1.0f + a_bias);
  float D = tmp1 * (1.0f + a_continuity) * (1.0f - a_bias);
  const vec2f& pnt0 = a_points[std::max(n - 1, 0)];
  const vec2f& pnt1 = a_points[n];
  const vec2f& pnt2 = a_points[std::min(pnts - 1, n + 1)];
  const vec2f& pnt3 = a_points[std::min(pnts - 1, n + 2)];
  vec2f vA =  -A*pnt0 +      (2+A-B-C)*pnt1 +  (-2+B+C-D)*pnt2 +  D*pnt3; // vA,vB,vC - polynomial factors
  vec2f vB = 2*A*pnt0 + (-3-2*A+2*B+C)*pnt1 + (3-2*B-C+D)*pnt2 + -D*pnt3;
  vec2f vC =  -A*pnt0 +          (A-B)*pnt1 +           B*pnt2;
  return ((vA*t + vB)*t + vC)*t + pnt1; // blends by the factors between the 4 closest control points
}

#if 0
struct vec4f
{
  union {
    float v[4];
    struct { float x, y, z, w; };
  };
};


struct mat4f
{
  union {
    vec4f m[4];
    struct { vec4f t, b, n, w; };  // tangent, bi-normal, normal
  };
};


vec4f operator +(const vec4f& a, const vec4f& b)
{
  vec4f res = {{{ a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }}};
  return res;
}


vec4f operator -(const vec4f& a, const vec4f& b)
{
  vec4f res = {{{ a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w }}};
  return res;
}


inline float dot3(const vec4f& a, const vec4f& b)
{
  //return Math::dotProduct(&a.x, &b.x, 1, 1, 3);
  return a.x * b.x + a.y * b.y + a.z * b.z;
}


vec4f operator *(const vec4f& a, const vec4f& b)
{
  vec4f res;
  res.x = a.x * b.x;
  res.y = a.y * b.y;
  res.z = a.z * b.z;
  res.w = a.w * b.w;
  return res;
}


vec4f scaleVector(const vec4f& a, float b)
{
  vec4f res;
  res.x = a.x * b;
  res.y = a.y * b;
  res.z = a.z * b;
  res.w = a.w * b;
  return res;
}
vec4f operator *(const vec4f& a, float b)
{
  return scaleVector(a, b);
}


struct rotation
{
  const vec4f& q()      // get the quaternion
  {
    return quaternion;
  }
  const mat4f& m()      // get as a matrix
  {
    if (dirty)
    {
      convert();
    }
    return cachedMatrix;
  }
  void set(vec4f quat)  // set
  {
    quaternion = quat;
    dirty = true;
  }
  // TODO: perhaps operator overload the get and sets
private:
  void convert()
  {
    Math::quatToMatrix4x4(&cachedMatrix.m[0].x, &quaternion.x);
    dirty = false;
  }
  vec4f quaternion;
  bool dirty = true;
  mat4f cachedMatrix; // only needs to be 3x3
};


vec4f rotateVector(const vec4f& vec, const mat4f& mat)
{
  vec4f res;
  Math::transformVector(&res.x, &mat.t.x, &vec.x);
  return res;
}
vec4f operator *(const vec4f& a, const mat4f& mat)
{
  return rotateVector(a, mat);
}


float vectorLength(vec4f& vec)
{
  return sqrt(Math::dotProduct(&vec.x, &vec.x, 1, 1, 3));
}


#include <OpenGL/gl.h> // suggest not include directly but make the framework provide an include which includes the system one
#endif

#include "Widget.h"
#include "CommonWidgets.h"
#include "Painter.h"
#include "Application.h"
USING_NAMESPACE


class NamedSlider : public HBox
{
public:
  NamedSlider(Widget* parent, Property<int>& value, const char* name) : HBox(parent)
  {
    label = name;
    new Label(this, label);
    new Slider(this, value);
  }
  Property<String> label;
};


class FontEditWindow : public Widget
{
  VBox vbox;
  Button quitButton;
  Property<bool> useRoundPen;
  Property<bool> useSquarePen;
  Property<bool> useFlatPen;
  Property<int> bias;
  Property<int> continuity;
  Property<int> tension;
  Property<int> ascent;
  Property<int> descent;
  Property<int> angle;
  Property<int> width;
  Property<int> penAngle;
  Property<int> penWidth;
  Property<int> character;
public:
  FontEditWindow(Widget* a_parent, const char* a_name)
    : Widget(a_parent, a_name)
    , vbox(this)
    , quitButton(&vbox, "Quit")
  {
    new CheckBox(&vbox, useRoundPen, "Use Round Pen");
    new CheckBox(&vbox, useSquarePen, "Use Square Pen");
    new CheckBox(&vbox, useFlatPen, "Use Flat Pen");
    Grid *g = new Grid(&vbox, 2);
    new GridSlider(g, bias, "Bias");
    new GridSlider(g, continuity, "Continuity");
    new GridSlider(g, tension, "Tension");
    new GridSlider(g, ascent, "Ascender Height");
    new GridSlider(g, descent, "Descender Height");
    new GridSlider(g, angle, "Angle");
    new GridSlider(g, width, "Width");
    new GridSlider(g, penAngle, "Pen Angle");
    new GridSlider(g, penWidth, "Pen Width");
    new GridSlider(g, character, "Character");
    connect(quitButton.activated, this, &FontEditWindow::onQuit); 
    bias = (1<<15);
    tension = (1<<15);
    continuity = (1<<16);
  }

  void onQuit(int)
  {
    exit(0);
  }

  void mouseEvent(MouseEvent& a_event) override
  {
    /*
    int x = a_event.m_position.m_x;
    int y = a_event.m_position.m_y;
    if (lastX == -1) { lastX = x; lastY = y; }
    if (a_event.m_buttons == MB_Right)
    {
      tx += x - lastX; ty += y - lastY;
    }
    else if (a_event.m_buttons == MB_Left)
    {
      trackingMouse = !trackingMouse;
      x = -1;
    }
    else if (trackingMouse)
    {
      rx += x - lastX; ry += y - lastY;
    }
    else
    {
      x = -1;
    }
    lastX = x; lastY = y;
    */
    
    //printf("got mouse event\n");
    repaint();
  }
    
  void paintEvent(PaintEvent& a_event) override
  {
    Painter p(this);
    p.setPen(0x000000);
    int w = width() / 2;
    int h = height() / 2;
    p.drawLine(5, 5, w-5, 5);
    p.drawLine(5, 5, 5, h-5);
    p.drawLine(w-5, 5, w-5, h-5);
    p.drawLine(5, h-5, w-5, h-5);
    //repaint();

    int scale = 50;
    int x1 = 350;
    int y1 = 100;
    int penWidth = 7;
    
    int xstep = scale;
    int ystep = scale;
    int w2 = ystep*3;
    int h2 = xstep*3;
    for (int i = 0; i < 4; i++)
      p.drawLine(x1, y1+ystep*i, x1+w2, y1+ystep*i);
    for (int i = 0; i < 4; i++)
      p.drawLine(x1+xstep*i, y1, x1+xstep*i, y1+h2);

    float cx = x1 + xstep + (xstep - penWidth) / 2;
    float cy = y1 + ystep * 1.5f;
 
    // For each stroke, quantize the points to a 16x16 grid - a position can be encoded in 8-bits
    // Then the composition of the strokes can place these stoke pieces relative to each other with
    // some positioning flags - v-flip, h-flip, left/top offset of 4bits each
    // ---- ---- ---- ----
    // ---- ---- ---- ----
    // ---- ---- ---- ----
    // ---- ---- ---- ----
    // ---- ---- ---- ----


    // Editor of the strokes with snapping to grid and with a box pen position so snapping can be aligned according to the pen and
    // the grid points

    std::vector<vec2f> pnts[16];
    // C-top
    pnts[0].push_back((vec2f){ float(x1 + xstep), cy });
    pnts[0].push_back((vec2f){ float(x1 + xstep) + 5, cy - 10 });
    pnts[0].push_back((vec2f){ cx - 10, float(y1 + ystep + 5) });
    pnts[0].push_back((vec2f){ cx, float(y1 + ystep) });
    pnts[0].push_back((vec2f){ cx + 10, float(y1 + ystep + 5) });
    pnts[0].push_back((vec2f){ float(x1 + 2*xstep - penWidth) - 5, cy - 15 });
    pnts[0].push_back((vec2f){ float(x1 + 2*xstep - penWidth), cy - 5 });
    
    // a-line
    pnts[1].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + ystep + 2) });
    pnts[1].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2*ystep + 2) });

    // b-line
    pnts[2].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2) });
    pnts[2].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2*ystep + 2) });
    
    // e-line
    pnts[3].push_back((vec2f){ float(x1 + xstep + penWidth), float(cy) });
    pnts[3].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(cy) });
    
    // n-line
    pnts[4].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(cy) });
    pnts[4].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2*ystep + 2) });
    
    // n-line
    pnts[4].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(cy) });
    pnts[4].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2*ystep + 2) });

   /* 
    int pw = 2;
    std::vector<unsigned char> pnts2[16];
    // C-top
    pnts2[0].push_back((vec2f){ 0,             8 });
    pnts2[0].push_back((vec2f){ 0 + 1,         8 - 1 });
    pnts2[0].push_back((vec2f){ 8 - 1,         0 + 1 });
    pnts2[0].push_back((vec2f){ 8,             0 });
    pnts2[0].push_back((vec2f){ 8 + 1,         0 + 1 });
    pnts2[0].push_back((vec2f){ (16 - pw) - 1, 8 - 2 });
    pnts2[0].push_back((vec2f){ 16 - pw,       8 - 1 });
    
    // a-line
    pnts2[1].push_back((vec2f){ 16 - pw, 5 });
    pnts2[1].push_back((vec2f){ 16 - pw, 10 });

    // b-line
    pnts2[2].push_back((vec2f){ 16 - pw, 0 });
    pnts2[2].push_back((vec2f){ 16 - pw, 16 });
    
    // e-line
    pnts2[3].push_back((vec2f){ 8 + pw, 4 });
    pnts2[3].push_back((vec2f){ 16 - pw, 4 });
    
    // n-line
    pnts2[4].push_back((vec2f){ 16 - pw, float(cy) });
    pnts2[4].push_back((vec2f){ 16 - pw, float(y1 + 2*ystep + 2) });
    
    // n-line
    pnts2[4].push_back((vec2f){ 16 - pw, float(cy) });
    pnts2[4].push_back((vec2f){ 16 - pw, float(y1 + 2*ystep + 2) });

    // pnts[0] is c-top
    // pnts[0] flipped vertically is c-bottom
    // pnts[1] is a-line
    // pnts[2] is b-line
    */

    // a ->  0,c-top  1,c-top  0,a-line

    struct { int flags; int index; } characters[][5] = {
      { { 0|0, 0 }, { 0|1, 0 }, { 0|0, 1 }, { -1, -1 } },  // 'a'
      { { 2|0, 0 }, { 2|1, 0 }, { 2|0, 2 }, { -1, -1 } },  // 'b'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'
      { { 0|0, 0 }, { 0|1, 0 }, { 0|0, 2 }, { -1, -1 } },  // 'd'
      { { 0|0, 0 }, { 0|1, 0 }, { 0|0, 3 }, { -1, -1 } },  // 'e'
      { { 0|0, 3 }, { 4|0, 2 }, { -1, -1 } },              // 'c'  'f'
      { { 0|0, 0 }, { 0|1, 0 }, { 4|2|0, 2 }, { -1, -1 } },// 'c'  'g'
      { { 2|0, 0 }, { 0|0, 4 }, { 2|0, 2 }, { -1, -1 } },  // 'b'  'h'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'i'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'j'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'k'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'l'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'm'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'n'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'o'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'p'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'q'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'r'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  's'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  't'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'u'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'v'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'w'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'x'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'y'
      { { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } },              // 'c'  'z'
    };

    int ch = 'a' + ((character.value() * 26) >> 16);
    int idx = ch - 'a';
    for (int i = 0; i < 5; i++)
    {
      int flags = characters[idx][i].flags;
      int index = characters[idx][i].index;
      if (flags == -1)
        break;
      drawCurve(p, applyFlags(flags, pnts[index], cx, cy), penWidth);
    }
  }

  std::vector<vec2f> applyFlags(int flags, std::vector<vec2f>& pnts, float cx, float cy)
  {
    std::vector<vec2f> out = pnts;
    if ( flags & 1 )
    {
      // Flip/mirror vertically
      for (vec2f& pt : out)
      {
        pt.y = 2*cy - pt.y;
      }
    }
    if ( flags & 2 )
    {
      // Flip/mirror horizontally
      for (vec2f& pt : out)
      {
        pt.x = 2*cx - pt.x;
      }
    }
    return out;
  }

  void drawCurve(Painter& p, const std::vector<vec2f>& pnts, int penWidth)
  {
    float t = (tension.value() - (1<<15)) / float(1<<15);
    float b = (bias.value() - (1<<15)) / float(1<<15);
    float c = (continuity.value() - (1<<15)) / float(1<<15);
    for (int i = 0; i < 100; i++)
    {
      vec2f kochanekBartelsSpline(const std::vector<vec2f>& a_points, float t, float a_tension, float a_bias, float a_continuity);
      vec2f p1 = kochanekBartelsSpline(pnts, (i-0.5f) * 0.01f, t, b, c);
      vec2f p2 = kochanekBartelsSpline(pnts, (i+0.5f) * 0.01f, t, b, c);
      p.drawLine(int(p1.x), int(p1.y), int(p2.x), int(p2.y));
      p.drawLine(int(p1.x) + penWidth, int(p1.y) - 3, int(p2.x) + penWidth, int(p2.y) - 3);
    }
  }

private:
  //bool trackingMouse = false;
  //int tx = 0, ty = 0, tz = -120;
  //int rx = 0, ry = 0, rz = 0;
  //int lastX = -1, lastY = -1;
};


void fontEditTest()
{
  Application app;
  FontEditWindow testWin(0, "Test Window");
  testWin.setNewSize(800, 600);
  app.exec();
  exit(0);
}


