/*
 * Simulator of microcontrollers (cmd.src/command.cc)
 *
 * Copyright (C) 2002,02 Drotos Daniel, Talker Bt.
 * 
 * To contact author send email to drdani@mazsola.iit.uni-miskolc.hu
 *
 */

/* This file is part of microcontroller simulator: ucsim.

UCSIM 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 2 of the License, or
(at your option) any later version.

UCSIM 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 UCSIM; see the file COPYING.  If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
/*@1@*/

//#include "ddconfig.h"

#include <stdio.h>
#include <stdlib.h>
//#include <stdarg.h>
#include <string.h>
#include <unistd.h>

//#include "i_string.h"

// prj
//#include "fiocl.h"
//#include "utils.h"
#include "appcl.h"

// local, cmd
#include "commandcl.h"
//#include "argcl.h"


/*
 * Command line
 *____________________________________________________________________________
 */

cl_cmdline::cl_cmdline(class cl_app *the_app,
		       const char *acmd, class cl_console_base *acon):
  cl_base()
{
  app= the_app;
  cmd= strdup(acmd);
  params= new cl_list(2, 2, "command line params");
  tokens= new cl_ustrings(2, 2, "command line tokens");
  set_name(0);
  matched_syntax= 0;
  con= acon;
}

cl_cmdline::~cl_cmdline(void)
{
  if (cmd)
    free(cmd);
  delete params;
  delete tokens;
}

int
cl_cmdline::init(void)
{
  rest= NULL;
  params->free_all();
  tokens->free_all();
  split();
  return(0);
}

char *
cl_cmdline::skip_delims(char *start)
{
  while (*start &&
	 strchr(" \t\v\r,", *start))
    start++;
  return(start);
}

int
cl_cmdline::split(void)
{
  //class cl_sim *sim;
  char *start= cmd;
  int i;//, j;
  class cl_cmd_arg *arg;

  set_name("\n");
  if (!cmd ||
      !*cmd)
    return(0);
  start+= strspn(start, " \t\v\r,");
  if (!start)
    return 0;
  set_name(0);
  if (*start == '\n')
    {
      // never, as \n stripped by readline
      set_name("\n");
      return(0);
    }
  else if (*start == '#')
    return *start= 0;
  else if (*start == ';')
    {
      rest= start+1;
      *start= 0;
      return 0;
    }
  if (!*start)
    return(0);
  // start now points to first word
  i= strcspn(start, " \t\v\r,;#");
  // i should be at end of it
  if (i)
    {
      if (*start == '#')
	return set_name("\n"), *start= 0;
      char *n= (char*)malloc(i+1);
      strncpy(n, start, i);
      n[i]= '\0';
      set_name(n);
      free(n);
    }
  start+= i;
  start= skip_delims(start);
  while (*start)
    {
      char *end= start, *param_str;
      if (*start == '#')
	return *start= '\0';
      else if (*start == ';')
	{
	  rest= start+1;
	  *start= 0;
	  return 0;
	}
      else if (*start == '"')
	split_out_string(&start, &end);
      else if (*start == '>')
	split_out_output_redirection(&start, &end);
      else
	{
	  char *dot;
          i= strcspn(start, " \t\v\r,#;");
          end= start+i;
          param_str= (char *)malloc(i+1);
          strncpy(param_str, start, i);
	  param_str[i]= '\0';
	  tokens->add(strdup(param_str));
	  if ((dot= strchr(param_str, '[')) != NULL)
            {
              char *p;
              for (p= dot+1; *p && *p != ']' && *p != ':'; p++);
              if (*p == ':')
	        split_out_bit(dot, param_str);
              else
                split_out_array(dot, param_str);
            }
	  else if ((dot= strchr(param_str, '.')) != NULL && dot != param_str)
	    split_out_bit(dot, param_str);
	  else if (param_str[0] == '0' && param_str[1] == 'b')
            {
              long n= 0;
	      for (int i= 2; param_str[i] == '0' || param_str[i] == '1'; i++)
                n = (n << 1) | (param_str[i] == '0' ? 0 : 1);
	      params->add(arg= new cl_cmd_int_arg(n));
	      arg->init();
            }
	  else if (strchr("0123456789-+", *param_str) != NULL)
	    {
	      // number
	      params->add(arg= new cl_cmd_int_arg((long)
						  strtol(param_str, 0, 0)));
	      arg->init();
	    }
	  else
	    {
	      // symbol
	      params->add(arg= new cl_cmd_sym_arg(param_str));
	      arg->init();
	    }
	  free(param_str);
	}
      start= end;
      start= skip_delims(start);
    }
  return(0);
}

