/*
#  Created by Boyd Multerer on June 1, 2018.
#  Copyright © 2018 Kry10 Industries. All rights reserved.
#

functions to play a compiled render script
*/

  #include <stdio.h>
  // #include <string.h>
  #include <math.h>

  #include <stdlib.h>
  // #include <GLFW/glfw3.h>
  #include <GLES2/gl2.h>

  #include "nanovg/nanovg.h"
  #include "types.h"
  #include "comms.h"
  #include "render_script.h"
  #include "tx.h"


  // state control
  #define OP_PUSH_STATE              0X01
  #define OP_POP_STATE               0X02
  #define OP_RESET_STATE             0X03

  #define OP_RUN_SCRIPT              0X04

  // RENDER STYLES
  #define OP_PAINT_LINEAR            0X06
  #define OP_PAINT_BOX               0X07
  #define OP_PAINT_RADIAL            0X08
  #define OP_PAINT_IMAGE             0X09

  #define OP_ANTI_ALIAS              0X0A

  #define OP_STROKE_WIDTH            0X0C
  #define OP_STROKE_COLOR            0X0D
  #define OP_STROKE_PAINT            0X0E

  #define OP_FILL_COLOR              0X10
  #define OP_FILL_PAINT              0x11

  #define OP_MITER_LIMIT             0X14
  #define OP_LINE_CAP                0X15
  #define OP_LINE_JOIN               0X16
  #define OP_GLOBAL_ALPHA            0X17

  // SCISSORING
  #define OP_SCISSOR                 0X1B
  #define OP_INTERSECT_SCISSOR       0X1C
  #define OP_RESET_SCISSOR           0X1D

  // PATH OPERATIONS
  #define OP_PATH_BEGIN              0X20
  
  #define OP_PATH_MOVE_TO            0X21
  #define OP_PATH_LINE_TO            0X22
  #define OP_PATH_BEZIER_TO          0X23
  #define OP_PATH_QUADRATIC_TO       0X24
  #define OP_PATH_ARC_TO             0X25
  #define OP_PATH_CLOSE              0X26
  #define OP_PATH_WINDING            0X27
  
  #define OP_FILL                    0X29
  #define OP_STROKE                  0X2A
  
  #define OP_TRIANGLE                0X2C
  #define OP_ARC                     0X2D
  #define OP_RECT                    0X2E
  #define OP_ROUND_RECT              0X2F
  #define OP_ROUND_RECT_VAR          0X30
  #define OP_ELLIPSE                 0X31
  #define OP_CIRCLE                  0X32
  #define OP_SECTOR                  0X33

  #define OP_TEXT                    0x34


  // TRANSFORM OPERATIONS
  #define OP_TX_RESET                0X36
  #define OP_TX_IDENTITY             0X37
  #define OP_TX_MATRIX               0X38
  #define OP_TX_TRANSLATE            0X39
  #define OP_TX_SCALE                0X3A
  #define OP_TX_ROTATE               0X3B
  #define OP_TX_SKEW_X               0X3C
  #define OP_TX_SKEW_Y               0X3D


  #define OP_FONT                    0X40
  #define OP_FONT_BLUR               0X41
  #define OP_FONT_SIZE               0X42
  #define OP_TEXT_ALIGN              0X43
  #define OP_TEXT_HEIGHT             0X44


  #define OP_TERMINATE               0XFF

  static const float        TAU  = NVG_PI * 2;

  NVGpaint current_paint;

//=============================================================================
// access functions for scripts


void delete_script( driver_data_t* p_data, GLuint id ) {
  if (p_data->p_scripts[id]) {
    free(p_data->p_scripts[id]);
  p_data->p_scripts[id] = NULL;
  }
}

void delete_all( driver_data_t* p_data ){
  for (GLuint i = 0; i < p_data->num_scripts; i++ ) {
    delete_script( p_data, i );
  }
}

void put_script( driver_data_t* p_data, GLuint id, void* p_script ) {
  delete_script( p_data, id );
  p_data->p_scripts[id] = p_script;
}

void* get_script( driver_data_t* p_data, GLuint id ) {
  return p_data->p_scripts[id];
}



//=============================================================================
// types

typedef struct __attribute__((__packed__)) 
{
  GLuint      r;
  GLuint      g;
  GLuint      b;
  GLuint      a;
} color_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     x;
  GLfloat     y;
} xy_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     x;
  GLfloat     y;
  GLfloat     w;
  GLfloat     h;
} xywh_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     w;
  GLfloat     h;
} wh_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     w;
  GLfloat     h;
  GLfloat     r;
} whr_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     c1x;
  GLfloat     c1y;
  GLfloat     c2x;
  GLfloat     c2y;
  GLfloat     x;
  GLfloat     y;
} bezier_to_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     cx;
  GLfloat     cy;
  GLfloat     x;
  GLfloat     y;
} quadratic_to_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     x1;
  GLfloat     y1;
  GLfloat     x2;
  GLfloat     y2;
  GLfloat     radius;
} arc_to_t;

// typedef struct __attribute__((__packed__)) 
// {
//   GLfloat     cx;
//   GLfloat     cy;
//   GLfloat     radius;
//   GLfloat     a0;
//   GLfloat     a1;
//   byte        direction;
// } arc_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     rx;
  GLfloat     ry;
} ellipse_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     r;
} circle_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     radius;
  GLfloat     start;
  GLfloat     finish;
} arc_sector_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     a;
  GLfloat     b;
  GLfloat     c;
  GLfloat     d;
  GLfloat     e;
  GLfloat     f;
} matrix_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     x0;
  GLfloat     y0;
  GLfloat     x1;
  GLfloat     y1;
  GLfloat     x2;
  GLfloat     y2;
} triangle_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     sx;
  GLfloat     sy;
  GLfloat     ex;
  GLfloat     ey;
  GLuint      sr;
  GLuint      sg;
  GLuint      sb;
  GLuint      sa;
  GLuint      er;
  GLuint      eg;
  GLuint      eb;
  GLuint      ea;
} linear_gradient_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     x;
  GLfloat     y;
  GLfloat     w;
  GLfloat     h;
  GLfloat     radius;
  GLfloat     feather;
  GLuint      sr;
  GLuint      sg;
  GLuint      sb;
  GLuint      sa;
  GLuint      er;
  GLuint      eg;
  GLuint      eb;
  GLuint      ea;
} box_gradient_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     cx;
  GLfloat     cy;
  GLfloat     r_in;
  GLfloat     r_out;
  GLuint      sr;
  GLuint      sg;
  GLuint      sb;
  GLuint      sa;
  GLuint      er;
  GLuint      eg;
  GLuint      eb;
  GLuint      ea;
} radial_gradient_t;

typedef struct __attribute__((__packed__)) 
{
  GLfloat     ox;
  GLfloat     oy;
  GLfloat     ex;
  GLfloat     ey;
  GLfloat     angle;
  GLuint      alpha;
  GLuint      key_size;
} image_pattern_t;

typedef struct __attribute__((__packed__)) 
{
  GLuint      size;
} text_t;

//=============================================================================
// operations

//---------------------------------------------------------
// run script
void* internal_run_script( void* p_script, driver_data_t* p_data ) {
  GLuint id = *(GLuint*)p_script;
  // char buff[200];
  // sprintf(buff, "run_script %d", id);
  // send_puts( buff );
  run_script( id, p_data );
  return p_script + sizeof(GLuint);
}

//---------------------------------------------------------
// paint setup

void* paint_linear( NVGcontext* p_ctx, void* p_script ) {
  linear_gradient_t* grad = (linear_gradient_t*)p_script;

  current_paint = nvgLinearGradient(
    p_ctx, 
    grad->sx, grad->sy, grad->ex, grad->ey,
    nvgRGBA(grad->sr, grad->sg, grad->sb, grad->sa),
    nvgRGBA(grad->er, grad->eg, grad->eb, grad->ea)
    );
  
  return p_script + sizeof(linear_gradient_t);
}

void* paint_box( NVGcontext* p_ctx, void* p_script ) {
  box_gradient_t* grad = (box_gradient_t*)p_script;

  current_paint = nvgBoxGradient(
    p_ctx, 
    grad->x, grad->y, grad->w, grad->h,
    grad->radius, grad->feather,
    nvgRGBA(grad->sr, grad->sg, grad->sb, grad->sa),
    nvgRGBA(grad->er, grad->eg, grad->eb, grad->ea)
    );
  
  return p_script + sizeof(box_gradient_t);
}

void* paint_radial( NVGcontext* p_ctx, void* p_script ) {
  radial_gradient_t* grad = (radial_gradient_t*)p_script;

  current_paint = nvgRadialGradient(
    p_ctx, 
    grad->cx, grad->cy, grad->r_in, grad->r_out,
    nvgRGBA(grad->sr, grad->sg, grad->sb, grad->sa),
    nvgRGBA(grad->er, grad->eg, grad->eb, grad->ea)
    );
  
  return p_script + sizeof(radial_gradient_t);
}

void* paint_image( NVGcontext* p_ctx, void* p_script, driver_data_t* p_data ) {
  image_pattern_t* img = (image_pattern_t*)p_script;
  p_script += sizeof(image_pattern_t);

  float alpha = (float)img->alpha / 255.0;

  // get the image id from the hash.
  int id = get_tx_id(p_data->p_tx_ids, p_script);

  // if the id is -1, then it isn't loaded
  if ( id < 0 ) {
    send_cache_miss( p_script );
  } else {

    // if ox, oy, ex, ey are all zero, then use the
    // natural h/w of the image
    float ox = img->ox;
    float oy = img->oy;
    float ex = img->ex;
    float ey = img->ey;
    if ( ox == 0.0 && oy == 0.0 && ex == 0.0 && ey == 0.0 ) {
      int w, h;
      nvgImageSize(p_ctx, id, &w, &h);
      ex = w;
      ey = h;
    }

    // the id is loaded and found
    current_paint = nvgImagePattern(
      p_ctx, 
      ox, oy, ex, ey,
      img->angle, id, alpha
    );
  }

  return p_script + img->key_size;
}


//---------------------------------------------------------
// render styles


void* shape_anti_alias( NVGcontext* p_ctx, void* p_script ) {
  nvgShapeAntiAlias(p_ctx, *(int*)p_script);
  return p_script + sizeof(int);
}

void* stroke_color( NVGcontext* p_ctx, void* p_script ) {
  color_t* color = (color_t*)p_script;
  nvgStrokeColor(p_ctx, nvgRGBA(color->r, color->g, color->b, color->a));
  return p_script + sizeof(color_t);
}

void* shape_width( NVGcontext* p_ctx, void* p_script ) {
  nvgStrokeWidth(p_ctx, *(float*)p_script);
  return p_script + sizeof(float);
}

void* fill_color( NVGcontext* p_ctx, void* p_script ) {
  color_t* color = (color_t*)p_script;
  nvgFillColor(p_ctx, nvgRGBA(color->r, color->g, color->b, color->a));
  return p_script + sizeof(color_t);
}


void* miter_limit( NVGcontext* p_ctx, void* p_script ) {
  nvgMiterLimit(p_ctx, *(float*)p_script);
  return p_script + sizeof(float);
}

void* line_cap( NVGcontext* p_ctx, void* p_script ) {
  uint32_t join = *(uint32_t*)p_script;
  switch( join ) {
    case 0:   nvgLineCap(p_ctx, NVG_BUTT);     break;
    case 1:   nvgLineCap(p_ctx, NVG_ROUND);     break;
    case 2:   nvgLineCap(p_ctx, NVG_SQUARE);     break;
    default: break;
  }
  return p_script + sizeof(uint32_t);
}

void* line_join( NVGcontext* p_ctx, void* p_script ) {
  uint32_t join = *(uint32_t*)p_script;
  switch( join ) {
    case 0:   nvgLineJoin(p_ctx, NVG_MITER);     break;
    case 1:   nvgLineJoin(p_ctx, NVG_ROUND);     break;
    case 2:   nvgLineJoin(p_ctx, NVG_BEVEL);     break;
    default: break;
  }
  return p_script + sizeof(uint32_t);
}

