/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include "error/error.hh"
#include "main/main.hh"
#include "init/init.hh"
#include "file/file.hh"
#include "lisp/li_types.hh"
#include "lisp/lisp.hh"
#include "status/status.hh"
#include "threads/threads.hh"

#include 
#include 
#include 
#include 
#include 

char li_last_file[150];
int li_last_line=0;

// returns the length of the list
int        li_length(li_object *o, li_environment *env) 
{
  if (o->type()!=LI_LIST)
    return 0;
  else
  {
    int t=0;
    while (o)
    {
      t++;
      o=li_cdr(o, env);
    }
    return t;
  }
    
}

i4_bool li_is_number(li_object *o) 
{ 
  return (i4_bool)(o->type()==LI_INT || o->type()==LI_FLOAT); 
}


float li_get_float(li_object *o, li_environment *env)  // will convert int to float
{
  if (o->type()==LI_INT)
    return li_int::get(o, env)->value();
  else
    return li_float::get(o, env)->value();
}

int li_get_int(li_object *o, li_environment *env)    // will convert float to int
{
  if (o->type()==LI_FLOAT)
    return (int)li_float::get(o, env)->value();
  else
    return li_int::get(o, env)->value();
}

char *li_get_string(li_object *o, li_environment *env)
{
  return li_string::get(o, env)->value();
}


void li_skip_c_comment(char *&s)
{
  s+=2;
  while (*s && (*s!='*' || *(s+1)!='/'))
  {
    if (*s=='/' && *(s+1)=='*')
      li_skip_c_comment(s);
    else s++;
  }
  if (*s) s+=2;
}


int li_read_token(char *&s, char *buffer)
{
  // skip space
  while (*s==' ' || *s=='\t' || *s=='\n' || *s=='\r' || *s==26)
  {
    if (*s=='\n')
      li_last_line++;
    s++;
  }
  
  if (*s==';')  // comment
  {
    while (*s && *s!='\n' && *s!=26)
    {
      if (*s=='\n')
        li_last_line++;
      s++;
    }
    
    return li_read_token(s,buffer);
  } else if  (*s=='/' && *(s+1)=='*')   // c style comment
  {
    li_skip_c_comment(s);
    return li_read_token(s,buffer);    
  }
  else if (*s==0)
    return 0;
  else if (*s==')' || *s=='(' || *s=='\'' || *s=='`' || *s==',' || *s==26)
  {
    *(buffer++)=*(s++);
    *buffer=0;
  } else if (*s=='"')    // string
  {
    *(buffer++)=*(s++);          // don't read off the string because it
    // may be to long to fit in the token buffer
    // so just read the '"' so the compiler knows to scan the rest.
    *buffer=0;
  } else if (*s=='#')
  {
    *(buffer++)=*(s++);      
    if (*s!='\'')
      *(buffer++)=*(s++);      
    *buffer=0;
  } else
  {
    while (*s && *s!=')' && *s!='(' && *s!=' ' && 
           *s!='\n' && *s!='\r' && *s!='\t' && *s!=';' && *s!=26)
      *(buffer++)=*(s++);      
    *buffer=0;
  }
  return 1;    
}

int li_streq(char *s1, char *s2)
{
  return strcmp(s1,s2)==0;
}


long li_str_token_len(char *st)
{
  long x=1;
  while (*st && (*st!='"' || st[1]=='"'))
  {
    if (*st=='\\' || *st=='"') st++;    
    st++; x++;
  }
  return x;
}

static i4_critical_section_class token_buf_lock;
enum {MAX_LISP_TOKEN_LEN=512};
static char li_token[MAX_LISP_TOKEN_LEN];  // assume all tokens will be < 512 characters


