# 
#   Define some macros for the globals
#
macro(CurveList=`Turtle/CurveList`);
macro(PointList=`Turtle/PointList`);

macro(Xpos=`Turtle/X`);
macro(Ypos=`Turtle/Y`);
macro(Heading=`Turtle/Heading`);
macro(InitHeading=`Turtle/InitHeading`);
macro(PenColor=`Turtle/PenColor`);
macro(PenIsDown=`Turtle/PenIsDown`);

macro(Angle=`Turtle/Angle`);
macro(Length=`Turtle/Length`);
macro(Scale=`Turtle/Scale`);

macro(StateList=`Turtle/StateList`);
#
# Mod360  reduces its arg to be between 0 and 360.
Mod360:= proc(angle::algebraic)
  local incr, ang;
  ang:=evalf(angle);
  if (ang < 0) then
     incr:= 360;
  elif (ang > 360) then
     incr:= -360;
  fi;
  while (ang < 0 or ang > 360) do
     ang := ang + incr;
  od;
  RETURN(ang);
end:
#
# Setup and drawing routines
Reset:= ()->ResetTurtle():
ResetTurtle:=proc()
  global Length, Angle, Scale,InitHeading,StateList,PenIsDown,PenColor;

  Length:=1:
  Scale:=0.5:
  Angle:=90:
  InitHeading:=0;
  StateList:=[];
  PenIsDown:=true;
  PenColor:=COLOUR(RGB,0,0,0):
  PenDown();
  ClearPage();
  NULL;
end:

ClearPage := proc()
  global CurveList, Xpos, Ypos, Heading, InitHeading, 
         PointList;
  Xpos    :=0:
  Ypos    :=0:
  CurveList:=[];
  PointList:=[[Xpos,Ypos]];
  SetHeading(InitHeading);
  NULL;
end:

TurtlePlotOptions:=axes=none,scaling=constrained:

ShowPath:=proc()
   global CurveList;
   CommitTurtlePath();
   if (nargs > 0) then
     plots[display](CurveList, args[1..nargs]);
   else
     plots[display](CurveList);
   fi;
end:
#
# Saving/Restoring Turtle's "State"
#
PushState:=proc()
  global Xpos, Ypos, Heading, InitHeading, 
         PenColor, PenIsDown, PointList,
         Length, Angle, Scale, StateList;
  StateList:=[ [Xpos, Ypos, Heading, InitHeading, PenColor, PenIsDown, 
                Length, Angle, Scale], op(StateList) ];
  NULL;
end:

PopState:=proc()
  local  i;
  global Xpos, Ypos, Heading, InitHeading, 
         PenColor, PenIsDown, PointList,
         Length, Angle, Scale, StateList;
  if (nops(StateList) > 0) then
     CommitTurtlePath();
     Xpos        :=StateList[1][1];
     Ypos        :=StateList[1][2];
     Heading     :=StateList[1][3];
     InitHeading :=StateList[1][4];
     PenColor    :=StateList[1][5];
     PenIsDown   :=StateList[1][6];
     Length      :=StateList[1][7];
     Angle       :=StateList[1][8];
     Scale       :=StateList[1][9];
     StateList   :=[StateList[i] $i=2..nops(StateList)];
  fi;
  NULL;
end:

# transfer the current path from point list to the curve list
CommitTurtlePath := proc()
  global PointList,PenColor,CurveList,Xpos,Ypos;
  if (nops(PointList) > 1) then
     CurveList:= [op(CurveList), CURVES(PointList,PenColor)];
     PointList:=[[Xpos,Ypos]];
  fi;
  NULL;
end:
#
# Pen Manipulation primitives
PenUp := proc()
  global PenIsDown,PointList,PenColor,CurveList;
  CommitTurtlePath();
  PenIsDown:=false;
  NULL;
end:


PenDown := proc()
  global PenIsDown,Xpos,Ypos,PointList;
  if ((not PenIsDown) or (nops(PointList) = 0)) then
       PointList:=[[Xpos,Ypos]];
  fi;
  PenIsDown:=true;
  NULL;
end:
SetPenColor:=proc(r::algebraic,g::algebraic,b::algebraic)
  global PenColor,PenIsDown;
  if (PenIsDown) then
     CommitTurtlePath();
  fi;
  PenColor:=COLOUR(RGB,r,g,b);
  NULL;
end:
#
# Low level motion primitives
MoveAhead:= proc(dist::algebraic)
  local rads;
  global PenIsDown, PointList, Xpos, Ypos, Heading;
  rads:=evalf(Heading*Pi/180);
  Xpos:=evalf(Xpos + dist*cos(rads));
  Ypos:=evalf(Ypos + dist*sin(rads));
  if (PenIsDown) then
     PointList := [op(PointList), [Xpos,Ypos]];
  fi;
  NULL;
end:


HeadTowards:= proc(x::algebraic, y::algebraic)
  local head;
  global Xpos, Ypos;
  if (evalf(x) = Xpos) then
     if (evalf(y) < Ypos) then
        SetHeading(-90);
     else
        SetHeading(90);
     fi;
  else
     head:=arctan((y-Ypos)/(x-Xpos))*180/Pi;
     if (evalf(y) > Ypos) then
        SetHeading(head);
     else
        SetHeading(180+head);
     fi;
  fi;
  NULL;
end:
MoveTo:= proc(x::algebraic, y::algebraic)
  global PenIsDown, PointList, Xpos, Ypos;
  HeadTowards(x,y);
  Xpos:=evalf(x);
  Ypos:=evalf(y);
  if (PenIsDown) then
     PointList := [op(PointList), [Xpos,Ypos]];
  fi;
  NULL;
