/*
 * (C) IBM Corp. 1997-1998
 *
 * The program is provided "as is" without any warranty express or
 * implied, including the warranty of non-infringement and the implied
 * warranties of merchantibility and fitness for a particular purpose.
 * IBM will not be liable for any damages suffered by you as a result
 * of using the Program. In no event will IBM be liable for any
 * special, indirect or consequential damages or lost profits even if
 * IBM has been advised of the possibility of their occurrence. IBM
 * will not be liable for any third party claims against you.
 */

package com.ibm.xml.siteoutliner;

import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*;
import com.ibm.xml.parser.*;


public class SiteOutliner extends java.awt.Frame implements java.awt.event.WindowListener { 

  /* static variables */
  static boolean opt_d = false;  // debug mode
  static DateFormat dateFormat;  // date format used in this program
  static boolean opt_UI = false;
  static boolean commandLineMode = false;
  static Date sinceDate = new Date((new Date()).getTime() - 1000*60*60*24); // Defaults to recent 24 hours
  static String[] exList = {};
  static String[] fTypes  = {".html",".htm",".HTML",".HTM"};
  static String dir = null;
  static String url = null;
  static String gencoding = null;
  static String goutencoding = null;
  static int gdepth = 10;

  public static void main(String[] args) {
    checkopt(args);
    if (dir==null) {
      // start GUI version
      SiteOutliner aSiteOutliner = new SiteOutliner();
      aSiteOutliner.show();
    } else {
      // run command line version
      commandLineMode = true;
      if (url==null) usage(args);
      System.err.println("Scan starting from "+dir+", urlPrefix="+url);
      System.err.println("  Only files modified since "+java.text.DateFormat.getDateInstance().format(sinceDate));
      System.err.print("  Looking for files that end with ");
      int i;
      for (i=0; i<fTypes.length; i++) System.err.print(fTypes[i]+";");
      System.err.println();
      System.err.print("  Except for ");
      for (i=0; i<exList.length; i++) System.err.print(exList[i]+";");
      System.err.println();

      SiteOutliner aSiteOutliner = new SiteOutliner(dir,url);
      aSiteOutliner.modifiedSince = sinceDate;
      aSiteOutliner.fileTypes = fTypes;
      aSiteOutliner.excludingPathNames = exList;
      aSiteOutliner.encoding = gencoding;
      aSiteOutliner.outencoding = goutencoding;
      aSiteOutliner.depth = gdepth;
      aSiteOutliner.doIt();
      aSiteOutliner.printIt();
    }
  }

  private static void checkopt(String[] args) {
    int n = 0;
    for (int i=0; i<args.length; i++) {
      if (args[i].charAt(0)=='-') {
	if (args[i].equals("-debug")) {
	  opt_d = true;
	} else if (args[i].equals("-since")) {
	  i++; if (i>=args.length) usage(args);
	  try {
	    sinceDate = java.text.DateFormat.getDateInstance().parse(args[i]);
	  } catch (java.text.ParseException e) {
	    System.err.println(e);
	    System.exit(-1);
	  }
	} else if (args[i].equals("-filetype")) {
	  i++; if (i>=args.length) usage(args);
	  StringTokenizer tn = new StringTokenizer(args[i],";",false);
	  fTypes = new String[tn.countTokens()];
	  for (int j=0; tn.hasMoreTokens(); j++) {
	    String s = (String)tn.nextToken();
	    fTypes[j] = s;
	  }
	} else if (args[i].equals("-exclude")) {
	  i++; if (i>=args.length) usage(args);
	  StringTokenizer tn = new StringTokenizer(args[i],";",false);
	  exList = new String[tn.countTokens()];
	  for (int j=0; tn.hasMoreTokens(); j++) {
	    String s = (String)tn.nextToken();
	    exList[j] = s;
	  }
	} else if (args[i].equals("-encoding")) {
	  i++; if (i>=args.length) usage(args);
	  gencoding = args[i];
	} else if (args[i].equals("-outencoding")) {
	  i++; if (i>=args.length) usage(args);
	  goutencoding = args[i];
	} else if (args[i].equals("-depth")) {
	  i++; if (i>=args.length) usage(args);
	  gdepth = java.lang.Integer.parseInt(args[i]);
	} else if (args[i].equals("-help")) {
	  usage(args);
	} else {
	  usage(args);
	}
      } else if (n==0) {
	dir = args[i];
	n++;
      } else if (n==1) {
	url = args[i];
	n++;
      }
    }
  }