li_object *li_locked_get_expression(char *&s, li_environment *env)
{

  li_object *ret=0;

  if (!li_read_token(s,li_token))
    return 0;
  if (li_streq(li_token,"nil"))
    return li_nil;
  else if (li_token[0]=='T' && !li_token[1])
    return li_true_sym;
  else if (li_token[0]=='\'')                    // short hand for quote function
    return new li_list(li_quote, new li_list(li_locked_get_expression(s, env), 0));    
  else if (li_token[0]=='`')                    // short hand for backquote function
    return new li_list(li_backquote, new li_list(li_locked_get_expression(s, env),0));
  else if (li_token[0]==',')              // short hand for comma function
    return new li_list(li_comma, new li_list(li_locked_get_expression(s, env), 0));
  else if (li_token[0]=='(')                     // make a list of everything in ()
  {
    li_list *first=NULL,*cur=NULL,*last=NULL;   

    int done=0;
    do
    {
      char *tmp=s;
      if (!li_read_token(tmp,li_token))           // check for the end of the list
        li_error(env, "unexpected end of program");
      if (li_token[0]==')') 
      {
        done=1;
        li_read_token(s,li_token);                // read off the ')'
      }
      else
      {     
        if (li_token[0]=='.' && !li_token[1])
        {
          if (!first)
            li_error(env, "token '.' not allowed here : %s\n",s);	      
          else 
          {
            li_read_token(s,li_token);              // skip the '.'
            last->set_next(li_locked_get_expression(s, env));          // link the last cdr to 
            last=NULL;
          }
        } else if (!last && first)
          li_error(env, "illegal end of dotted list\n");
        else
        {	
          li_list *p=new li_list(li_locked_get_expression(s, env), 0);
          if (last)
            last->set_next(p);
          else
            first=p;
          last=p;
        }
      } 
    } while (!done);

    if (!first)
      return li_nil;
    else return first;

  } else if (li_token[0]==')')
    li_error(env, "mismatched ) at %s",s);
  else if (isdigit(li_token[0]) || (li_token[0]=='-' && isdigit(li_token[1])))
  {
    int i=0,per=0,hex=0,x;
    
    if (li_token[0]=='0' && li_token[1]=='x')     // hex number
    {
      hex=1;
      i+=2;
    }
        
    for (; li_token[i] && (isdigit(li_token[i]) || li_token[i]=='.' || li_token[i]=='-'); i++)
      if (li_token[i]=='.')
        per=1;

    if (per)
    {
      float y;
      sscanf(li_token,"%f",&y);      
      return new li_float(y);
    }
    else if (hex)
    {
      sscanf(li_token,"%x",&x);
      return new li_int(x);
    }
    else
    {
      sscanf(li_token,"%d",&x);
      return new li_int(x);
    }
  } else if (li_token[0]=='"')
  {
    li_string *r=new li_string(li_str_token_len(s));

    char *start=r->value();

    for (;*s && (*s!='"' || s[1]=='"');s++,start++)
    {
      if (*s=='\\')
      {
        s++;
        if (*s=='n') *start='\n';
        if (*s=='r') *start='\r';
        if (*s=='t') *start='\t';
        if (*s=='\\') *start='\\';
      } else *start=*s;
      if (*s=='"') s++;
    }
    *start=0;
    s++;

    return r;
  } else if (li_token[0]=='#')
  {
    if (li_token[1]=='\\')
    {
      li_read_token(s,li_token);                   // read character name
      if (li_streq(li_token,"newline"))
        ret=new li_character('\n');
      else if (li_streq(li_token,"space"))
        ret=new li_character(' ');       
      else 
        ret=new li_character(li_token[0]);       
    }
    else if (li_token[1]==0)                           // short hand for function
      return new li_list(li_function_symbol, new li_list(li_locked_get_expression(s, env), 0));
    else
    {
      li_error(env, "Unknown #\\ notation : %s\n",li_token);
      exit(0);
    }
  } else 
    return li_get_symbol(li_token);

  return ret;
}

// because we can only allow one thread to use the token buffer at a time
// so we don't have to allocate it on the stack (because it's fairly recursive)
// I lock access to the token buffer per thread
li_object *li_get_expression(char *&s, li_environment *env)
{
  token_buf_lock.lock();
  li_object *ret=li_locked_get_expression(s, env);
  token_buf_lock.unlock();
  return ret;
}


void lip(li_object *o)
{
  if (!o)
  {
    i4_debug->printf("(null object)\n");
    return ;
  }

  if (!li_valid_object(o))
  {
    i4_debug->printf("(invalid object)\n");
    return ;
  }

  li_get_type(o->type())->print(o, i4_debug);
  i4_debug->printf("\n");
}

li_object *li_print(li_object *o, li_environment *env)
{
  li_object *ret=0;
  while (o)
  {
    ret=li_eval(li_car(o,env),env);
    lip(ret);
    o=li_cdr(o,env);
  }
  return ret;
}

li_list *li_make_list(li_object *first, ...)
{
  va_list ap;
  va_start(ap, first);
  
  li_list *ret=new li_list(first,0), *last;
  last=ret;
  
  while (1)
  {
    li_object *o=va_arg(ap, li_object *);
    if (o)
    {
      li_list *next=new li_list(o,0);
      last->set_next(next);
      last=next;
    }
    else
    {
      va_end(ap);
      return ret;
    }
  }
}



