/*
Copyright (C) 2004 R. Nagel

All programs in this directory and
subdirectories are published under the GNU General Public License as
described below.

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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA

Further information about the GNU GPL is available at:
http://www.gnu.org/copyleft/gpl.ja.html

*/

/**
 * <p>Title: Latex Aux to Bibtex</p>
 *
 * <p>Description: generates a sub-database which contains only bibtex entries
 * from input aux file</p>
 *
 * <p>Copyright: Copyright (c) 2004</p>
 *
 * <p>Company: </p>
 *
 * @version 1.0
 * @author r.nagel
 *
 * @todo Redesign of dialog structure for an assitent like feeling....
 *   Now - the unknown bibtex entries cannot inserted into the reference
 *   database without closing the dialog.
 */

// created by : r.nagel 23.08.2004
//
// modified : - 11.04.2005
//              handling \\@input{file.aux} tag in aux files (nested aux files)


package net.sf.jabref.wizard.auximport ;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sf.jabref.*;

public class AuxSubGenerator
{

  private HashSet<String> mySet ; // all unique bibtex keys in aux file

  private Vector<String> notFoundList ; // all not solved bibtex keys

  private BibtexDatabase db ; // reference database
  private BibtexDatabase auxDB ; // contains only the bibtex keys who found in aux file

  private int nestedAuxCounter ;  // counts the nested aux files
  private int crossreferencedEntriesCount = 0; // counts entries pulled in due to crossref

  public AuxSubGenerator(BibtexDatabase refDBase)
  {
    mySet = new HashSet<String>(20) ;
    notFoundList = new Vector<String>() ;
    db = refDBase ;
  }

  public final void setReferenceDatabase(BibtexDatabase newRefDB)
  {
    db = newRefDB ;
  }

  /**
   * parseAuxFile
   * read the Aux file and fill up some intern data structures.
   * Nested aux files (latex \\include) supported!
   *
   * @param filename String : Path to LatexAuxFile
   * @return boolean, true = no error occurs
   */

  // found at comp.text.tex
  //  > Can anyone tell be the information held within a .aux file?  Is there a
  //  > specific format to this file?
  //
  // I don't think there is a particular format. Every package, class
  // or document can write to the aux file. The aux file consists of LaTeX macros
  // and is read at the \begin{document} and again at the \end{document}.
  //
  // It usually contains information about existing labels
  //  \\newlabel{sec:Intro}{{1}{1}}
  // and citations
  //  \citation{hiri:conv:1993}
  // and macros to write information to other files (like toc, lof or lot files)
  //  \@writefile{toc}{\contentsline {section}{\numberline
  // {1}Intro}{1}}
  // but as I said, there can be a lot more

  // aux file :
  //
  // \\citation{x}  x = used reference of bibtex library entry
  //
  // \\@input{x}  x = nested aux file
  //
  // the \\bibdata{x} directive contains information about the
  // bibtex library file -> x = name of bib file
  //
  // \\bibcite{x}{y}
  //   x is a label for an item and y is the index in bibliography

  public final boolean parseAuxFile(String filename)
  {
    // regular expressions
    Pattern pattern ;
    Matcher matcher ;

    // while condition
    boolean weiter = false ;

    // return value -> default: no error
    boolean back = true ;

    // fileopen status
    boolean loopFileOpen = false ;

    // the important tag
    pattern = Pattern.compile( "\\\\citation\\{.+\\}" ) ;

    // input-file-buffer
    BufferedReader br = null ;

    // filelist, used for nested aux files
    Vector<String> fileList = new Vector<String>(5) ;
    fileList.add( filename );

    // get the file path
    File dummy = new File( filename ) ;
    String path = dummy.getParent() ;
    if (path != null)
      path = path + File.separator ;
    else
      path = "" ;

    nestedAuxCounter = -1 ;  // count only the nested reads

    // index of current file in list
    int fileIndex = 0 ;

    while (fileIndex < fileList.size())
    {
      String fName = fileList.elementAt( fileIndex ) ;
      try
      {
//        System.out.println("read #"+fName +"#") ;
        br = new BufferedReader( new FileReader( fName ) ) ;
        weiter = true ;
        loopFileOpen = true ;
      }
      catch ( FileNotFoundException fnfe )
      {
        System.out.println( "Cannot locate input file! " + fnfe.getMessage() ) ;
        // System.exit( 0 ) ;
        back = false ;
        weiter = false ;
        loopFileOpen = false ;
      }

      while ( weiter )
      {
        String line ;
        try
        {
            if (br == null)
                throw new IOException();
            line = br.readLine() ;
        }
        catch ( IOException ioe )
        {
          line = null ;
          weiter = false ;
        }

        if ( line != null )
        {
          matcher = pattern.matcher( line ) ;

          while ( matcher.find() )
          {
            // extract the bibtex-key(s) XXX from \citation{XXX} string
            int len = matcher.end() - matcher.start() ;
            if ( len > 11 )
            {
              String str = matcher.group().substring( matcher.start() + 10,
                  matcher.end() - 1 ) ;
              // could be an comma separated list of keys
              String keys[] = str.split( "," ) ;
              if ( keys != null )
              {
                int keyCount = keys.length ;
                for ( int t = 0 ; t < keyCount ; t++ )
                {
                  String dummyStr = keys[t] ;
                  if ( dummyStr != null )
                  {
                    // delete all unnecessary blanks and save key into an set
                    mySet.add( dummyStr.trim() ) ;
//                System.out.println("found " +str +" in AUX") ;
                  }
                }
              }
            }
          }
          // try to find a nested aux file
          int index = line.indexOf( "\\@input{" ) ;
          if ( index >= 0 )
          {
            int start = index + 8 ;
            int end = line.indexOf( "}", start ) ;
            if ( end > start )
            {
              String str = path + line.substring( index + 8, end ) ;

              // if filename already in filelist
              if (!fileList.contains( str ) )
              {
                 fileList.add(str);   // insert file into filelist
              }
            }
          }
        } // line != null
        else weiter = false ;
      } // end of while

      if ( loopFileOpen ) // only close, if open sucessful
      {
        try
        {
            if (br != null)
                br.close() ;
          nestedAuxCounter++ ;
        }
        catch ( IOException ioe )
        {}
      }

      fileIndex++ ; // load next file
    }

    return back ;
  }

