/*

  This module implements a simple calculator machine for mbot. There's only
one command, "!calc <expression>", where expression can use:

binary operators: +, -, *, /, %, ^, ** (same as ^), =
functions: see struct funcs[] below, those include most (all?) libm functions
which only require one parameter (and some aliases), fact(), rand(), rad(),
deg().
constants: see struct consts[] below, descriptions are in portuguese.
variables: use <name>=<expression> to define them, and use them wherever you
would put a constant.

*/

#include "mbot.h"

#define LEVEL_CALC 0

#define DEST s->script.dest
#define BUF s->script.buf
#define SEND_TEXT s->script.send_text

#define PI 3.14159265358979323846

#define VARS_MAX 100

/////////////////////
// class definition
/////////////////////

class CCalc {
public:
  CCalc (CNetServer *server) : s (server) {}
  ~CCalc (void);

  double calc (c_char);

  CNetServer *s;
  int error_pos;

private:

  enum s_type { S_NUM, S_OP, S_ID, S_BRACE_L, S_BRACE_R, S_EOF };
  enum op_type { OP_ASSIGN, OP_PLUS, OP_MINUS, OP_TIMES, OP_DIV, OP_MOD,
                 OP_POW };
  struct symbol_type {
    int pos;
    s_type type;
    op_type op;		// used if type is S_OP
    char priority;	// used if type is S_OP
    double value;	// used if type is S_NUM
    CString id;		// used if type is S_ID
    symbol_type *next;
    symbol_type (int p, double v) : pos (p), type (S_NUM), value (v) {}
    symbol_type (int p, op_type o, char pr) : pos (p), type (S_OP), op (o), priority (pr) {}
    symbol_type (int p, c_char i) : pos (p), type (S_ID), id (i, MSG_SIZE) {}
    symbol_type (int p, s_type t) : pos (p), type (t) {}
  } *top, *bottom;

  void scan_symbols (c_char);

  double parse_explist (void);
  bool parse_expsimple (void);
  bool parse_exp_id (symbol_type *);

  struct var_type {
    CString name;
    double value;
    time_t time;
    var_type (c_char n, double v) : name (n, MSG_SIZE), value (v),
      time (get_time ()) {}
  };
  CList vars;

  int braces;

  void delete_symbols (void);
  void push_symbol_bottom (symbol_type *);
  void push_symbol (symbol_type *);
  symbol_type *pop_symbol (void);

};

CList *calc_list;

///////////////
// prototypes
///////////////