void
cl_cmdline::split_out_string(char **_start, char **_end)
{
  char *start= *_start, *end;
  start++;
  end= start;
  while (*end &&
        *end != '"')
    {
      if (*end == '\\')
       {
         end++;
         if (*end)
           end++;
       }
      else
       end++;
    }
  if (*end == '"')
    end--;
  else
    con->dd_printf("Unterminated string\n");
  char *param_str= (char *)malloc(end-start+2);
  strncpy(param_str, start, 1+end-start);
  param_str[1+end-start]= '\0';
  tokens->add(strdup(param_str));
  class cl_cmd_arg *arg;
  params->add(arg= new cl_cmd_str_arg(param_str));
  arg->init();
  free(param_str);
  if (*end)
    end++;
  if (*end == '"')
    end++;
  *_start= start;
  *_end= end;
}

void
cl_cmdline::split_out_output_redirection(char **_start, char **_end)
{
  char *start= *_start;
  char *end;
  int j;
  char mode[2];

  mode[0]= 'w';
  mode[1]= '\0';
  start++;
  if (*start=='>')
    {
      mode[0]= 'a';
      start++;
    }
  j= token_length(start);
  end= start+j;
  con->redirect(get_token(start).c_str(), mode);
  *_start= start;
  *_end= end;
}

void
cl_cmdline::add_bit(char *dot, char *colon, class cl_cmd_arg *sfr)
{
  class cl_cmd_arg *bit_low = NULL, *bit_high = NULL;

  if (colon)
    {
      *(colon++) = '\0';

      char *end = strchr(colon, ']');

      if (end)
          *end = '\0';
      else
        {
          con->dd_printf("Incomplete bit address\n");
          delete sfr;
          return;
        }
    }

  if (*dot == '\0')
    {
      con->dd_printf("Incomplete bit address\n");
      delete sfr;
      return;
    }

  while (dot)
    {
      bit_high= bit_low;

      if (strchr("0123456789", *dot) != NULL)
       {
         bit_low= new cl_cmd_int_arg((long)strtol(dot, 0, 0));
         bit_low->init();
       }
      else
       {
         bit_low= new cl_cmd_sym_arg(dot);
         bit_low->init();
       }

      dot= colon;
      colon= NULL;
    }

  class cl_cmd_arg *arg;
  params->add(arg= new cl_cmd_bit_arg(sfr, bit_low, bit_high));
  arg->init();
}

void
cl_cmdline::split_out_bit(char *dot, char *param_str)
{
  char *colon = NULL;
  class cl_cmd_arg *sfr;

  if (*dot == '[')
    colon = strchr(dot+1, ':');

  *(dot++) = '\0';

  if (strchr("0123456789", *param_str) != NULL)
    {
      sfr= new cl_cmd_int_arg((long)strtol(param_str, 0, 0));
      sfr->init();
    }
  else
    {
      sfr= new cl_cmd_sym_arg(param_str);
      sfr->init();
    }

  add_bit(dot, colon, sfr);
}

