import java.util.*;
import java.io.*;
import java.text.*;

import jlib.util.*;



/**
 * JGrep searchs text in text files. The text to search for is 
 * specified by a regular expression pattern. JGrep looks for the 
 * pattern looking at one line at a time. If a match is found, JGrep 
 * outputs the file name, the line number and the line itself. Execute 
 * the program without any option to get a usage screen explaining how 
 * to use it.<p>
 *
 * <i>Notice that JGrep requires the JLib library.</i>
 */
public class JGrep
  {
    private static ResourceBundle resources;    // localized resources



    /**
     * Private constructor to prevent class instantiation.
     */
    private JGrep()
      {
      }



    /**
     * JGrep entry point. Checks and parses the command line and 
     * drives the  program operation.
     *
     * @param   args        program arguments
     */
    public static void main( String[] args )
      {
        boolean   caseSensitive = false;
        boolean   recurse       = false;
        int       next          = 0;
        RegExp    pattern       = null;
        Vector    files;
        
        resources = ResourceBundle.getBundle(JGrep.class.getName());
        
        System.out.println( resources.getString("title") );
        
        if ( args.length == 0 )
          printUsage();
        
        if ( args.length < 2 )
          {
            System.out.println();
            System.out.println( resources.getString
                                ("insufficientArguments") );
            printUsage();
          }
          
        if ( args[0].charAt(0) == '-' )
          {
            next++;
            if ( args.length < 3 )
              {
                System.out.println();
                System.out.println( resources.getString
                                    ("insufficientArguments") );
                printUsage();
              }
            String options = args[0].toUpperCase();
            for ( int i=1; i<options.length(); i++ )
              switch ( options.charAt(i) )
                {
                  case 'C':
                    caseSensitive = true;
                    break;

                  case 'R':
                    recurse = true;
                    break;

                  default:
                    System.out.println();
                    System.out.println
                      ( 
                        MessageFormat.format
                          (
                            resources.getString("unrecognizedOption"),
                            new Object[] {args[0].substring(i,i+1)}
                          )
                      );
                    printUsage();
                    break;
                }
          }

        try
          {
            pattern = new RegExp(args[next],caseSensitive);
          }
        catch ( SyntaxError e )
          {
            System.out.println();
            System.out.println
              ( 
                MessageFormat.format
                  (
                    resources.getString("patternErrorMessage"),
                    new Object[] {e.toString()}
                  )
              );
            printUsage();
          }

        files = new Vector();
        for ( next++; next<args.length; next++ )
          expandFiles( args[next], recurse, files );

        for ( int i=0; i<files.size(); i++ )
          find( (String)files.elementAt(i), pattern );
      }



    /**
     * Expand a given file specification in all files that match,
     * recursing directories if necessary. Adds the files to a list.
     * The specification can contain one or more wildcard character 
     * ('*'), which will match any character(s).
     *
     * @param   fileSpec    file specification to expand
     * @param   recurse     true to recurse directories
     * @param   fileList    list to where add the files
     */
    private static void expandFiles( String fileSpec, boolean recurse, 
                                     Vector filesList )
      {
        File      file    = new File(fileSpec);
        String    name;
        String    parent;
        String[]  files;
        String[]  pattern;

        if ( file.isDirectory() )
          file = new File(file,"*");

        name = file.getName();
        if ( name.indexOf("*") == -1 )
          {
            filesList.addElement( fileSpec );
            return;
          }

        parent = file.getParent();
        if ( parent == null )
          files = (new File(System.getProperty("user.dir"))).list();
        else
          files = (new File(parent)).list();
        if ( files == null )
          return;

        pattern = parseWildcards(name);
        for ( int i=0; i<files.length; i++ )
          filterFile( new File(parent,files[i]), pattern, recurse, 
                      filesList );
      }



    /**
     * Tests if a given file matches a given pattern. Adds the file
     * to a list if it matches. If the file is a directory and the 
     * recurse flag is true, recursively filters all files in the 
     * directory.
     *
     * @param   file        file to filter
     * @param   pattern     pattern as returned by method 
     *                      parseWildcards
     * @param   recurse     true to recurse directories
     * @param   filesList   list to add the file if it matches the 
     *                      pattern
     */
    private static void filterFile( File file, String[] pattern, 
                                    boolean recurse, 
                                    Vector filesList )
      {
        String name   = file.getName().toUpperCase();
        int    begin  = pattern[0].length();
        int    index;
        
        if ( file.isDirectory() )
          {
            if ( recurse )
              {
                String   parent = file.getPath();
                String[] files  = file.list();
                for ( int i=0; i<files.length; i++ )
                  filterFile( new File(parent,files[i]), pattern, 
                              recurse, filesList );
              }
            return;
          }
          
        if ( ( !pattern[0].equals("") ) && 
             ( !name.startsWith(pattern[0]) ) )
          return;

        for ( int i=1; i<pattern.length-1; i++ )
          {
            index = name.indexOf(pattern[i],begin);
            if ( index == -1 )
              return;
            begin = index + pattern[i].length();
          }

        String last = pattern[pattern.length-1];
        if ( ( !last.equals("") ) && 
             ( ( begin > name.length()-last.length() ) ||
               ( !name.endsWith(last) ) ) )
          return;

        filesList.addElement( file.getPath() );
      }



    /**
     * Searchs the given pattern in a file, listing all lines that 
     * match.
     *
     * @param   fileName        file where to search
     * @param   pattern         pattern to search
     */
    private static void find( String fileName, RegExp pattern )
      {
        LineNumberReader  file;
        boolean           firstMatch = true;
        String            line, searchLine;

        try
          {
            file = new LineNumberReader(new BufferedReader
                                        (new FileReader(fileName)));
            while ( true )
              {
                searchLine = line = file.readLine();
                if ( line == null )
                  break;
                if ( pattern.search(line) )
                  {
                    if ( firstMatch )
                      System.out.println( "\n"+fileName );
                    firstMatch = false;
                    System.out.println( file.getLineNumber()+": "+
                                        line );
                  }
              }
            file.close();
          }
        catch ( Exception e )
          {
            System.out.println();
            System.out.println
              ( 
                MessageFormat.format
                  (
                    resources.getString("fileErrorMessage"),
                    new Object[] {fileName, e.toString()}
                  )
              );
          }
      }



    /**
     * Parses a pattern containing wildcards, breaking it down in its
     * components.
     *
     * @param   pattern   wildcard pattern
     *
     * @return  a String array with the following meaning:
     *            <ul>
     *            <li>the first element is the pattern prefix 
     *                (if empty, don't care)</li>
     *            <li>the last element is the pattern sufix 
     *                (if empty, don't care)</li>
     *            <li>the remaining elements are the pattern fixed 
     *                parts</li>
     *            </ul>
     */
    private static String[] parseWildcards( String pattern )
      {
        int       wildcards = 0;
        String[]  parsed;
        int       begin, end;

        pattern = pattern.toUpperCase();
        
        for ( int i=0; i<pattern.length(); i++ )
          if ( pattern.charAt(i) == '*' )
            wildcards++;

        parsed = new String[wildcards+1];

        begin = 0;
        for ( int i=0; i<wildcards; i++ )
          {
            end = pattern.indexOf('*',begin);
            parsed[i] = pattern.substring(begin,end);
            begin = end+1;
          }

        if ( begin < pattern.length() )
          parsed[wildcards] = pattern.substring(begin);
        else
          parsed[wildcards] = "";

        return parsed;
      }



    /**
     * Prints a usage message for JFind.
     */
    private static void printUsage()
      {
        System.out.println();
        try
          {
            int line = 0;
            while ( true )
              {
                System.out.println( 
                   resources.getString("usage"+line).substring(1) );
                line++;
              }
          }
        catch ( MissingResourceException e )
          {
          }
        System.exit( 0 );
      }
  }