
(********************************************************************)
(*                                                                  *)
(*  diff7.sd7     Compare two files line by line.                   *)
(*  Copyright (C) 2010 - 2017  Thomas Mertes                        *)
(*                                                                  *)
(*  This program is free software; you can redistribute it and/or   *)
(*  modify it under the terms of the GNU General Public License as  *)
(*  published by the Free Software Foundation; either version 2 of  *)
(*  the License, or (at your option) any later version.             *)
(*                                                                  *)
(*  This program is distributed in the hope that it will be useful, *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of  *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *)
(*  GNU General Public License for more details.                    *)
(*                                                                  *)
(*  You should have received a copy of the GNU General Public       *)
(*  License along with this program; if not, write to the           *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


$ include "seed7_05.s7i";
  include "osfiles.s7i";
  include "getf.s7i";


const proc: showLines (in string: marker, in array string: lines,
    in integer: startIndex, in integer: stopIndex) is func
  local
    var integer: index is 0;
  begin
    for index range startIndex to stopIndex do
      writeln(marker <& lines[index]);
    end for;
  end func;


const proc: changeMessage (in integer: startIdx1, in integer: endIdx1,
    in integer: startIdx2, in integer: endIdx2) is func
  begin
    write(startIdx1);
    if startIdx1 <> endIdx1 then
      write("," <& endIdx1);
    end if;
    write("c" <& startIdx2);
    if startIdx2 <> endIdx2 then
      write("," <& endIdx2);
    end if;
    writeln;
  end func;


const proc: diffContent (in string: content1, in string: content2) is func
  local
    var array string: lines1 is 0 times "";
    var array string: lines2 is 0 times "";
    var integer: index1 is 1;
    var integer: index2 is 1;
    var integer: index1b is 1;
    var integer: index2b is 1;
    var integer: index1c is 1;
    var integer: index2c is 1;
    var boolean: match is FALSE;
  begin
    lines1 := split(content1, '\n');
    lines2 := split(content2, '\n');
    while index1 <= length(lines1) or index2 <= length(lines2) do
      while index1 <= length(lines1) and index2 <= length(lines2) and
          lines1[index1] = lines2[index2] do
        # writeln("A " <& lines1[index1]);
        # writeln("B " <& lines2[index2]);
        incr(index1);
        incr(index2);
      end while;
      # writeln("index1: " <& index1);
      # writeln("index2: " <& index2);
      if index1 <= length(lines1) or index2 <= length(lines2) then
        # writeln("-----");
        index1b := succ(index1);
        index2b := succ(index2);
        match := FALSE;
        while not match and (index1b <= length(lines1) or index2b <= length(lines2)) do
          # writeln("index1b: " <& index1b);
          # writeln("index2b: " <& index2b);
          if index1b <= length(lines1) and index2 <= length(lines2) and
              lines1[index1b] = lines2[index2] then
            if index1 <> pred(index1b) then
              write(index1 <& ",");
            end if;
            writeln(pred(index1b) <&"d" <& pred(index2));
            showLines("< ", lines1, index1, pred(index1b));
            index1 := succ(index1b);
            incr(index2);
            match := TRUE;
          elsif index1 <= length(lines1) and index2b <= length(lines2) and
              lines1[index1] = lines2[index2b] then
            write(pred(index1) <& "a" <& index2);
            if index2 <> pred(index2b) then
              write("," <& pred(index2b));
            end if;
            writeln;
            showLines("> ", lines2, index2, pred(index2b));
            incr(index1);
            index2 := succ(index2b);
            match := TRUE;
          else
            if index1b <= length(lines1) and index2b <= length(lines2) then
              index1c := succ(index1);
              while not match and index1c <= index1b do
                if lines1[index1c] = lines2[index2b] then
                  changeMessage(index1, pred(index1c), index2, pred(index2b));
                  showLines("< ", lines1, index1, pred(index1c));
                  writeln("---");
                  showLines("> ", lines2, index2, pred(index2b));
                  index1 := succ(index1c);
                  index2 := succ(index2b);
                  match := TRUE;
                else
                  incr(index1c);
                end if;
              end while;
              if not match then
                index2c := succ(index2);
                while not match and index2c <= index2b do
                  if lines1[index1b] = lines2[index2c] then
                    changeMessage(index1, pred(index1b), index2, pred(index2c));
                    showLines("< ", lines1, index1, pred(index1b));
                    writeln("---");
                    showLines("> ", lines2, index2, pred(index2c));
                    index1 := succ(index1b);
                    index2 := succ(index2c);
                    match := TRUE;
                  else
                    incr(index2c);
                  end if;
                end while;
              end if;
            end if;
          end if;
          if not match then
            incr(index1b);
            incr(index2b);
          end if;
        end while;
        if not match then
          changeMessage(index1, length(lines1), index2, length(lines2));
          showLines("< ", lines1, index1, length(lines1));
          writeln("---");
          showLines("> ", lines2, index2, length(lines2));
          index1 := succ(length(lines1));
          index2 := succ(length(lines2));
        end if;
      end if;
    end while;
  end func;


const proc: diff (in string: path1, in string: path2) is func
  local
    var fileType: fileType1 is FILE_ABSENT;
    var fileType: fileType2 is FILE_ABSENT;
    var string: currName is "";
    var string: fileContent1 is "";
    var string: fileContent2 is "";
  begin
    fileType1 := fileType(path1);
    fileType2 := fileType(path2);
    if fileType1 = FILE_DIR then
      if fileType2 <> FILE_DIR then
        writeln(literal(path1) <& " is a directory");
        writeln(literal(path2) <& " is not a directory");
      else
        for currName range readDir(path1) do
          # writeln(currName);
          diff(path1 & "/" & currName, path2 & "/" & currName);
        end for;
        for currName range readDir(path2) do
          if fileType(path1 & "/" & currName) = FILE_ABSENT then
            writeln(literal(path1 & "/" & currName) <& " missing");
          end if;
        end for;
      end if;
    elsif fileType1 = FILE_REGULAR then
      if fileType2 = FILE_ABSENT then
        writeln(literal(path2) <& " missing");
      elsif fileType2 <> FILE_REGULAR then
        writeln(literal(path2) <& " is not a regular file");
      else
        fileContent1 := getf(path1);
        fileContent2 := getf(path2);
        if fileContent1 <> fileContent2 then
          if replace(fileContent1, "\r\n", "\n") = fileContent2 then
            writeln(literal(path1) <& " uses CRLF as line ending");
          elsif fileContent1 = replace(fileContent2, "\r\n", "\n") then
            writeln(literal(path2) <& " uses CRLF as line ending");
          else
            writeln(literal(path1) <& " and " <& literal(path2) <& " differ");
            if pos(fileContent1, "\r\n") <> 0 and pos(fileContent2, "\r\n") = 0 then
              writeln(literal(path1) <& " uses CRLF as line ending");
              fileContent1 := replace(fileContent1, "\r\n", "\n");
            end if;
            if pos(fileContent1, "\r\n") = 0 and pos(fileContent2, "\r\n") <> 0 then
              writeln(literal(path2) <& " uses CRLF as line ending");
              fileContent2 := replace(fileContent2, "\r\n", "\n");
            end if;
            diffContent(fileContent1, fileContent2);
          end if;
        end if;
      end if;
    else
      if fileType1 = FILE_ABSENT then
        writeln(literal(path1) <& " missing");
      else
        writeln(literal(path1) <& " is not a regular file");
      end if;
      if fileType2 = FILE_ABSENT then
        writeln(literal(path2) <& " missing");
      elsif fileType2 <> FILE_REGULAR and fileType2 <> FILE_DIR then
        writeln(literal(path2) <& " is not a regular file");
      end if;
    end if;
  end func;


const proc: main is func
  local
    var string: path1 is "";
    var string: path2 is "";
    var integer: slashPos is 0;
    var string: fileName is "";
  begin
    if length(argv(PROGRAM)) <> 2 then
      writeln("Diff7 Version 1.0 - Compare two files line by line.");
      writeln("Copyright (C) 2010 - 2017 Thomas Mertes");
      writeln("This is free software; see the source for copying conditions.  There is NO");
      writeln("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.");
      writeln("Diff7 is written in the Seed7 programming language");
      writeln("Homepage: http://seed7.sourceforge.net");
      writeln;
      writeln("usage: diff7 file1 file2");
    else
      path1 := convDosPath(argv(PROGRAM)[1]);
      path2 := convDosPath(argv(PROGRAM)[2]);
      if fileType(path1) = FILE_REGULAR and fileType(path2) = FILE_DIR then
        slashPos := rpos(path1, '/');
        if slashPos <> 0 then
          fileName := path1[succ(slashPos) ..];
        else
          fileName := path1;
        end if;
        if path2 = "/" then
          path2 &:= fileName;
        else
          path2 &:= "/" & fileName;
        end if;
      end if;
      diff(path1, path2);
    end if;
  end func;