extern "C" {

static char error[MSG_SIZE+1];

struct const_type {
  c_char name;
  double value;
};
static const const_type consts[] = {
  { "c", 3.00E8 },	// velocidade de propagação da luz no vazio
  { "e", 2.7182818284590452354 },
  { "E0", 8.85E-12 },	// permitividade do vazio
  { "F", 9.65E4 },	// constante de Faraday
  { "g", 9.8 },		// força da gravidade na Terra
  { "G", 6.67E-11 },	// constante gravitacional
  { "h", 6.63E-34 },	// constante de Planck
  { "k0", 8.99E9 },	// constante de Coulomb
  { "me", 9.11E-31 },	// massa própria do electrão
  { "mp", 1.67E-27 },	// massa própria do protão
  { "mn", 1.67E-27 },	// massa própria do neutrão
  { "NA", 6.02E23 },	// constante de Avogadro
  { "pi", PI },
  { "u", 1.66E-27 },	// unidade de massa atómica
  { "u0", 1.26E-6 },	// permeabilidade no vazio
  { "R", 8.31 },	// constante (molar) de gás ideal
  { "RH", 1.10E7 },	// constante de Rydberg
  { NULL, 0 }
};

enum func_type {
  FUNC_SQRT = 0,
  FUNC_CBRT,
  FUNC_FABS,
  FUNC_CEIL,
  FUNC_FLOOR,
  FUNC_RINT, FUNC_INT,
  FUNC_ERF,
  FUNC_ERFC,
  FUNC_GAMMA,
  FUNC_LGAMMA,
  FUNC_EXP,
  FUNC_EXPM1,
  FUNC_LOG,
  FUNC_LOG10,
  FUNC_LOG1P,
  FUNC_LOGB,
  FUNC_SINH, FUNC_SENH,
  FUNC_COSH,
  FUNC_TANH,
  FUNC_COSIN, FUNC_COSEN,
  FUNC_COCOS,
  FUNC_COTAN,
  FUNC_SIN, FUNC_SEN,
  FUNC_COS,
  FUNC_TAN,
  FUNC_ASIN, FUNC_ARSIN, FUNC_ARCSIN, FUNC_ASEN, FUNC_ARSEN, FUNC_ARCSEN,
  FUNC_ACOS, FUNC_ARCOS, FUNC_ARCCOS,
  FUNC_ATAN, FUNC_ARTAN, FUNC_ARCTAN,
  FUNC_ASINH, FUNC_ARSINH, FUNC_ARCSINH, FUNC_ASENH, FUNC_ARSENH, FUNC_ARCSENH,
  FUNC_ACOSH, FUNC_ARCOSH, FUNC_ARCCOSH,
  FUNC_ATANH, FUNC_ARTANH, FUNC_ARCTANH,
  FUNC_RAND,
  FUNC_FACT,
  FUNC_RAD,
  FUNC_DEG
};

static const c_char funcs[] = {
  "sqrt",
  "cbrt",
  "fabs",
  "ceil",
  "floor",
  "rint", "int",
  "erf",
  "erfc",
  "gamma",
  "lgamma",
  "exp",
  "expm1",
  "log",
  "log10",
  "log1p",
  "logb",
  "sinh", "senh",
  "cosh",
  "tanh",
  "cosin", "cosen",
  "cocos",
  "cotan",
  "sin", "sen",
  "cos",
  "tan",
  "asin", "arsin", "arcsin", "asen", "arsen", "arcsen",
  "acos", "arcos", "arccos",
  "atan", "artan", "arctan",
  "asinh", "arsinh", "arcsinh", "asenh", "arsenh", "arcsenh",
  "acosh", "arcosh", "arccosh",
  "atanh", "artanh", "arctanh",
  "rand",
  "fact",
  "rad",
  "deg",
  NULL
};

static void calc_cmd_calc (CNetServer *);

static CCalc *server2calc (CNetServer *);
static void calc_add (CNetServer *);
static void calc_conf (CNetServer *, c_char);
static void calc_stop (CModule *);
static void calc_start (CModule *);

}

////////////////////
// class functions
////////////////////

CCalc::~CCalc (void)
{
  var_type *v;
  vars.rewind ();
  while ((v = (var_type *)vars.next ()) != NULL)
    delete v;
}

double
CCalc::calc (c_char exp)
{
  error[0] = 0;
  braces = 0;
  top = bottom = NULL;
  scan_symbols (exp);
  if (error[0] != 0)
    return 0;
  double result = parse_explist ();
  delete_symbols ();
  return result;
}

/*
  call parse_expsimple() repeatedly until the entire symbol stack is reduced
  to a single numeric symbol.
*/
double
CCalc::parse_explist (void)
{
  while (error[0] == 0)
    if (parse_expsimple ())
      {
        symbol_type *symbol = pop_symbol ();
        if (symbol == NULL || symbol->type != S_NUM)
          {
            snprintf (error, MSG_SIZE, "FUNCTION PARAMETER ERROR!");
            break;
          }
        return symbol->value;
      }
  return 0;
}

