import org.sablecc.sablecc.expander.Expander;
import java.io.*;
import java.util.*;

public class SvnWebifier
{
  final static String NL = System.getProperty("line.separator");

  private final Expander expander;
  private final Set usedRevisions = new TreeSet();
  private final TreeMap shortLogs = new TreeMap();

  private final LinkedList directoryNames = new LinkedList();

  public static void main(String[] args)
  throws Exception
  {
    SvnWebifier sw = new SvnWebifier();
    sw.findRevs("");
    sw.createLogs();
    sw.handleDir("", "");
  }

  private SvnWebifier() throws Exception
  {
    expander = new Expander(new InputStreamReader(getClass().getResourceAsStream("template.html")));
  }

  private boolean ignored()
  {
    int size = directoryNames.size();

    // root is not ignored
    if (size == 0)
      return false;

    // ignore **/.svn directories
    if (directoryNames.get(size - 1).equals(".svn"))
      return true;

    return false;
  }

  private void findRevs(String currentPath) throws Exception
  {
    // unsorted dir content
    File[] fileArray = new File("repository/" + currentPath).listFiles();
    // use a set to sort content
    TreeSet files = new TreeSet();

    for (int i = 0; i < fileArray.length; i++)
    {
      files.add(fileArray[i]);
    }

    for (Iterator i = files.iterator(); i.hasNext();)
    {
      File file = (File) i.next();
      String fileName = file.getName();

      if (file.isDirectory())
      {
        directoryNames.addLast(fileName);

        if (!ignored())
        {
          findRevs(currentPath + fileName + "/");
        }

        directoryNames.removeLast();
        continue;
      }

      // get status
      Process process = Runtime.getRuntime().exec(new String[]
                        {"svn", "st", "-N", "-v", "repository/" + currentPath + fileName});

      BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));

      try
      {
        String line = in.readLine();
        line = line.substring(8); // skip useless fields

        StringTokenizer st = new StringTokenizer(line, " ");
        Integer committedRev;

        while((line = in.readLine()) != null)
        {}

        st.nextToken(); // skip working rev
        committedRev = new Integer(st.nextToken());

        usedRevisions.add(committedRev);
      }
      catch(Exception e)
      {
        continue; // skip unversioned files
      }
      finally
      {
        in.close();
        process.waitFor();
      }
    }
  }

  private void createLogs() throws Exception
  {
    for (Iterator i = usedRevisions.iterator(); i.hasNext();)
    {
      Integer rev = (Integer) i.next();

      // get full log
      Process process = Runtime.getRuntime().exec(new String[]
                        {"svn", "log", "-v", "-r", rev.toString(), "svn+ssh://svn.sablevm.org/"});

      BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));

      try
      {
        String line;
        StringBuffer fullLog = new StringBuffer();

        while((line = in.readLine()) != null)
        {
          fullLog.append(line);
          fullLog.append(NL);
        }

        Expander.Expansion expansion = expander.new Expansion(new FileWriter("log" + rev + ".txt"), "log");

        TreeMap args = new TreeMap();
        args.put("content", fullLog);
        expansion.applyMacro("logcontent", args);
        expansion.done();
      }
      finally
      {
        in.close();
        process.waitFor();
      }

      // get short log
      process = Runtime.getRuntime().exec(new String[]
                                          {"svn", "log", "-r", rev.toString(), "svn+ssh://svn.sablevm.org/"
                                          }
                                         );

      in = new BufferedReader(new InputStreamReader(process.getInputStream()));

      try
      {
        in.readLine(); // skip ----
        String headerLine = in.readLine();
        in.readLine(); // skip empty line
        String shortLog = in.readLine();

        String line;
        while((line = in.readLine()) != null)
        {}

        StringTokenizer st = new StringTokenizer(headerLine, "|");
        st.nextToken(); //skip rev
        st.nextToken(); //skip author
        String committedDate = st.nextToken();

        shortLogs.put(rev, new ShortLog(committedDate, shortLog));
      }
      finally
      {
        in.close();
        process.waitFor();
      }
    }
  }

  private void handleDir(String rootPath, String currentPath) throws Exception
  {
    int filenum = 0;
    int dirnum = 0;
    boolean darkdir = false;
    boolean darkfile = false;
    String currentFileName;

    currentFileName = currentPath + "index.html";

    if (!currentPath.equals(""))
    {
      new File(currentPath).mkdir();
    }

    Expander.Expansion expansion = expander.new Expansion(new FileWriter(currentFileName), "main");

    {
      TreeMap args = new TreeMap();
      args.put("path", "/" + currentPath);
      expansion.applyMacro("title", args);
    }

    if(!currentPath.equals(""))
    {
      TreeMap args = new TreeMap();
      expansion.applyMacro("uplink", args);
    }
    else
    {
      TreeMap args = new TreeMap();
      expansion.applyMacro("nouplink", args);
    }

    // unsorted dir content
    File[] fileArray = new File("repository/" + currentPath).listFiles();
    // use a set to sort content
    TreeSet files = new TreeSet();

    for (int i = 0; i < fileArray.length; i++)
    {
      files.add(fileArray[i]);
    }

    for (Iterator i = files.iterator(); i.hasNext();)
    {
      File file = (File) i.next();
      String fileName = file.getName();
      TreeMap args = new TreeMap();

      if (file.isDirectory())
      {
        directoryNames.addLast(fileName);

        if (!ignored())
        {
          args.put("name", fileName);
          args.put("link", fileName);
          if (darkdir)
          {
            expansion.applyMacro("darkdir", args);
          }
          else
          {
            expansion.applyMacro("paledir", args);
          }
          darkdir = !darkdir;
          dirnum++;
          handleDir("../" + rootPath, currentPath + fileName + "/");
        }

        directoryNames.removeLast();
        continue;
      }

      Process process = Runtime.getRuntime().exec(new String[]
                        {"svn", "st", "-N", "-v", "repository/" + currentPath + fileName});

      BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));

      try
      {
        String line = in.readLine();

        // skip useless fields
        line = line.substring(8);

        StringTokenizer st = new StringTokenizer(line, " ");
        Integer committedRev;
        String committedAuth;

        while((line = in.readLine()) != null)
        {}

        st.nextToken(); // skip working rev
        committedRev = new Integer(st.nextToken());
        committedAuth = st.nextToken();

        ShortLog log = (ShortLog) shortLogs.get(committedRev);

        args.put("name", file.getName());
        args.put("author", committedAuth);
        args.put("date", log.committedDate);
        args.put("log", log.log);
        args.put("loglink", rootPath + "log" + committedRev + ".txt");
        args.put("version", committedRev);
      }
      catch(Exception e)
      {
        continue; // skip unvesrioned files
      }
      finally
      {
        in.close();
        process.waitFor();
      }

      args.put("link", rootPath + "repository/" + currentPath + fileName);
      if (darkfile)
      {
        expansion.applyMacro("darkfile", args);
      }
      else
      {
        expansion.applyMacro("palefile", args);
      }
      darkfile = !darkfile;
      filenum++;
    }

    {
      TreeMap args = new TreeMap();
      args.put("path", "/" + currentPath);
      args.put("dirnum", "" + dirnum);
      args.put("filenum", "" + filenum);
      expansion.applyMacro("body", args);
    }

    expansion.done();
  }

  private static class ShortLog
  {
    String committedDate;
    String log;

    ShortLog(String committedDate, String log)
    {
      this.committedDate = committedDate;
      this.log = log;
    }
  }
}