li_object *li_get_fun(li_symbol *sym, li_environment *env) 
{ 
  if (env)
    return env->fun(sym);
  else return sym->fun();
}

li_object *li_get_fun(char *sym, li_environment *env)
{
  return li_get_fun(li_get_symbol(sym),env);
}


li_object *li_eval(li_object *expression, li_environment *env)
{
  if (!expression)
    return li_nil;
   
  int type=expression->type();
  switch (type)
  {    
    case LI_SYMBOL : 
    {
      li_object *v=li_get_value(li_symbol::get(expression,env), env);
      if (!v)
        li_error(env, "Symbol '%O' has no value", expression);
      return v;

    } break;

    case LI_LIST :
    {
      li_list *o=li_list::get(expression,env);
      li_symbol *sym=li_symbol::get(o->data(),env);
      return li_call(sym, o->next(), env);
    } break;

    default :
      return expression; 
      break;
  }

  return 0;
}


li_object *li_load(i4_file_class *fp, li_environment *env, i4_status_class *status)
{
  li_object *ret=0;
  li_last_line=0;


  int l=fp->size();

  char *buf=(char *)i4_malloc(l+1,"");
  buf[l]=0;
  fp->read(buf,l);

  char *s=buf;
  

  li_object *exp;
  do
  {
    if (status)
      status->update((s-buf)/(float)l);

    exp=li_get_expression(s, env);
    if (exp)
      ret=li_eval(exp, env);    
  } while (exp);

  i4_free(buf);
  return ret;
}

li_object *li_load(li_object *name, li_environment *env)
{
  return li_load(name, env, 0);
}

li_object *li_load(li_object *name, li_environment *env, i4_status_class *status)
{
  li_object *ret=0;

  char old_file[256];
  strcpy(old_file, li_last_file);
  int old_line=li_last_line;
  
  li_gc();

  while (name)
  {
    char *s=li_string::get(li_eval(li_car(name,env),env),env)->value();
    strcpy(li_last_file, s);

    i4_file_class *fp=i4_open(i4_const_str(s));
    if (fp)
    {
      ret=li_load(fp, env, status);
      delete fp;
    }
    else
      i4_warning("li_load : file missing %s", s);

    name=li_cdr(name,env);
  }
  
  strcpy(li_last_file, old_file);
  li_last_line=old_line;
  

  return ret;
}

li_object *li_read_eval(li_object *o, li_environment *env)
{
  char line[1000], *c=line;
  int t=0;
  i4_debug->printf("eval>");
  do
  {
    if (i4_debug->read(c,1)!=1)
      return 0;
    t++;
    c++;
  } while (c[-1]!='\n' && t<998);
  
  *c=0;
  c=line;
  li_object *ret=li_eval(li_get_expression(c, env), env);
  lip(ret);
  return ret;
}

li_object *li_load(char *filename, li_environment *env, i4_status_class *status)
{
  return li_load(new li_list(new li_string(filename), 0), env, status);
}

void li_add_function(li_symbol *sym, 
                     li_function_type fun,
                     li_environment *env)
{
  li_function *f=new li_function(fun);

  if (env)
    env->set_fun(sym, f);
  else
    sym->set_fun(f);
}


void li_add_function(char *sym_name, li_function_type fun, li_environment *env)
{
  li_add_function(li_get_symbol(sym_name), fun, env);
}

i4_bool li_get_bool(li_object *o, li_environment *env)
{ 
  if (!o) return i4_F;

  li_symbol *s=li_symbol::get(o,env);

  if (o==li_nil)
    return i4_F;
  else if (o==li_true_sym)
    return i4_T;
  else
    li_error(env, "expecting T or nil, got %O", o);

  return 0;
}

static inline int fmt_char(char c)
{
  if ((c>='a' && c<='z') || (c>='A' && c<='Z'))
    return 1;
  return 0;
}

static w8 li_recursive_error=0;