/*
  parse a simple expression (<num><operation><num>) from the symbol stack
  and push the result. expands <num> to identifiers (constants, variables or
  functions), and to another expression inside braces.
  return 1 if S_EOF is found, 0 otherwise.
*/
bool
CCalc::parse_expsimple (void)
{
  double d = 0;
  bool lvalue = 0;
  symbol_type *symbol, *op = NULL, *var = NULL;

  while (1)
    {
      symbol = pop_symbol ();
      if (symbol == NULL)
        {
          snprintf (error, MSG_SIZE, "STACK ERROR!");
          error_pos = 0;
          return 0;
        }
      error_pos = symbol->pos;

      if (symbol->type == S_EOF)
        {
          if (!lvalue || op != NULL || braces != 0)
            {
              snprintf (error, MSG_SIZE, "unexpected end of expression");
              break;
            }
          push_symbol (new symbol_type (symbol->pos, d));
          delete symbol;
          return 1;
        }

      else if (symbol->type == S_OP)
        {
          if (symbol->op == OP_ASSIGN)
            {
              if (var == NULL)
                {
                  snprintf (error, MSG_SIZE, "non-variable lvalue in assignment");
                  break;
                }
            }
          else 
            {
              if (op != NULL ||
                  (!lvalue && symbol->op != OP_PLUS && symbol->op != OP_MINUS))
                {
                  if (op != NULL)
                    snprintf (error, MSG_SIZE, "missing rvalue for operator");
                  else
                    snprintf (error, MSG_SIZE, "missing lvalue for operator");
                  break;
                }
              if (!lvalue && (symbol->op == OP_PLUS || symbol->op == OP_MINUS))
                {
                  d = 0;
                  lvalue = 1;
                }
            }
          op = symbol;
        }

      else if (symbol->type == S_NUM)
        {
          if (op == NULL)
            {
              if (lvalue)
                {
                  snprintf (error, MSG_SIZE, "two consecutive values");
                  break;
                }
              d = symbol->value;
              lvalue = 1;
              delete symbol;
              continue;
            }
          if (top != NULL && top->type == S_OP && top->priority > op->priority)
            {
              push_symbol (new symbol_type (symbol->pos, symbol->value));
              parse_expsimple ();
              if (error[0] != 0)
                break;
              delete symbol;
              continue;
            }
          switch (op->op)
            {
              case OP_ASSIGN:
                d = symbol->value;
                var_type *v;
                vars.rewind ();
                while ((v = (var_type *)vars.next ()) != NULL)
                  if (var->id == v->name)
                    break;
                if (v != NULL)
                  {
                    v->value = d;
                    v->time = get_time ();
                  }
                else
                  {
                    vars.add ((void *)new var_type (var->id, d));
                    if (vars.count () == VARS_MAX)
                      {
                        time_t t_old = (size_t)-1;
                        var_type *v_old = NULL;
                        vars.rewind ();
                        while ((v = (var_type *)vars.next ()) != NULL)
                          if (v->time < t_old)
                            {
                              t_old = v->time;
                              v_old = v;
                            }
                        vars.del ((void *)v_old);
                      }
                  }
                delete var;
                var = NULL;
                break;
              case OP_PLUS:
                d += symbol->value;
                break;
              case OP_MINUS:
                d -= symbol->value;
                break;
              case OP_TIMES:
                d *= symbol->value;
                break;
              case OP_DIV:
                d /= symbol->value;
                break;
              case OP_MOD:
                d = fmod (d, symbol->value);
                break;
              case OP_POW:
                d = pow (d, symbol->value);
                break;
            }
          push_symbol (new symbol_type (symbol->pos, d));
          delete op;
          delete symbol;
          return 0;
        }
      
      else if (symbol->type == S_BRACE_L)
        {
          int old_braces = braces;
          braces++;
          while (braces != old_braces)
            {
              parse_expsimple ();
              if (error[0] != 0)
                break;
            }
          delete symbol;
        }

      else if (symbol->type == S_BRACE_R)
        {
          if (!lvalue)
            snprintf (error, MSG_SIZE, "missing value in braces");
          else if (op != NULL)
            snprintf (error, MSG_SIZE, "missing rvalue for operator");
          else if (braces == 0)
            snprintf (error, MSG_SIZE, "extra ')' used");
          else
            {
              braces--;
              push_symbol (new symbol_type (symbol->pos, d));
              delete symbol;
              if (op != NULL)
                delete op;
              return 0;
            }
          break;
        }

      else if (symbol->type == S_ID)
        {
          if (parse_exp_id (symbol))
            {
              if (lvalue)
                {
                  snprintf (error, MSG_SIZE, "invalid lvalue in assignment");
                  break;
                }
              var = symbol;
            }
          if (error[0] != 0)
            break;
        }
    }

  // only reached in case of error
  if (symbol != NULL)
    delete symbol;
  if (op != NULL)
    delete op;
  if (var != NULL)
    delete var;
  return 0;
}