end:


# Setting of  several globals: Heading, AngleIncremt, length increment,
# scaling factor
SetHeading:=h->SetTurtleHeading(h):
SetTurtleHeading:= proc(heading::algebraic)
  global Heading;
  Heading:=Mod360(heading);
  NULL;
end:
GetTurtleHeading:= proc()
  global Heading;
  Heading;
end:

SetAngleIncr:=ang->SetTurtleAngle(ang):
SetTurtleAngle := proc(angle::algebraic)
  global Angle;
  Angle:=evalf(angle);
  NULL;
end:
GetTurtleAngle := proc()
  global Angle;
  Angle;
end:



SetLengthIncr:= l->SetTurtleStepsize(l):
SetTurtleStepsize:= proc(len::algebraic)
  global Length;
  Length:=evalf(len);
  NULL;
end:
GetTurtleStepsize := proc()
  global Length;
  Length;
end:

SetScale:=s->SetTurtleScale(s):
SetTurtleScale:=proc(scale::algebraic)
  global Scale;
  Scale := scale;
  NULL;
end:
GetTurtleScale:=proc()
  global Scale;
  Scale;
end:

SetInitHeading:=h->SetInitialTurtleHeading(h):
SetInitialTurtleHeading:=proc(head::algebraic)
  global InitHeading;
  InitHeading:=head;
  NULL;
end:
GetInitialTurtleHeading:=proc()
  global InitHeading;
  InitHeading;
end:

# Higher-level motion and orientation primitives
Forward:=proc()
  global Length;
  MoveAhead(Length);
end:

Back   :=proc()
  global Length;
  MoveAhead(-Length);
end:
Left   :=proc()
  global Heading, Angle;
  SetHeading(Heading+Angle);
end:

Right  := proc()
  global Heading, Angle;
  SetHeading(Heading-Angle);
end:
Shrink:=proc()
  global Scale, Length;
  Length := evalf(Length*Scale);
  NULL;
end:

Grow := proc()
  global Scale, Length;
  Length := evalf(Length/Scale);
  NULL;
end:
#
#  Now remove the macro defs
Reset():
#
macro(CurveList=`CurveList`);
macro(PointList=`PointList`);
macro(Xpos=`Xpos`);
macro(Ypos=`Ypos`);
macro(Heading=`Heading`);
macro(InitHeading=`InitHeading`);
macro(PenColor=`PenColor`);
macro(PenIsDown=`PenIsDown`);

macro(Angle=`Angle`);
macro(Length=`Length`);
macro(Scale=`Scale`);

# Stringy Stuff
# 
TurtleCmd := proc(cmd)
  local c, i, j,m, n, plotopts;
  ClearPage();
  if (nargs>1) then
	plotopts := TurtlePlotOptions,args[2..nargs];
  else
	plotopts := TurtlePlotOptions;
  fi;
  n:=0;
  for i from 1 to length(cmd) do
     c:=substring(cmd,i);
     if (searchtext(c,"0123456789") >0 ) then
       n := 10*n + parse(c,statement);
     else
       if (n>0) then
       for j from 1 to n do
         DoCommand(c);
       od;
       n:=0;
       else
         DoCommand(c);
       fi;
     fi; 
  od;
  ShowPath(plotopts);
end:

DrawString := s -> TurtleCmd(s):

AnimateString := s-> AnimateTurtleCmd(s):

AnimateTurtleCmd := proc(cmd::name)
  global `Turtle/Length`, `Turtle/Scale`, `Turtle/Angle`,`Turtle/CurveList`;
  local i, j, c, len, s, a,frames;
  len:=`Turtle/Length`;
  s  :=`Turtle/Scale`;
  a  :=`Turtle/Angle`;
  frames:=[];
  for i from 1 to length(cmd)+1 do
   ClearPage();
   SetPenColor(0,0,0);
   SetTurtleStepsize(len);
   SetScale(s);
   SetAngleIncr(a);

   for j from 1 to i-1 do
     c:=substring(cmd,j);
     DoCommand(c);
   od;
   if (i <=length(cmd) ) then
      SetPenColor(1,0,0);
      DoCommand(substring(cmd,i));
   fi;
   PenUp();
   frames:=[op(frames), 
           PLOT(op(`Turtle/CurveList`),AXESSTYLE(NONE),SCALING(CONSTRAINED))];
  od;
  plots[display](frames, insequence=true,scaling=constrained);
end:
DoCommand:=proc(cmd)
   local c;
   c:=convert(cmd,name);  # just in case we're given a string
   if   (c=`F`) then
      Forward();
   elif (c=`B`) then
      Back();
   elif (c=`L`) then
      Left();
   elif (c=`R`) then
      Right();
   elif (c=`S`) then
      Shrink();
   elif (c=`G`) then
      Grow();
   elif (c=` `) then
      NULL;
   elif (c=`U`) then
      PenUp();
   elif (c=`D`) then
      PenDown();
   elif (c=`w`) then
      SetPenColor(1,1,1);
   elif (c=`k`) then
      SetPenColor(0,0,0);
   elif (c=`r`) then
      SetPenColor(1,0,0);
   elif (c=`g`) then
      SetPenColor(0,1,0);
   elif (c=`b`) then
      SetPenColor(0,0,1);
   elif (c=`v`) then
      PushState();
   elif (c=`^`) then
      PopState();
   elif (c=`[`) then
      PushState();
      SetTurtleAngle(90);
   elif (c=`]`) then
      PopState();
   else
      if (not DoUserCommand(c)) then
         WARNING(sprintf("Unknown turtle command %c encountered",c));
      fi;
   fi;
   NULL;
end:
DoUserCommand:=c->false: