import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStreamWriter;

import java.util.Calendar;
import java.util.Date;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

public class MyFilesystemParser {
  
  File logFile;
  private String curLine;
  private String ignoreDir;
  private long counter;
  
  public MyFilesystemParser(String logfile) {
    logFile = new File(logfile+".gz");
    ignoreDir = System.getProperty("user.dir");
    while (ignoreDir.charAt(ignoreDir.length()-1)=='\\') ignoreDir = ignoreDir.substring(0, ignoreDir.length()-1);
  }
  
  public boolean driveIsWritable(char drive) {
    File flTest = new File (drive+":\\");
    return flTest.canWrite();
  }
  
  public boolean driveExists(char drive) {
    File flTest = new File(drive+":\\");
    return flTest.exists();
  }
  
  public boolean driveIsReadable(char drive) {
    File flTest = new File(drive+":\\");
    return flTest.canRead();
  }
  
/*  public File[] listRoots() {
    File[] result = new File[1];
    result[0] = new File("E:\\");
    return result;
  } */
  
  public File[] listRoots() {
    int count = 0;
    for (char d='A';d<='Z';d++) {
      if (driveExists(d)) count++;
    }
    File[] result = new File[count];
    count = 0;
    for (char d='A';d<='Z';d++) {
      if (driveExists(d)) result[count++] = new File(d+":\\"); 
    }
    return result;
  }
  
  /** Returns all files of a specified directory
      ATTENTION! ignoreDir has to contain a directory which should be ignored (temp, etc.).
      The String must have no trailing backslash or slash. Also keep in mind that we work with
      equalsIgnoreCase - so this won't work correctly on UNIXish machines
      @param dir Directory to get contents of
      @param noIgnoreDir If false, the startup-directory is ignored (because there are temporary files, data files, etc.)
      @return File[] with File-Objects of all files in that directory
      */
  public File[] listFiles(File dir, boolean noIgnoreDir) {
    String[] files = dir.list();
    File[] result;
    if ( files != null && ( !dir.getAbsolutePath().equalsIgnoreCase(ignoreDir) || noIgnoreDir )) {
      result = new File[files.length];
      for (int i=0; i<files.length; i++) {
        String lastItem = dir.getAbsolutePath();
        result[i] = new File(lastItem+((lastItem.charAt(lastItem.length()-1)=='\\')?"":"\\")+files[i]);
      }
    } else {
      result = new File[0];
    }
    return result;
  }
  
  /** Swaps two items of the files array */
  public void swap(File[] files, int i1, int i2) {
    File tmp = files[i2];
    files[i2] = files[i1];
    files[i1] = tmp;
  }
  
  /** String.compareToIgnoreCase() */
  public int lge(String s1, String s2) {
    return s1.toLowerCase().compareTo(s2.toLowerCase());
  }
  
  /** Helper method to partition QuickSort-areas */
  public int sortQuickPart(File[] files, int lo, int hi) {
    String p = files[(lo+hi)/2].getName();  // Pivot-Element

    while (lo <= hi) {
      while (lge(files[hi].getName(), p) > 0) hi--;
      while (lge(files[lo].getName(), p) < 0) lo++;
      if (lo < hi) {
        swap(files, lo++, hi--);
      } else {
        return hi;
      }
    }
    return hi;
  }
  
  /** QuickSort implementation */
  public void sortQuick(File[] files, int lo, int hi) {
    if (lo<hi) {
      int p = sortQuickPart(files, lo, hi);
      sortQuick(files, p+1, hi);
      sortQuick(files, lo, p);
    }
  }
  
  /** Arranges all directory-entries of the list at top */
  public void sortDirsFirst(File[] files) {
    int x;
    if (files.length>0 && files[0].isDirectory()) x=1; else x=0;
    File tmp;
    for (int i=1;i<files.length;i++) {
      if (files[i].isDirectory()) {
        tmp = files[i];
        for (int j=i;j>x;j--) {
          files[j] = files[j-1];
        }
        files[x] = tmp;
        x++;
      }
    }
  }
  