/*
  push the value of <symbol> into the stack, unless it's a variable in an
  assignment operation. <symbol> can be a defined variable, a constant or a
  function name.
  return 1 if it's a variable and the next symbol in the stack is an assign
  operator, 0 otherwise.
*/
bool
CCalc::parse_exp_id (symbol_type *symbol)
{
  // check if it's a function
  bool is_function = 0;
  for (size_t i = 0; funcs[i] != NULL; i++)
    if (symbol->id == funcs[i])
      {
        is_function = 1;
        break;
      }
  if (!is_function && top != NULL && top->type == S_BRACE_L)
    {
      snprintf (error, MSG_SIZE, "unknown function '%s'", (c_char)symbol->id);
      return 0;
    }
  if (is_function)
    {
      if (top == NULL || top->type != S_BRACE_L)
        {
          snprintf (error, MSG_SIZE, "missing parameter for function '%s'", (c_char)symbol->id);
          return 0;
        }
      delete pop_symbol ();
      int old_braces = braces;
      braces++;
      while (braces != old_braces)
        {
          parse_expsimple ();
          if (error[0] != 0)
            return 0;
        }
      symbol_type *s = pop_symbol ();
      if (s == NULL || s->type != S_NUM)
        {
          snprintf (error, MSG_SIZE, "FUNCTION PARAMETER ERROR!");
          return 0;
        }
      double parameter = s->value, result;
      if (symbol->id == funcs[FUNC_SQRT])
        result = sqrt (parameter);
      else if (symbol->id == funcs[FUNC_CBRT])
        result = cbrt (parameter);
      else if (symbol->id == funcs[FUNC_FABS])
        result = fabs (parameter);
      else if (symbol->id == funcs[FUNC_CEIL])
        result = ceil (parameter);
      else if (symbol->id == funcs[FUNC_FLOOR])
        result = floor (parameter);
      else if (symbol->id == funcs[FUNC_RINT]
               || symbol->id == funcs[FUNC_INT])
        result = rint (parameter);
      else if (symbol->id == funcs[FUNC_ERF])
        result = erf (parameter);
      else if (symbol->id == funcs[FUNC_ERFC])
        result = erfc (parameter);
      else if (symbol->id == funcs[FUNC_GAMMA])
        result = gamma (parameter);
      else if (symbol->id == funcs[FUNC_LGAMMA])
        result = lgamma (parameter);
      else if (symbol->id == funcs[FUNC_EXP])
        result = exp (parameter);
      else if (symbol->id == funcs[FUNC_EXPM1])
        result = expm1 (parameter);
      else if (symbol->id == funcs[FUNC_LOG])
        result = log (parameter);
      else if (symbol->id == funcs[FUNC_LOG10])
        result = log10 (parameter);
      else if (symbol->id == funcs[FUNC_LOG1P])
        result = log1p (parameter);
      else if (symbol->id == funcs[FUNC_LOGB])
        result = logb (parameter);
      else if (symbol->id == funcs[FUNC_SINH]
               || symbol->id == funcs[FUNC_SENH])
        result = sinh (parameter);
      else if (symbol->id == funcs[FUNC_COSH])
        result = cosh (parameter);
      else if (symbol->id == funcs[FUNC_TANH])
        result = tanh (parameter);
      else if (symbol->id == funcs[FUNC_COSIN]
               || symbol->id == funcs[FUNC_COSEN])
        result = pow (sin (parameter), -1);
      else if (symbol->id == funcs[FUNC_COCOS])
        result = pow (cos (parameter), -1);
      else if (symbol->id == funcs[FUNC_COTAN])
        result = pow (tan (parameter), -1);
      else if (symbol->id == funcs[FUNC_SIN]
               || symbol->id == funcs[FUNC_SEN])
        result = sin (parameter);
      else if (symbol->id == funcs[FUNC_COS])
        result = cos (parameter);
      else if (symbol->id == funcs[FUNC_TAN])
        result = tan (parameter);
      else if (symbol->id == funcs[FUNC_ASIN]
               || symbol->id == funcs[FUNC_ARSIN]
               || symbol->id == funcs[FUNC_ASEN]
               || symbol->id == funcs[FUNC_ARSEN])
        result = asin (parameter);
      else if (symbol->id == funcs[FUNC_ACOS]
               || symbol->id == funcs[FUNC_ARCOS])
        result = acos (parameter);
      else if (symbol->id == funcs[FUNC_ATAN]
               || symbol->id == funcs[FUNC_ARTAN])
        result = atan (parameter);
      else if (symbol->id == funcs[FUNC_ASINH]
               || symbol->id == funcs[FUNC_ARSINH]
               || symbol->id == funcs[FUNC_ASENH]
               || symbol->id == funcs[FUNC_ARSENH])
        result = asinh (parameter);
      else if (symbol->id == funcs[FUNC_ACOSH]
               || symbol->id == funcs[FUNC_ARCOSH])
        result = acosh (parameter);
      else if (symbol->id == funcs[FUNC_ATANH]
               || symbol->id == funcs[FUNC_ARTANH])
        result = atanh (parameter);
      else if (symbol->id == funcs[FUNC_RAND])
        result = random_num ((long int)rint (parameter));
      else if (symbol->id == funcs[FUNC_FACT])
        {
          if (parameter < 0)
            {
              snprintf (error, MSG_SIZE, "negative parameter in fact()");
              return 0;
            }
          result = 0;
          for (int i = (int)rint (parameter); i > 0; i--)
            result *= i;
        }
      else if (symbol->id == funcs[FUNC_RAD])
        result = parameter*PI/180;
      else if (symbol->id == funcs[FUNC_DEG])
        result = parameter*180/PI;
      else
        {
          error_pos = symbol->pos;
          snprintf (error, MSG_SIZE, "FUNCTION LOOKUP ERROR!");
          return 0;
        }
      push_symbol (new symbol_type (symbol->pos, result));
      return 0;
    }

  // check if it's a constant
  for (size_t i = 0; consts[i].name != NULL; i++)
    if (symbol->id == consts[i].name)
      {
        push_symbol (new symbol_type (symbol->pos, consts[i].value));
        return 0;
      }

  // if we got here, it must be a variable
  if (top != NULL && top->type == S_OP && top->op == OP_ASSIGN)
    return 1;
  var_type *v;
  vars.rewind ();
  while ((v = (var_type *)vars.next ()) != NULL)
    if (symbol->id == v->name)
      {
        push_symbol (new symbol_type (symbol->pos, v->value));
        return 0;
      }
  snprintf (error, MSG_SIZE, "unknown identifier");
  return 0;
}

