new object $user: $reads_lines, $has_commands;

var $root inited = 1;
var $has_commands shortcuts = #[["\"", 'say_cmd], [":", 'emote_cmd], [";", 'eval_cmd]];
var $has_commands commands = #[["say", 'say_cmd], ["emote", 'emote_cmd], ["@program", 'program_cmd], ["feh", 'feh_cmd], ["@eval", 'eval_cmd], ["@edit", 'edit_cmd]];
var $user name = "the Generic User Object";
var $user connection = 0;
var $user eval_offset = 0;

public method .new_connection() {
  arg @args;

  sender().write("Welcome to ColdTurkey 0.0!");
  return (> pass(@args) <);
};

public method .init() {
  arg conn, username;

  connection = conn;
  name = username;
};

public method .name() {
  return name;
};

protected method .say_cmd() {
  arg cmdstr, cmd, what;
  var line;

  line = .name() + " says, \"" + what + "\"";
  $user.children().mmap('tell, line);
};

protected method .emote_cmd() {
  arg cmdstr, cmd, what;
  var line;

  if (what[1] == ":") {
    line = .name() + substr(what, 2);
  } else {
    line = .name() + " " + what;
  }
  $user.children().mmap('tell, line);
};

public method .tell() {
  arg what;
  var i;

  switch (type(what)) {
    case 'string:
      connection.write(what);
    case 'list:
      for i in (what) {
	.tell(i);
      }
    case 'symbol:
      connection.write(tostr(what));
  }
};

protected method .feh_cmd() {
  arg cmdstr, com, @whatever;

  .debug(.read_lines());
};

//
// These are programmer commands
//

private method ._obj_meth() {
  arg methref;

  switch (methref[1]) {
    case "$":
      methref = methref.subrange(2);
    case ".":
      methref = .objname().subrange(2) + methref;
      // Maybe put in something about objnums here.
  }
  return methref.explode(".").mmap('to_symbol) + [methref];
};
  
protected method .edit_cmd() {
  arg cmdstr, com, methref;
  var obj, meth, code;

  [obj, meth, methref] = ._obj_meth(methref);

  // verify what we have is correct
  catch ~namenf, ~methodnf {
    obj = lookup(obj);
  } with {
    return traceback()[1][2];
  }

  // List the method out as an MCP edit command, woohoo
  code = (| obj.list_method(meth) |) || [];
  return ["#$# edit name: $" + methref + "() upload: @program $" + methref] + code + ["."];
};

protected method .program_cmd() {
  arg cmdstr, com, methref;
  var obj, meth, code, errs;

  [obj, meth, methref] = ._obj_meth(methref);

  // verify what we have is correct
  catch ~namenf {
    obj = lookup(obj);
  } with {
    return traceback()[1][2];
  }

  // Read in the text
  if ((code = .read_lines("Programing $" + methref + "().  Enter \".\" to finish, \"@abort\" to abort.")) == 0) {
    return;
  }

  // Now try to add the method
  catch any {
    if ((errs = obj.add_method(code, meth))) {
      return errs;
    }
    if (0) {
      (> ref[3].set_method_flags(meth, fl) <);
      (> ref[3].set_method_access(meth, acc) <);
      if ((line = (> $code_lib.verify_code(code, meth, warn) <))) {
	.tell(line);
      }
    }
    return "Method " + methref + "() compiled";
  } with {
    return traceback()[1][2];
  }
};

public method .parse_line() {
  arg line;
  var ret;

  set_user();
  catch any {
    ret = pass(line);
  } with {
    .tell($parse_lib.traceback(traceback()));
  }
  return ret;
};

protected method .eval_cmd() {
  arg cmdstr, com, str;
  var result, adjust, vars, v, evalp, times, line, reg, obj, definer, ref, debug;

  // Taken from tCD

  vars = "me";
  v = "me=" + this() + ";";

  // check for debug flags
  if ((reg = str.regexp("^(trace|debug|profile) *;*(.*)$"))) {
    debug = tosym(reg[1]);
    str = reg[2];
  } else {
      debug = 0;
  }  

  // who are we evaluating as?
  if ((reg = regexp(str, "^ *as +([^; ]+)"))) {
      ref = $parse_lib.ref(reg[1]);
      obj = ref[2];
      definer = ref[3];
      str = strsed(str, "^ *as +([^; ]+)[ ;]+", "");
      if (!obj.is(definer))
          return obj + " isn't a child of " + definer;
  } else {
      obj = (definer = this());
  }
  
  // are we just adjusting our offset?
  if (!str) {
      result = (> .evaluate("var " + vars + ";" + v + "return (> 1 <);", obj, definer, 'no_offset) <);
      result = replace(result[1], 1, result[1][1] - 1);
      if (eval_offset)
          line = strfmt("adjusted by %s ticks and %s.%6{0}r seconds.", eval_offset[1] - result[1], eval_offset[2] - result[2], abs(eval_offset[3] - result[3]));
      else
          line = strfmt("set to %s ticks and %s.%6{0}r seconds.", @result);
      eval_offset = result;
      return "Eval offset " + line;
  }
  
  // format it
  if (match_begin(str, "var") && (reg = regexp(str, "var ([^;]+)"))) {
      str = strsed(str, "var ([^;]+);", "");
      str = "var " + vars + ", " + reg.join(",") + ";" + v + str;
  } else if ("return" in str) {
      str = "var " + vars + ";" + v + str;
  } else {
      str = strsed(str, " *;* *$", "");
      str = "var " + vars + ";" + v + "return (> " + str + " <);";
  }
  if (debug) {
    result = (> .evaluate(str, obj, definer, debug) <);
    if (! (| [times, result, debug] = result |)) {
      debug = 0;
    }
  } else {
    [times, result] = (> .evaluate(str, obj, definer) <);
  }
  
  // Display the errors, or the result.
  if (result[1] == 'errors) {
      .tell(result[2]);
  } else if (result[1] == 'traceback) {
      .tell($parse_lib.traceback(result[2]));
      line = strfmt("[ seconds: %l.%6{0}r; operations: %s", times[2], times[3], times[1]);
      if (times[2])
          line += " (" + times[1] / times[2] + " ticks per second)";
      return line + " ]";
  } else {
      if (type(result[2]) == 'objnum)
          .tell("=> " + ((| result[2].namef('xref) |) || result[2]));
      else
          .tell("=> " + toliteral(result[2]));
      if (debug)
          .tell(debug);
      line = strfmt("[ seconds: %l.%6{0}r; operations: %s", times[2], times[3], times[1]);
      if (times[2])
          line += " (" + times[1] / times[2] + " ticks per second)";
      return line + " ]";
  }
  
  // $#Edited: 11 Apr 97 03:12 $brad
  // $#Edited: 09 Jul 97 14:39 $user_bruce
  // $#Edited: 10 Dec 97 14:09 $brandon
};

private method .evaluate() {
  arg str, obj, definer, @mode;
  var start, end, time, ticks, mtime, times1, times2, method, errs, trace, result, is_error;
  
  // Taken from tCD

  mode = mode ? mode[1] : 0;
  method = tosym("tmp_eval_" + time());
  if ((errs = (> definer.add_method([str], method) <))) {
    if (mode) {
      return [[0, 0, 0], ['errors, errs, 0, 0], []];
    } else {
      return [[0, 0, 0], ['errors, errs, 0, 0]];
    }
  }
  catch any {
    if (mode in ['trace, 'profile]) {
      debug_callers(1);
    } else if (mode == 'debug) {
      debug_callers(2);
    }
    times1 = [tick(), time(), mtime()];
    result = (> obj.(method)() <);
    times2 = [mtime(), time(), tick()];
    trace = call_trace();
    debug_callers(0);
  } with {
    times2 = [mtime(), time(), tick()];
    result = traceback();
    is_error = 1;
    debug_callers(0);
  }
  (| definer.del_method(method) |);
  
  // figure up the actual times
  time = times2[2] - times1[2];
  ticks = times2[3] - times1[1];
  if (times2[1] > times1[3]) {
    mtime = times2[1] - times1[3];
  } else if (time) {
    mtime = time * 1000000 + (1000000 - times1[3]) + times2[1];
  } else {
    mtime = 1000000 - times2[1] + times1[3];
  }
  
  // offset it?
  if (eval_offset && mode != 'no_offset) {
    ticks -= eval_offset[1];
    time -= eval_offset[2];
    mtime -= eval_offset[3];
  }
  if (trace) {
    return [[ticks, time, abs(mtime)], ['result, result], $code_lib.generate_debug_listing(trace, mode)];
  } else {
    return [[ticks, time, abs(mtime)], [is_error ? 'traceback : 'result, result]];
  }
  
  // $#Edited: 11 Apr 97 03:17 $brad
  // $#Edited: 03 Aug 97 14:34 $brad
  // $#Edited: 15 Aug 97 10:15 $brandon
};