void* global_alpha( NVGcontext* p_ctx, void* p_script ) {
  nvgGlobalAlpha(p_ctx, *(float*)p_script);
  return p_script + sizeof(float);
}

//---------------------------------------------------------
// scissors

void* scissor( NVGcontext* p_ctx, void* p_script ) {
  wh_t* wh = (wh_t*)p_script;
  nvgScissor(p_ctx, 0, 0, wh->w, wh->h);
  return p_script + sizeof(wh_t);
}

void* intersect_scissor( NVGcontext* p_ctx, void* p_script ) {
  wh_t* wh = (wh_t*)p_script;
  nvgIntersectScissor(p_ctx, 0, 0, wh->w, wh->h);
  return p_script + sizeof(wh_t);
}

//---------------------------------------------------------
// paths

void* move_to( NVGcontext* p_ctx, void* p_script ) {
  xy_t* xywh = (xy_t*)p_script;
  nvgMoveTo(p_ctx, xywh->x, xywh->y);
  return p_script + sizeof(xy_t);
}

void* line_to( NVGcontext* p_ctx, void* p_script ) {
  xy_t* xywh = (xy_t*)p_script;
  nvgLineTo(p_ctx, xywh->x, xywh->y);
  return p_script + sizeof(xy_t);
}

void* bezier_to( NVGcontext* p_ctx, void* p_script ) {
  bezier_to_t* bezier = (bezier_to_t*)p_script;
  nvgBezierTo(p_ctx, bezier->c1x, bezier->c1y, bezier->c2x, bezier->c2y, bezier->x, bezier->y);
  return p_script + sizeof(bezier_to_t);
}

void* quadratic_to( NVGcontext* p_ctx, void* p_script ) {
  quadratic_to_t* quad = (quadratic_to_t*)p_script;
  nvgQuadTo(p_ctx, quad->cx, quad->cy, quad->x, quad->y);
  return p_script + sizeof(quadratic_to_t);
}

void* arc_to( NVGcontext* p_ctx, void* p_script ) {
  arc_to_t* arc = (arc_to_t*)p_script;
  nvgArcTo(p_ctx, arc->x1, arc->y1, arc->x2, arc->y2, arc->radius);
  return p_script + sizeof(arc_to_t);
}

void* path_winding( NVGcontext* p_ctx, void* p_script ) {
  if (*(int*)p_script) {
    nvgPathWinding(p_ctx, NVG_SOLID);
  } else {
    nvgPathWinding(p_ctx, NVG_HOLE);
  }
  return p_script + sizeof(int);
}

void* triangle( NVGcontext* p_ctx, void* p_script ) {
  triangle_t* tri = (triangle_t*)p_script;
  nvgMoveTo(p_ctx, tri->x0, tri->y0);
  nvgLineTo(p_ctx, tri->x1, tri->y1);
  nvgLineTo(p_ctx, tri->x2, tri->y2);
  nvgClosePath(p_ctx);
  return p_script + sizeof(triangle_t);
}

void* rect( NVGcontext* p_ctx, void* p_script ) {
  wh_t* wh = (wh_t*)p_script;
  nvgRect(p_ctx, 0, 0, wh->w, wh->h);
  return p_script + sizeof(wh_t);
}

void* round_rect( NVGcontext* p_ctx, void* p_script ) {
  whr_t* whr = (whr_t*)p_script;
  nvgRoundedRect(p_ctx, 0, 0, whr->w, whr->h, whr->r);
  return p_script + sizeof(whr_t);
}

void* ellipse( NVGcontext* p_ctx, void* p_script ) {
  ellipse_t* ellipse = (ellipse_t*)p_script;
  nvgEllipse(p_ctx, 0, 0, ellipse->rx, ellipse->ry);
  return p_script + sizeof(ellipse_t);
}

void* circle( NVGcontext* p_ctx, void* p_script ) {
  GLfloat radius = *(GLfloat*)p_script;
  nvgCircle(p_ctx, 0, 0, radius);
  return p_script + sizeof(GLfloat);
}

