/*
 *  JSON helpers
 *  Copyright (C) 2011 Andreas Öman
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>

#include "json.h"
#include "dbl.h"
#include "tvheadend.h"

#define NOT_THIS_TYPE ((void *)-1)

static const char *json_parse_value(const char *s, void *parent, 
				    const char *name,
				    const json_deserializer_t *jd,
				    void *opaque,
				    const char **failp, const char **failmsg);

/**
 * Returns a newly allocated string
 */
static char *
json_parse_string(const char *s, const char **endp,
		  const char **failp, const char **failmsg)
{
  const char *start;
  char *r, *a, *b;
  int l, esc = 0;

  while(*s > 0 && *s < 33)
    s++;

  if(*s != '"')
    return NOT_THIS_TYPE;

  s++;
  start = s;

  while(1) {
    if(*s == 0) {
      *failmsg = "Unexpected end of JSON message";
      *failp = s;
      return NULL;
    }

    if(*s == '\\') {
      esc = 1;
      /* skip the escape */
      s++;
      if (*s == 'u') s += 4;
      // Note: we could detect the lack of support here!
    } else if(*s == '"') {

      *endp = s + 1;

      /* End */
      l = s - start;
      r = malloc(l + 1);
      memcpy(r, start, l);
      r[l] = 0;

      if(esc) {
	/* Do deescaping inplace */

	a = b = r;

	while(*a) {
	  if(*a == '\\') {
	    a++;
	    if(*a == 'b')
	      *b++ = '\b';
      else if(*a == '\\')
        *b++ = '\\';
	    else if(*a == 'f')
	      *b++ = '\f';
	    else if(*a == 'n')
	      *b++ = '\n';
	    else if(*a == 'r')
	      *b++ = '\r';
	    else if(*a == 't')
	      *b++ = '\t';
	    else if(*a == 'u') {
	      // Unicode character
	      int i, v = 0;

	      a++;
	      for(i = 0; i < 4; i++) {
		v = v << 4;
		switch(a[i]) {
		case '0' ... '9':
		  v |= a[i] - '0';
		  break;
		case 'a' ... 'f':
		  v |= a[i] - 'a' + 10;
		  break;
		case 'A' ... 'F':
		  v |= a[i] - 'F' + 10;
		  break;
		default:
		  free(r);
		  *failmsg = "Incorrect escape sequence";
		  *failp = (a - r) + start;
		  return NULL;
		}
	      }
	      a+=3;
	      b += put_utf8(b, v);
	    } else {
	      *b++ = *a;
	    }
	    a++;
	  } else {
	    *b++ = *a++;
	  }
	}
	*b = 0;
      }
      return r;
    }
    s++;
  }
}


/**
 *
 */
static void *
json_parse_map(const char *s, const char **endp, const json_deserializer_t *jd,
	       void *opaque, const char **failp, const char **failmsg)

{
  char *name;
  const char *s2;
  void *r;

  while(*s > 0 && *s < 33)
    s++;

  if(*s != '{')
    return NOT_THIS_TYPE;

  s++;

  r = jd->jd_create_map(opaque);
  
  while(*s > 0 && *s < 33)
    s++;

  if(*s != '}') {

    while(1) {
      name = json_parse_string(s, &s2, failp, failmsg);
      if(name == NOT_THIS_TYPE) {
	*failmsg = "Expected string";
	*failp = s;
	return NULL;
      }

      if(name == NULL)
	return NULL;

      s = s2;
    
      while(*s > 0 && *s < 33)
	s++;

      if(*s != ':') {
	jd->jd_destroy_obj(opaque, r);
	free(name);
	*failmsg = "Expected ':'";
	*failp = s;
	return NULL;
      }
      s++;

      s2 = json_parse_value(s, r, name, jd, opaque, failp, failmsg);
      free(name);

      if(s2 == NULL) {
	jd->jd_destroy_obj(opaque, r);
	return NULL;
      }

      s = s2;

      while(*s > 0 && *s < 33)
	s++;

      if(*s == '}')
	break;

      if(*s != ',') {
	jd->jd_destroy_obj(opaque, r);
	*failmsg = "Expected ','";
	*failp = s;
	return NULL;
      }
      s++;
    }
  }

  s++;
  *endp = s;
  return r;
}


/**
 *
 */
static void *
json_parse_list(const char *s, const char **endp, const json_deserializer_t *jd,
		void *opaque, const char **failp, const char **failmsg)
{
  const char *s2;
  void *r;

  while(*s > 0 && *s < 33)
    s++;

  if(*s != '[')
    return NOT_THIS_TYPE;

  s++;

  r = jd->jd_create_list(opaque);
  
  while(*s > 0 && *s < 33)
    s++;

  if(*s != ']') {

    while(1) {

      s2 = json_parse_value(s, r, NULL, jd, opaque, failp, failmsg);

      if(s2 == NULL) {
	jd->jd_destroy_obj(opaque, r);
	return NULL;
      }

      s = s2;

      while(*s > 0 && *s < 33)
	s++;

      if(*s == ']')
	break;

      if(*s != ',') {
	jd->jd_destroy_obj(opaque, r);
	*failmsg = "Expected ','";
	*failp = s;
	return NULL;
      }
      s++;
    }
  }
  s++;
  *endp = s;
  return r;
}

/**
 *
 */
static const char *
json_parse_double(const char *s, double *dp)
{
  const char *ep;
  while(*s > 0 && *s < 33)
    s++;

  double d = my_str2double(s, &ep);

  if(ep == s)
    return NULL;

  *dp = d;
  return ep;
}


/**
 *
 */
static char *
json_parse_integer(const char *s, long *lp)
{
  char *ep;
  while(*s > 0 && *s < 33)
    s++;
  const char *s2 = s;
  if(*s2 == '-')
    s2++;
  while(*s2 >= '0' && *s2 <= '9')
    s2++;

  if(*s2 == 0)
    return NULL;
  if(s2[0] == '.' || s2[0] == 'e' || s2[0] == 'E')
    return NULL; // Is floating point

  long v = strtol(s, &ep, 10);
  if(v == LONG_MIN || v == LONG_MAX)
    return NULL;

  if(ep == s)
    return NULL;

  *lp = v;
  return ep;
}

/**
 *
 */
static const char *
json_parse_value(const char *s, void *parent, const char *name,
		 const json_deserializer_t *jd, void *opaque,
		 const char **failp, const char **failmsg)
{
  const char *s2;
  char *str;
  double d = 0;
  long l = 0;
  void *c;

  if((c = json_parse_map(s, &s2, jd, opaque, failp, failmsg)) == NULL)
    return NULL;

  if(c != NOT_THIS_TYPE) {
    jd->jd_add_obj(opaque, parent, name, c);
    return s2;
  }

  if((c = json_parse_list(s, &s2, jd, opaque, failp, failmsg)) == NULL)
    return NULL;
  
  if(c != NOT_THIS_TYPE) {
    jd->jd_add_obj(opaque, parent, name, c);
    return s2;
  }

  if((str = json_parse_string(s, &s2, failp, failmsg)) == NULL)
    return NULL;

  if(str != NOT_THIS_TYPE) {
    jd->jd_add_string(opaque, parent, name, str);
    return s2;
  }

  if((s2 = json_parse_integer(s, &l)) != NULL) {
    jd->jd_add_long(opaque, parent, name, l);
    return s2;
  } else if((s2 = json_parse_double(s, &d)) != NULL) {
    jd->jd_add_double(opaque, parent, name, d);
    return s2;
  }

  while(*s > 0 && *s < 33)
    s++;

  if(!strncmp(s, "true", 4)) {
    jd->jd_add_bool(opaque, parent, name, 1);
    return s + 4;
  }

  if(!strncmp(s, "false", 5)) {
    jd->jd_add_bool(opaque, parent, name, 0);
    return s + 5;
  }

  if(!strncmp(s, "null", 4)) {
    jd->jd_add_null(opaque, parent, name);
    return s + 4;
  }

  *failmsg = "Unknown token";
  *failp = s;
  return NULL;
}


/**
 *
 */
void *
json_deserialize(const char *src, const json_deserializer_t *jd, void *opaque,
		 char *errbuf, size_t errlen)
{
  const char *end;
  void *c;
  const char *errmsg;
  const char *errp;

  c = json_parse_map(src, &end, jd, opaque, &errp, &errmsg);
  if(c == NOT_THIS_TYPE)
    c = json_parse_list(src, &end, jd, opaque, &errp, &errmsg);

  if(c == NOT_THIS_TYPE) {
    snprintf(errbuf, errlen, "Invalid JSON, expected '{' or '['");
    return NULL;
  }

  if(c == NULL) {
    size_t len = strlen(src);
    ssize_t offset = errp - src;
    if(offset > len || offset < 0) {
      snprintf(errbuf, errlen, "%s at (bad) offset %d", errmsg, (int)offset);
    } else {
      offset -= 10;
      if(offset < 0)
	offset = 0;
      snprintf(errbuf, errlen, "%s at offset %d : '%.20s'", errmsg, (int)offset,
	       src + offset);
    }
  }
  return c;
}
