
/*
TODO:
- Func(a=b) prematurely evaluates a=b
- clean up the code!
  - document the code!!!
- prefix/postfix currently not used!!!
- arrays, vectors and matrices!
- some rules for rendering the formula are slooooww....
  
- sqrt, integral and summation symbols.

   /
   |
   |
   |
   /
*/

/*
NLog(str):=
[
  WriteString(str);
  NewLine();
];
*/

CharList(length,item):=
[
  Local(line,i);
  line:="";
  For(i:=0,i<length,i++)
    line := line:item;
  line;
];

CharField(width,height) := ArrayCreate(height,CharList(width," "));

WriteCharField(charfield):=
[
  Local(i);
  For(i:=1,i<=Length(charfield),i++)
  [
    WriteString(charfield[[i]]);
    NewLine();
  ];
  True;
];

ColumnFilled(charfield,column):=
[
  Local(i,result);
  result:=False;
  For(i:=1,(result = False) And (i<=Length(charfield)),i++)
  [
    If(StringMid(column,1,charfield[[i]]) != " ",result:=True);
  ];
  result;
];
WriteCharField(charfield,width):=
[
  Local(pos,length);
  length:=Length(charfield[[1]]);
  pos:=1;
  While(pos<=length)
  [
    Local(i,thiswidth);
    thiswidth:=width;
    If(thiswidth>(length-pos)+1,
      [
        thiswidth:=(length-pos)+1;
      ],
      [
        While (thiswidth>1 And ColumnFilled(charfield,pos+thiswidth-1))
        [
          thiswidth--;
        ];
        If(thiswidth = 1, thiswidth := width);
      ]
    );
    For(i:=1,i<=Length(charfield),i++)
    [
      WriteString(StringMid(pos,thiswidth,charfield[[i]]));
      NewLine();
    ];
    pos:=pos+thiswidth;
    NewLine();
  ];
  True;
];



PutString(charfield,x,y,string):=
[
  cf[[y]] := SetStringMid(x,string,cf[[y]]);
  True;
];

MakeOper(x,y,width,height,oper,args,base):=
[
  ArrayCreateFromList({x,y,width,height,oper,args,base});
];

MoveOper(f,x,y):=
[
  f[[1]]:=f[[1]]+x; /* move x */
  f[[2]]:=f[[2]]+y; /* move y */
  f[[7]]:=f[[7]]+y; /* move base */
];

AlignBase(i1,i2):=
[
  Local(base);
  base:=Max(i1[[7]],i2[[7]]);
  MoveOper(i1,0,(base-(i1[[7]])));
  MoveOper(i2,0,(base-(i2[[7]])));
];

RuleBase("BuildArgs",{list});
Rule("BuildArgs",1,1,Length(list)=0)
[
  Formula(Atom(" "));
];
Rule("BuildArgs",1,1,Length(list)=1)
[
  list[[1]];
];

Rule("BuildArgs",1,2,True)
[
  Local(item1,item2,comma,base,newitem);
  item1:=list[[1]];
  item2:=list[[2]];
  comma:=Formula(Atom(","));
  base:=Max(item1[[7]],item2[[7]]);
  MoveOper(item1,0,(base-(item1[[7]])));
  MoveOper(comma,item1[[3]]+1,base);

  MoveOper(item2,comma[[1]]+comma[[3]]+1,(base-(item2[[7]])));
  newitem:=MakeOper(0,0,item2[[1]]+item2[[3]],Max(item1[[4]],item2[[4]]),"Func",{item1,comma,item2},base);
  BuildArgs(newitem:Tail(Tail(list)));
];

FormulaBracket(f):=
[
  Local(left,right);
  left:=Formula(Atom("("));
  right:=Formula(Atom(")"));
  left[[4]]:=f[[4]];
  right[[4]]:=f[[4]];
  MoveOper(left,f[[1]],f[[2]]);
  MoveOper(f,2,0);
  MoveOper(right,f[[1]]+f[[3]]+1,f[[2]]);
  MakeOper(0,0,right[[1]]+right[[3]],f[[4]],"Func",{left,f,right},f[[7]]);
];


RuleBase("Formula",{f});
Rule("Formula",1,1,IsAtom(f))
  MakeOper(0,0,Length(String(f)),1,"Atom",String(f),0);

Rule("Formula",1,2,Type(f)="^")
[
  Local(l,r);
  l:=Formula(f[[1]]);
  r:=Formula(f[[2]]);

  l:=BracketOn(l,f[[1]],OpLeftPrecedence(Type(f)));
  r:=BracketOn(r,f[[2]],OpRightPrecedence(Type(f)));

  MoveOper(l,0,r[[4]]);
  MoveOper(r,l[[3]],0);
  MakeOper(0,0,l[[3]]+r[[3]],l[[4]]+r[[4]],"Func",{l,r},l[[2]]+l[[4]]-1);
];