void* arc( NVGcontext* p_ctx, void* p_script ) {
  // bring sector data onto the stack
  arc_sector_t sector = *(arc_sector_t*)p_script;

  // clamp the angle to a circle
  float angle = sector.finish - sector.start;
  angle = angle > TAU ? TAU : angle;
  angle = angle < -TAU ? -TAU : angle;

  // calculate the number of segments
  int segment_count = log2(sector.radius) * fabsf(angle) * 2;
  float increment = angle / segment_count;
  float a = sector.start;


  // don't draw anything if the angle is so small that the segment_count is zero
  if ( segment_count > 0 ) {
    // Arc starts on the perimeter. Sector starts in the center.
    for (int i = 0; i <= segment_count; ++i) {
      float px, py;
      px = sector.radius * cos(a);
      py = sector.radius * sin(a);
      if (i == 0 ) {
        nvgMoveTo(p_ctx, px, py);
      } else {
        nvgLineTo(p_ctx, px, py);
      }
      a += increment ;
    }
    // arc doesn't close
  }

  return p_script + sizeof(arc_sector_t);
}


void* sector( NVGcontext* p_ctx, void* p_script ) {
  // bring sector data onto the stack
  arc_sector_t sector = *(arc_sector_t*)p_script;

  // clamp the angle to a circle
  float angle = sector.finish - sector.start;
  angle = angle > TAU ? TAU : angle;
  angle = angle < -TAU ? -TAU : angle;


  // calculate the number of segments
  int segment_count = log2(sector.radius) * fabsf(angle) * 2;
  float increment = angle / segment_count;
  float a = sector.start;

  // don't draw anything if the angle is so small that the segment_count is zero
  if ( segment_count > 0 ) {
    // Arc starts on the perimeter. Sector starts in the center.
    nvgMoveTo(p_ctx, 0, 0);

    for (int i = 0; i <= segment_count; ++i) {
      float px, py;
      px = sector.radius * cos(a);
      py = sector.radius * sin(a);
      nvgLineTo(p_ctx, px, py);
      a += increment ;
    }
    nvgClosePath(p_ctx);    
  }

  return p_script + sizeof(arc_sector_t);
}

void* text( NVGcontext* p_ctx, void* p_script ) {
  text_t* text_info = (text_t*)p_script;
  p_script += sizeof(text_t);

  float x = 0;
  float y = 0;
  const char* start = p_script;
  const char* end = start + text_info->size;
  float lineh;
  nvgTextMetrics(p_ctx, NULL, NULL, &lineh);
  NVGtextRow rows[3];
  int nrows, i;

  // up to this code to break the lines...
  while ((nrows = nvgTextBreakLines(p_ctx, start, end, 1000, rows, 3))) {
    for (i = 0; i < nrows; i++) {
      NVGtextRow* row = &rows[i];
      nvgText(p_ctx, x, y, row->start, row->end);
      y += lineh;
    }
    // Keep going...
    start = rows[nrows-1].next;
  }

  // Text is padded to 32-bits
  return p_script + ((text_info->size + 3) & ~3);
}



// typedef struct __attribute__((__packed__)) 
// {
//   GLfloat     x;
//   GLfloat     y;
//   GLuint      size;
// } text_t;




//---------------------------------------------------------
// transforms


void* tx_rotate( NVGcontext* p_ctx, void* p_script ) {
  nvgRotate(p_ctx, *(float*)p_script);
  return p_script + sizeof(float);
}

void* tx_translate( NVGcontext* p_ctx, void* p_script ) {
  xy_t* xy = (xy_t*)p_script;
  nvgTranslate(p_ctx, xy->x, xy->y);
  return p_script + sizeof(xy_t);  
}

void* tx_scale( NVGcontext* p_ctx, void* p_script ) {
  xy_t* xy = (xy_t*)p_script;
  nvgScale(p_ctx, xy->x, xy->y);
  return p_script + sizeof(xy_t);  
}