void
cl_cmdline::split_out_array(char *dot, char *param_str)
{
  class cl_cmd_arg *aname, *aindex;
       
  *dot= '\0';
  dot++;
  if (strchr("0123456789", *param_str) != NULL)
    {
      aname= new cl_cmd_int_arg((long)strtol(param_str, 0, 0));
      aname->init();
    }
  else
    {
      aname= new cl_cmd_sym_arg(param_str);
      aname->init();
    }
  if (*dot == '\0')
    {
      aname= 0;
      con->dd_printf("Incomplete array\n");
    }
  else
    {
      char *p = strchr(dot, ']');
      if (!p)
        {
          con->dd_printf("Missing ']' in array index\n");
          delete aname;
        }
      else
        {
          *(p++)= '\0';
          if (strlen(dot) == 0)
            {
              con->dd_printf("Incomplete array index\n");
              delete aname;
            }
          else
            {
              if (strchr("0123456789", *dot) != NULL)
                {
                  aindex= new cl_cmd_int_arg((long)strtol(dot, 0, 0));
                  aindex->init();
                }
              else
                {
                  aindex= new cl_cmd_sym_arg(dot);
                  aindex->init();
                }
              class cl_cmd_arg *arg= new cl_cmd_array_arg(aname, aindex);
              arg->init();
              if (*p == '.' || *p == '[')
                  add_bit(p+1, strchr(p+1, ':'), arg);
              else
                  params->add(arg);
            }
        }
    }
}

int
cl_cmdline::token_length(char *start)
{
  if (start==NULL ||
      *start=='\0')
    return 0;
  if (*start == '\"')
    {
      int i;
      char c;
      for (i=1; start[i]!=0 && start[i]!='\"'; i++)
	{
	  c= start[i];
	  if (c == '\\')
	    i+= 2;
	}
      return i+1;
    }
  else
    return strcspn(start, " \t\v\r,;");
}

chars
cl_cmdline::get_token(char *start)
{
  int i;
  char c;
  chars cs= chars();
  if (start==NULL ||
      *start=='\0')
    return cs;
  if (*start == '\"')
    {
      for (i=1; start[i]!=0 && start[i]!='\"'; i++)
	{
	  c= start[i];
	  if (c == '\\')
	    {
	      i++;
	      if (start[i])
		{
		  switch (start[i])
		    {
		    case 'a': cs+= '\a'; break;
		    case 'b': cs+= '\b'; break;
		    case 'e': cs+= '\x1b'; break;
		    case 'f': cs+= '\f'; break;
		    case 'n': cs+= '\n'; break;
		    case 'r': cs+= '\r'; break;
		    case 't': cs+= '\t'; break;
		    case 'v': cs+= '\v'; break;
		    case '\'': cs+= '\''; break;
		    case '\"': cs+= '\"'; break;
		    default:
		      cs+= start[i];
		      break;
		    }
		}
	      //i++;
	    }
	  else
	    cs+= c;
	}
      return cs;
    }
  else
    {
      int l= strcspn(start, " \t\v\r,;");
      for (i=0; i<l; i++)
	cs+= start[i];
    }
  return cs;
}

int
cl_cmdline::shift(void)
{
  char *s= skip_delims(cmd);

  params->free_all();
  tokens->free_all();
  set_name(0);
  if (s && *s)
    {
      while (*s &&
	     strchr(" \t\v\r,;#", *s) == NULL)
	s++;
      s= skip_delims(s);
      char *p= strdup(s), *r= rest?strdup(rest):NULL;
      free(cmd);
      cmd= p;
      rest= r;
      //params= new cl_list(2, 2, "params");
      split();
      if (strcmp(get_name(), "\n") == 0)
	set_name(0);
    }
  return(have_real_name());
}

int
cl_cmdline::repeat(void)
{
  const char *n;
  return((n= get_name()) &&
	 *n == '\n');
}

class cl_cmd_arg *
cl_cmdline::param(int num)
{
  if (num >= params->count)
    return(0);
  return((class cl_cmd_arg *)(params->at(num)));
}

void
cl_cmdline::insert_param(int pos, class cl_cmd_arg *param)
{
  if (pos >= params->count)
    params->add(param);
  else
    params->add_at(pos, param);
}