// split <e> into symbols and push them into the symbol stack
void
CCalc::scan_symbols (c_char e)
{
  char token[MSG_SIZE+1];
  c_char e_orig = e;
  size_t pos;

  while (1)
    {
      e += num_spaces (e);
      error_pos = e - e_orig;
      if (isdigit (e[0]) || e[0] == '.')
        {
          pos = 0;
          bool dot = 0, exp = 0, expsign = 0;
          while ((isdigit (e[pos]) || e[pos] == '.' || e[pos] == '-'
                  || e[pos] == '+' || tolower (e[pos]) == 'e')
                 && pos < MSG_SIZE)
            {
              error_pos = e - e_orig + pos;
              if (e[pos] == '.')
                {
                  if (dot)
                    {
                      snprintf (error, MSG_SIZE, "multiple dots in number");
                      return;
                    }
                  dot = 1;
                }
              if (tolower (e[pos]) == 'e')
                {
                  if (exp)
                    {
                      snprintf (error, MSG_SIZE, "multiple 'E's in number");
                      return;
                    }
                  exp = 1;
                }
              if (e[pos] == '-' || e[pos] == '+')
                {
                  if (!expsign && exp)
                    expsign = 1;
                  else
                    break;
                }
              token[pos] = e[pos++];
            }
          token[pos] = 0;
          e += pos;
          push_symbol_bottom (new symbol_type (error_pos, atof (token)));
        }
      else if (isalpha (e[0]))
        {
          pos = 0;
          while (isalnum (e[pos]))
            token[pos] = e[pos++];
          token[pos] = 0;
          e += pos;
          push_symbol_bottom (new symbol_type (error_pos, token));
        }
      else if (e[0] == '=')
        {
          e++;
          push_symbol_bottom (new symbol_type (error_pos, OP_ASSIGN, 0));
        }
      else if (e[0] == '+')
        {
          e++;
          push_symbol_bottom (new symbol_type (error_pos, OP_PLUS, 1));
        }
      else if (e[0] == '-')
        {
          e++;
          push_symbol_bottom (new symbol_type (error_pos, OP_MINUS, 1));
        }
      else if (e[0] == '/')
        {
          e++;
          push_symbol_bottom (new symbol_type (error_pos, OP_DIV, 2));
        }
      else if (e[0] == '%')
        {
          e++;
          push_symbol_bottom (new symbol_type (error_pos, OP_MOD, 2));
        }
      else if (e[0] == '^')
        {
          e++;
          push_symbol_bottom (new symbol_type (error_pos, OP_POW, 3));
        }
      else if (e[0] == '*' && e[1] == '*')
        {
          e += 2;
          push_symbol_bottom (new symbol_type (error_pos, OP_POW, 3));
        }
      else if (e[0] == '*')
        {
          e++;
          push_symbol_bottom (new symbol_type (error_pos, OP_TIMES, 2));
        }
      else if (e[0] == '(')
        {
          e++;
          push_symbol_bottom (new symbol_type (error_pos, S_BRACE_L));
        }
      else if (e[0] == ')')
        {
          e++;
          push_symbol_bottom (new symbol_type (error_pos, S_BRACE_R));
        }
      else if (e[0] == 0 || e[0] == '#')
        {
          push_symbol_bottom (new symbol_type (error_pos, S_EOF));
          break;
        }
      else
        {
          snprintf (error, MSG_SIZE, "unmatched symbol '%c'", e[0]);
          break;
        }
    }
}