  /** Returns the sorted files-array */
  public File[] sortDir(File[] files) {
    if (files.length>=2) {
      sortQuick(files, 0, files.length-1);
      // sortDirsFirst(files);
    }
    return files;
  }
  
  /** Writes all file-entries incl. size and time of dir and sub-dirs to fw */
  public void dumpFiles(BufferedOutputStream fw, File dir) throws IOException {
    File[] entries = sortDir(listFiles(dir, false));
    for (int i=0; i<entries.length; i++) {
      if (entries[i].isDirectory() && entries[i].canRead()) {
        fw.flush();
        dumpFiles(fw, entries[i]);
      } else if (entries[i].isFile()) {
        fw.write((entries[i].getPath()+"\t"+entries[i].length()+"\t"+entries[i].lastModified()+"\r\n").getBytes());
      }
    }
    fw.flush();
  }
  
  /** Walks the root-directories of all drives and calls dumpFiles() */
  public void dumpAllDrives() throws IOException {
      BufferedOutputStream fw = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(logFile)));
      File[] roots = listRoots();
      for (int i=0;i<roots.length;i++) {
        if (roots[i].canWrite()) {
          dumpFiles(fw, roots[i]);
        }
      }
      fw.close();
  }
  
  /** Compares file-liste from cmp to actual files on disk and writes changes to out */
  public void cmpFiles(BufferedReader cmp, BufferedOutputStream out, File dir, BufferedOutputStream dout) throws IOException {
    File[] entries = sortDir(listFiles(dir, false));
    String myLine = "";
    for (int i=0; i<entries.length; i++) {
      if (entries[i].isDirectory() && entries[i].canRead()) {
        // readable directory ... submerge
        out.flush();
        if (dout != null) dout.flush();
        cmpFiles(cmp, out, entries[i], dout);
      } else if (entries[i].isFile()) {
        // it's a file
        myLine = entries[i].getPath()+"\t"+entries[i].length()+"\t"+entries[i].lastModified();
        if (dout != null) dout.write((myLine+"\r\n").getBytes());
        String fn1 = curLine.substring(0, curLine.indexOf("\t"));
        String fn2 = myLine.substring(0, myLine.indexOf("\t"));
        while (lge(fn1, fn2) < 0) {
          // file from dump is behind our current file on disk.
          // i.e.: a file got deleted - never mind, do not log
          out.write(("-\t"+curLine+"\r\n").getBytes());
          counter++;
          curLine = cmp.readLine();
          fn1 = curLine.substring(0, curLine.indexOf("\t"));
        }
        if (!myLine.equals(curLine)) {
          // something's different - name (new file) or just size or date - log it!
          if (fn1.equals(fn2)) {
            // both entries are about the same file, but size or date differs
            out.write(("*\t"+myLine+"\r\n").getBytes());
            counter++;
            curLine = cmp.readLine();
          } else {
            // there's a new file on disk
            out.write(("+\t"+myLine+"\r\n").getBytes());
            counter++;
          }
        } else {
          // both entries are equal - never mind, advance to next
          curLine = cmp.readLine();
        }
      } // else if (entries[i].isFile())
    } // for (int i=0; i<entries.length; i++)
    out.flush();
  }

  /** Walks all root-directories and calls cmpFiles() */
  public long dumpDifferences(String outfile, boolean redump) throws IOException {
    BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(outfile)));
    BufferedOutputStream dout;
    if (redump) dout = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("$$mfs$$.$$$"))); else dout = null;
    BufferedReader cmp = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(logFile))));
    counter = 0;
    File[] roots = listRoots();
    curLine = cmp.readLine();
    for (int i=0;i<roots.length;i++) {
      if (roots[i].canWrite()) {
        cmpFiles(cmp, out, roots[i], dout);
      }
    }
    cmp.close();
    out.close();
    if (dout != null) {
      // redumped! Try to delete old file and rename new one.
      dout.close();
      File dump = new File("$$mfs$$.$$$");
      if (logFile.delete()) {
        if (!dump.renameTo(logFile)) {
          System.out.println("ERROR: Could not rename new log to "+logFile.getName()+".");
        }
      } else {
        System.out.println("ERROR: Could not delete old logfile. Redump is still there.");
      }
    }
    return counter;
  }
  
  public long dumpDifferences(String outfile) throws IOException {
    return dumpDifferences(outfile, false);
  }
  
  /** Removes a line from a log, doesn't touch the file on disk */ 
  public boolean removeLine(String infile, String filename) {
    try {
      BufferedWriter tout = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream("$$mfs$$.$$$"))));
      BufferedReader fin = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(infile))));
      String tmp;
      int tab;
      String myfn;
      while ((tmp = fin.readLine()) != null) {
        tab = tmp.indexOf("\t");
        myfn = tmp.substring(tab+1, tmp.indexOf("\t", tab+1));
        if (!myfn.equals(filename)) tout.write(tmp+"\r\n");
      }
      tout.close();
      fin.close();
      File oldlog = new File(infile);
      File newlog = new File("$$mfs$$.$$$");
      if (oldlog.delete()) {
        if (!newlog.renameTo(oldlog)) {
          System.out.println("ERROR: Could not rename new log to "+logFile.getName()+".");
          return false;
        }
      } else {
        System.out.println("ERROR: Could not delete old logfile. Redump is still there.");
        return false;
      }
      return true;
    } catch (IOException ioe) {
      ioe.printStackTrace();
      return false;
    }
  }
  
  /** Deletes dump */
  public boolean delDump() {
    return logFile.delete();
  }
  
  /** Fills a string with preceding zeroes until its length is digits */
  private String formatNumber(int value, int digits) {
    String result = String.valueOf(value);
    while (result.length() < digits) {
      result = "0" + result;
    }
    return result;
  }
  
  /** Returns the formatted date of the dump file as String */
  public String getDumpDate(File flLog) {
    String result = "";
    if (flLog.exists()) {
      Date dtTime = new Date(flLog.lastModified());
      Calendar clTime = Calendar.getInstance();
      clTime.setTime(dtTime);
      
      result += formatNumber(clTime.get(Calendar.YEAR), 4) + "-";
      result += formatNumber(clTime.get(Calendar.MONTH), 2) + "-";
      result += formatNumber(clTime.get(Calendar.DATE), 2) + " ";
      result += formatNumber(clTime.get(Calendar.HOUR_OF_DAY), 2) + ":";
      result += formatNumber(clTime.get(Calendar.MINUTE), 2) + ".";
      result += formatNumber(clTime.get(Calendar.SECOND), 2);
    } else {
      result = "<DOES NOT EXIST>";
    }
    return result;
  }
  
  public String getDumpDate() {
    return getDumpDate(logFile);
  }
  
  public String getDumpDate(String name) {
    File flLog = new File(name);
    return getDumpDate(flLog);
  }
  
  /** Returns a list of data files */
  public String[] getMonitored(String mask) {
    File[] files = sortDir(listFiles(new File("."), true));
    String fn = "";
    String[] result = new String[0];
    String[] tmp;
    for (int i=0;i<files.length;i++) {
      fn = files[i].getName();
      if (fn.length()-mask.length()>0 && fn.substring(fn.length()-mask.length()).equals(mask)) {
        tmp = new String[result.length+1];
        for (int j=0;j<result.length;j++) {
          tmp[j] = result[j];
        }
        tmp[tmp.length-1] = fn.substring(0, fn.length()-mask.length());
        result = tmp;
      }
    }
    return result;
  }
  
  /** Returns all entries of specified file */
  public String[] getEntries(String fname) {
    File infile = new File(fname);
    String[] result = new String[0];
    String[] tmp;
    String ln;
    int tab;
    try {
      BufferedReader in = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(infile))));
      ln = in.readLine();
      while (ln != null) {
        tmp = new String[result.length+1];
        for (int i=0;i<result.length;i++) {
          tmp[i] = result[i];
        }
        tab = ln.indexOf("\t");
        String fn = ln.substring(tab+1, ln.indexOf("\t", tab+1));
        File fl = new File(fn);
        String status = ln.substring(0, tab);
        if (!fl.exists()) {
          status += "!";
        }
        tmp[tmp.length-1] = status+" "+fn;
        result = tmp;
        ln = in.readLine();
      }
      in.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    return result;
  }
  
}