  private static void usage(String[] args) {
    System.err.println("1. Command line version");
    System.err.println("  Usage: java [options..] SiteOutliner dir URLprefix");
    System.err.println("    Example: java SiteOutliner k:/xml http://anego.trl.ibm.com");
    System.err.println("    Option:");
    System.err.println("      -debug: debug mode on");
    System.err.println("      -since <date>: Files only modifined since <date>");
    System.err.println("      -filetype <filetypes>: Files types separated by ';', default='.htm;.html;.HTM;.HTML'");
    System.err.println("      -exclude <filenames>: File/dir names to be excluded, separated by ';', default=''");
    System.err.println("      -encoding <javaencoding>: Specify encoding for input  eg, 'JISAutoDetect'");
    System.err.println("      -outencoding <mimecharset>: Specify encoding for output  eg, 'ISO-2022-JP'");
    System.err.println("      -depth <depth>: Depth limit of directory search");
    System.err.println("2. GUI version");
    System.err.println("  Usage: java [options..] SiteOutliner");
    System.exit(0);
  }


  /**
    *  instance variables 
    */
  private String docRootDir;  // Document root absolute pathname (local)
  private String urlPrefix; // Document root (URL)
  private File rootDir = null;
  private Date modifiedSince = null;
  private String[] fileTypes;
  private String[] excludingPathNames;
  private SiteOutliner me;
  private String encoding = null;
  private int depth = 10;

  TXDocument doc;  // XML document

  private SiteOutlinerUI siteOutlinerUI= null;

  /* Constructor */
  public SiteOutliner() {
    // GUI version
    initializeFrame();
    me = this;
  }

  public SiteOutliner(SiteOutlinerUI siteOutlinerUI) {
    setParameters(siteOutlinerUI);
    doIt();
  }

  public SiteOutliner(String dir,String urlPrefix) {
    docRootDir = dir;
    this.urlPrefix = urlPrefix;
    // check if urlPrefix is valid
    URL url = null;
    try {
      url = new URL(urlPrefix);
    } catch (MalformedURLException e) {
      System.err.println("URL is invalid, try again. "+e);
      System.exit(-1);
    }
    try {
      rootDir = new File(docRootDir);
      if (!rootDir.isDirectory()) {
	System.err.println("Error: "+docRootDir+" is not a directory");
	System.exit(-1);
      }
      docRootDir = rootDir.getAbsolutePath();
    } catch (Throwable e) {
      System.err.println("Error: "+e);
      System.exit(-1);
    }
  }

  public TXDocument getDocument() {
    return doc;
  }

  private void setParameters(SiteOutlinerUI siteOutlinerUI) {
    rootDir = siteOutlinerUI.getRootDir();
    urlPrefix = siteOutlinerUI.getDocumentBaseURL();
    modifiedSince = siteOutlinerUI.getSince();
    fileTypes = siteOutlinerUI.getFileTypes();
    excludingPathNames = siteOutlinerUI.getExcludingPathNames();
    depth = siteOutlinerUI.getSearchDepth();
  }

  private void doIt() {
    if (!commandLineMode) {
      if (processDialog==null) {
	processDialog = new java.awt.Dialog(this);
	processDialog.setTitle("Processing ...");
	processDialog.setBounds(50,50,400,100);
	processDialogLabel = new java.awt.Label("");
	processDialog.add(processDialogLabel);
	// processDialogCancelButton = new java.awt.Button("Cancel");
	// processDialog.add(processDialogCancelButton);
	processDialog.show();
      }
      processDialog.setVisible(true);
    }

    // 1. Create document
    doc = new TXDocument();
    doc.setPrintInternalDTD(true);
    doc.setVersion("1.0");
    TXElement rootElement = new TXElement("CHANNEL");
    doc.addElement(rootElement);


    // 2. Scan directory
    processDirectory(rootElement,rootDir,urlPrefix,depth);

    if (!commandLineMode) processDialog.setVisible(false);
  }

