/*
 *  gnu/regexp/util/Grep.java
 *  Copyright (C) 1998 Wes Biggs
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published
 *  by the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package gnu.regexp.util;

import gnu.getopt.Getopt;
import gnu.getopt.LongOpt;
import gnu.regexp.RE;
import gnu.regexp.REException;
import gnu.regexp.REMatch;
import gnu.regexp.RESyntax;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintStream;

/**
 * Grep is a pure-Java clone of the GNU grep utility.  As such, it is much
 * slower and not as full-featured, but it has the advantage of being
 * available on any system with a Java virtual machine.
 *
 * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A>
 * @version 1.01
 * @use gnu.getopt
 */
public class Grep {
  private static final int BYTE_OFFSET = 0;
  private static final int COUNT = 1;
  private static final int LINE_NUMBER = 2;
  private static final int QUIET = 3;
  private static final int SILENT = 4;
  private static final int NO_FILENAME = 5;
  private static final int REVERT_MATCH = 6;
  private static final int FILES_WITH_MATCHES = 7;
  private static final int LINE_REGEXP = 8;
  private static final int FILES_WITHOUT_MATCH = 9;

  private static final String PROGNAME = "gnu.regexp.util.Grep";
  private static final String PROGVERSION = "1.01";

  private Grep() { }
  /**
   * Invokes the grep() function below with the command line arguments
   * and using the RESyntax.RE_SYNTAX_GREP syntax, which attempts to
   * emulate the traditional UNIX grep syntax.
   */
  public static void main(String[] argv) {
    System.exit(grep(argv, RESyntax.RE_SYNTAX_GREP, System.out));
  }

  /**
   * Runs Grep with the specified arguments.  For a list of 
   * supported options, specify "--help".
   *
   * This is the meat of the grep routine, but unlike main(), you can
   * specify your own syntax and PrintStream to use for output.
   */
  public static int grep(String[] argv, RESyntax syntax, PrintStream out) {
    // use gnu.getopt to read arguments
    int cflags = 0;
    
    boolean[] options = new boolean [10];
    
    LongOpt[] longOptions = { 
        new LongOpt("byte-offset",         LongOpt.NO_ARGUMENT, null, 'b'),
	new LongOpt("count",               LongOpt.NO_ARGUMENT, null, 'c'),
	new LongOpt("no-filename",         LongOpt.NO_ARGUMENT, null, 'h'),
	new LongOpt("ignore-case",         LongOpt.NO_ARGUMENT, null, 'i'),
	new LongOpt("files-with-matches",  LongOpt.NO_ARGUMENT, null, 'l'),
	new LongOpt("help",                LongOpt.NO_ARGUMENT, null, '!'),
	new LongOpt("line-number",         LongOpt.NO_ARGUMENT, null, 'n'),
	new LongOpt("quiet",               LongOpt.NO_ARGUMENT, null, 'q'),
	new LongOpt("silent",              LongOpt.NO_ARGUMENT, null, 'q'),
	new LongOpt("no-messages",         LongOpt.NO_ARGUMENT, null, 's'),
	new LongOpt("revert-match",        LongOpt.NO_ARGUMENT, null, 'v'),
	new LongOpt("line-regexp",         LongOpt.NO_ARGUMENT, null, 'x'),
	new LongOpt("extended-regexp",     LongOpt.NO_ARGUMENT, null, 'E'),
	new LongOpt("fixed-strings",       LongOpt.NO_ARGUMENT, null, 'F'), // TODO
	new LongOpt("basic-regexp",        LongOpt.NO_ARGUMENT, null, 'G'),
	new LongOpt("files-without-match", LongOpt.NO_ARGUMENT, null, 'L'),
	new LongOpt("version",             LongOpt.NO_ARGUMENT, null, 'V')
	  };

    Getopt g = new Getopt(PROGNAME, argv, "bchilnqsvxyEFGLV", longOptions);
    int c;
    String arg;
    while ((c = g.getopt()) != -1) {
      switch (c) {
      case 'b':
	options[BYTE_OFFSET] = true;
	break;
      case 'c':
	options[COUNT] = true;
	break;
      case 'h':
	options[NO_FILENAME] = true;
	break;
      case 'i':
      case 'y':
	cflags |= RE.REG_ICASE;
	break;
      case 'l':
	options[FILES_WITH_MATCHES] = true;
	break;
      case 'n':
	options[LINE_NUMBER] = true;
	break;
      case 'q':
	options[QUIET] = true;
	break;
      case 's':
	options[SILENT] = true;
	break;
      case 'v':
	options[REVERT_MATCH] = true;
	break;
      case 'x':
	options[LINE_REGEXP] = true;
	break;
      case 'E':  // TODO: check compatibility with grep
	syntax = RESyntax.RE_SYNTAX_EGREP;
	break;
      case 'F':  // TODO: fixed strings
	break;
      case 'G':
	syntax = RESyntax.RE_SYNTAX_GREP;
	break;
      case 'L':
	options[FILES_WITHOUT_MATCH] = true;
	break;
      case 'V':
	System.err.println(PROGNAME+' '+PROGVERSION);
	return 0;
      case '!': // help
	BufferedReader br = new BufferedReader(new InputStreamReader((Grep.class).getResourceAsStream("GrepUsage.txt")));
	String line;
	try {
	  while ((line = br.readLine()) != null)
	    out.println(line);
	} catch (IOException ie) { }
	return 0;
      }
    }	      
    
    InputStream is = null;
    RE pattern = null;
    int optind = g.getOptind();
    if (optind >= argv.length) {
      System.err.println("Usage: java " + PROGNAME + " [OPTION]... PATTERN [FILE]...");
      System.err.println("Try `java " + PROGNAME + " --help' for more information.");
      return 2;
    }
    try {
      pattern = new RE(argv[g.getOptind()],cflags,syntax);
    } catch (REException e) {
      System.err.println("Error in expression: "+e);
      return 2;
    }
    int retval = 1;
    if (argv.length >= g.getOptind()+2) {
      for (int i = g.getOptind() + 1; i < argv.length; i++) {
	if (argv[i].equals("-")) {
	  if (processStream(pattern,System.in,options,(argv.length == g.getOptind()+2) || options[NO_FILENAME] ? null : "(standard input)",out)) {
	    retval = 0;
	  }
	} else {
	  try {
	    is = new FileInputStream(argv[i]);
	    if (processStream(pattern,is,options,(argv.length == g.getOptind()+2) || options[NO_FILENAME] ? null : argv[i],out))
	      retval = 0;
	  } catch (FileNotFoundException e) {
	    if (!options[SILENT])
	      System.err.println(PROGNAME+": "+e);
	  }
	}
      }
    } else {
      if (processStream(pattern,System.in,options,null,out))
	retval = 1;
    }
    return retval;
  }

