#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <yafl_usr.h>
#include <yafl_rnt.h>
#include "dbx_rnt.h"
#include "dbx_modu.h"
#include "dbx_type.h"
#include "dbx_vari.h"
#include "dbx_clas.h"
#include "dbx_obar.h"
#include "dbx_meth.h"
#include "dbx_fiel.h"
#include "dbx_call.h"
#include "dbx_loca.h"
#include "dbx_list.h"
#include "dbx_fram.h"
#include "dbx_ctxt.h"
#include "dbx_auto.h"
#include "dbx_out.h"


static char *error_table[] = {"",
                              "call not found",
                              "local not found in call",
                              "not found",
                              "class not found",
                              "field not found in class",
                              "field not found",
                              "subscript out of bounds",
                              "context cannot be expanded further",
                              "syntax error"};

#define MAX_CHARS1 100
static char buffer[MAX_CHARS1+1]; /* one more for final zero character */
static int used_chars1 = 0;

#define MAX_CHARS2 1000
static char expression[MAX_CHARS2+2]; /* one more for added end character */
                                      /* plus one more for final zero character */
static int used_chars2 = 0;


static char current;

static int is_alpha YPARAMS2(char,  c)
{
  return (('a'<=c)&&(c<='z'))||(('A'<=c)&&(c<='Z'));
}

static int is_num YPARAMS2(char,  c)
{
  return ('0'<=c)&&(c<='9');
}

static int is_special YPARAMS2(char,  c)
{
  return (c=='_');
}

static int is_ident YPARAMS2(char,  c)
{
  return (is_alpha(c)||is_num(c)||is_special(c));
}

static void init YPARAMS0
{
  used_chars2 = 0;
  current = expression[0];
  expression[0] = 0;
  used_chars1 = 0;
  buffer[0] = 0;
}

static void show YPARAMS0
{
  writestring(first_part());
  highlight_on();
  writechar(the_current());
  highlight_off();
  writestring(second_part());
  writeln();
}

static void shift YPARAMS0
{
  assert(used_chars1 < MAX_CHARS1);
/*  show(); */
  buffer[used_chars1] = current;
  expression[used_chars2] = current;
  used_chars1++;
  used_chars2++;
  current = expression[used_chars2];
  expression[used_chars2] = 0;
}

static void drop YPARAMS0
{
/*  show(); */
  expression[used_chars2] = current;
  used_chars2++;
  current = expression[used_chars2];
  expression[used_chars2] = 0;
}

static void end YPARAMS0
{
  buffer[used_chars1] = 0;
  used_chars1 = 0;
}

int try_change_call YPARAMS4(dbx_context *,  pc,
                            char *,  ident)
{
  dbx_call *pca;
  reset_context(pc);
  pca = find_prev_call(last_call(),ident);
  if (pca)        
  {
    change_call(pc,pca);
    return ERR_AUTO_OK;
  }
  else
    return ERR_AUTO_CALL;
}
			    
int try_change_local1 YPARAMS4(dbx_context *,  pc,
                            char *,  ident)
{
  dbx_local *pl;
  pl = find_next_local(first_local(context_method(pc)),ident);
  if (pl)
  {
    change_local(pc,pl);
    return ERR_AUTO_OK;
  }
  else
    return ERR_AUTO_LOCAL;
}

int try_change_local2 YPARAMS4(dbx_context *,  pc,
                            char *,  ident)
{
  dbx_local *pl;
  pl = local_in_methods(call_method(last_call()),ident);
  if (pl)
  {
    change_call(pc,search_call(last_call(),local_method(pl)));
    change_local(pc,pl);
    return ERR_AUTO_OK;
  }
  else
    return ERR_AUTO_IDENT;
}