bool
cl_cmdline::syntax_match(class cl_uc *uc, const char *syntax)
{
  if (!syntax)
    return(false);
  if (!*syntax &&
      !params->count)
    {
      matched_syntax= syntax;
      return(true);
    }
  if (!params->count)
    return(false);
  //printf("syntax %s?\n",syntax);
  const char *p= syntax;
  int iparam= 0;
  class cl_cmd_arg *parm= (class cl_cmd_arg *)(params->at(iparam));
  while (*p &&
	 parm)
    {
      //printf("***Checking %s as %c\n",parm->get_svalue(),*p);
      if (uc)
	{
	  switch (*p)
	    {
	    case SY_ADDR:
	      if (!parm->as_address(uc))
		return(false);
	      //printf("ADDRESS match %lx\n",parm->value.address);
	      break;
	    case SY_MEMORY:
	      if (!parm->as_memory(uc))
		return(false);
	      //printf("MEMORY match %s\n",parm->value.memory->class_name);
	      break;
	    case SY_BIT:
	      if (!parm->as_bit(uc))
		return(false);
	      break;
	    case SY_HW:
	      if (!parm->as_hw(uc))
		return(false);
	      break;
	    case SY_CELL:
	      if (!parm->as_cell(uc))
		return false;
	      break;
	    }
	}
      //else
	{
	  switch (*p)
	    {
	    case SY_ADDR: case SY_MEMORY: case SY_BIT: break;
	    case SY_NUMBER:
	      if (!parm->as_number())
		return(false);
	      break;
	    case SY_DATA:
	      if (!parm->as_data())
		return(false);
	      break;
	    case SY_STRING:
	      if (!parm->as_string())
		return(false);
	      break;
	    case SY_DATALIST:
	      if (!set_data_list(parm, &iparam))
		return(false);
	      break;
	      //default: return(false);
	    }
	}
      p++;
      iparam++;
      if (iparam < params->count)
	parm= (class cl_cmd_arg *)(params->at(iparam));
      else
	parm= 0;
    }
  if (!*p &&
      !parm)
    {
      matched_syntax= syntax;
      return(true);
    }
  return(false);
}

bool
cl_cmdline::set_data_list(class cl_cmd_arg *parm, int *iparm)
{
  class cl_cmd_arg *next_parm;
  int len, i, j;
  t_mem *array, *temp;

  len= 0;
  array= 0;
  for (i= *iparm, next_parm= param(i); next_parm; i++, next_parm= param(i))
    {
      if (next_parm->is_string())
	{
	  int l;
	  char *s;
	  //s= proc_escape(next_parm->get_svalue(), &l);
	  if (!next_parm->as_string())
	    continue;
	  s= next_parm->value.string.string;
	  l= next_parm->value.string.len;
	  if (!array)
	    array= (t_mem*)malloc(sizeof(t_mem)*l);
	  else
	    {
	      temp= (t_mem*)realloc(array, sizeof(t_mem)*(l+len));
	      if (temp == NULL)
		break;
	      array= temp;
	    }
	  for (j= 0; j < l; j++)
	    {
	      array[len]= s[j];
	      len++;
	    }
	  //if (s)
	  //free(s);
	}
      else
	{
	  if (!next_parm->as_data())
	    {
	      if (array)
		free(array);
	      return(false);
	    }
	  if (!array)
	    array= (t_mem*)malloc(sizeof(t_mem));
	  else
	    {
	      temp= (t_mem*)realloc(array, sizeof(t_mem)*(1+len));
	      if (temp == NULL)
		break;
	      array= temp;
	    }
	  array[len]= next_parm->value.data;
	  len++;
	}
    }
  *iparm= i;
  parm->value.data_list.array= array;
  parm->value.data_list.len= len;
  return(true);
}

bool
cl_cmdline::restart_at_rest(void)
{
  char *newcmd;
  if ((rest == NULL) ||
      (*rest == 0))
    {
      return false;
    }
  newcmd= strdup(rest);
  if (cmd)
    free(cmd);
  cmd= newcmd;
  return true;
}

  
/*
 * Command
 *____________________________________________________________________________
 */