  private static boolean processStream(RE pattern, InputStream is, boolean[] options, String filename, PrintStream out) {
    int newlineLen = System.getProperty("line.separator").length();
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    int count = 0;
    long atByte = 0;
    int atLine = 1;
    String line;
    REMatch match;
    
    try {
      while ((line = br.readLine()) != null) {
	match = pattern.getMatch(line);
	if (((options[LINE_REGEXP] && pattern.isMatch(line))
	     || (!options[LINE_REGEXP] && (match != null))) 
	    ^ options[REVERT_MATCH]) {
	  count++;
	  if (!options[COUNT]) {
	    if (options[QUIET]) {
	      return true;
	    }
	    if (options[FILES_WITH_MATCHES]) {
	      if (filename != null)
		out.println(filename);
	      return true;
	    }
	    if (options[FILES_WITHOUT_MATCH]) {
	      return false;
	    }
	    if (filename != null) {
	      out.print(filename);
	      out.print(':');
	    }
	    if (options[LINE_NUMBER]) {
	      out.print(atLine);
	      out.print(':');
	    }
	    if (options[BYTE_OFFSET]) {
	      out.print(atByte + match.getStartIndex() );
	      out.print(':');
	    }
	    out.println(line);
	  }
	} // a match
	atByte += line.length() + newlineLen; // could be troublesome...
	atLine++;
      } // a valid line
      br.close();

      if (options[COUNT]) {
	if (filename != null)
	  out.println(filename+':');
	out.println(count);
      }
      if (options[FILES_WITHOUT_MATCH] && count==0) {
	if (filename != null)
	  out.println(filename);
      }
    } catch (IOException e) {
      System.err.println(PROGNAME+": "+e);
    }
    return ((count > 0) ^ options[REVERT_MATCH]);
  }
}