  /* processDirectory() returns number of HTML documents under the directory */
  private int processDirectory(TXElement el, File dir, String prefix, int d) {
    if (d--<=0) return 0;
    if (opt_d) System.err.println("Processing "+dir.getAbsolutePath()+"["+d+"]");
    int nDocuments = 0;
    String titleString = prefix;
    File f = null;
    try {
      String[] fileNameList = dir.list();
      if (fileNameList!=null) {
	for (int i=0; i<fileNameList.length; i++) {
	  if (exclude(fileNameList[i])) continue;
	  f = new File(dir,fileNameList[i]);
	  String url = prefix+"/"+fileNameList[i];
	  if (processDialogLabel!=null) processDialogLabel.setText("  Processing ...  "+url);
	  if (f.isDirectory()) {
	    // create new  channel
	    TXElement newChannel = new TXElement("CHANNEL");
	    int n = processDirectory(newChannel,f,url,d);
	    if (n>0) {
	      // add only nonempty directory as new channel
	      el.addElement(newChannel);
	      nDocuments += n;
	    }
	  } else if (filterFile(f)) {
	    // create new item
	    TXElement newItem = new TXElement("ITEM");
	    newItem.setAttribute("HREF",url);
	    processItem(newItem,f);
	    el.addElement(newItem);
	    nDocuments++;
	    if (fileNameList[i].startsWith("index.htm")) {
              titleString = ((TXElement)newItem.getElementNamed("TITLE")).getText();
	    }
	  } else if (fileNameList[i].startsWith("index.htm")) {
	    titleString = getTitle(f);
	  }
	}
      }
      TXElement title = new TXElement("TITLE");
      title.addElement(new TXText(titleString+" ("+nDocuments+")"));
      el.addElement(title);
      el.setAttribute("HREF",prefix);
    } catch (Throwable e) {
      if (f!=null) {
	System.err.println("Error in processDirectory():"+e+" File="+f.getAbsolutePath());
      } else {
	System.err.println("Error in processDirectory():"+e+" Dir="+dir);
      }
    }
    return nDocuments;
  }

  private void processItem(TXElement el,File f) {
    TXElement title = new TXElement("TITLE");
    el.addElement(title);
    String titleString = getTitle(f);
    if (titleString == null) titleString = "No title";
    title.addElement(new TXText(titleString));
    TXElement abs = new TXElement("ABSTRACT");
    el.addElement(abs);
  }

  private String getTitle(File f) {
    String title = "";
    String line;
    BufferedReader aBufferedReader;
    int i;
    if (opt_d) {
      System.err.println("Processing file "+f.getAbsolutePath()+" with encoding "+encoding);
    }
    try {
      if (encoding!=null) {
	aBufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(f),encoding));
      } else {
	aBufferedReader = new BufferedReader(new FileReader(f));
      }

      // 1. look for beginning of title
      while ((line = aBufferedReader.readLine()) != null) {
	i = line.indexOf("<title>");
	if (i<0) i = line.indexOf("<TITLE>");
	if (i<0) i = line.indexOf("<Title>");
	if (i>=0) {
	  title += line.substring(i+7);
	  break;
	}
      }

