PROGRAM uudecode;

{v1.1 Toad Hall Tweak, 9 May 90
 - Reformatted in case, style, indentation, etc. to my preferences.
 - Tweaked for Turbo Pascal v5.0
 David Kirschbaum
 Toad Hall
}

Uses  Dos,Crt;

CONST
  DefaultSuffix = '.uue';
  OFFSET = 32;

TYPE
  Str80 = STRING[80];

VAR
  Infile: TEXT;
  Fi    : FILE OF Byte;
  Outfile: FILE OF Byte;
  linenum: INTEGER;
  Line: Str80;
  size,remaining : longint;  {v1.1 REAL;}


PROCEDURE Abort(Msg: Str80);
  BEGIN
    WRITELN;
    IF linenum > 0 THEN WRITE('Line ', linenum, ': ');
    WRITELN(Msg);
    HALT
  END; {of Abort}


PROCEDURE NextLine(VAR S: Str80);
  BEGIN
    Inc(linenum);
    {write('.');}
    READLN(Infile, S);
    Dec(remaining,LENGTH(S)-2);  {-2 is for CR/LF}
    WRITE('bytes remaining: ',remaining:7,' (',
          remaining/size*100.0:3:0,'%)',CHR(13));
  END; {of NextLine}


PROCEDURE Init;

  PROCEDURE GetInFile;
    VAR Infilename: Str80;
    BEGIN
      IF ParamCount = 0 THEN Abort ('Usage: uudecode <filename>');

      Infilename := ParamStr(1);
      IF POS('.', Infilename) = 0
      THEN Infilename := CONCAT(Infilename, DefaultSuffix);
      ASSIGN(Infile, Infilename);
      {$I-}
      RESET(Infile);
      {$i+}
      IF IOResult > 0 THEN Abort (CONCAT('Can''t open ', Infilename));

      WRITELN ('Decoding ', Infilename);
      ASSIGN(Fi,Infilename); RESET(Fi);
      size := FileSize(Fi);
      CLOSE(Fi);
{      IF size < 0 THEN size:=size+65536.0; }
      remaining := size;
    END;  {of GetInFile}

  PROCEDURE GetOutFile;
    VAR
      Header, Mode, Outfilename: Str80;
      Ch: CHAR;

    PROCEDURE ParseHeader;
      VAR index: INTEGER;

      PROCEDURE NextWord(VAR Word:Str80; VAR index: INTEGER);
        BEGIN
          Word := '';
          WHILE Header[index] = ' ' DO BEGIN
            Inc(index);
            IF index > LENGTH(Header) THEN Abort ('Incomplete header')
          END;
          WHILE Header[index] <> ' ' DO BEGIN
            Word := CONCAT(Word, Header[index]);
            Inc(index);
          END
        END; {of NextWord}

      BEGIN {ParseHeader}
        Header := CONCAT(Header, ' ');
        index := 7;
        NextWord(Mode, index);
        NextWord(Outfilename, index)
      END; {of ParseHeader}

    BEGIN {GetOutFile}
      IF EOF(Infile) THEN Abort('Nothing to decode.');
      NextLine (Header);
      WHILE NOT ((COPY(Header, 1, 6) = 'begin ') OR EOF(Infile)) DO
        NextLine(Header);
      WRITELN;
      IF EOF(Infile) THEN Abort('Nothing to decode.');

      ParseHeader;
      ASSIGN(Outfile, Outfilename);
      WRITELN ('Destination is ', Outfilename);
      {$I-}
      RESET(Outfile);
      {$I+}
      IF IOResult = 0 THEN BEGIN
        WRITE ('Overwrite current ', Outfilename, '? [Y/N] ');
        REPEAT
          Ch := Upcase(ReadKey);  {v1.1}
        UNTIL Ch IN ['Y', 'N'];
        WRITELN(Ch);
        IF Ch = 'N' THEN Abort ('Overwrite cancelled.')
      END;
      REWRITE (Outfile);
    END; {of GetOutFile}

  BEGIN {Init}
    linenum := 0;
    GetInFile;
    GetOutFile;
  END; { init}

FUNCTION Check_Line: BOOLEAN;
  BEGIN
    IF Line = '' THEN Abort ('Blank line in file');
    Check_Line := NOT (Line[1] IN [' ', '`'])
  END; {of Check_Line}


PROCEDURE DecodeLine;
  VAR
    lineIndex, byteNum, count, i: INTEGER;
    chars: ARRAY [0..3] OF Byte;
    hunk: ARRAY [0..2] OF Byte;

{    procedure debug;
      var i: integer;

      procedure writebin(x: byte);
        var i: integer;
        begin
          for i := 1 to 8 do begin
              write ((x and $80) shr 7);
              x := x shl 1
            end;
          write (' ')
        end;

      begin
        writeln;
        for i := 0 to 3 do writebin(chars[i]);
        writeln;
        for i := 0 to 2 do writebin(hunk[i]);
        writeln
      end;      }


  FUNCTION Next_Ch: CHAR;
    BEGIN
      Inc(lineIndex);
      IF lineIndex > LENGTH(Line) THEN Abort('Line too short.');

      IF NOT (Line[lineindex] IN [' '..'`'])
      THEN Abort('Illegal character in line.');
{     write(line[lineindex]:2);}
      IF Line[lineindex] = '`' THEN Next_Ch := ' '
                               ELSE Next_Ch := Line[lineIndex]
    END; {of Next_Ch}


  PROCEDURE DecodeByte;

    PROCEDURE GetNextHunk;
      VAR i: INTEGER;
      BEGIN
        FOR i := 0 TO 3 DO chars[i] := ORD(Next_Ch) - OFFSET;
        hunk[0] := (chars[0] ShL 2) + (chars[1] ShR 4);
        hunk[1] := (chars[1] ShL 4) + (chars[2] ShR 2);
        hunk[2] := (chars[2] ShL 6) + chars[3];
        byteNum := 0  {;
        debug          }
      END; {of GetNextHunk}

    BEGIN {DecodeByte}
      IF byteNum = 3 THEN GetNextHunk;
      WRITE (Outfile, hunk[byteNum]);
      {writeln(bytenum, ' ', hunk[byteNum]);}
      Inc(byteNum)
    END; {of DecodeByte}

  BEGIN {DecodeLine}
    lineIndex := 0;
    byteNum := 3;
    count := (ORD(Next_Ch) - OFFSET);
    FOR i := 1 TO count DO DecodeByte
  END; {of DecodeLine}


PROCEDURE Terminate;
  VAR Trailer: Str80;
  BEGIN
    IF EOF(Infile) THEN Abort ('Abnormal end.');

    NextLine (trailer);
    IF LENGTH (trailer) < 3 THEN Abort ('Abnormal end.');

    IF COPY (trailer, 1, 3) <> 'end' THEN Abort ('Abnormal end.');

    CLOSE (Infile);
    CLOSE (Outfile)
  END;  {of Terminate}

BEGIN {uudecode}
  Init;
  NextLine(Line);
  WHILE Check_Line DO BEGIN
    DecodeLine;
    NextLine(Line)
  END;
  Terminate
END.
