package scm;

                                // This is the (usual) compound procedure
                                // object
/**
 * This is a container class that is overidden
 * by primitives. It can be generated through
 * @see jas.Lambda procedures.
 */

class Procedure implements Obj
{
  Cell body;                    // the seq of expressions constituting
                                // the body of the procedure
  Cell formals;                 // the arglist expected by the procedure
  Env procenv;                  // env in which the proc was created


  Env extendargs(Cell args, Env f)
    throws Exception
  {
    Cell params = null;
    Cell tail = null;
    while (args != null)
      {
        Obj now = args.car;
        if (now != null) 
          { now = now.eval(f); } // eval args in context of old expression
        if (tail != null)
          {
            tail.cdr = new Cell(now, null);
            tail = tail.cdr;
          }
        else
          {
            params = new Cell(now, params);
            tail = params;
          }
        args = args.cdr;
      }
                                // make new frame, with appropriate
                                // bindings. The enclosing frame
                                // is the env in which the procedure
                                // was created.
    return (procenv.extendenv(formals, params));
  }

  Obj apply(Cell args, Env f)
    throws Exception
  {
    Env newEnv = extendargs(args, f);
    Cell expr = body;
    Obj ret = null;
                                // eval body with new bindings
    while (expr != null)
      {
        ret = expr.car;
        if (ret != null)
          { ret = ret.eval(newEnv); }
        expr =  expr.cdr;
      }
    return (ret);
  }
  public Obj eval(Env e)
  { throw new SchemeError("Cant eval procedures directly"); }

  public String toString()
  {
    return ("<lambda generated> " + body);
  }
}

                                // do a few primitives here
/**
 * Add two integers
 * <code> (+ int1 int2) </code>
 */

class Plus extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Obj l1 = args.car.eval(f);
    Obj l2 = args.cdr.car.eval(f);

    return (new Selfrep(((Selfrep)l1).num + ((Selfrep)l2).num));
  }
  public String toString()
  {
    return ("<#plus#>");
  }
}
/**
 * Subtract integers
 * <code>(- int1 int2)
 */

class Minus extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Obj l1 = args.car.eval(f);
    Obj l2 = args.cdr.car.eval(f);

    return (new Selfrep(((Selfrep)l1).num - ((Selfrep)l2).num));
  }
  public String toString()
  {
    return ("<#minus#>");
  }
}
/**
 * Multiply integers
 * <code> (* int1 int2) </code>
 */

class Mult extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Obj l1 = args.car.eval(f);
    Obj l2 = args.cdr.car.eval(f);

    return (new Selfrep(((Selfrep)l1).num * ((Selfrep)l2).num));
  }
  public String toString()
  {
    return ("<#mult#>");
  }
}

/**
 * divide integers
 * <code> (/ int1 int2) </code>
 */

class Div extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Obj l1 = args.car.eval(f);
    Obj l2 = args.cdr.car.eval(f);

    return (new Selfrep(((Selfrep)l1).num / ((Selfrep)l2).num));
  }
  public String toString()
  {
    return ("<#div#>");
  }
}
/**
 * Bitwise or of integers
 * <code> (| int1 int2) </code>
 */

class Or extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Obj l1 = args.car.eval(f);
    Obj l2 = args.cdr.car.eval(f);

    return (new Selfrep
            ((int)(Math.round(((Selfrep)l1).num)) |
             (int)(Math.round(((Selfrep)l2).num))));
  }
  public String toString()
  {
    return ("<#or#>");
  }
}

/**
 * Yup. just as it says.
 * <code> (car (quote (a b)))
 * => a
 * </code>
 */

class Car extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Cell tmp = (Cell) args.car.eval(f);
    return (tmp.car);
  }
  public String toString()
  { return ("<#car#>"); }
}

/**
 * More lispisms.
 * <code> (cdr (quote (a b)))
 * => (b)
 * </code>
 */

