#include "stralloc.h"
#include "alloc.h"
#include "str.h"
#include "token.h"
#include "gen_allocdefs.h"

static struct token comma = { TOKEN_COMMA };

void token_reverse(ta)
token_alloc *ta;
{
 int i;
 int n;
 struct token temp;

 n = ta->len - 1;
 for (i = 0;i + i < n;++i)
  {
   temp = ta->t[i];
   ta->t[i] = ta->t[n - i];
   ta->t[n - i] = temp;
  }
}

GEN_ALLOC_ready(token_alloc,struct token,t,len,a,i,n,x,30,token_ready)
GEN_ALLOC_readyplus(token_alloc,struct token,t,len,a,i,n,x,30,token_readyplus)
GEN_ALLOC_append(token_alloc,struct token,t,len,a,i,n,x,30,token_readyplus,token_append)

static int needspace(t1,t2)
int t1;
int t2;
{
 if (!t1) return 0;
 if (t1 == TOKEN_COLON) return 1;
 if (t1 == TOKEN_COMMA) return 1;
 if (t2 == TOKEN_LEFT) return 1;
 switch(t1)
  {
   case TOKEN_ATOM: case TOKEN_LITERAL:
   case TOKEN_QUOTE: case TOKEN_COMMENT:
     switch(t2)
      {
       case TOKEN_ATOM: case TOKEN_LITERAL:
       case TOKEN_QUOTE: case TOKEN_COMMENT:
         return 1;
      }
  }
 return 0;
}

static int atomok(ch)
char ch;
{
 if (ch <= 32) return 0;
 if (ch >= 127) return 0;
 switch(ch)
  {
   case '(': case ')': case '[': case ']':
   case '"': case '\\':
   case '<': case '>': case ';': case ':':
   case '@': case ',': case '.':
     return 0;
  }
 return 1;
}

int token_unparse(sa,ta)
stralloc *sa;
token_alloc *ta;
{
 struct token *t;
 int len;
 int ch;
 int i;
 int j;
 int lasttype;
 int newtype;
 char *s;
 char *lineb;
 char *linee;

 len = 0;
 lasttype = 0;
 for (i = 0;i < ta->len;++i)
  {
   t = ta->t + i;
   newtype = t->type;
   if (needspace(lasttype,newtype))
     ++len;
   lasttype = newtype;
   switch(newtype)
    {
     case TOKEN_COMMA:
       len += 3; break;
     case TOKEN_AT: case TOKEN_DOT: case TOKEN_LEFT: case TOKEN_RIGHT:
     case TOKEN_SEMI: case TOKEN_COLON:
       ++len; break;
     case TOKEN_ATOM: case TOKEN_QUOTE: case TOKEN_LITERAL: case TOKEN_COMMENT:
       if (t->type != TOKEN_ATOM) len += 2;
       for (j = 0;j < t->slen;++j)
	 switch(ch = t->s[j])
	  {
	   case '"': case '[': case ']': case '(': case ')':
	   case '\\': case '\r': case '\n': ++len;
	   default: ++len;
	  }
       break;
    }
  }
 len += 2;

 if (!stralloc_ready(sa,len))
   return -1;

 s = sa->s;
 lineb = s;
 linee = 0;

 lasttype = 0;
 for (i = 0;i < ta->len;++i)
  {
   t = ta->t + i;
   newtype = t->type;
   if (needspace(lasttype,newtype))
     *s++ = ' ';
   lasttype = newtype;
   switch(newtype)
    {
     case TOKEN_COMMA:
       *s++ = ',';
#define NSUW \
 s[0] = '\n'; s[1] = ' '; \
 if (linee && (s - lineb <= 80)) \
  { while (linee < s) { linee[0] = linee[2]; ++linee; } linee -= 2; } \
 else { if (linee) lineb = linee + 1; linee = s; s += 2; }
       NSUW
       break;
     case TOKEN_AT: *s++ = '@'; break;
     case TOKEN_DOT: *s++ = '.'; break;
     case TOKEN_LEFT: *s++ = '<'; break;
     case TOKEN_RIGHT: *s++ = '>'; break;
     case TOKEN_SEMI: *s++ = ';'; break;
     case TOKEN_COLON: *s++ = ':'; break;
     case TOKEN_ATOM: case TOKEN_QUOTE: case TOKEN_LITERAL: case TOKEN_COMMENT:
       if (t->type == TOKEN_QUOTE) *s++ = '"';
       if (t->type == TOKEN_LITERAL) *s++ = '[';
       if (t->type == TOKEN_COMMENT) *s++ = '(';
       for (j = 0;j < t->slen;++j)
	 switch(ch = t->s[j])
	  {
	   case '"': case '[': case ']': case '(': case ')':
	   case '\\': case '\r': case '\n': *s++ = '\\';
	   default: *s++ = ch;
	  }
       if (t->type == TOKEN_QUOTE) *s++ = '"';
       if (t->type == TOKEN_LITERAL) *s++ = ']';
       if (t->type == TOKEN_COMMENT) *s++ = ')';
       break;
    }
  }
 NSUW
 --s;
 sa->len = s - sa->s;
 return 1;
}

int token_unquote(sa,ta)
stralloc *sa;
token_alloc *ta;
{
 struct token *t;
 int len;
 int i;
 int j;
 char *s;

 len = 0;
 for (i = 0;i < ta->len;++i)
  {
   t = ta->t + i;
   switch(t->type)
    {
     case TOKEN_COMMA: case TOKEN_AT: case TOKEN_DOT: case TOKEN_LEFT: 
     case TOKEN_RIGHT: case TOKEN_SEMI: case TOKEN_COLON: 
       ++len; break;
     case TOKEN_LITERAL:
       len += 2;
     case TOKEN_ATOM: case TOKEN_QUOTE:
       len += t->slen;
    }
  }

 if (!stralloc_ready(sa,len))
   return -1;

 s = sa->s;

 for (i = 0;i < ta->len;++i)
  {
   t = ta->t + i;
   switch(t->type)
    {
     case TOKEN_COMMA: *s++ = ','; break;
     case TOKEN_AT: *s++ = '@'; break;
     case TOKEN_DOT: *s++ = '.'; break;
     case TOKEN_LEFT: *s++ = '<'; break;
     case TOKEN_RIGHT: *s++ = '>'; break;
     case TOKEN_SEMI: *s++ = ';'; break;
     case TOKEN_COLON: *s++ = ':'; break;
     case TOKEN_ATOM: case TOKEN_QUOTE: case TOKEN_LITERAL:
       if (t->type == TOKEN_LITERAL) *s++ = '[';
       for (j = 0;j < t->slen;++j)
	 *s++ = t->s[j];
       if (t->type == TOKEN_LITERAL) *s++ = ']';
       break;
     case TOKEN_COMMENT: break;
    }
  }
 sa->len = s - sa->s;
 return 1;
}

int token_parse(ta,sa,buf)
token_alloc *ta;
stralloc *sa;
stralloc *buf;
{
 int i;
 int salen;
 int level;
 struct token *t;
 int numtoks;
 int numchars;
 char *cbuf;

 salen = sa->len;

 numchars = 0;
 numtoks = 0;
 for (i = 0;i < salen;++i)
   switch(sa->s[i])
    {
     case '.': case ',': case '@': case '<': case '>': case ':': case ';':
       ++numtoks; break;
     case ' ': case '\t': case '\r': case '\n': break;
     case ')': case ']': return 0;
     /* other control chars and non-ASCII chars are also bad, in theory */
     case '(':
       level = 1;
       while (level)
	{
	 if (++i >= salen) return 0;
	 switch(sa->s[i])
	  {
	   case '(': ++level; break;
	   case ')': --level; break;
	   case '\\': if (++i >= salen) return 0;
	   default: ++numchars;
	  }
	}
       ++numtoks;
       break;
     case '"':
       level = 1;
       while (level)
	{
	 if (++i >= salen) return 0;
	 switch(sa->s[i])
	  {
	   case '"': --level; break;
	   case '\\': if (++i >= salen) return 0;
	   default: ++numchars;
	  }
	}
       ++numtoks;
       break;
     case '[':
       level = 1;
       while (level)
	{
	 if (++i >= salen) return 0;
	 switch(sa->s[i])
	  {
	   case ']': --level; break;
	   case '\\': if (++i >= salen) return 0;
	   default: ++numchars;
	  }
	}
       ++numtoks;
       break;
     default:
       do
	{
	 ++numchars;
	 if (++i >= salen)
	   break;
	}
       while (atomok(sa->s[i]));
       --i;
       ++numtoks;
    }

 if (!token_ready(ta,numtoks))
   return -1;
 if (!stralloc_ready(buf,numchars))
   return -1;
 cbuf = buf->s;
 ta->len = numtoks;

 t = ta->t;
 for (i = 0;i < salen;++i)
   switch(sa->s[i])
    {
     case '.': t->type = TOKEN_DOT; ++t; break;
     case ',': t->type = TOKEN_COMMA; ++t; break;
     case '@': t->type = TOKEN_AT; ++t; break;
     case '<': t->type = TOKEN_LEFT; ++t; break;
     case '>': t->type = TOKEN_RIGHT; ++t; break;
     case ':': t->type = TOKEN_COLON; ++t; break;
     case ';': t->type = TOKEN_SEMI; ++t; break;
     case ' ': case '\t': case '\r': case '\n': break;
     case '(':
       t->type = TOKEN_COMMENT; t->s = cbuf; t->slen = 0;
       level = 1;
       while (level)
	{
	 ++i; /* assert: < salen */
	 switch(sa->s[i])
	  {
	   case '(': ++level; break;
	   case ')': --level; break;
	   case '\\': ++i; /* assert: < salen */
	   default: *cbuf++ = sa->s[i]; ++t->slen;
	  }
	}
       ++t;
       break;
     case '"':
       t->type = TOKEN_QUOTE; t->s = cbuf; t->slen = 0;
       level = 1;
       while (level)
	{
	 ++i; /* assert: < salen */
	 switch(sa->s[i])
	  {
	   case '"': --level; break;
	   case '\\': ++i; /* assert: < salen */
	   default: *cbuf++ = sa->s[i]; ++t->slen;
	  }
	}
       ++t;
       break;
     case '[':
       t->type = TOKEN_LITERAL; t->s = cbuf; t->slen = 0;
       level = 1;
       while (level)
	{
	 ++i; /* assert: < salen */
	 switch(sa->s[i])
	  {
	   case ']': --level; break;
	   case '\\': ++i; /* assert: < salen */
	   default: *cbuf++ = sa->s[i]; ++t->slen;
	  }
	}
       ++t;
       break;
     default:
       t->type = TOKEN_ATOM; t->s = cbuf; t->slen = 0;
       do
	{
	 *cbuf++ = sa->s[i]; ++t->slen;
	 if (++i >= salen)
	   break;
	}
       while (atomok(sa->s[i]));
       --i;
       ++t;
    }
 return 1;
}