int try_change_field YPARAMS4(dbx_context *,  pc,
                             char *,  ident)
{
  dbx_field *pf;
  pf = find_next_field(first_field(method_class(call_method(last_call()))),ident);
  if (pf)
  {
    change_call(pc,last_call());
    change_local(pc,first_local(call_method(last_call()))); /* THIS */
    if (expand_context(pc))
      return ERR_AUTO_CONTEXT;
    change_top_class(pc,field_class(pf));
    change_top_field(pc,pf);
    return ERR_AUTO_OK;
  }
  else
    return ERR_AUTO_IDENT;
}

int try_change_once YPARAMS4(dbx_context *,  pc,
                            char *,  ident)
{
  dbx_class *pcl;
  pcl = find_next_once(first_once(),ident);
  if (pcl)
  {
    change_once(pc,pcl);
    return ERR_AUTO_OK;
  }
  else
    return ERR_AUTO_IDENT;
}


int try_change_all YPARAMS4(dbx_context *,  pc,
                           char *,  ident)
{
  if (try_change_local2(pc,ident) == 0)
    return ERR_AUTO_OK;
  if (try_change_once(pc,ident) == 0)
    return ERR_AUTO_OK;
  if (try_change_field(pc,ident) == 0)
    return ERR_AUTO_OK;
  return ERR_AUTO_IDENT;
}

int try_change_class YPARAMS4(dbx_context *,  pc,
                            char *,  ident)
{
  dbx_class *pcl;
  if (expand_context(pc))
    return ERR_AUTO_CONTEXT;
  pcl = search_class(lowest_class(top_objarr(pc)),ident);
  if (pcl)
  {
    change_top_class(pc,pcl);
    return ERR_AUTO_OK;
  }
  else
    return ERR_AUTO_CLASS;
}

int try_change_field1 YPARAMS4(dbx_context *,  pc,
                            char *,  ident)
{
  dbx_field *pf;
  pf = find_next_field(first_field(top_class(pc)),ident);
  if (pf)
  {
    change_top_field(pc,pf);
    return ERR_AUTO_OK;
  }
  else
    return ERR_AUTO_FIELD; 
}

int try_change_field2 YPARAMS4(dbx_context *,  pc,
                            char *,  ident)
{
  dbx_field *pf;
  if (expand_context(pc)) 
    return ERR_AUTO_CONTEXT; /* do not forget to expand context in all appropriates cases */
  pf = field_in_classes(lowest_class(top_objarr(pc)),ident);
  if (pf)
  {
    change_top_class(pc,field_class(pf));
    change_top_field(pc,pf);
    return ERR_AUTO_OK;
  }
  else 
    return ERR_AUTO_FIELD2;
}

int try_change_subscript YPARAMS4(dbx_context *,  pc,
                            char *,  ident)
{
  unsigned index;
  if (expand_context(pc))
    return ERR_AUTO_CONTEXT;
  index = atoi(ident);
  if (index < array_size(top_objarr(pc)))
  {
    change_top_subscript(pc,index);
    return ERR_AUTO_OK;
  }
  else
    return ERR_AUTO_SUBSCRIPT;
}

char *identifier YPARAMS0
{
  return buffer;
}

char *first_part YPARAMS0
{
  return expression;
}

char *second_part YPARAMS0
{
  return (&expression[used_chars2+1]);
}

char the_current YPARAMS0
{
  return current;
}

char *error_messg YPARAMS2(unsigned,  code)
{
  return error_table[code];
}