  /**
   * resolveTags
   * Try to find an equivalent bibtex entry into reference database for all keys
   * (found in aux file). This methode will fill up some intern data structures.....
   */
  public final void resolveTags()
  {
    auxDB = new BibtexDatabase() ;
    notFoundList.clear();

    Iterator<String> it = mySet.iterator() ;

    // forall bibtex keys (found in aux-file) try to find an equivalent
    // entry into reference database
    while (it.hasNext())
    {
      String str = it.next() ;
      BibtexEntry entry = db.getEntryByKey(str);

      if (entry == null)
      {
        notFoundList.add(str) ;
      } else
      {
          insertEntry(auxDB, entry);
          // Check if the entry we just found references another entry which
          // we don't already have in our list of entries to include. If so,
          // pull in that entry as well:
          String crossref = entry.getField("crossref");
          if ((crossref != null) && (!mySet.contains(crossref))) {
              BibtexEntry refEntry = db.getEntryByKey(crossref);
              /**
               * [ 1717849 ] Patch for aux import by Kai Eckert
               */
              if (refEntry == null) {
                  notFoundList.add(crossref);
              } else {
                  insertEntry(auxDB, refEntry);
                  crossreferencedEntriesCount++;
              }
          }

      }
    }


      // If we have inserted any entries, make sure to copy the source database's preamble and
      // strings:
      if (auxDB.getEntryCount() > 0) {
          auxDB.setPreamble(db.getPreamble());
          Set<String> keys = db.getStringKeySet();
          for (String key : keys) {
              BibtexString string = db.getString(key);
              auxDB.addString(string);
          }
      }
  }

    /**
     * Insert a clone of the given entry. The clone is given a new unique ID.
     * @param auxDB The database to insert into.
     * @param entry The entry to insert a copy of.
     */
    private void insertEntry(BibtexDatabase auxDB, BibtexEntry entry) {
        try {
            BibtexEntry clonedEntry = (BibtexEntry)entry.clone();
            clonedEntry.setId(Util.createNeutralId());
            auxDB.insertEntry(clonedEntry);
        } catch (KeyCollisionException e) {
            e.printStackTrace();
        }
    }

    /**
     * generate
     * Shortcut methode for easy generation.
     *
     * @param auxFileName String
     * @param bibDB BibtexDatabase - reference database
     * @return Vector - contains all not resolved bibtex entries
     */
    public final Vector<String> generate(String auxFileName, BibtexDatabase bibDB)
    {
      setReferenceDatabase(bibDB);
      parseAuxFile(auxFileName) ;
      resolveTags();

      return notFoundList ;
    }

  public BibtexDatabase getGeneratedDatabase()
  {
    if (auxDB == null)
      auxDB = new BibtexDatabase() ;

    return auxDB ;
  }

  public final int getFoundKeysInAux()
  {
    return mySet.size() ;
  }

  public final int getResolvedKeysCount()
  {
    return auxDB.getEntryCount() - crossreferencedEntriesCount;
  }

  public final int getNotResolvedKeysCount()
  {
    return notFoundList.size() ;
  }

    /**
     * Query the number of extra entries pulled in due to crossrefs from other
     * entries.
     * @return The number of additional entries pulled in due to crossref
     */
    public final int getCrossreferencedEntriesCount()
    {
        return crossreferencedEntriesCount;
    }

  /** reset all used datastructures */
  public final void clear()
  {
    mySet.clear() ;
    notFoundList.clear();
    crossreferencedEntriesCount = 0;
    // db = null ;  ???
  }

  /** returns a vector off all not resolved bibtex entries found in auxfile */
  public Vector<String> getNotFoundList()
  {
    return notFoundList ;
  }

  /** returns the number of nested aux files, read by the last call of
   *  generate method */
  public int getNestedAuxCounter()
  {
    return this.nestedAuxCounter ;
  }
}