void* tx_skew_x( NVGcontext* p_ctx, void* p_script ) {
  nvgSkewX(p_ctx, *(float*)p_script);
  return p_script + sizeof(float);
}

void* tx_skew_y( NVGcontext* p_ctx, void* p_script ) {
  nvgSkewY(p_ctx, *(float*)p_script);
  return p_script + sizeof(float);
}

void* tx_matrix( NVGcontext* p_ctx, void* p_script ) {
  matrix_t* tx = (matrix_t*)p_script;
  nvgTransform(p_ctx, tx->a, tx->b, tx->c, tx->d, tx->e, tx->f);
  return p_script + sizeof(matrix_t);
}

//---------------------------------------------------------
// font styles

void* font( NVGcontext* p_ctx, void* p_script ) {
  GLuint  name_length = *(GLuint*)p_script;
  p_script += sizeof(GLuint);

  // get the id for the font. If it isn't loaded, request it
  int font_id = nvgFindFont(p_ctx, p_script);
  if ( font_id >= 0 ) {
    nvgFontFaceId(p_ctx, font_id);
  } else {
    // the font is NOT loaded. Request it from the ex code above
    send_font_miss( p_script );
  }

  return p_script + name_length;
}

void* font_blur( NVGcontext* p_ctx, void* p_script ) {
  nvgFontBlur(p_ctx, *(float*)p_script);
  return p_script + sizeof(float);
}

void* font_size( NVGcontext* p_ctx, void* p_script ) {
  nvgFontSize(p_ctx, *(float*)p_script);
  return p_script + sizeof(float);
}

void* text_align( NVGcontext* p_ctx, void* p_script ) {
  nvgTextAlign(p_ctx, *(uint32_t*)p_script);
  return p_script + sizeof(uint32_t);
}

void* text_height( NVGcontext* p_ctx, void* p_script ) {
  nvgTextLineHeight(p_ctx, *(float*)p_script);
  return p_script + sizeof(float);
}



//=============================================================================
// the main script function