int apply_descriptor YPARAMS4(dbx_context *,  pc,
                             char *,   s)
{
  int code;
  assert(strlen(s)<=MAX_CHARS2);
  strcpy(expression,s);
  strcat(expression,"#");
  init();
    if (current == '{')
    {
      drop();
      goto CallId;
    }
    else if (is_ident(current))
    {
      shift();
      goto LocalId2;
    }
    else if (current == '.')
    {
      drop();
      goto Dot;
    }
    else if (current == '[')
    {
      drop();
      goto ElemId;
    }
    else
    {
      goto Error;
    }
    
  CallId:
    if (is_ident(current))
    {
      shift();
      goto CallId;
    }
    else if (current == '}')
    {
      end();
      code = try_change_call(pc,buffer);
      if (code)
        return code;
      drop();
      goto LocalId1;
    }
    else
    {
      goto Error;
    }
    
  LocalId1:
    if (is_ident(current))
    {
      shift();
      goto LocalId1;
    }
    else if (current == '.')
    {
      end();
      code = try_change_local1(pc,buffer);
      if (code)
        return code;
      drop();
      goto Dot;
    }
    else if (current == '[')
    {
      end();
      code = try_change_local1(pc,buffer);
      if (code)
        return code;
      drop();
      goto ElemId;
    }
    else if (current == '#')
    {
      end();
      code = try_change_local1(pc,buffer);
      if (code)
        return code;
      goto Stop;
    }
    else
    {  
      goto Error;
    }
    
  LocalId2:
    if (is_ident(current))
    {
      shift();
      goto LocalId2;
    }
    else if (current == '.')
    {
      end();
      code = try_change_all(pc,buffer); 
      if (code) 
        return code;
      drop();
      goto Dot;
    }
    else if (current == '[')
    {
      end();
      code = try_change_all(pc,buffer);
      if (code)
        return code;
      drop();
      goto ElemId;
    }
    else if (current == '#')
    {
      end();
      code = try_change_all(pc,buffer);
      if (code)
        return code;
      goto Stop;
    }
    else
    {
      goto Error;
    }
    
  Dot:
    if (current == '<')
    {
      drop();
      goto ClassId;
    }
    else if (is_ident(current))
    {
      shift();
      goto FieldId2;
    }
    else
    {
      goto Error;
    }
    
  ClassId:
    if (is_ident(current))
    {
      shift();
      goto ClassId;
    }
    else if (current == '>')
    {
      end();
      code = try_change_class(pc,buffer);
      if (code)
        return code;
      drop();
      goto FieldId1;
    }
    else
    {
      goto Error;
    }
    
  FieldId1:
    if (is_ident(current))
    {
      shift();
      goto FieldId1;
    }
    else if (current == '.')
    {
      end();
      code = try_change_field1(pc,buffer);
      if (code)
        return code;
      drop();
      goto Dot;
    }
    else if (current == '[')
    {
      end();
      code = try_change_field1(pc,buffer);
      if (code)
        return code;
      drop();
      goto ElemId;
    }
    else if (current == '#')
    {
      end();
      code = try_change_field1(pc,buffer);
      if (code)
        return code;
      goto Stop;
    }
    else
    {
      goto Error;
    }

  FieldId2:
    if (is_ident(current))
    {
      shift();
      goto FieldId2;
    }
    else if (current == '.')
    {
      end();
      code = try_change_field2(pc,buffer);
      if (code)
        return code;
      drop();
      goto Dot;
    }
    else if (current == '[')
    {
      end();
      code = try_change_field2(pc,buffer);
      if (code)
        return code;
      drop();
      goto ElemId;
    }
    else if (current == '#')
    {
      end();
      code = try_change_field2(pc,buffer);
      if (code)
        return code;
      goto Stop;
    }
    else
    {
      goto Error;
    }
  
  ElemId:
    if (is_num(current))
    {
      shift();
      goto ElemId;
    }
    else if (current == ']')
    {
      end();
      code = try_change_subscript(pc,buffer);
      if (code)
        return code;
      drop();
      goto RightBracket;
    }
    else
    {
      goto Error;
    }
  
  RightBracket:
    if (current == '.')
    {
      drop();
      goto Dot;
    }
    else if (current == '[')
    {
      drop();
      goto ElemId;
    }
    else if (current == '#')
    {
      goto Stop;
    }
    else
    {
      goto Error;
    }
  
  Stop:  
    return ERR_AUTO_OK;
  Error:
    return ERR_AUTO_SYNTAX;
}			     