Rule("Formula",1,2,Type(f)="/")
[
  Local(l,r,dash,width);
  l:=Formula(f[[1]]);
  r:=Formula(f[[2]]);

  l:=BracketOn(l,f[[1]],OpLeftPrecedence(Type(f)));
  r:=BracketOn(r,f[[2]],OpRightPrecedence(Type(f)));

  width:=Max(l[[3]],r[[3]]);
  dash:=Formula(Atom(CharList(width,"-")));
  MoveOper(dash,0,l[[4]]);
  MoveOper(l,((width-l[[3]])>>1),0);
  MoveOper(r,((width-r[[3]])>>1),(dash[[2]]) + (dash[[4]]));
  MakeOper(0,0,width,(r[[2]])+(r[[4]]),"Func",{l,r,dash},dash[[2]]);
];

RuleBase("BracketOn",{op,f,prec});
Rule("BracketOn",3,1,IsFunction(f) And NrArgs(f) = 2
     And IsInFix(Type(f)) And OpPrecedence(Type(f)) > prec)
[
 FormulaBracket(op);
];
Rule("BracketOn",3,2,True)
[
  op;
];

Rule("Formula",1,10,IsFunction(f) And NrArgs(f) = 2
     And IsInFix(Type(f)))
[
  Local(l,r,oper,width,height,base);
  l:=Formula(f[[1]]);
  r:=Formula(f[[2]]);

  l:=BracketOn(l,f[[1]],OpLeftPrecedence(Type(f)));
  r:=BracketOn(r,f[[2]],OpRightPrecedence(Type(f)));

  oper:=Formula(f[[0]]);
  base := Max(l[[7]],r[[7]]);
  MoveOper(oper,l[[3]]+1,(base-(oper[[7]])));
  MoveOper(r,oper[[1]] + oper[[3]]+1,(base-(r[[7]])));
  MoveOper(l,0,(base-(l[[7]])));
  height:=Max((l[[2]])+(l[[4]]),(r[[2]])+(r[[4]]));
  MakeOper(0,0,r[[1]]+r[[3]],height,"Func",{l,r,oper},base);
];


Rule("Formula",1,11,IsFunction(f))
[
  Local(head,args,all);
  head:=Formula(f[[0]]);
  all:=Tail(Listify(f));

/*Write(all);*/
/*  args:=FormulaBracket(BuildArgs(Map("Formula",{all})));*/
args:=FormulaBracket(BuildArgs(MapSingle("Formula",Apply("Hold",{all}))));
  AlignBase(head,args);
  MoveOper(args,head[[3]],0);
  MakeOper(0,0,args[[1]]+args[[3]],Max(head[[4]],args[[4]]),"Func",{head,args},head[[7]]);
];



RuleBase("RenderFormula",{cf,f,x,y});

/*
/   /  /
\   |  |
    \  |
       \
*/
Rule("RenderFormula",4,1,f[[5]] = "Atom" And f[[6]] = "(" And f[[4]] > 1)
[
  Local(height,i);
  x:=x+f[[1]];
  y:=y+f[[2]];
  height:=f[[4]];

  cf[[y]] := SetStringMid(x, "/", cf[[y]]);
  cf[[y+height-1]] := SetStringMid(x, "\\", cf[[y+height-1]]);
  For (i:=1,i<height-1,i++)
    cf[[y+i]] := SetStringMid(x, "|", cf[[y+i]]);
];

Rule("RenderFormula",4,1,f[[5]] = "Atom" And f[[6]] = ")" And f[[4]] > 1)
[
  Local(height,i);
  x:=x+f[[1]];
  y:=y+f[[2]];
  height:=f[[4]];
  cf[[y]] := SetStringMid(x, "\\", cf[[y]]);
  cf[[y+height-1]] := SetStringMid(x, "/", cf[[y+height-1]]);
  For (i:=1,i<height-1,i++)
    cf[[y+i]] := SetStringMid(x, "|", cf[[y+i]]);
];

Rule("RenderFormula",4,5,f[[5]] = "Atom")
[
  cf[[y+(f[[2]]) ]]:= SetStringMid(x+(f[[1]]),f[[6]],cf[[y+(f[[2]]) ]]);
];

Rule("RenderFormula",4,6,True)
[
  ForEach(item,f[[6]])
  [
    RenderFormula(cf,item,x+(f[[1]]),y+(f[[2]]));
  ];
];

FormulaMaxWidth:=60;

PrettyForm(ff):=
[
  Local(cf,f);
  f:=Formula(ff);
  cf:=CharField(f[[3]],f[[4]]);
  RenderFormula(cf,f,1,1);
  NewLine();
  WriteCharField(cf,FormulaMaxWidth);
  True;
];

EvalFormula(f):=
[
  Local(result);
  result:= UnList({Atom("="),f,Eval(f)});
  PrettyForm(result);
  True;
];
HoldArg("EvalFormula",f);

/*
{x,y,width,height,oper,args,base}
*/