static int gotaddr(taout,taaddr,callback)
token_alloc *taout;
token_alloc *taaddr;
int (*callback)();
{
 int i;

 if (callback(taaddr) != 1)
   return 0;

 if (!token_readyplus(taout,taaddr->len))
   return 0;
 
 for (i = 0;i < taaddr->len;++i)
   taout->t[taout->len++] = taaddr->t[i];

 taaddr->len = 0;
 return 1;
}

int token_addrlist(taout,taaddr,ta,callback)
token_alloc *taout;
token_alloc *taaddr;
token_alloc *ta;
int (*callback)();
{
 struct token *t;
 struct token *beginning;
 int ingroup;
 int wordok;

 taout->len = 0;
 taaddr->len = 0;

 if (!token_readyplus(taout,1)) return -1;
 if (!token_readyplus(taaddr,1)) return -1;
 
 ingroup = 0;
 wordok = 1;

 beginning = ta->t + 2;
 t = ta->t + ta->len - 1;

 /* rfc 822 address lists are easy to parse from right to left */

#define FLUSH if (taaddr->len) if (!gotaddr(taout,taaddr,callback)) return -1;
#define FLUSHCOMMA if (taaddr->len) { \
if (!gotaddr(taout,taaddr,callback)) return -1; \
if (!token_append(taout,&comma)) return -1; }
#define ADDRLEFT if (!token_append(taaddr,t--)) return -1;
#define OUTLEFT if (!token_append(taout,t--)) return -1;

 while (t >= beginning)
  {
   switch(t->type)
    {
     case TOKEN_SEMI:
       FLUSHCOMMA
       if (ingroup) return 0;
       ingroup = 1;
       wordok = 1;
       break;
     case TOKEN_COLON:
       FLUSH
       if (!ingroup) return 0;
       ingroup = 0;
       while ((t >= beginning) && (t->type != TOKEN_COMMA))
	 OUTLEFT
       if (t >= beginning)
	 OUTLEFT
       wordok = 1;
       continue;
     case TOKEN_RIGHT:
       FLUSHCOMMA
       OUTLEFT
       while ((t >= beginning) && (t->type != TOKEN_LEFT))
	 ADDRLEFT
       /* important to use address here even if it's empty: <> */
       if (!gotaddr(taout,taaddr,callback)) return -1;
       if (t < beginning) return 0;
       OUTLEFT
       while ((t >= beginning) && ((t->type == TOKEN_COMMENT) || (t->type == TOKEN_ATOM) || (t->type == TOKEN_QUOTE) || (t->type == TOKEN_AT) || (t->type == TOKEN_DOT)))
	 OUTLEFT
       wordok = 0;
       continue;
     case TOKEN_ATOM: case TOKEN_QUOTE: case TOKEN_LITERAL:
       if (!wordok)
	 FLUSHCOMMA
       wordok = 0;
       ADDRLEFT
       continue;
     case TOKEN_COMMENT:
       /* comment is lexically a space; shouldn't affect wordok */
       break;
     case TOKEN_COMMA:
       FLUSH
       wordok = 1;
       break;
     default:
       wordok = 1;
       ADDRLEFT
       continue;
    }
   OUTLEFT
  }
 FLUSH
 while (t >= ta->t)
   OUTLEFT

 token_reverse(taout);
 return 1;
}