void
CCalc::delete_symbols (void)
{
  symbol_type *symbol;
  while (top != NULL)
    {
      symbol = top->next;
      delete top;
      top = symbol;
    }
}

void
CCalc::push_symbol_bottom (symbol_type *symbol)
{
  if (top == NULL)
    {
      push_symbol (symbol);
      bottom = top;
    }
  else
    {
      symbol->next = NULL;
      bottom->next = symbol;
      bottom = symbol;
    }
}

void
CCalc::push_symbol (symbol_type *symbol)
{
  symbol->next = top;
  top = symbol;
}

CCalc::symbol_type *
CCalc::pop_symbol (void)
{
  if (top == NULL)
    return NULL;
  symbol_type *symbol = top;
  top = top->next;
  return symbol;
}

/////////////
// commands
/////////////

extern "C" {

// !calc expression
static void
calc_cmd_calc (CNetServer *s)
{
  CCalc *calc = server2calc (s);
  if (calc == NULL)
    return;
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !calc expression");
      return;
    }
  if (strcasecmp (BUF[1], "help") == 0)
    {
      SEND_TEXT (DEST, "Read the source, Luke...");
      return;
    }
  double result = calc->calc (BUF[1]);
  if (error[0] != 0)
    SEND_TEXT (DEST, "Error evaluating expression at %d: %s.", calc->error_pos+1, error);
  else if (result == HUGE_VAL)
    SEND_TEXT (DEST, "Result too large.");
  else if (result == -HUGE_VAL)
    SEND_TEXT (DEST, "Result too small.");
  else if (isnan (result))
    SEND_TEXT (DEST, "Not a number.");
  else
    {
      char buf[MSG_SIZE+1];
      snprintf (buf, MSG_SIZE, "%.15f", result);
      if (strchr (buf, '.') != NULL)
        {
          size_t len = strchr (buf, '.')-buf;
          if (len > 15 || (1E-15 > result && result > 0))
            {
              SEND_TEXT (DEST, "%.15e", result);
              return;
            }
          len = strlen (buf);
          if (len > (size_t)(16 + (result < 0)))
            {
              buf[16 + (result < 0)] = 0;
              len = strlen (buf);
            }
          for (size_t i = len-1; buf[i] == '0' || buf[i] == '.'; i--)
            {
              if (buf[i] == '.')
                {
                  buf[i] = 0;
                  break;
                }
              buf[i] = 0;
            }
        }
      SEND_TEXT (DEST, "%s", buf);
    }
}

////////////////////
// module managing
////////////////////

// returns the calc object for a given server, NULL if nonexistant
static CCalc *
server2calc (CNetServer *s)
{
  CCalc *calc;
  calc_list->rewind ();
  while ((calc = (CCalc *)calc_list->next ()) != NULL)
    if (calc->s == s)
      return calc;
  return NULL;
}

// adds a calc with <server> to the list
static void
calc_add (CNetServer *s)
{
  calc_list->add ((void *)new CCalc (s));
}

// configuration file's local parser
static void
calc_conf (CNetServer *s, c_char bufread)
{
  char buf[2][MSG_SIZE+1];

  strsplit (bufread, buf, 1);

  if (strcasecmp (buf[0], "bot") == 0)
    {
      calc_add (s);
      s->script.bind_cmd (calc_cmd_calc, LEVEL_CALC, "!calc");
    }
}

// module termination
static void
calc_stop (CModule *m)
{
  CCalc *calc;
  calc_list->rewind ();
  while ((calc = (CCalc *)calc_list->next ()) != NULL)
    {
      calc->s->script.unbind_cmd ("!calc");
      delete calc;
    }
  delete calc_list;
}

// module initialization
static void
calc_start (CModule *m)
{
  calc_list = new CList ();
}

CModule::module_type module = {
  MODULE_VERSION,
  "calc",
  calc_start,
  calc_stop,
  calc_conf,
  NULL
};

}