class Cdr extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Cell tmp = (Cell) args.car.eval(f);
    return (tmp.cdr);
  }
  public String toString()
  { return ("<#cdr#>"); }
}

/**
 * Generate new list
 * <code> (cons (quote a) (quote (b c))) => (a b c) </code>
 */

 
class Cons extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Obj ncar = args.car.eval(f);
    Obj ncdr = args.cdr.car.eval(f);
    return (new Cell(ncar, (Cell) ncdr));
  }
  public String toString()
  { return ("<#cons#>"); }
}

/**
 * Prevent from evaluation.
 * <code> (quote a) => a </code>
 */

class Quote extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    if (args == null)
      { throw new SchemeError("null args to Quote"); }
    return args.car;
  }
  public String toString()
  { return ("<#Quote#>"); }
}

/**
 * bind a value to a symbol.<p>
 *
 * <code> (define some-new-symbol "some thing") => "some thing"</code><br>
 * <code> some-new-symbol => "some thing" </code>
 */

class Define extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Symbol v;                   // (symbol value)
    if (args == null)
      { throw new SchemeError("null args to define"); }
    if (args.car instanceof Symbol)
      { v = (Symbol) args.car; }
    else
      { throw new SchemeError("bad argtype to define" + args.car); }

    if (v == null)
      { throw new SchemeError("null symbol value"); }

    Cell val =  args.cdr;
    if (val == null)
      { throw new SchemeError("not enough args to define"); }
    Obj ret = val.car;
    if (ret != null) 
      { ret = ret.eval(f); }
    f.definevar(v, ret);
    return ret;
  }
  public String toString()
  { return ("<#define#>"); }
}
/**
 * reset a value to a symbol.<p>
 *
 * <code> (set! some-old-symbol "xyz")
 */

class Setvar extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Symbol v;                   // (symbol value)
    if (args == null)
      { throw new SchemeError("null args to define"); }
    if (args.car instanceof Symbol)
      { v = (Symbol) args.car; }
    else
      { throw new SchemeError("bad argtype to set!" + args.car); }

    if (v == null)
      { throw new SchemeError("null symbol value"); }

    Cell val =  args.cdr;
    if (val == null)
      { throw new SchemeError("not enough args to set!"); }
    Obj ret = val.car;
    if (ret != null) 
      { ret = ret.eval(f); }
    f.setvar(v, ret);
    return ret;
  }
  public String toString()
  { return ("<#set!#>"); }
}

/**
 * (cond (condition body) (condition body)...)
 */

class Cond extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Cell t = args;

    while (t != null)
      {
                                // examine condition part
        if (t.car == null)
          { throw new SchemeError("null clause for cond"); }
        Obj clause = t.car;
        if (!(clause instanceof Cell))
          { throw new SchemeError("need a condition body for cond clause"); }
        Obj result = (((Cell)clause).car);
        if (result != null) { result = result.eval(f); }
        if (result == null)
          { t = t.cdr; continue; }
                                // Got a non nill, so do body and
                                // return.
        Obj body = (((Cell)clause).cdr).car;
        return (body.eval(f));
      }
    return null;
  }
  public String toString()
  { return ("<#cond#>"); }
}

/**
 * (num? thing)
 */

class NumP extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    if (args == null) return null;

    Obj target = args.car;
    if (target != null) target = target.eval(f);
    if (target == null) return null;
    if ((target instanceof Selfrep) &&
        (((Selfrep)target).val == null))
      return target;
    return null;
  }
  public String toString()
  { return ("<#num?#>"); }
}
/**
 * <
 */

class LessP extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    if (args == null)
      { throw new SchemeError("< expects a pair of arguments"); }

    Obj target1 = args.car;
    if (target1 != null) target1 = target1.eval(f);
    args = args.cdr;
    Obj target2 = args.car;
    if (target2 != null) target2 = target2.eval(f);

    if ((target1 == null) ||
        (target2 == null))
      { throw new SchemeError("< expects a pair of arguments"); }
    if (!(target1 instanceof Selfrep) ||
        !(target2 instanceof Selfrep))
      { throw new SchemeError("< expects a pair of numbers as args"); }
    if ((((Selfrep)target1).num) < (((Selfrep)target2).num))
      { return target1; }
    return null;
  }
  public String toString()
  { return ("<#<#>"); }
}
/**
 * >
 */