cl_cmd::cl_cmd(enum cmd_operate_on op_on,
	       const char *aname,
	       int can_rep):
  cl_base()
{
  operate_on= op_on;
  names= new cl_strings(1, 1, "names of a command");
  names->add(aname?strdup(aname):strdup("unknown"));
  can_repeat= can_rep;
  usage_help= 0;
  short_help= 0;//short_hlp;//?strdup(short_hlp):NULL;
  long_help= 0;//long_hlp;//?strdup(long_hlp):NULL;
}

/*cl_cmd::cl_cmd(class cl_sim *asim):
  cl_base()
{
  sim= asim;
  name= short_help= long_help= 0;
  can_repeat= 0;
}*/

void
cl_cmd::set_help(const char *usage_hlp, const char *short_hlp, const char *long_hlp)
{
  usage_help= usage_hlp;
  short_help= short_hlp;
  long_help= long_hlp;
}

cl_cmd::~cl_cmd(void)
{
  delete names;
  //if (short_help) free((void*)short_help);
  //if (long_help) free((void*)long_help);
}

void
cl_cmd::add_name(const char *nam)
{
  if (nam)
    names->add(strdup(nam));
}

int
cl_cmd::name_match(const char *aname, int strict)
{
  int i;
  
  if (names->count == 0 &&
      !aname)
    return(1);
  if (!aname)
    return(0);
  if (strict)
    {
      for (i= 0; i < names->count; i++)
	{
	  const char *n= names->at(i);
	  if (strcmp(aname, n) == 0)
	    return(1);
	}
    }
  else
    {
      for (i= 0; i < names->count; i++)
	{
	  const char *n= names->at(i);
	  if (strstr(n, aname) == n)
	    return(1);
	}
    }
  return(0);
}

int
cl_cmd::name_match(class cl_cmdline *cmdline, int strict)
{
  return(name_match(cmdline->get_name(), strict));
}

int
cl_cmd::syntax_ok(class cl_cmdline *cmdline)
{
  return(1);
}

int
cl_cmd::work(class cl_app *app,
	     class cl_cmdline *cmdline, class cl_console_base *con)
{
  if (!syntax_ok(cmdline))
    return(0);
  class cl_sim *sim= app->get_sim();
  class cl_uc *uc= 0;
  if (sim)
    uc= sim->uc;
  switch (operate_on)
    {
    case operate_on_app:
      if (!app)
	{
	  con->dd_printf("There is no application to work on!\n");
	  return(false);
	}
      return(do_work(app, cmdline, con));
    case operate_on_sim:
      if (!sim)
	{
	  con->dd_printf("There is no simulator to work on!\n");
	  return(false);
	}
      return(do_work(sim, cmdline, con));
    case operate_on_uc:
      if (!sim)
	{
	  con->dd_printf("There is no microcontroller to work on!\n");
	  return(false);
	}
      return(do_work(uc, cmdline, con));
    default:
      return(do_work(cmdline, con));
    }
}

int
cl_cmd::do_work(class cl_cmdline *cmdline, class cl_console_base *con)
{
  con->dd_printf("Command \"%s\" does nothing.\n",
		 names->at(0));
  return(0);
}

int
cl_cmd::do_work(class cl_app *app,
		class cl_cmdline *cmdline, class cl_console_base *con)
{
  con->dd_printf("Command \"%s\" does nothing on application.\n",
		 names->at(0));
  return(0);
}

int
cl_cmd::do_work(class cl_sim *sim,
		class cl_cmdline *cmdline, class cl_console_base *con)
{
  con->dd_printf("Command \"%s\" does nothing on simulator.\n",
		 names->at(0));
  return(0);
}

int
cl_cmd::do_work(class cl_uc *uc,
		class cl_cmdline *cmdline, class cl_console_base *con)
{
  con->dd_printf("Command \"%s\" does nothing on microcontroller.\n",
		 names->at(0));
  return(0);
}

void
cl_cmd::print_short(class cl_console_base *con)
{
  int l= usage_help.len();

  if (!con)
    return;
  
  if (usage_help.nempty())
    con->dd_printf("%s", usage_help.c_str());
  if (l > 19)
    {
      con->dd_printf("\n");
      l=0;
    }
  while (l < 20)
    {
      con->dd_printf(" ");
      l++;
    }
  if (short_help.nempty())
    {
	con->dd_printf("%s", short_help.c_str());
    }
  else
    con->dd_printf("%s", names->at(0));
  con->dd_printf("\n");
}
  
