// wget http://projects.scratch.mit.edu/internalapi/project/21234602/get/

/*
    to do: top-level of codegen, extract all the separate scripts, remember
           the comments re x/y location of blocks.  Also, how do I attach
           actual comments to the code?  They use some sort of index into
           some block table, which I might not be able to locate.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#ifndef JSMN_PARENT_LINKS
#define JSMN_PARENT_LINKS
#endif

#define JSMN_STRICT
#include "jsmn/jsmn.c"
#define JSMN_CHILD 255

// I originally added JSMN_CHILD to the enum, but I reverted jsmn to its original code
// and added that element here as a #define.  Being careful that it is higher than
// the elements in the enum in case the author adds any new elements of his own.

int my_strncmp(char *left, char *right, int chars) {
  //  fflush(stderr); fflush(stdout);
  //  fprintf(stdout, "/*strncmp(\"%.*s\", \"%.*s\", %d)*/", strlen(left) < chars ? strlen(left) : chars, left, strlen(right) < chars ? strlen(right) : chars, right, chars);
  //  fflush(stderr); fflush(stdout);
  return strncmp(left, right, chars);
}

#define tokstrcmp(js, t, s) ((my_strncmp((js)+(t).start, (s), (t).end - (t).start) == 0) && (strlen(s) == (t).end - (t).start))

#ifndef TRUE
#define TRUE (0==0)
#define FALSE (0!=0)
#endif

void showtok (char *js, jsmntok_t * token, int tok)
{
   int i;

   if ((token[tok].type != JSMN_OBJECT) && (token[tok].type != JSMN_ARRAY)) {
      // to do - handle escapes as necessary
      if (token[tok].type == JSMN_STRING)
	 putchar ('"');
      for (i = token[tok].start; i < token[tok].end; i++)
	 putchar (js[i]);
      if (token[tok].type == JSMN_STRING)
	 putchar ('"');
   }
}

void debug_showtok (char *js, jsmntok_t * token, int tok)
{
  int i,j=0;
  static char result[1024];

  if ((token[tok].type != JSMN_OBJECT) && (token[tok].type != JSMN_ARRAY)) {
    // to do - handle escapes as necessary
    for (i = token[tok].start; i < token[tok].end; i++) {
      if (isalnum(js[i])) {
        result[j++] = js[i];
      } else {
        //printf ("/*");
        if (js[i] == '%' && js[i+1] == 'n') {
          i += 1;
        } else if (js[i] != ' ' && js[i] != '#' && js[i] != '?') result[j++] = js[i];
        //printf ("*/");
      }
    }
  }
  result[j] = '\0';
  if (result[0] == '*' && isalpha(result[1]) && isupper(result[1])) {
    fprintf(stdout, "%s", result+1);
  } else if (strcmp(result, "char") == 0) {
    fprintf(stdout, "tchar");
  } else fprintf(stdout, "%s", result);
}

void print_var_name (char *js, jsmntok_t * token, int tok)
{
   int i;

   if ((token[tok].type != JSMN_OBJECT) && (token[tok].type != JSMN_ARRAY)) {
      // to do - handle escapes as necessary
      for (i = token[tok].start; i < token[tok].end; i++) {
	if (js[i] != '\\') {
          if (js[i] == ':') {
            if (i+1 == token[tok].end) {
	    } else {
              putchar ('_');
	    }
	  } else {
            putchar (js[i]);
	  }
	}
      }
   }
}

void debug_var_name (char *js, jsmntok_t * token, int tok)
{
   int i;

   if ((token[tok].type != JSMN_OBJECT) && (token[tok].type != JSMN_ARRAY)) {
      // to do - handle escapes as necessary
      for (i = token[tok].start; i < token[tok].end; i++) {
        putchar (js[i]);
      }
   }
}

void indent (int n)
{
   while (n-- > 0)
      fprintf (stdout, "  ");	// better than \t
}

void reorder (char *js, jsmntok_t * token, jsmntok_t * tree, int tree_max,
	      int root, int depth, int display)
{
   // This makes up for a deficit in the lightweight json parser.  The jsmn
   // code as supplied requires you to walk *all* leaves of the tree in left
   // to right order whenever you access the json structure.  This is not
   // useful when you want to rearrange the order of blocks etc
   // so this code takes the serialised tree and inserts explicit branch
   // nodes, so that you may follow any path down the tree using your
   // preferred traverse order and omitting any parts that you are not
   // interested in.  Unfortunately I don't see how to add this to the original
   // code as it builds the tree, so I have retrofitted it by having it copy
   // from the original tree to a new copy.  Somewhat wasteful of space, but
   // not really an issue for Scratch json.

   static int tokenptr = 0, treeptr = 0;
   int base, children, i;

   tree[treeptr].type = token[root].type;
   tree[treeptr].start = token[root].start;
   tree[treeptr].end = token[root].end;
   tree[treeptr].size = token[root].size;
#ifdef JSMN_PARENT_LINKS
   tree[treeptr].parent = token[root].parent;
#endif
   if (treeptr + 1 >= tree_max) {
      // we could actually calculate the output size exactly with a dummy
      // pass through the data, allocate enough space off the heap, and then
      // copy the result back on top of the original array, followed by
      // freeing the heap space.  Checking of course that the original array
      // is large enough before mallocing the temp workspace...  next version 
      // perhaps...
      fprintf (stderr,
	       "reorder: the output array is not large enough - %d limit reached\n",
	       tree_max);
      exit (1);
   }
   tokenptr += 1;
   treeptr += 1;
   base = treeptr;

   if (token[root].type == JSMN_OBJECT || token[root].type == JSMN_ARRAY) {
      children = token[root].size;
      if (treeptr + 1 + children >= tree_max) {
	 fprintf (stderr,
		  "reorder: the output array is not large enough - %d limit exceeded\n",
		  tree_max);
	 exit (1);
      }
      treeptr += children;	// reserve extra space for the children
      for (i = 0; i < children; i++) {
	 tree[base + i].type = JSMN_CHILD;
	 tree[base + i].size = treeptr;
	 reorder (js, token, tree, tree_max, tokenptr, depth + 3, display);
      }
   }
}

void tree_walk (char *js, jsmntok_t * token, int root, int depth, int display)
{
   int keyword;			// token index
   int children, i;

   // I expect to write several procedures for walking parts of the json tree 
   // - this one just displays the whole tree in the original format (with
   // some contents elided for brevity since this is for doing a visual check)

   // (The next one most likely print the script components in C-like
   // notation.)

   // So this version is not especially pretty but it'll do as a debug
   // tool...

   switch (token[root].type) {
   case JSMN_OBJECT:
      children = token[root].size;
      printf("\n");
      if (display)
	 putchar ('{');
      for (i = 0; i < children; i++) {
	 if ((i & 1) == 0) {
	    if (display)
	       putchar ('\n');
	    if (display)
	       indent (depth + 1);
	    keyword = token[root + 1 + i].size;
	 } else {
	    if (display)
	       putchar (':');
	    if (display)
	       putchar (' ');
	 }
	 // fprintf(stdout, "%d@%d: ", i+1, root+1+i);

	 if (i && ((i & 1) == 1)
	     && (tokstrcmp (js, token[keyword], "variables")
		 || tokstrcmp (js, token[keyword], "contents")
		 || tokstrcmp (js, token[keyword], "lists")
		 || tokstrcmp (js, token[keyword], "sounds")
		 || tokstrcmp (js, token[keyword], "scripts")
		 || tokstrcmp (js, token[keyword], "costumes")
	     )) {
	    // now that we've added explicit child links using the 'reorder'
	    // procedure, we can skip the subtree entirely, without requiring
            // to traverse it all in order to step over data and reach the
            // following sibling...
	    if (tokstrcmp (js, token[keyword], "scripts")) {
	       tree_walk (js, token, token[root + 1 + i].size, 0, TRUE);
	    } else {
	       if (display)
		  fprintf (stdout, " << skipped >> ");
	    }
	 } else {
	    tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
            // these are all JSMN_CHILD
	 }
	 if (i & 1) {
	    if ((i + 1) != children)
	       if (display)
		 printf (",");
	 }
      }
      if (display)
	 putchar ('\n');
      if (display)
	 indent (depth);
      if (display)
	 putchar ('}');
      break;

   case JSMN_ARRAY:
      // if (display) printf ("[DEPTH%0d ", depth);
      children = token[root].size;
      if ((depth == 0) || (depth == 2)) {
        // list of sprite files
        for (i = 0; i < children; i++) {
	   tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
        }
      } else if (depth == 1) {
        // list of blocks  x y subblock
        for (i = 2; i < children; i++) {
	   tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
        }
      } else if (depth >= 3) {
        // single statement
        if (token[token[root + 1].size].type == JSMN_STRING) {
          if (tokstrcmp(js, token[token[root + 1].size], "doIf")) {
            printf("if (");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(") {\n");
	    tree_walk (js, token, token[root + 1 + 2].size, 2, display);
            printf("}\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "doUntil")) {
            printf("while (!(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(")) {\n");
	    tree_walk (js, token, token[root + 1 + 2].size, 2, display);
            printf("}\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "doWaitUntil")) {
            printf("do { wait_elapsed_from (0.001); } while (");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(");\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "doForever")) {
            printf("for (;;) {");
	    tree_walk (js, token, token[root + 1 + 1].size, 2, display);
            printf("}\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "doRepeat")) {
            printf("{\nint _i;\nfor (_i = 0; _i < ");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf("; _i++) {\n");
	    tree_walk (js, token, token[root + 1 + 2].size, 2, display);
            printf("}\n}\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "doIfElse")) {
            printf("if (");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(") {\n");
	    tree_walk (js, token, token[root + 1 + 2].size, 2, display);
            printf("} else {\n");
	    tree_walk (js, token, token[root + 1 + 3].size, 2, display);
            printf("}\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "%")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" %% ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "<")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" < ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "=")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" == ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], ">")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" > ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "|")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" || ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "&")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" && ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "+")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" + ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "-")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" - ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "*")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" * ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "\\/")) {
            printf("(");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" / ");
	    tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf(")");
/* ADD HERE */
          } else if (tokstrcmp(js, token[token[root + 1].size], "getAttribute:of:")) {
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
          } else if (tokstrcmp(js, token[token[root + 1].size], "wait:elapsed:from:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            printf("fsleep(");
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "penSize:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            printf("linewidth(");
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "stringLength:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            printf("numdigits(");
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "lineCountOfList:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            printf("(sizeof(");
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")/sizeof(");
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf("[0]))");
          } else if (tokstrcmp(js, token[token[root + 1].size], "keyPressed:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            printf("keypressed(");
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "penColor:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            printf("colour(");
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "randomFrom:to:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            printf("pickrandom(");
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "gotoX:y:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            printf("gotoxy(");
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "broadcast:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf("(");
            for (i = 2; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "computeFunction:of:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf("(");
            for (i = 2; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(")");
          } else if (tokstrcmp(js, token[token[root + 1].size], "setLine:ofList:to:")) {
            tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display);
            printf("[");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf("] = ");
            for (i = 3; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(";\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "setVar:to:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" = ");
            for (i = 2; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(";\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "changeVar:by:")) {
	    //debug_var_name (js, token, token[root + 1].size);
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf(" += ");
            for (i = 2; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(";\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "getLine:ofList:")) {
            for (i = 2; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf("[");
            tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            printf("]");
          } else if (tokstrcmp(js, token[token[root + 1].size], "call")) {
            tree_walk (js, token, token[root + 2].size, depth + 1, display);
            putchar('(');
            for (i = 2; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            printf(");\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "getParam")) {
            tree_walk (js, token, token[root + 2].size, depth + 1, display);
            //putchar('(');
            //for (i = 2; i < children; i++) {
	    //   tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	    //   if ((i + 1) != children) printf (",");
            //}
            //printf(");\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "readVariable")) {
            tree_walk (js, token, token[root + 2].size, depth + 1, display);
            //putchar('(');
            //for (i = 2; i < children; i++) {
	    //   tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	    //   if ((i + 1) != children) printf (",");
            //}
            //printf(");\n");
          } else if (tokstrcmp(js, token[token[root + 1].size], "whenIReceive")) {
            printf("\n}\nvoid ");
            tree_walk (js, token, token[root + 2].size, depth + 1, display);
            printf(" (void) {\n");
	    //} else if (tokstrcmp(js, token[token[root + 1].size], "whenGreenFlag")) {
	    //  printf("\n}\nvoid ");
	    //  tree_walk (js, token, token[root + 2].size, depth + 1, display);
	    //  printf(" (void) {\n");
	  } else {
	    debug_var_name (js, token, token[root + 1].size);
            putchar('(');
            for (i = 1; i < children; i++) {
	       tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	       if ((i + 1) != children) printf (",");
            }
            putchar(')');
	  }
	} else {
          for (i = 0; i < children; i++) {
	     tree_walk (js, token, token[root + 1 + i].size, depth + 1, display);
	     if ((i + 1) != children) printf (";\n");
          }
	}
        if (depth == 3) printf (";\n");
      }
      // if (display) putchar (']');
      break;

   default:
      if (display) {
	if (tokstrcmp(js, token[root], "readVariable")) {
	    //tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display);
            //printf(" := ");
	    //tree_walk (js, token, token[root + 1 + 2].size, 2, display);
            //printf(";\n");
	} else {
	 debug_showtok (js, token, root);
	}
      }
   }
}

// if you declare tokens[] on the stack inside main, (as was originally done following the
// example in the jsmn package) you'll get a run time error if BIGPROG is much over 400000
// - it's better to be static or allocated off the heap.

#define BIGPROG 1000000
static jsmntok_t tokens[BIGPROG];
static jsmntok_t tree[BIGPROG];

int main (int argc, char **argv)
{
   char *js;
   int r;
   jsmn_parser p;
   int json_fd;
   off_t flen;

   if (argc != 2) {
      fprintf (stderr, "syntax: %s file.json\n", argv[1]);
      exit (1);
   }

   json_fd = open (argv[1], O_RDONLY);
   if (json_fd == -1) {
      fprintf (stderr, "%s - %s\n", argv[0], strerror (errno));
      exit (2);
   }

   flen = lseek (json_fd, (off_t) 0L, SEEK_END);
   js = mmap (NULL, flen, PROT_READ, MAP_SHARED, json_fd, (off_t) 0L);

   jsmn_init (&p);
   r = jsmn_parse (&p, js, strlen (js), tokens, BIGPROG);
   if (r <= 0) {
      fprintf (stderr, "syntax: %s - parse failed\n", argv[1]);
   }

   reorder (js, tokens, tree, BIGPROG, 0, 0, FALSE);
   printf("#include <Scratc.h>\nint main(int arc, char **argv) {\n");
   tree_walk (js, tree, 0, 0, FALSE);
   printf("}\n");
   exit (0);
   return 0;
}