class MoreP extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    if (args == null)
      { throw new SchemeError("> expects a pair of arguments"); }

    Obj target1 = args.car;
    if (target1 != null) target1 = target1.eval(f);
    args = args.cdr;
    Obj target2 = args.car;
    if (target2 != null) target2 = target2.eval(f);

    if ((target1 == null) ||
        (target2 == null))
      { throw new SchemeError("> expects a pair of arguments"); }
    if (!(target1 instanceof Selfrep) ||
        !(target2 instanceof Selfrep))
      { throw new SchemeError("> expects a pair of numbers as args"); }
    if ((((Selfrep)target1).num) > (((Selfrep)target2).num))
      { return target1; }
    return null;
  }
  public String toString()
  { return ("<#>#>"); }
}
/**
 * (eq? obj1 obj2)
 */

class EqP extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    if (args == null) return null;

    Obj target1 = args.car;
    if (target1 != null) target1 = target1.eval(f);
    args = args.cdr;
    Obj target2 = args.car;
    if (target2 != null) target2 = target2.eval(f);

    if ((target1 == null) &&
        (target2 == null)) return (new Selfrep(1));
    if ((target1 == null) ||
        (target2 == null))
      { return null; }
    
    if (target1 == target2)
      {
        return (target1);
      }
        
    if ((target1 instanceof Selfrep) &&
        (target2 instanceof Selfrep))
      {
        if ((((Selfrep)target1).val) == null)
          {
            if ((((Selfrep)target1).num) == (((Selfrep)target2).num))
              { return new Selfrep(1); }
          }
        else
          {
            if ((((Selfrep)target1).val).equals((((Selfrep)target2).val)))
              { return new Selfrep(1); }
          }            
      }
    return null;
  }
  public String toString()
  { return ("<#eq?#>"); }
}

/**
 * (string? thing)
 */

class StringP extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    if (args == null) return null;

    Obj target = args.car;
    if (target != null) target = target.eval(f);
    if (target == null) return null;
    if ((target instanceof Selfrep) &&
        (((Selfrep)target).val != null))
      return target;
    return null;
  }
  public String toString()
  { return ("<#string?#>"); }
}

/**
 * (progn body1 body2 ...)
 */

class Progn extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Cell t = args;
    Obj result = null;
    while (t != null)
      {
        if (t.car == null)
          {
            result = null;
          }
        else
          {
            result = t.car.eval(f);
          }
        t = t.cdr;
      }
    return result;
  }
  public String toString()
  { return ("<#progn#>"); }
}

/**
 * (mapcar function (args1 args2 ...))
 */

class Mapcar extends Procedure implements Obj
{
  Obj apply(Cell args, Env f)
    throws Exception
  {
    Obj ftmp = args.car;
    if (ftmp != null) ftmp = ftmp.eval(f);
    if (ftmp == null)
      { throw new SchemeError("null function for mapcar"); }
    if (!(ftmp instanceof Procedure))
      { throw new SchemeError("expected a procedure for mapcar"); }
    Procedure fn = (Procedure) ftmp;

    Cell t = (Cell)((args.cdr.car).eval(f));
    Cell res = null;
    Cell tail = null;
    while (t != null)
      {
        if (tail == null)
          { 
            res =
              new Cell
              (fn.apply
               (new Cell((t.car), null), f),
              null);
            tail = res;
          }
        else
          {
            tail.cdr =
              new Cell
              (fn.apply
               (new Cell((t.car), null), f),
               null);
          }
        t = t.cdr;
      }
    return res;
  }
}