void
cl_cmd::syntax_error(class cl_console_base *con)
{
  if (con)
    {
      if (short_help.nempty())
	print_short(con);
      else
	con->dd_printf("Error: wrong syntax\n");
    }
}

/*
 * Set of commands
 *____________________________________________________________________________
 */

cl_cmdset::cl_cmdset(void):
  cl_list(5, 5, "cmdset")
{
  //sim= 0;
  //last_command= 0;
}

/*cl_cmdset::cl_cmdset(class cl_sim *asim):
  cl_list(5, 5)
{
  sim= asim;
  last_command= 0;
}*/

class cl_cmd *
cl_cmdset::get_cmd(class cl_cmdline *cmdline, bool accept_last)
{
  int i;

  // exact match
  for (i= 0; i < count; i++)
    {
      class cl_cmd *c= (class cl_cmd *)at(i);
      if (c->name_match(cmdline, 1))
	return(c);
    }
  // not exact match
  class cl_cmd *c_matched= 0;
  for (i= 0; i < count; i++)
    {
      class cl_cmd *c= (class cl_cmd *)at(i);
      if (c->name_match(cmdline, 0))
	{
	  if (!c_matched)
	    c_matched= c;
	  else
	    return(0);
	}
    }
  return(c_matched);
  //return(0);
}

class cl_cmd *
cl_cmdset::get_cmd(const char *cmd_name)
{
  int i;

  for (i= 0; i < count; i++)
    {
      class cl_cmd *c= (class cl_cmd *)at(i);
      if (c->name_match(cmd_name, 1))
	return(c);
    }
  return(0);
}

void
cl_cmdset::del(char *nam)
{
  int i;

  if (!nam)
    return;
  for (i= 0; i < count; i++)
    {
      class cl_cmd *cmd= (class cl_cmd *)(at(i));
      if (cmd->name_match(nam, 1))
	free_at(i);
    }
}

void
cl_cmdset::replace(char *nam, class cl_cmd *cmd)
{
  int i;

  if (!nam)
    return;
  for (i= 0; i < count; i++)
    {
      class cl_cmd *c= (class cl_cmd *)(at(i));
      if (c->name_match(nam, 1))
	{
	  delete c;
	  put_at(i, cmd);
	}
    }
}


/*
 * Composed command: subset of commands
 *____________________________________________________________________________
 */

cl_super_cmd::cl_super_cmd(const char *aname,
			   int  can_rep,
			   class cl_cmdset *acommands):
  cl_cmd(operate_on_none, aname, can_rep)
{
  commands= acommands;
}

cl_super_cmd::~cl_super_cmd(void)
{
  if (commands)
    delete commands;
}

int
cl_super_cmd::work(class cl_app *app,
		   class cl_cmdline *cmdline, class cl_console_base *con)
{
  class cl_cmd *cmd= 0;

  if (!commands)
    return(0);
  
  if (!cmdline->shift())
    {
      if ((cmd= commands->get_cmd("_no_parameters_")) != 0)
	return(cmd->work(app, cmdline, con));
      int i;
      con->dd_printf("\"%s\" must be followed by the name of a subcommand\n"
		     "List of subcommands:\n", names->at(0));
      for (i= 0; i < commands->count; i++)
	{
	  cmd= (class cl_cmd *)(commands->at(i));
	  //con->dd_printf("%s\n", cmd->short_help.c_str());
	  cmd->print_short(con);
	}
      return(0);
    }
  if ((cmd= commands->get_cmd(cmdline, con->is_interactive())) == NULL)
    {
      con->dd_printf("Undefined subcommand: \"%s\". Try \"help %s\".\n",
		     cmdline->get_name(), names->at(0));
      return(0);
    }
  return(cmd->work(app, cmdline, con));
}


/* End of cmd.src/command.cc */