void li_vprintf(i4_file_class *fp,
                char *fmt,
                va_list ap)
{
  
  while (*fmt)
  {
    if (*fmt=='%')
    {
      char *fmt_end=fmt;
      while (!fmt_char(*fmt_end) && *fmt_end) fmt_end++;
      char f[10], out[500]; 
      memcpy(f, fmt, fmt_end-fmt+1);
      f[fmt_end-fmt+1]=0;
      out[0]=0;

      switch (*fmt_end)
      {
        case 'O' : 
        {
          li_object *o=va_arg(ap,li_object *);
          li_get_type(o->type())->print(o, fp);
        } break;

        case 'd' :
        case 'i' :
        case 'x' :
        case 'X' :
        case 'o' :
          ::sprintf(out,f,va_arg(ap,int));
          break;

        case 'f' :
        {
          float fl=va_arg(ap, double);
          ::sprintf(out,f,fl);
        } break;

        case 'g' :
          ::sprintf(out,f,va_arg(ap,double));
          break;

        default :
          ::sprintf(out,f,va_arg(ap,void *));
          break;
      }
      fp->write(out, strlen(out));
      fmt=fmt_end;
      if (*fmt)
        fmt++;
    }
    else
    {
      fp->write_8(*fmt);
      fmt++;
    }


  }
}


void li_printf(i4_file_class *fp,
               char *fmt,                   // typical printf format, with %o == li_object
              ...)
{  
  va_list ap;
  va_start(ap, fmt);
  li_vprintf(fp, fmt, ap);
  va_end(ap);

}

void li_error(li_environment *env,
              char *fmt,
              ...)
{
  if (!li_recursive_error)      // error shouldn't call error again!
  {
    li_recursive_error++;
    i4_file_class *fp=i4_open("li_error.txt", I4_WRITE);

    if (fp)
    {
      va_list ap;
      va_start(ap, fmt);
  
      li_vprintf(fp, fmt, ap);
      fp->printf("\nCall stack:\n");
      if (env)
        env->print_call_stack(fp);

      fp->printf("\nlast file %s:%d", li_last_file, li_last_line);
      delete fp;
      

      fp=i4_open("li_error.txt");
      if (fp)
      {
        int size=fp->size();
        char *b=(char *)i4_malloc(size+1,"");
        fp->read(b, size);
        b[size]=0;
        delete fp;

        i4_get_error_function_pointer(li_last_file, 0)(b);

        i4_free(b);
      }
    }

    li_recursive_error--;
  }  
}

li_object *li_new(char *type_name, li_object *params, li_environment *env)
{
  li_symbol *s=li_find_symbol(type_name);
  if (!s) return 0;

  li_object *v=li_get_value(s, env);
  if (!v || v->type()!=LI_TYPE) return 0;
  
  li_type_number type=li_type::get(v,env)->value();
  return li_get_type(type)->create(params, env);
}

li_object *li_new(int type, li_object *params, li_environment *env)
{
  return li_get_type(type)->create(params, env);
}

li_object *li_call(li_symbol *val, li_object *params, li_environment *env)
{
  if (val)
  {
    li_symbol *old_fun=0;
    li_object *old_args=0;
    if (env)
    {     
      old_fun=env->current_function();
      old_args=env->current_arguments();
    }
    else
      env=new li_environment(env, i4_F);

    env->current_function()=val;
    env->current_arguments()=params;
    
    
    li_object *ret=0;    
    li_object *f=li_get_fun(val, env);
    if (f)
    {
      li_function_type fun=li_function::get(f,env)->value();
      if (fun)
        ret=fun(params, env);      
    }
    else
      li_error(env, "symbol %O has no function", val);
    
    if (old_fun)
    {
      env->current_function()=old_fun;
      env->current_arguments()=old_args;
    }

    return ret;
  }

  return 0;
}

li_object *li_call(char *fun_name, li_object *params, li_environment *env)
{
  return li_call(li_get_symbol(fun_name), params, env);
}

li_object  *li_first(li_object *o, li_environment *env) { return li_car(o,env); }
li_object  *li_second(li_object *o, li_environment *env) { return li_car(li_cdr(o,env),env); }
li_object  *li_third(li_object *o, li_environment *env) { return li_car(li_cdr(li_cdr(o,env),env),env); }
li_object  *li_fourth(li_object *o, li_environment *env) 
{ return li_car(li_cdr(li_cdr(li_cdr(o,env),env),env),env); }

li_object  *li_fifth(li_object *o, li_environment *env) 
{ return li_car(li_cdr(li_cdr(li_cdr(li_cdr(o,env),env),env),env),env); }

li_object  *li_nth(li_object *o, int x, li_environment *env) 
{ while (x--) o=li_cdr(o,env); return li_car(o,env); }