//---------------------------------------------------------
void run_script( GLuint script_id, driver_data_t* p_data ) {
  char buff[200];

  // sprintf(buff, "script id: %d", script_id);
  // send_puts(buff);

  // get the script in question. bail if it isn't there
  void* p_script = get_script( p_data, script_id );
  if (p_script == NULL) {
    // sprintf(buff, "Tried to render NULL script %d", script_id);
    // send_puts( buff );
    return;
  };

  // setup
  NVGcontext* p_ctx = p_data->p_ctx;

  // get the first op
  GLuint op = *(GLuint*)p_script;

  // loop though the script, running each command in turn.
  // recurse into more script calls if necessary
  while ( op != OP_TERMINATE ) {
    // sprintf(buff, "op: 0x%X", op);
    // send_puts(buff);

    // advance the pointer past the op code
    p_script += sizeof(GLuint);

    // take the appropriate action based on the command id
    switch( op ) {
      // state control
      case OP_PUSH_STATE:               nvgSave(p_ctx);     break;
      case OP_POP_STATE:                nvgRestore(p_ctx);  break;
      case OP_RESET_STATE:              nvgReset(p_ctx);    break;

      // script control
      case OP_RUN_SCRIPT:               p_script = internal_run_script( p_script, p_data ); break;

      // render styles
      case OP_PAINT_LINEAR:             p_script = paint_linear( p_ctx, p_script ); break;
      case OP_PAINT_BOX:                p_script = paint_box( p_ctx, p_script ); break;
      case OP_PAINT_RADIAL:             p_script = paint_radial( p_ctx, p_script ); break;
      case OP_PAINT_IMAGE:              p_script = paint_image( p_ctx, p_script, p_data ); break;

      case OP_ANTI_ALIAS:               p_script = shape_anti_alias( p_ctx, p_script ); break;

      case OP_STROKE_WIDTH:             p_script = shape_width( p_ctx, p_script ); break;
      case OP_STROKE_COLOR:             p_script = stroke_color( p_ctx, p_script ); break;
      case OP_STROKE_PAINT:             nvgStrokePaint(p_ctx, current_paint); break;

      case OP_FILL_COLOR:               p_script = fill_color( p_ctx, p_script ); break;
      case OP_FILL_PAINT:               nvgFillPaint(p_ctx, current_paint); break;

      case OP_MITER_LIMIT:              p_script = miter_limit( p_ctx, p_script ); break;
      case OP_LINE_CAP:                 p_script = line_cap( p_ctx, p_script ); break;
      case OP_LINE_JOIN:                p_script = line_join( p_ctx, p_script ); break;
      case OP_GLOBAL_ALPHA:             p_script = global_alpha( p_ctx, p_script ); break;

      // scissoring
      case OP_SCISSOR:                  p_script = scissor( p_ctx, p_script ); break;
      case OP_INTERSECT_SCISSOR:        p_script = intersect_scissor( p_ctx, p_script ); break;
      case OP_RESET_SCISSOR:            nvgResetScissor(p_ctx); break;

      // path operations
      case OP_PATH_BEGIN:               nvgBeginPath(p_ctx); break;

      case OP_PATH_MOVE_TO:             p_script = move_to( p_ctx, p_script ); break;
      case OP_PATH_LINE_TO:             p_script = line_to( p_ctx, p_script ); break;
      case OP_PATH_BEZIER_TO:           p_script = bezier_to( p_ctx, p_script ); break;
      case OP_PATH_QUADRATIC_TO:        p_script = quadratic_to( p_ctx, p_script ); break;
      case OP_PATH_ARC_TO:              p_script = arc_to( p_ctx, p_script ); break;
      case OP_PATH_CLOSE:               nvgClosePath(p_ctx); break;
      case OP_PATH_WINDING:             p_script = path_winding( p_ctx, p_script ); break;

      case OP_FILL:                     nvgFill(p_ctx); break;
      case OP_STROKE:                   nvgStroke(p_ctx); break;

      case OP_TRIANGLE:                 p_script = triangle( p_ctx, p_script ); break;
      case OP_ARC:                      p_script = arc( p_ctx, p_script ); break;
      case OP_RECT:                     p_script = rect( p_ctx, p_script ); break;
      case OP_ROUND_RECT:               p_script = round_rect( p_ctx, p_script ); break;
      case OP_ROUND_RECT_VAR:           break;
      case OP_ELLIPSE:                  p_script = ellipse( p_ctx, p_script ); break;
      case OP_CIRCLE:                   p_script = circle( p_ctx, p_script ); break;
      case OP_SECTOR:                   p_script = sector( p_ctx, p_script ); break;
      case OP_TEXT:                     p_script = text( p_ctx, p_script ); break;

      // transform operations
      case OP_TX_RESET:                 nvgResetTransform(p_ctx); break;
      case OP_TX_IDENTITY:              break;
      case OP_TX_MATRIX:                p_script = tx_matrix( p_ctx, p_script ); break;
      case OP_TX_TRANSLATE:             p_script = tx_translate( p_ctx, p_script ); break;
      case OP_TX_SCALE:                 p_script = tx_scale( p_ctx, p_script ); break;
      case OP_TX_ROTATE:                p_script = tx_rotate( p_ctx, p_script ); break;
      case OP_TX_SKEW_X:                p_script = tx_skew_x( p_ctx, p_script ); break;
      case OP_TX_SKEW_Y:                p_script = tx_skew_y( p_ctx, p_script ); break;

      // font styles
      case OP_FONT:                     p_script = font( p_ctx, p_script ); break;
      case OP_FONT_BLUR:                p_script = font_blur( p_ctx, p_script ); break;
      case OP_FONT_SIZE:                p_script = font_size( p_ctx, p_script ); break;
      case OP_TEXT_ALIGN:               p_script = text_align( p_ctx, p_script ); break;
      case OP_TEXT_HEIGHT:              p_script = text_height( p_ctx, p_script ); break;


      case OP_TERMINATE:                return;

      default:
        sprintf(buff, "!!!Unknown script command: %d", op);
        send_puts( buff );
        // send_puts( "!!!Unknown script command" );
        return;
    }

    // prep the next op code
    op = *(GLuint*)p_script;
  }
}
