package basics.filesystem.scanner;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import basics.application.Binder;
import basics.testing.Test;
import basics.utl.SUtl;

/**
 * Der FileSystemScanner traversiert durch das Dateisystem. Um ihn nutzen zu koennen,
 * muss ein andere Klasse (die aufrufende Klasse) zumindest eine der Schnittstellen
 * @see FileEventListener oder DirectoryEventListener implementieren.
 * @author KStalmann
 * @created Donnerstag, 4. Mai 2001 01:04:12
 */
public class FileSystemScanner
{
private File                startPath       = null;
private boolean             recursive       = true;
private FolderEventListener onDir           = null;
private static final String fileSeparator   = System.getProperty("file.separator");
private Pattern []          patterns        = null;
private int                 startPathLength = 0;

private void expandFileMask(String [] in)
{
   if(in == null)
      return;
   patterns = new Pattern [in.length];
   for(int i = 0; i < in.length; i++)
   {
      String p = in[i].toLowerCase();
      p = SUtl.replaceAll(p, ".", "\\.");
      p = SUtl.replaceAll(p, "?", ".?");
      p = SUtl.replaceAll(p, "*", ".*");
      patterns[i] = Pattern.compile(p);;
   }
}

public void setPattern(String [] pattern)
{
   expandFileMask(pattern);
}

private boolean patternAccept(File file)
{
   if(patterns.length == 0)
      return true;
   String f = file.getName().toLowerCase();
   for(Pattern p : patterns)
   {
      Matcher m = p.matcher(f);
      if(m.matches())
         return true;
   }
   return false;
}

/**
 * Enthaelt die Listener
 */
private ArrayList<FileEventListener> fileEventListener = new ArrayList<FileEventListener>();

public FileSystemScanner(FileFolderEventAdapter adapter)
{
   if(adapter != null)
   {
      onDir = adapter;
      fileEventListener.add(adapter);
   }
}

public FileSystemScanner(FolderEventListener del)
{
   onDir = del;
}

public FileSystemScanner(FileEventListener fel)
{
   fileEventListener.add(fel);
}

public FileSystemScanner(FileEventListener fel, FolderEventListener del)
{
   fileEventListener.add(fel);
   onDir = del;
}

public void addFileEventListener(FileEventListener listener)
{
   fileEventListener.add(listener);
}

public void startScan(String start, boolean recursive)
{
   this.recursive = recursive;
   startScan(start);
}

public void startScan(String start)
{
   if(start == null)
   {
      start = ".";
   }
   File f = new File(start);
   startPath = new File(f.getAbsolutePath());
   if(start.toString().endsWith(fileSeparator))
   {
      startPathLength = (int)startPath.getAbsolutePath().length();
   }
   else
   {
      startPathLength = (int)startPath.getAbsolutePath().length() + fileSeparator.length();
   }
   if(patterns == null || patterns.length == 0)
      rscan_all(startPath);
   else
      rscan(startPath);
}

private void rscan(File currentDir)
{
   File fileList[] = currentDir.listFiles();
   if(fileList == null)
   {
      return;
   }
   ArrayList<File> files = new ArrayList<File>();
   ArrayList<File> folders = new ArrayList<File>();
   for(int i = 0; i < fileList.length; i++)
   {
      if(fileList[i].isDirectory())
         folders.add(fileList[i]);
      else
         files.add(fileList[i]);
   }
   Iterator<File> filesIt = files.iterator();
   Iterator<File> foldersIt = folders.iterator();
   while(filesIt.hasNext())
   {
      File f = (File)filesIt.next();
      Iterator<FileEventListener> it = fileEventListener.iterator();
      while(it.hasNext())
      {
         FileEventListener currentListener = (FileEventListener)it.next();
         if(patternAccept(f) && currentListener.acceptFile(f))
            currentListener.nextFile(f);
      }
   }
   while(foldersIt.hasNext())
   {
      File f = (File)foldersIt.next();
      if(onDir != null)
         onDir.nextFolder(f);
      if(recursive)
      {
         if(onDir != null)
            onDir.changeDown(f);
         rscan(f);
         if(onDir != null)
            onDir.changeUp(f);
      }
   }
}

private void rscan_all(File currentDir)
{
   File fileList[] = currentDir.listFiles();
   if(fileList == null)
   {
      return;
   }
   ArrayList<File> files = new ArrayList<File>();
   ArrayList<File> folders = new ArrayList<File>();
   for(int i = 0; i < fileList.length; i++)
   {
      if(fileList[i].isDirectory())
         folders.add(fileList[i]);
      else
         files.add(fileList[i]);
   }
   Iterator<File> filesIt = files.iterator();
   Iterator<File> foldersIt = folders.iterator();
   while(filesIt.hasNext())
   {
      File f = (File)filesIt.next();
      Iterator<FileEventListener> it = fileEventListener.iterator();
      while(it.hasNext())
      {
         FileEventListener currentListener = (FileEventListener)it.next();
         if(currentListener.acceptFile(f))
            currentListener.nextFile(f);
      }
   }
   while(foldersIt.hasNext())
   {
      File f = (File)foldersIt.next();
      if(onDir != null)
         onDir.nextFolder(f);
      if(recursive)
      {
         if(onDir != null)
            onDir.changeDown(f);
         rscan_all(f);
         if(onDir != null)
            onDir.changeUp(f);
      }
   }
}

/**
 * Returns the relative path starting at scanner start folder.
 */
public String toRelativeFilePath(File file)
{
   return file.getAbsolutePath().substring(startPathLength);
}

/**
 * Returns the relative path starting at scanner start folder.
 */
public String toClassName(String relativeFilePath)
{
   if(!relativeFilePath.toLowerCase().endsWith(".java"))
      return null;
   String javaFile = SUtl.replaceAll(relativeFilePath, fileSeparator, ".");
   return javaFile.substring(0, javaFile.length() - 5);
}

//------------------------------- test ------------------------------//
public static void unittest()
{
   File p = new File(Binder.getBasedir(), "work");
   TestConsumer c = new TestConsumer();
   FileSystemScanner s = new FileSystemScanner(c);
   s.setPattern(new String [] { "*.*" });
   s.startScan(p.getAbsolutePath(), true);
   Test.assertTrue(c.upAndDowns == 0, "up / down movements not 0");
   Test.assertTrue(c.files.size() == 12, "12 files expected, not " + c.files.size());
   Test.assertTrue(c.folders.size() == 3, "3 foldes expected, not " + c.folders.size());
}

private static class TestConsumer extends FileFolderEventAdapter
{
Vector<File> folders    = new Vector<File>();
Vector<File> files      = new Vector<File>();
int          upAndDowns = 0;

public boolean acceptFile(File file)
{
   return true;
}

public void nextFile(File file)
{
   files.add(file);
}

public void changeDown(File file)
{
   upAndDowns--;
}

public void changeUp(File file)
{
   upAndDowns++;
}

public void nextFolder(File file)
{
   folders.add(file);
}
}
//------------------------------- test ------------------------------//
}