      // 2. look for </title>
      i = title.indexOf("</title>");
      if (i<0) i = title.indexOf("</TITLE>");
      if (i<0) i = title.indexOf("</Title>");
      if (i>=0) {
	title = title.substring(0,i);
	if (title.length()==0) return null;
	return title;
      }
      while ((line = aBufferedReader.readLine()) != null) {
	i = line.indexOf("</title>");
	if (i<0) i = line.indexOf("</TITLE>");
	if (i<0) i = line.indexOf("</Title>");
	if (i>=0) {
	  title += line.substring(0,i);
	  break;
	}
	title += line;
      }
    } catch (Throwable e) {
      System.err.println("Error in getTitle("+f.getAbsolutePath()+"): "+e);
      return "Title could not be processed";
    }
    if (title.length()==0) return null;
    return title;
  }

  private final String getHREF(File f) {
    return urlPrefix +
      f.getAbsolutePath().substring(docRootDir.length());
  }

  private boolean filterFile(File f) {
    String s = f.getName();
    // if (opt_d) System.err.println(s+":"+new Date(f.lastModified()));
    if (f.lastModified()<modifiedSince.getTime()) return false;
    for (int i=0; i<fileTypes.length; i++) {
      if (s.endsWith(fileTypes[i])) return true;
    }
    return false;
  }

  private boolean exclude(String name) {
    for (int i=0; i<excludingPathNames.length; i++) {
      if (name.equals(excludingPathNames[i])) return true;
    }
    return false;
  }

  private String outencoding = null;
  private void printIt() {
    // System.out.println("printing the tree");
    OutputStreamWriter anOutputStreamWriter = null;
    if (null == outencoding || null == MIME2Java.convert(outencoding)) {
      anOutputStreamWriter = new OutputStreamWriter(System.out);
      doc.setEncoding(MIME2Java.reverse(anOutputStreamWriter.getEncoding()));
    } else {
      try {
        anOutputStreamWriter = new OutputStreamWriter(System.out, MIME2Java.convert(outencoding));
        doc.setEncoding(outencoding);
      } catch (UnsupportedEncodingException e) {
          e.printStackTrace();
      }
    }
    com.ibm.xml.parser.Format.print(doc, new PrintWriter(anOutputStreamWriter));
    // System.out.println("done");
  }

  private java.awt.MenuBar menuBar = null;
  private java.awt.Menu fileMenu = null;
  private java.awt.Menu optionMenu = null;
  private java.awt.Menu helpMenu = null;
  private java.awt.MenuItem scanMenuItem = null;
  private java.awt.MenuItem saveMenuItem = null;
  private java.awt.MenuItem exitMenuItem = null;
  private java.awt.MenuItem aboutMenuItem = null;
  private java.awt.CheckboxMenuItem debugMenuItem = null;
  private java.awt.Dialog aboutDialog = null;
  private java.awt.Button aboutDialogOkButton = null;
  private java.awt.Label aboutDialogLabel = null;
  private java.awt.TextArea textArea = null;
  private java.awt.FileDialog saveFileDialog = null;
  private java.awt.Dialog processDialog = null;
  private java.awt.Label processDialogLabel = null;
  private java.awt.Button processDialogCancelButton = null;

  private void initializeFrame() {
    setName("SiteOutliner GUI");
    setSize(600,400);
    setTitle("Site Outliner Version 1.0");

    textArea = new java.awt.TextArea();
    add(textArea);

    menuBar = new java.awt.MenuBar();
    setMenuBar(menuBar);

    // 1. File menu
    fileMenu = new java.awt.Menu("File");
    menuBar.add(fileMenu);
    scanMenuItem = new java.awt.MenuItem("Scan ...");
    fileMenu.add(scanMenuItem);
    scanMenuItem.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
	// create GUI dialog
	siteOutlinerUI = new SiteOutlinerUI(me);
	siteOutlinerUI.show();
	if (!siteOutlinerUI.canceled()) {
	  setParameters(siteOutlinerUI);
	  doIt();
	  // print results to textarea
	  StringWriter stringWriter = new StringWriter();
	  doc.print(new PrintWriter(stringWriter));
	  textArea.setText(stringWriter.toString());
	}
      }
    });
    saveMenuItem = new java.awt.MenuItem("Save ...");
    fileMenu.add(saveMenuItem);
    saveMenuItem.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
	save();
      }
    });
    exitMenuItem = new java.awt.MenuItem("Exit");
    fileMenu.add(exitMenuItem);
    exitMenuItem.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
	dispose();
	System.exit(0);
      }
    });

    // 2. Option menu
    optionMenu = new java.awt.Menu("Option");
    menuBar.add(optionMenu);
    debugMenuItem = new java.awt.CheckboxMenuItem();
    debugMenuItem.setLabel("Debug");
    optionMenu.add(debugMenuItem);
    debugMenuItem.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
	opt_d = ((java.awt.CheckboxMenuItem)e.getSource()).getState();
      }
    });

    // 3. Help menu
    helpMenu = new java.awt.Menu("Help");
    menuBar.add(helpMenu);
    aboutMenuItem = new java.awt.MenuItem("About ...");
    helpMenu.add(aboutMenuItem);
    aboutMenuItem.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
	aboutDialog.show();
      }
    });

    // about dialog
    aboutDialog = new java.awt.Dialog(this,"About SiteOutliner");
    aboutDialog.setLayout(null);
    aboutDialog.setBounds(5, 569, 418, 134);
    aboutDialogOkButton = new java.awt.Button("OK");
    aboutDialog.add(aboutDialogOkButton);
    aboutDialogOkButton.setBounds(230, 100, 60, 30);
    aboutDialogOkButton.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {
	aboutDialog.dispose();
      }
    });
    aboutDialogLabel = new java.awt.Label("SiteOutliner v. 0.1 -- Sample XML application");
    aboutDialogLabel.setBounds(21, 22, 362, 30);
    aboutDialogLabel.setAlignment(java.awt.Label.CENTER);
    aboutDialog.add(aboutDialogLabel);
    aboutDialog.addWindowListener(this);

    // save file dialog
    saveFileDialog = new java.awt.FileDialog(this,"Save file to ...",java.awt.FileDialog.SAVE);
  }

  private void save() {
    saveFileDialog.show();
    if (saveFileDialog.getFile()==null) return;
    try {
      PrintWriter printWriter = new PrintWriter(new FileWriter(saveFileDialog.getDirectory()+
							       saveFileDialog.getFile()));
      printWriter.print(textArea.getText());
      printWriter.flush();
      printWriter.close();
    } catch (java.io.IOException e) {
      System.err.println("Error:"+e);
    }
  }


  /**
    * Method to handle events for the WindowListener interface.
    * @param e java.awt.event.WindowEvent
    */
  /* WARNING: THIS METHOD WILL BE REGENERATED. */
  public void windowActivated(java.awt.event.WindowEvent e) {
    // user code begin {1}
    // user code end
    // user code begin {2}
    // user code end
  }

  /**
    * Method to handle events for the WindowListener interface.
    * @param e java.awt.event.WindowEvent
    */
  /* WARNING: THIS METHOD WILL BE REGENERATED. */
  public void windowClosed(java.awt.event.WindowEvent e) {
    // user code begin {1}
    // user code end
    // user code begin {2}
    // user code end
  }

  /**
    * Method to handle events for the WindowListener interface.
    * @param e java.awt.event.WindowEvent
    */
  /* WARNING: THIS METHOD WILL BE REGENERATED. */
  public void windowClosing(java.awt.event.WindowEvent e) {
    Object s = e.getSource();
    if (s instanceof java.awt.Frame) ((java.awt.Frame)s).dispose();
    if (s instanceof java.awt.Dialog) ((java.awt.Dialog)s).dispose();
    dispose();
  }

  /**
    * Method to handle events for the WindowListener interface.
    * @param e java.awt.event.WindowEvent
    */
  /* WARNING: THIS METHOD WILL BE REGENERATED. */
  public void windowDeactivated(java.awt.event.WindowEvent e) {
  }

  public void windowDeiconified(java.awt.event.WindowEvent e) {
  }

  /**
    * Method to handle events for the WindowListener interface.
    * @param e java.awt.event.WindowEvent
    */
  /* WARNING: THIS METHOD WILL BE REGENERATED. */
  public void windowIconified(java.awt.event.WindowEvent e) {
  }

  /**
    * Method to handle events for the WindowListener interface.
    * @param e java.awt.event.WindowEvent
    */
  /* WARNING: THIS METHOD WILL BE REGENERATED. */
  public void windowOpened(java.awt.event.WindowEvent e) {
  }


}
