package nxm.ucl.lib;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.StringTokenizer;

import nxm.sys.lib.StringUtil;
import nxm.ucl.inc.DiffTool;

/** This is an implementation of {@link DiffTool} that uses the unix command
    <code>diff</code> to get the differences between two files. <br>
    <br>
    This will only run on unix systems with <code>diff</code> in the path.

    @author  J. Czechowski IV
    @since   NeXtMidas 1.9.2
 */
public class NativeDiffTool implements DiffTool {

  /** default constructor */
  public NativeDiffTool () {}

  public DiffTool getDiffTool(String mimeType) {
    return this;
  }

  public FileDiff[] getDiffs (final String[] oldFile, final String[] newFile)
                            throws IllegalArgumentException {
    final ArrayList<FileDiff> diffs = new ArrayList<>();
    final String[] diffOutputs = runDiffTool(oldFile, newFile);

    for (String diffOut : diffOutputs) {
      if (diffOut.startsWith(">")) {
        /* Ignore */
      } else if(diffOut.startsWith("<")) {
        /* Ignore */
      } else if(diffOut.startsWith("-")) {
        /* Ignore */
      } else {
        diffs.add(makeDiff(diffOut));
      }
    }

    return diffs.toArray(new FileDiff[0]);
  }

  /** Creates a new FileDiff from a diff string.
      @param str The diff string from the diff tool.
   */
  private static FileDiff makeDiff(final String str) {
    try {
      final String[] fileDat = str.split("[cad]"); /* Split on 'c', 'a', 'd' */

      final String[] oldLines = fileDat[0].split(",");
      final String[] newLines = fileDat[1].split(",");

      int oldStart = Integer.parseInt(oldLines[0]);
      int newStart = Integer.parseInt(newLines[0]);

      int oldEnd = (oldLines.length == 1)? oldStart:
                                           Integer.parseInt(oldLines[1]);
      int newEnd = (newLines.length == 1)? newStart:
                                           Integer.parseInt(newLines[1]);

      int insert = newEnd - newStart;
      int remove = oldEnd - oldStart;

      if (str.indexOf('c') >= 0) {
        insert++;
        remove++;

      } else if (str.indexOf('a') >= 0) {
        insert++;
        remove = 0;
        oldStart++;

      } else if (str.indexOf('d') >= 0) {
        insert = 0;
        remove++;
        newStart++;
      }

      return makeDiff(oldStart-1, newStart-1, insert, remove);

    } catch (Exception e) {
      throw new RuntimeException("Unknown diff tool output (" + str + ").");
    }
  }

  /** Creates a new FileDiff.
      @param oldStart The start line in the old file.
      @param newStart The start line in the new file.
      @param insert   The number of lines inserted.
      @param remove   The number of lines removed.
   */
  private static FileDiff makeDiff(final int oldStart, final int newStart,
                                   final int insert,   final int remove) {
    return new FileDiff() {
      public int getNewFileStart()  { return newStart; }
      public int getOldFileStart()  { return oldStart; }
      public int getLinesInserted() { return insert;   }
      public int getLinesRemoved()  { return remove;   }
    };
  }

  private static String[] runDiffTool(String oldFile[], String newFile[]) {
    ArrayList<String> diffs = new ArrayList<>();
    String errs  = "";

    try {
      File oldData = File.createTempFile("temp_old_file", ".tmp");
      File newData = File.createTempFile("temp_new_file", ".tmp");

      PrintWriter oldOut = new PrintWriter(new FileWriter(oldData));
      PrintWriter newOut = new PrintWriter(new FileWriter(newData));

      for (String file : oldFile) {
        oldOut.println(file);
      }
      for (String nfile: newFile) {
        newOut.println(nfile);
      }
      oldOut.close();
      newOut.close();

      final String cmdline = "diff " + oldData.getAbsolutePath()
                           + " "     + newData.getAbsolutePath();

      StringTokenizer st = new StringTokenizer(cmdline);
      String[] cmdArray = new String[st.countTokens()];
      for (int i = 0; st.hasMoreTokens(); i++) {
        cmdArray[i] = st.nextToken();
      }
      Process osProcess = Runtime.getRuntime().exec(cmdArray);

      BufferedReader in  = new BufferedReader(new InputStreamReader(osProcess.getInputStream()));
      BufferedReader err = new BufferedReader(new InputStreamReader(osProcess.getErrorStream()));
      String inLine  = in.readLine();
      String errLine = err.readLine();

      while ((inLine != null) || (errLine != null)) {
        if(inLine != null) {
          diffs.add(inLine);
          inLine = in.readLine();
        }

        if (errLine != null) {
          errs += errLine;
          errLine = err.readLine();
        }
      }
      osProcess.waitFor();
      if (osProcess.exitValue() < 0) {
        throw new RuntimeException(errs);
      }

      in.close();
      err.close();
      oldData.delete();
      newData.delete();

      return diffs.toArray(StringUtil.EMPTY_STRING_ARRAY);
    } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException("Running OS cmd: "+e.getMessage());
    }
  }
}
