// Program written by Maarten Meuris AKA Nyerguds
program dune_edit;
uses crt, dos;

TYPE
   chset  = array [1..8] of byte;
   coords = array [1..4] of integer;
   keymapper = Record
      up, down, left, right, space, enter, esc, back, pgup, pgdn, home, endk :char;
   end;
   option = Record
      typeid: Integer;
      text: String[20];
   end;
    lev2Lst = array[0..27] of String;
    lev3Lst = array[0..49] of option;

var inputval,level: integer;
    keyread:char;
    errormsg:String;
    select,scroll,maxdisplay,listlen:array [1..4] of integer;
    lev2List:lev2Lst;
    lev3List:lev3Lst;
const
    framechar:array[1..2] of chset = (
                         ($DA, $BF, $C0, $D9, $C4, $B3, $C2, $C1),
                         ($C9, $BB, $C8, $BC, $CD, $BA, $CB, $CA));
    selectBoundaries:array[1..4] of coords = (
                         (3,4,25,5), (3,9,25,23), (32,4,54,23), (17,12,38,18));
    typeids:array[0..9] of String[15] = (
                         'unsigned byte', 'signed byte', '2-byte', '4-byte',
                         'owner', 'command', 'movement type', 'weapon type',
                         'structure', 'unit');

    mainlistlen:integer=1;
    mainlist:array[0..1] of String[20] = ('Units       ','Structures  ');

    unitlistlen:integer=26;
    unitlist:lev2Lst = (
             'Carryall    ','''Thopter    ','Infantry    ','Troopers    ',
             'Soldier     ','Trooper     ','Saboteur    ','Launcher    ',
             'Deviator    ','Tank        ','Siege Tank  ','Devastator  ',
             'Sonic Tank  ','Trike       ','Raider Trike','Quad        ',
             'Harvester   ','MCV         ','Death Hand  ','Rocket      ',
             'ARocket     ','GRocket     ','MiniRocket  ','Bullet      ',
             'Sonic Blast ','Sandworm    ','Frigate     ','');
    structlistlen:integer=18;
    structlist:lev2Lst = (
             'Concrete    ','Concrete4   ','Palace      ','Light Fctry ',
             'Heavy Fctry ','Hi Tech     ','IX          ','WOR         ',
             'Const Yard  ','Windtrap    ','Barracks    ','Starport    ',
             'Refinery    ','Repair      ','Wall        ','Turret      ',
             'R-Turret    ','Spice Silo  ','Outpost     ','','','','','','','','','');

    unitoptsListlen:integer=46;
    unitOptsList:lev3Lst = (
              (typeid: 2; text: 'Short name ID       '),
              (typeid: 3; text: 'Name code ref.      '),
              (typeid: 2; text: 'Long name ID        '),
              (typeid: 3; text: 'WSA file code ref.  '),
              (typeid: 2; text: 'Unknown 005         '),
              (typeid: 2; text: 'Unknown 006         '),
              (typeid: 2; text: 'Hit points          '),
              (typeid: 2; text: 'Unknown 008         '),
              (typeid: 2; text: 'Sidebar icon gfxID  '),
              (typeid: 2; text: 'Cost                '),
              (typeid: 2; text: 'Build Time          '),
              (typeid: 2; text: 'Tech level          '),
              (typeid: 8; text: 'Prerequisites       '),
              (typeid: 2; text: 'Unknown 014         '),
              (typeid: 0; text: 'Unknown 015         '),
              (typeid: 1; text: 'Upgrades needed     '),
              (typeid: 5; text: 'Sidebar command #1  '),
              (typeid: 5; text: 'Sidebar command #2  '),
              (typeid: 5; text: 'Sidebar command #3  '),
              (typeid: 5; text: 'Sidebar command #4  '),
              (typeid: 2; text: 'Unknown 021         '),
              (typeid: 0; text: 'Unknown 022         '),
              (typeid: 2; text: 'Unknown 023         '),
              (typeid: 2; text: 'Unknown 024         '),
              (typeid: 4; text: 'Owner               '),
              (typeid: 2; text: 'Unknown 026         '),
              (typeid: 2; text: 'Unknown 027         '),
              (typeid: 0; text: 'Unknown 028         '),
              (typeid: 0; text: 'Unknown 029         '),
              (typeid: 0; text: 'Unknown 030         '),
              (typeid: 0; text: 'Unknown 031         '),
              (typeid: 2; text: 'Sight               '),
              (typeid: 6; text: 'Movement type       '),
              (typeid: 2; text: 'Unknown 034         '),
              (typeid: 2; text: 'Speed               '),
              (typeid: 2; text: 'Unknown 036         '),
              (typeid: 2; text: 'Unit gfxID          '),
              (typeid: 2; text: 'Turret gfxID        '),
              (typeid: 2; text: 'Unknown 039         '),
              (typeid: 2; text: 'Unknown 040         '),
              (typeid: 2; text: 'Unknown 041         '),
              (typeid: 2; text: 'Weapon rate of fire '),
              (typeid: 2; text: 'Weapon range        '),
              (typeid: 2; text: 'Weapon damage       '),
              (typeid: 2; text: 'Unknown 045         '),
              (typeid: 7; text: 'Weapon type         '),
              (typeid: 2; text: 'Unknown 047         '),
              (typeid: 0; text: ''),
              (typeid: 0; text: ''),
              (typeid: 0; text: ''));

      keymap:keymapper = (up:    char(72);
                          down:  char(80);
                          left:  char(75);
                          right: char(77);
                          space: char(32);
                          enter: char(13);
                          esc:   char(27);
                          back:  char(8);
                          pgup:  char(73);
                          pgdn:  char(81);
                          home:  char(71);
                          endk:  char(79));

procedure cntin;
var pause: char;
begin
   gotoxy(1,25);
   repeat until keypressed;
    pause:=ReadKey();
end; {cntin}

function inputValue(minval,maxval:integer):boolean;
var charin: char;
    inputstr:String[10];
    minlen,maxlen:integer;
begin
   charin:=char(0);
   str(minval,inputstr);
   minlen:=Length(inputstr);
   str(maxval,inputstr);
   maxlen:=Length(inputstr);
   inputstr:='';
   while (charin<>keymap.esc) and (charin<>keymap.enter) do
   begin
    charin:=ReadKey();

    if ((byte(charin)=45) and (Length(inputstr)=0) and (minval<0))
        or ((byte(charin)>=48) and (byte(charin)<=57)
          and (     (Length(inputstr)<minlen)
                and (Copy(inputstr,1,1)='-')
                 or (Length(inputstr)<maxlen)
                and (Copy(inputstr,1,1)<>'-')
              )
       ) then
     begin
       inputstr:=inputstr+charin;
       write(charin);
     end
    else if (charin=keymap.back) and (Length(inputstr)>0) then
     begin
       inputstr:=Copy(inputstr, 1, (Length(inputstr)-1));
       write(keymap.back);
       write('_');
       write(keymap.back);
     end;
   end;
   val(inputstr,inputval);
   inputValue:=false;
   errormsg:='';
   if (charin=keymap.esc) then errormsg:='Canceled'
   else if (inputval>maxval) then errormsg:='Value too large!'
   else if (inputval<minval) then errormsg:='Value too small!'
   else inputValue:=true;

end; {cntin}


procedure drawLine(x,y,len:integer; c:char);
var count:integer;
begin
   for count:=0 to (len-1) do
    begin
      gotoxy(x,y+count);
      write(c);
    end;
end;

procedure drawFrame(x,y,w,h,m:integer; active,fill:boolean );
var charset,count,count2:integer;
begin
   if active then charset:=2
   else charset:=1;
   gotoxy(x,y);
   write(char(framechar[charset,1]));
   for count:=1 to (w-2) do
    write(char(framechar[charset,5]));
   write(char(framechar[charset,2]));
   for count:=1 to (h-2) do
    begin
      gotoxy(x,y+count);
      write(char(framechar[charset,6]));
      if fill then for count2:=1 to (w-2) do write(' ');
      gotoxy(x+w-1,y+count);
      write(char(framechar[charset,6]));
    end;
   gotoxy(x,y+h-1);
   write(char(framechar[charset,3]));
   for count:=1 to (w-2) do
    write(char(framechar[charset,5]));
   write(char(framechar[charset,4]));

   if (m>1) then
    begin
      gotoxy(x+m-1,y);
      write(char(framechar[charset,7]));
      gotoxy(x+m-1,y+h-1);
      write(char(framechar[charset,8]));
      drawLine(x+m-1,y+1,h-2,char(framechar[charset,6]));
    end;
   gotoxy(0,0);
end; {drawFrame}

procedure drawSelect(l:integer);
begin
  if (l<>4) or (l=level) then
   begin
     drawLine(selectBoundaries[l][1],selectBoundaries[l][2],maxdisplay[l]+1,' ');
     gotoxy(selectBoundaries[l][1],selectBoundaries[l][2]+select[l]);
     write('[');
     drawLine(selectBoundaries[l][3],selectBoundaries[l][2],maxdisplay[l]+1,' ');
     gotoxy(selectBoundaries[l][3],selectBoundaries[l][2]+select[l]);
     write(']');
   end;
end;

procedure drawSelect();
begin
  drawSelect(level);
end;

procedure drawSelects();
var i:integer;
begin
  for i:=1 to 4 do drawSelect(i);
end;


function openFile(filename:String ) : file;
var
  status:integer;
begin
  assign(openFile,filename);
  {$I-}
  reset(openFile,1);
  status:=IOResult;
  if status<>0 then
   begin
    writeln();
    writeln('Error opening "',filename,'"');
    {$I+}
   end;
end; {openFile}

procedure closeFile(f:file);
begin
  close(f);
  {$I+}
end; {closeFile}

procedure drawLev3();
var i,last: integer;
    showlist:array [0..19] of String[20];
begin
  if listlen[3]<maxdisplay[3] then last:=listlen[3]
  else last:=maxdisplay[3];
  for i:=0 to last do
  begin
    showlist[i]:=lev3List[(i+scroll[3])].text;
  end;
  for i:=0 to last do
  begin
    gotoxy(selectBoundaries[3][1]+2,selectBoundaries[3][2]+i);
    write(showlist[i]);
  end;
end;

procedure drawLev2();
var i,last: integer;
    showlist:array [0..19] of String[20];
begin
  if listlen[2]<maxdisplay[2] then last:=listlen[2]
  else last:=maxdisplay[2];
  for i:=0 to last do
  begin
    showlist[i]:=lev2List[(i+scroll[2])];
  end;
  for i:=0 to last do
  begin
    gotoxy(selectBoundaries[2][1]+2,selectBoundaries[2][2]+i);
    write(showlist[i]);
  end;
end;

procedure drawLev1();
var i,last: integer;
    showlist:array [0..2] of String[20];
begin
  if listlen[1]<maxdisplay[1] then last:=listlen[1]
  else last:=maxdisplay[1];
  for i:=0 to last do
  begin
    showlist[i]:=mainlist[(i+scroll[1])];
  end;
  for i:=0 to last do
  begin
    gotoxy(selectBoundaries[1][1]+2,selectBoundaries[1][2]+i);
    write(showlist[i]);
  end;
end;


procedure drawList(l:integer);
begin
    if l=1 then drawLev1()
    else if l=2 then
     begin
       if select[1]=0 then
        begin
           listlen[2]:=UnitListLen;
           lev2List:=UnitList;
         end
        else if select[1]=1 then
         begin
           listlen[2]:=StructListLen;
           Lev2List:=StructList;
         end;
       drawLev2();
     end
    else if l=3 then
     begin
       if select[1]=0 then
        begin
         listlen[3]:=unitOptsListLen;
         lev3List:=unitOptsList;
         end
        else if select[1]=1 then
         begin
           listlen[3]:=unitOptsListLen; //structOptsListLen
           lev3List:=unitOptsList;  //structOptsList
         end;
       drawLev3();
     end;
end;

procedure drawList();
begin
   drawList(level);
end;

procedure drawLists();
var i:integer;
begin
   for i:=1 to 3 do drawList(i);
end;


procedure updateLists();
var i: integer;
begin
  for i:=2 to 3 do
    begin
      scroll[i]:=0;
      select[i]:=0;
      drawSelect(i);
      drawList(i);
    end;
end;

procedure MoveDown(lines:integer);
begin
  if ((select[level]+lines)<maxdisplay[level]) then
     select[level]:=select[level]+lines
  else
    begin
     lines:=lines-(maxdisplay[level]-select[level]);
     select[level]:=maxdisplay[level];
    end;
  if (lines>0) and (select[level]=maxdisplay[level]) and (maxdisplay[level]<listlen[level]) then
    begin
      scroll[level]:=scroll[level]+lines;
      if scroll[level] > (listlen[level]-maxdisplay[level]) then scroll[level]:=(listlen[level]-maxdisplay[level]);
      drawList();
    end;
  if level=1 then updateLists();
end;

procedure MoveUp(lines:integer);
begin
  if ((select[level]-lines)>0) then
    select[level]:=select[level]-lines
  else
    begin
     lines:=lines-select[level];
     select[level]:=0;
    end;
  if (lines>0) and (select[level]=0) and (scroll[level]>=0) then
    begin
      scroll[level]:=scroll[level]-lines;
      if scroll[level] < 0 then scroll[level]:=0;
      drawList();
    end;
  if level=1 then updateLists();
end;



procedure clearWorkArea();
var i: integer;
begin
    for i:=3 to 25 do
      begin
        gotoxy(1,i);
        clreol;
      end;
end;

procedure inputInt(minval,maxval:integer);
var isInput:boolean;
begin
   gotoxy(17,8);
   write(typeids[lev3list[select[3]+scroll[3]].typeid]);
   write(' (');
   write(minval);
   write(',');
   write(maxval);
   write(')');
   gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
   write('__________________');
   drawSelect();
   gotoxy(selectBoundaries[4][1]+2,selectBoundaries[4][2]);
   isInput:=inputValue(minval,maxval);
   gotoxy(selectBoundaries[4][1]+32,selectBoundaries[4][2]);
   if isInput then
    begin
      write('Saved.');
      gotoxy(selectBoundaries[4][1]+32,selectBoundaries[4][2]+2);
      write(inputval);
    end
   else write(errormsg);
   cntin();
   level:=3;
end;

procedure drawLevFrame(lvl:integer);
begin
  case lvl of
   1 : drawFrame(1,3,27,4,-1,level=1,false);
   2 : drawFrame(1,8,27,17,-1,level=2,false);
   3 : drawFrame(30,3,50,22,27,level=3,false);
   4 : drawFrame(15,5,52,15,-1,level=4,true);
  end;
end;

procedure drawLevFrame();
begin
  drawLevFrame(level)
end;



procedure drawLayout();
begin
   if level=4 then
   begin
     drawLevFrame(3);
     drawLevFrame(level);
     gotoxy(17,6);
     write(lev2list[select[2]+scroll[2]]);
     gotoxy(17,7);
     write(lev3list[select[3]+scroll[3]].text);
     case lev3list[select[3]+scroll[3]].typeid of
      0 : inputInt(0,255);                    //unsigned byte
      1 : inputInt(-128,127);                 //signed byte
      2 : inputInt(-32768,32767);             //2-byte
      3 : inputInt(-32768,32767);             //4-byte (HEX?)
      4 : inputInt(0,255);                    //owner
      5 : inputInt(-32768,32767);             //command
      6 : inputInt(-32768,32767);             //Movement type
      7 : inputInt(-32768,32767);             //weapon type
      8 : inputInt(0,255);                    //structures
      9 : inputInt(0,255);                    //unit
      end;
     clearWorkArea();
     drawLists();
     drawSelects();
   end;
   drawLevFrame(1);
   drawLevFrame(2);
   drawLevFrame(3);
   drawSelect();
   gotoxy(1,25);
end;

procedure FillWorkArea();
begin
      ClearWorkArea();
      drawLayout();
      drawLists();
      drawSelects();
end;

procedure input();
begin
 if level<>4 then
  begin
   if (keyread=keymap.ESC) then keyread:=char(255)
   else if (level=3) and (keyread=keymap.ENTER) or (keyread=keymap.SPACE) then level:=4
   else if (level<>3) and (keyread=keymap.RIGHT) then level:=level+1
   else if (level=3) and (keyread=keymap.RIGHT) then level:=1
   else if (level<>1) and (keyread=keymap.LEFT) then level:=level-1
   else if (level=1) and (keyread=keymap.LEFT) then level:=3
   else if (keyread=keymap.DOWN) then MoveDown(1)
   else if (keyread=keymap.UP) then   MoveUp(1)
   else if (keyread=keymap.PGDN) then MoveDown(maxdisplay[level])
   else if (keyread=keymap.PGUP) then MoveUp(maxdisplay[level])
   else if (keyread=keymap.ENDK) then MoveDown(listlen[level])
   else if (keyread=keymap.HOME) then MoveUp(listlen[level])
  end
 else
  begin
   if (keyread=keymap.DOWN) then MoveDown(level)
   else if (keyread=keymap.UP) then MoveUp(level);
   if (keyread=keymap.ESC) and (level=4) then begin level:=level-1; FillWorkArea(); end;
  end;

end;

procedure init();
var i: integer;
begin
  for i:=1 to 25 do
  begin
    gotoxy(1,i);
    clreol;
  end;
  gotoxy(1,1);
  for i:=1 to 80 do
    write(char(framechar[2,5]));
  gotoxy(3,1);
  write(' Dune II Editor ');
  gotoxy(65,1);
  write(' by Nyerguds ');
  level:=1;
  listlen[1]:=mainlistlen;
  listlen[2]:=unitListLen;
  listlen[3]:=unitOptsListLen;
  listlen[4]:=0;
  for i:=1 to 4 do
  begin
   select[i]:=0;
   scroll[i]:=0;
   maxdisplay[i]:=selectBoundaries[i][4]-selectBoundaries[i][2];
   drawList(i);
   drawSelect(i);
  end;
end;

begin
  init();
  drawLayout();
  keyread:=char(0);
    while keyread <> char(255) do
    begin
      keyread:=ReadKey();
      gotoxy(1,25);
      input();
      drawLayout();
    end;
end.
