#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include "php_java.h"
#include "parser.h"

#define APPEND(c) { \
  if(i>=(*env)->len-1) { \
    unsigned char* s1=realloc(s, (*env)->len*=2); \
    if(!s1) exit(9); else s=(*env)->s=s1; \
  } \
  s[i++]=c; \
}
#define PUSH(t) { \
  parser_string_t *str = tag[t].strings; \
  short n = tag[t].n; \
  assert((t!=0) || (t==0 && !tag[0].n)); \
  s[i]=0; \
  str[n].string=&((*env)->s); \
  str[n].off=i0; \
  str[n].length=i-i0; \
  ++tag[t].n; \
  APPEND(0); \
  i0=i; \
}
#define RESET() { \
  type=VOJD;\
  level=0; \
  eor=0; \
  blen=0; \
  in_dquote=0; \
  i=0; \
  i0=0; \
  (*env)->c=0; (*env)->pos=0; \
}
#define CALL_BEGIN() { \
  if(cb->begin) (*cb->begin)(tag, cb); \
}
#define CALL_END() { \
  if(cb->end) (*cb->end)(tag[0].strings, cb); \
}

short EXT_GLOBAL (parse) (proxyenv *env, parser_cb_t *cb) {
  parser_string_t v1[1], v2[MAX_ARGS], v3[MAX_ARGS];
  parser_tag_t tag[] = {{0, v1}, {0, v2}, {0, v3}};
  unsigned char ch;
  // VOJD is VOID for f... windows (VOID is in winsock2.h)
  enum {BEGIN, KEY, VAL, ENTITY, BLOB, VOJD, END} type = VOJD;
  short level=0, in_dquote=0, eor=0, blen=0;
  register ssize_t pos=(*env)->pos, c=(*env)->c; size_t i=0, i0=0, e;
  register unsigned char *s=(*env)->s;
  assert(s); if(!s) return 1;

  while(!eor) {
    if(c>=pos) { 
    res: 
      errno=0;
      pos=(*env)->f_recv(env, (*env)->recv_buf, sizeof (*env)->recv_buf);
      if(!pos && errno==EINTR) goto res; // Solaris, see INN FAQ
      if(pos<=0) break;
      c=0; 
    }
    switch(ch=(*env)->recv_buf[c]) 
      {/* --- This block must be compilable with an ansi C compiler or javac  --- */
      case '<': if(in_dquote) {APPEND(ch); break;}
	level++;
	type=BEGIN;
	break;
      case '\t': case '\f': case '\n': case '\r': case ' ': if(in_dquote) {APPEND(ch); break;}
	if(type==BEGIN) {
	  PUSH(type); 
	  type = KEY; 
	}
	break;
      case '=': if(in_dquote) {APPEND(ch); break;}
	PUSH(type);
	type=VAL;
	break;
      case '/': if(in_dquote) {APPEND(ch); break;}
	if(type==BEGIN) { type=END; level--; }
	level--;
	break;
      case '>': if(in_dquote) {APPEND(ch); break;}
	if(type==END){
	  PUSH(BEGIN);
	  CALL_END();
	} else {
	  if(type==VAL||type==BEGIN) PUSH(type);
	  CALL_BEGIN();
	}
	tag[0].n=tag[1].n=tag[2].n=0; i0=i=0;      		/* RESET */
	type=VOJD;
	if(level==0) eor=1; 
	break;
      case ';':
	if(type==ENTITY) {
	  switch (s[e+1]) {
	  case 'l': s[e]='<'; i=e+1; break; /* lt */
	  case 'g': s[e]='>'; i=e+1; break; /* gt */
	  case 'a': s[e]= (s[e+2]=='m'?'&':'\''); i=e+1; break; /* amp, apos */
	  case 'q': s[e]='"'; i=e+1; break; /* quot */
	  default: APPEND(ch);
	  }
	  type=VAL; //& escapes may only appear in values
	} else {
	  APPEND(ch);
	}
	break;
      case '&': 
	type = ENTITY;
	e=i;
	APPEND(ch);
	break;
      case '"':
	in_dquote = !in_dquote;
	if(!in_dquote && type==VAL) {
	  PUSH(type);
	  type = KEY;
	}
	break;
      default:
	APPEND(ch);
      }
    c++;
  }
  RESET();
  return 0;
}

short EXT_GLOBAL (parse_header) (proxyenv *env, parser_cb_t *cb) {
  parser_string_t v1[1], v2[1], v3[1];
  parser_tag_t tag[] = {{0, v1}, {0, v2}, {0, v3}};
  unsigned char ch;
  // VOJD is VOID for f... windows (VOID is in winsock2.h)
  enum {BEGIN, KEY, VAL, VOJD, END} type = VOJD;
  short level=0, in_dquote=0, eor=0, blen=0;
  register ssize_t pos=0, c=0; size_t i=0, i0=0;
  register unsigned char *s=(*env)->s;
  assert(s); if(!s) return 1;

  while(!eor) {
    if(c>=pos) { 
    res: 
      errno=0;
      pos=(*env)->f_recv(env, (*env)->recv_buf, sizeof (*env)->recv_buf);
      if(!pos && errno==EINTR) goto res; // Solaris, see INN FAQ

      c=0; 
      if(pos<=0) break;
    }
    switch(ch=(*env)->recv_buf[c]) 
      {
      case '\r': case '\f': case ' ': case '\t': break; /* skip */
      case '\n':
	if(type==BEGIN) eor=1;
	else if(type==KEY || type==VAL) {
	  PUSH(type); 
	  CALL_BEGIN();
	}
	tag[0].n=tag[1].n=tag[2].n=0; i0=i=0;      		/* RESET */
	in_dquote=0;
	type = BEGIN; 
	break;
      case ':': if(in_dquote) {APPEND(ch); break;}
	in_dquote=1;
	PUSH(type);
	type=KEY;
	break;
      case '=':
	if(type==KEY) { 
	  PUSH(type);
	  type=VAL;
	} else {
	  APPEND(ch);
	}
	break;
      default:
	APPEND(ch);
      }
    c++;
  }
  RESET();
  (*env)->c=c; (*env)->pos=pos;
  return 0;
}

#if 0
void begin(parser_tag_t tag[3], struct parser_cb *cb){
  int i;
  for(i=0; i<tag[2].n; i++)
    printf("<begin: %s, %s (%d, %d)\n", tag[1].strings[i].string, tag[2].strings[i].string, tag[1].strings[i].length, tag[2].strings[i].length);
}
void end(parser_string_t str[1], struct parser_cb *cb){
  printf("<end> %s (%d)\n", str[0].string, str[0].length);
}

main() {
  parser_cb_t cb={begin, end, 0};
  FILE *f=fopen("xmltest.xml", "r");
  parse(f, &cb);
  fclose(f);
}

#endif
