/*
   Copyright (C) 2013-2020 Nicola L.C. Talbot
   www.dickimaw-books.com

   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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
package com.dickimawbooks.makeglossariesgui;

import java.util.*;
import java.util.regex.*;
import java.io.*;
import java.nio.charset.Charset;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.Element;

public class Glossaries
{
  public Glossaries(MakeGlossariesInvoker invoker, String istName, String order)
  {
     this.invoker = invoker;
     this.istName = istName;
     this.order = order;
     glossaryList = new Vector<Glossary>();
  }

  public Glossaries(MakeGlossariesInvoker invoker)
  {
     this.invoker = invoker;
     glossaryList = new Vector<Glossary>();
  }

  public void clear()
  {
     glossaryList.clear();
  }

  public void add(Glossary glossary)
  {
     glossaryList.add(glossary);
  }

  public Glossary getGlossary(int i)
  {
     return glossaryList.get(i);
  }

  public Glossary getGlossary(String label)
  {
     for (int i = 0, n = glossaryList.size(); i < n; i++)
     {
        Glossary g = glossaryList.get(i);

        if (g.label.equals(label))
        {
           return g;
        }
     }

     return null;
  }

  public static Glossaries loadGlossaries(MakeGlossariesInvoker invoker,
   File file)
     throws IOException
  {
     Glossaries glossaries = new Glossaries(invoker);

     Hashtable<String,String> languages = new Hashtable<String,String>();
     Hashtable<String,String> codePages = new Hashtable<String,String>();

     invoker.getMessageSystem().message(
      invoker.getLabelWithValues("message.loading", file));
     BufferedReader in = new BufferedReader(new InputStreamReader(
             new FileInputStream(file), invoker.getEncoding()));

     String line;

     boolean override = invoker.getProperties().isOverride();

     while ((line = in.readLine()) != null)
     {
        glossaries.parseAux(file.getParentFile(),
           line, override, languages, codePages);
     }

     in.close();

     if (glossaries.noidx)
     {
        glossaries.addDiagnosticMessage(invoker.getLabel("diagnostics.noidx"));
     }
     else if (glossaries.requiresBib2Gls && glossaries.istName == null)
     {
        String base = file.getName();

        if (base.endsWith(".aux"))
        {
           base = base.substring(0, base.length()-4);
        }

        glossaries.addDiagnosticMessage(String.format("%s %s",
            invoker.getLabelWithValues(
            "diagnostics.bib2gls", base),
            invoker.getLabel("diagnostics.build")));

        if (!glossaries.hasRecords && !glossaries.selectionAllFound)
        {
           glossaries.addDiagnosticMessage(invoker.getLabel(
            "diagnostics.bib2gls_norecords"));
        }

        // Is bib2gls on the system PATH?

        File bib2gls = invoker.findApp("bib2gls", "bib2gls.exe", "bib2gls.sh");

        if (bib2gls == null)
        {
           glossaries.addDiagnosticMessage(invoker.getLabelWithValues(
             "error.missing_application", "bib2gls", System.getenv("PATH")));
        }
        else
        {
           glossaries.addDiagnosticMessage(invoker.getLabelWithValues(
             "message.found", bib2gls));
        }
     }
     else if (!override)
     {
        for (Enumeration<String> en = languages.keys(); en.hasMoreElements();)
        {
           String label = en.nextElement();
           String language = languages.get(label);

           Glossary g = glossaries.getGlossary(label);

           if (g == null)
           {
              glossaries.addErrorMessage(invoker.getLabelWithValues(
                 "error.language_no_glossary", language, label));
              glossaries.addDiagnosticMessage(invoker.getLabelWithValues(
                 "diagnostics.language_no_glossary", language, label));
           }
           else
           {
              g.setLanguage(language);
           }
        }

        for (Enumeration<String> en = codePages.keys(); en.hasMoreElements();)
        {
           String label = en.nextElement();
           String code = codePages.get(label);

           Glossary g = glossaries.getGlossary(label);

           if (g == null)
           {
              glossaries.addErrorMessage(invoker.getLabelWithValues(
                 "error.codepage_no_glossary", code, label));
              glossaries.addDiagnosticMessage(invoker.getLabelWithValues(
                 "diagnostics.codepage_no_glossary", code, label));
           }
           else
           {
              g.setCodePage(code);
           }
        }
     }

     if (glossaries.requiresBib2Gls)
     {
     }

     return glossaries;
  }

  private void parseAux(File dir, String line, boolean override,
    Hashtable<String,String> languages, Hashtable<String,String> codePages)
  throws IOException
  {
     Matcher matcher = newGlossaryPattern.matcher(line);

     if (matcher.matches())
     {
        add(new Glossary(invoker, matcher.group(1), matcher.group(2),
          matcher.group(3), matcher.group(4)));

        return;
     }

     matcher = istFilePattern.matcher(line);

     if (matcher.matches())
     {
        istName = matcher.group(1);
        return;
     }

     matcher = bib2glsPattern.matcher(line);

     if (matcher.matches())
     {
        requiresBib2Gls = true;

        matcher = selectAllPattern.matcher(matcher.group(1));

        if (matcher.matches())
        {
           selectionAllFound = true;
        }

        return;
     }

     matcher = orderPattern.matcher(line);

     if (matcher.matches())
     {
        order = matcher.group(1);
        return;
     }

     if (!override)
     {
        matcher = languagePattern.matcher(line);

        if (matcher.matches())
        {
           String label = matcher.group(1);

           String language = matcher.group(2);

           if (language.isEmpty())
           {
              language = invoker.getDefaultLanguage();

              String variant = invoker.getDefaultXindyVariant();

              if (variant != null && !variant.isEmpty())
              {
                 language = String.format("%s-%s", language, variant);
              }

              addErrorMessage(invoker.getLabelWithValues(
                 "error.no_language", label));
              addDiagnosticMessage(invoker.getLabelWithValues(
                 "diagnostics.no_language", label, language));
           }

           languages.put(label, language);

           return;
        }

        matcher = codepagePattern.matcher(line);

        if (matcher.matches())
        {
           String label = matcher.group(1);

           String code = matcher.group(2);

           if (code.isEmpty())
           {
              code = invoker.getDefaultCodePage();

              addErrorMessage(invoker.getLabelWithValues(
                 "error.no_codepage", label));
              addDiagnosticMessage(invoker.getLabelWithValues(
                 "diagnostics.no_codepage", label, code));
           }

           codePages.put(label, code);

           return;
        }
     }

     matcher = glsreferencePattern.matcher(line);

     if (matcher.matches())
     {
        noidx = true;
        return;
     }

     matcher = glsrecordPattern.matcher(line);

     if (matcher.matches())
     {
        hasRecords = true;
        return;
     }

     matcher = extraMakeIndexOptsPattern.matcher(line);

     if (matcher.matches())
     {
        String opts = matcher.group(1);

        extraMakeIndexOpts = splitArgs(opts);

        return;
     }

     matcher = inputPattern.matcher(line);

     if (matcher.matches())
     {
        BufferedReader in = null;

        String aux = matcher.group(1)+".aux";

        String[] split = aux.split("/");

        File f = dir;

        for (int i = 0; i < split.length; i++)
        {
           f = new File(f, split[i]);
        }

        try
        {
           invoker.getMessageSystem().message(
             invoker.getLabelWithValues("message.loading", f));

           in = new BufferedReader(new InputStreamReader(
             new FileInputStream(f), invoker.getEncoding()));

           while ((line = in.readLine()) != null)
           {
              parseAux(f.getParentFile(), line, override, languages, codePages);
           }
        }
        finally
        {
           if (in != null)
           {
              in.close();
           }
        }

        return;
     }
  }

  public static Vector<String> splitArgs(String str)
  {
     Vector<String> args = new Vector<String>();
     StringBuilder builder = null;
     int delim = -1;

     for (int i = 0, n = str.length(); i < n; i++)
     {
        char c = str.charAt(i);

        if (builder == null)
        {
           if (Character.isWhitespace(c))
           {
              continue;
           }

           builder = new StringBuilder();

           if (c == '\'' || c == '"')
           {
              delim = c;
           }
           else
           {
              delim = -1;
              builder.append(c);
           }
        }
        else if (c == '\\')
        {
           i++;

           if (i < n)
           {
              c = str.charAt(i);
           }

           builder.append(c);
        }
        else if (delim == c
             || (delim == -1 && Character.isWhitespace(c)))
        {
           args.add(builder.toString());
           builder = null;
        }
        else
        {
           builder.append(c);
        }
     }

     if (builder != null)
     {
        args.add(builder.toString());
     }

     return args;
  }

  public void process()
     throws GlossaryException,IOException,InterruptedException
  {
     File file = invoker.getFile();

     String baseName = file.getName();

     int idx = baseName.lastIndexOf(".");

     if (idx != -1)
     {
        baseName = baseName.substring(0, idx);
     }

     File dir = file.getParentFile();

     Vector<Charset> indexerEncodings = new Vector<Charset>();

     if (!noidx)
     {
        String mess = getIndexerError();

        if (mess != null)
        {
           if (invoker.isBatchMode())
           {
              throw new GlossaryException(mess);
           }

           addErrorMessage(mess);
           parseLog(dir, baseName);

           return;
        }

        String lang = null;
        String codePage = null;

        if (invoker.getProperties().isOverride())
        {
           lang = invoker.getDefaultLanguage();
           codePage = invoker.getDefaultCodePage();

           String variant = invoker.getDefaultXindyVariant();

           if (variant != null && !variant.isEmpty())
           {
              lang = String.format("%s-%s", lang, variant);
           }
        }

        if (glossaryList.isEmpty())
        {
           addDiagnosticMessage(invoker.getLabel("diagnostics.no_glossaries"));
           addErrorMessage(invoker.getLabel("error.no_glossaries"));
        }

        for (int i = 0, n = getNumGlossaries(); i < n; i++)
        {
           Glossary g = getGlossary(i);

           try
           {
              if (useXindy())
              {
                 if (lang != null)
                 {
                    g.setLanguage(lang);
                 }

                 if (codePage != null)
                 {
                    g.setCodePage(codePage);
                 }

                 g.xindy(dir, baseName, isWordOrder(), istName);
              }
              else
              {
                 g.makeindex(dir, baseName, isWordOrder(), istName,
                   extraMakeIndexOpts);
              }

              String errMess = g.getErrorMessages();

              if (errMess != null)
              {
                 addErrorMessage(errMess);
              }
           }
           catch (IOException e)
           {
              File istFile = new File(dir, istName);

              if (!istFile.exists())
              {
                 throw new GlossaryException(invoker.getLabelWithValues(
                    "error.no_ist", istName),
                    invoker.getLabel("diagnostics.no_ist"), e);
              }
              else
              {
                 throw e;
              }
           }

           Charset encoding = invoker.getEncoding();

           if (!indexerEncodings.contains(encoding))
           {
              indexerEncodings.add(encoding);
           }
        }
     }

     // Skip the log file check when in batch mode

     if (invoker.isBatchMode()) return;

     parseLog(dir, baseName);

     // Now the log has been parsed, the document encodings should
     // be known (if support has been provided).

     if (supportedEncodings == null)
     {
        // No document encoding detected, assume ASCII (which is
        // fine for makeindex). Provide advisory note for xindy.

        if (useXindy())
        {
           addAdvisoryMessage(invoker.getLabel(
             "diagnostics.xindy_no_doc_encoding"));
        }
     }
     else
     {
        for (Charset encoding: indexerEncodings)
        {
           if (!supportedEncodings.contains(encoding))
           {
              addAdvisoryMessage(invoker.getLabelWithValues(
               "diagnostics.doc_indexer_encoding_mismatch", encoding,
                supportedEncodings.size(), inputEnc));
           }
        }
     }
  }

  public void parseLog(File dir, String baseName)
    throws IOException
  {

     // Now check the log file for any problems

     File log = new File(dir, baseName+".log");

     if (!log.exists())
     {
        addDiagnosticMessage(invoker.getLabelWithValues(
          "diagnostics.no_log", log.getAbsolutePath()));

        // Are there other .aux files in the same directory?

        File[] list = dir.listFiles(new FileFilter()
         {
            public boolean accept(File f)
            {
               return f.getName().toLowerCase().endsWith(".aux");
            }
         });

        if (list != null && list.length > 1)
        {
           addDiagnosticMessage(invoker.getLabelWithValues(
             "diagnostics.multi_aux", dir));
        }

        return;
     }

     BufferedReader reader = null;

     boolean checkNonAscii = false;

     String vers = null;

     String build = null;

     int makeglossDisabledLine = -1;
     int makeglossRestoredLine = -1;

     try
     {
        reader = new BufferedReader(new InputStreamReader(
             new FileInputStream(log), invoker.getEncoding()));

        String line;

        while ((line = reader.readLine()) != null)
        {
           Matcher m;

           if (istName == null && line.startsWith("Package glossaries Info:") && !line.endsWith("."))
           {
              String nextLine;

              while ((nextLine = reader.readLine()) != null)
              {
                 line += nextLine;

                 if (nextLine.endsWith("."))
                 {
                    break;
                 }
              }

              // Need to determine which option was the last in
              // effect but line numbers may refer to different files.

              m = makeglossDisabledPattern.matcher(line);

              if (m.matches())
              {
                 String lineRef = m.group(1);

                 addDiagnosticMessage(invoker.getLabelWithValues(
                    "diagnostics.makeglossdisabled", lineRef));

                 makeglossDisabledLine = 1;

                 if (makeglossRestoredLine > -1)
                 {
                    makeglossRestoredLine = 0;
                 }
              }

              m = makeglossRestoredPattern.matcher(line);

              if (m.matches())
              {
                 String lineRef = m.group(1);

                 addDiagnosticMessage(invoker.getLabelWithValues(
                    "diagnostics.makeglossrestored", lineRef));

                 makeglossRestoredLine = 1;

                 if (makeglossDisabledLine > -1)
                 {
                    makeglossDisabledLine = 0;
                 }
              }
           }

           if (latexFormat == null)
           {
              m = formatPattern.matcher(line);

              if (m.matches())
              {
                 latexFormat = m.group(1);

                 if (latexFormat.startsWith("xe")
                     || latexFormat.startsWith("lua"))
                 {
                    addSupportedEncoding("utf8");
                 }

                 continue;
              }
           }

           m = inputEncPattern.matcher(line);

           if (m.matches())
           {
              addSupportedEncoding(m.group(1));
              continue;
           }

           m = glossariesStyPattern.matcher(line);

           if (m.matches())
           {
              Calendar cal = Calendar.getInstance();

              String year = m.group(1);
              String mon  = m.group(2);
              String day  = m.group(3);
              vers = m.group(4);

              try
              {
                 cal.set(Integer.parseInt(year),
                         Integer.parseInt(mon),
                         Integer.parseInt(day));
              }
              catch (NumberFormatException e)
              {// shouldn't happen
                 invoker.getMessageSystem().debug(e);
              }

              Calendar v416 = Calendar.getInstance();
              v416.set(2015, 7, 8);

              if (cal.before(v416))
              {
                 addDiagnosticMessage(invoker.getLabelWithValues(
                   "diagnostics.oldversion", vers,
                   // use same format as LaTeX package info:
                   String.format("%s/%s/%s", year, mon, day)
                 ));
              }

              continue;
           }

           m = wrongGloTypePattern.matcher(line);

           if (m.matches())
           {
              String type = m.group(2);

              addDiagnosticMessage(invoker.getLabelWithValues(
                "diagnostics.wrong_type", type));
              addErrorMessage(invoker.getLabelWithValues(
                "error.wrong_type", type));

              continue;
           }

           if (invoker.isDocDefCheckOn())
           {
              m = docDefsPattern.matcher(line);

              if (m.matches())
              {
                 File f = new File(dir, baseName+".glsdefs");

                 addDiagnosticMessage(invoker.getLabelWithValues(
                   "diagnostics.doc_defs", f.getAbsolutePath()));

                 continue;
              }
           }

           if (invoker.isMissingLangCheckOn())
           {
              m = missingLangPattern.matcher(line);

              if (m.matches())
              {
                 addDiagnosticMessage(invoker.getLabelWithValues(
                   "diagnostics.missing_lang", m.group(1)));
                 continue;
              }
           }

           m = missingStyPattern.matcher(line);

           if (m.matches())
           {
              String sty = m.group(1);

              if (sty.equals("datatool-base"))
              {
                 addDiagnosticMessage(invoker.getLabel(
                    "diagnostics.missing_datatool_base"));
              }
              else
              {
                 addDiagnosticMessage(invoker.getLabelWithValues(
                    "diagnostics.missing_sty", sty));
              }

              continue;
           }

           m = glsGroupHeadingPattern.matcher(line);

           if (m.matches())
           {
              String val = m.group(1);

              if (!problemGroupLabels.contains(val))
              {
                 addDiagnosticMessage(String.format("%s<pre>%s</pre>%s",
                    invoker.getLabelWithValues(
                    "diagnostics.problem_group_label", val),
                    invoker.escapeHTML(line),
                    invoker.getLabel(isUnicodeEngine() ?
                      "diagnostics.suggest_bib2gls" :
                      "diagnostics.suggest_unicode_or_bib2gls")));

                 problemGroupLabels.add(val);
              }

              continue;
           }

           m = warningPattern.matcher(line);

           if (m.matches())
           {
              StringBuilder builder = new StringBuilder(line);

              while ((line = reader.readLine()) != null)
              {
                 if (line.isEmpty())
                 {
                    break;
                 }

                 builder.append(line);
              }

              String msg = builder.toString();

              if (msg.contains("Package inputenc Error: Unicode char"))
              {
                 // If a warning message contains this, it's likely
                 // that an entry label contains non-ASCII
                 // characters.

                 checkNonAscii = true;
                 continue;
              }

              if (requiresBib2Gls)
              {
                 m = missingGlsTeXPattern.matcher(msg);

                 if (m.matches())
                 {
                    addDiagnosticMessage(invoker.getLabelWithValues(
                     istName == null ? "diagnostics.missing_glstex" :
                     "diagnostics.missing_glstex_hybrid",
                     m.group(1)));

                    if (build == null)
                    {
                       if (istName == null)
                       {
                          build = invoker.getLabelWithValues(
                            "diagnostics.bib2gls_build", getLaTeXFormat(),
                             baseName
                          );
                       }
                       else
                       {
                          build = invoker.getLabelWithValues(
                            "diagnostics.hybrid_build", getLaTeXFormat(),
                             baseName,
                            "makeglossaries"
                          );
                       }

                       addDiagnosticMessage(build);
                    }
                    continue;
                 }
              }

              addDiagnosticMessage(msg);

              if (noidx)
              {
                 m = emptyNoIdxGlossaryPattern.matcher(msg);

                 if (m.matches())
                 {
                    String type = m.group(1);

                    if (!glossaryList.contains(type))
                    {
                       addDiagnosticMessage(invoker.getLabelWithValues(
                         "diagnostics.wrong_type_noidx", type));
                    }
                 }
              }

              continue;
           }

           m = systemPattern.matcher(line);

           if (m.matches())
           {
              StringBuilder builder = new StringBuilder(line);

              while (!line.endsWith(".") && (line = reader.readLine()) != null)
              {
                 if (line.isEmpty())
                 {
                    break;
                 }

                 builder.append(line);
              }

              m = disabledSystemPattern.matcher(builder);

              if (m.matches())
              {
                 String cmd = m.group(1);
                 String rest = m.group(2);

                 if (m.groupCount() == 2)
                 {
                    addAdvisoryMessage(invoker.getLabelWithValues(
                       "diagnostics.shell_disabled", cmd+rest));
                 }
                 else
                 {
                    addAdvisoryMessage(invoker.getLabelWithValues(
                       "diagnostics.shell_restricted", cmd+rest, cmd));
                 }


              }

              continue;
           }

           m = undefControlSequencePattern.matcher(line);

           if (m.matches())
           {
              line = reader.readLine();

              m = fragileBrokenPattern.matcher(line);

              if (m.matches())
              {
                 addDiagnosticMessage(invoker.getLabel(
                   "diagnostics.fragile"));
                 continue;
              }

              if (line.contains("\\GenericError"))
              {
                 // Too complicated, so don't show to avoid
                 // overcluttering the diagnostic panel.
                 continue;
              }

              if (line.endsWith("\\indexspace "))
              {
                 addDiagnosticMessage(invoker.getLabel(
                   "diagnostics.undef_indexspace"));
                 continue;
              }

              addDiagnosticMessage(invoker.getLabelWithValues(
                "diagnostics.undef_cs", line));

              continue;
           }

           m = unknownOptPattern.matcher(line);

           if (m.matches())
           {
              addDiagnosticMessage(invoker.getLabelWithValues(
                "diagnostics.undef_opt", m.group(1)));

              continue;
           }

           m = infoPattern.matcher(line);

           if (m.matches())
           {
              StringBuilder builder = new StringBuilder(m.group(1));

              while (!line.endsWith(".")
                     && (line = reader.readLine()) != null
                     && !line.isEmpty())
              {
                 builder.append(line);
              }

              m = wrglossaryPattern.matcher(builder);

              if (m.matches())
              {
                 String type = m.group(1);
                 String info = m.group(2);
                 String lineNum = m.group(3);

                 addDiagnosticMessage(String.format("%s%n<pre>%s</pre>%n",
                    invoker.getLabelWithValues(
                     "diagnostics.wrglossary", type, lineNum),
                     info));
              }

              continue;
           }

           m = inputPattern.matcher(line);

           if (m.matches())
           {
              addDiagnosticMessage(invoker.getLabelWithValues(
                 "diagnostics.include", m.group(1)));
              continue;
           }

           m = istFilePattern.matcher(line);

           if (m.matches())
           {
              addDiagnosticMessage(invoker.getLabel(
                 "diagnostics.ist_in_log"));
              continue;
           }
        }
     }
     finally
     {
        if (reader != null)
        {
           reader.close();
        }
     }

     if (makeglossDisabledLine > -1)
     {
        if (makeglossRestoredLine == -1)
        {
           addDiagnosticMessage(invoker.getLabel("diagnostics.restoremakegloss"));
        }
        else if (makeglossRestoredLine < makeglossDisabledLine)
        {
           addDiagnosticMessage(
              invoker.getLabel("diagnostics.restoremakegloss.cancelled"));
        }
        else
        {
           addDiagnosticMessage(
              invoker.getLabel("diagnostics.restoremakegloss.late"));
        }
     }

     if (vers == null)
     {
        addDiagnosticMessage(invoker.getLabel(
         "diagnostics.no_version"));
     }

     if (checkNonAscii)
     {
        addDiagnosticMessage(invoker.getLabel(
           "diagnostics.inputenc"));

        for (int i = 0, n = glossaryList.size(); i < n; i++)
        {
           Glossary glossary = glossaryList.get(i);

           glossary.checkNonAsciiLabels();
        }
     }
  }

  public String getLaTeXFormat()
  {
     return latexFormat == null ? "latex" : latexFormat;
  }

  public boolean isUnicodeEngine()
  {
     if (latexFormat == null)
     {
        return false;
     }

     return latexFormat.startsWith("xe") || latexFormat.startsWith("lua");
  }

  public String displayGlossaryList()
  {
     String str = null;

     for (int i = 0, n = glossaryList.size(); i < n; i++)
     {
        Glossary glossary = glossaryList.get(i);

        if (str == null)
        {
           str = glossary.label;
        }
        else
        {
           str += ", " + glossary.label;
        }
     }

     return str;
  }

  public String getDisplayGlossaryListError()
  {
     return getNumGlossaries() == 0 ?
        invoker.getLabel("error.no_glossaries"):
        null;
  }

  public String displayEncoding()
  {
     return invoker.getEncoding().name();
  }

  private void addSupportedEncoding(String encLabel)
  {
     Charset charset = invoker.getCharset(encLabel);

     if (charset == null)
     {
        addDiagnosticMessage(invoker.getLabelWithValues(
         "diagnostics.unknown.encoding", encLabel));
     }
     else
     {
        if (supportedEncodings == null)
        {
           supportedEncodings = new Vector<Charset>();
        }

        supportedEncodings.add(charset);
     }

     if (inputEnc == null)
     {
        inputEnc = encLabel;
     }
     else
     {
        inputEnc = String.format("%s, %s", inputEnc, encLabel);
     }
  }

  public String displayDocumentEncoding()
  {
     return inputEnc == null ? invoker.getLabel("error.unknown") : inputEnc;
  }

  public String getOrder()
  {
     return order;
  }

  public boolean isWordOrder()
  {
     if (order == null) return false;
     return order.equals("word");
  }

  public boolean isLetterOrder()
  {
     if (order == null) return false;
     return order.equals("letter");
  }

  public boolean isValidOrder()
  {
     if (order == null) return false;

     return order.equals("word") || order.equals("letter");
  }

  public String getOrderError()
  {
     if (order == null) return invoker.getLabel("error.missing_order");

     return isValidOrder() ? null
       : invoker.getLabelWithValues("error.invalid_order", order);
  }

  public String displayFormat()
  {
     if (istName == null)
     {
        if (requiresBib2Gls)
        {
           return "bib2gls";
        }
        else
        {
           return invoker.getLabel("error.unknown");
        }
     }

     return useXindy() ? "xindy" : "makeindex";
  }

  public String getIstName()
  {
     return istName;
  }

  public String getIstNameError()
  {
     return istName == null ? invoker.getLabel("error.missing_ist") : null;
  }

  public boolean useXindy()
  {
     if (istName == null) return false;

     return istName.endsWith(".xdy");
  }

  public String getIndexerError()
  {
     if (noidx) return null;

     if (istName == null)
     {
        return
          invoker.getLabel(requiresBib2Gls ? "error.bib2gls_indexer" :
            "error.cant_determine_indexer");
     }

     if (useXindy())
     {
        if (invoker.getXindyApp() == null)
        {
           return invoker.getLabel("error.no_xindy");
        }
     }
     else
     {
        if (invoker.getMakeIndexApp() == null)
        {
           return invoker.getLabel("error.no_makeindex");
        }
     }

     return null;
  }

  public int getNumGlossaries()
  {
     return glossaryList.size();
  }

  public String getDiagnostics()
  {
     String mess;

     if (!noidx && istName == null && order == null)
     {
        mess = getDiagnosticMessages();

        if (mess != null)
        {
           return mess;
        }
        else if (glossaryList.size() == 0)
        {
           return invoker.getFileName().toLowerCase().endsWith(".aux") ?
                  invoker.getLabel("diagnostics.no_glossaries"):
                  invoker.getLabel("diagnostics.not_aux");
        }
        else
        {
           return invoker.getLabel("diagnostics.no_makeglossaries");
        }
     }

     mess = getIndexerError();

     if (mess != null)
     {
        return String.format("%s%n%s", mess,
           invoker.getLabelWithValues("diagnostics.no_indexer",
           displayFormat()));
     }

     mess = getDiagnosticMessages();

     for (int i = 0, n = getNumGlossaries(); i < n; i++)
     {
        String errmess = getGlossary(i).getDiagnosticMessages();

        if (errmess != null)
        {
           if (mess == null)
           {
              mess = errmess;
           }
           else
           {
              mess = String.format("%s%n<p>%s", mess, errmess);
           }
        }
     }

     return mess;
  }

  private String getDiagnosticMessages()
  {
     return diagnosticMessages == null ? null : diagnosticMessages.toString();
  }

  public void addDiagnosticMessage(String mess)
  {
     if (diagnosticMessages == null)
     {
        diagnosticMessages = new StringBuilder(mess);
     }
     else
     {
        diagnosticMessages.append(String.format("<p>%s", mess));
     }
  }

  public String getAdvisoryMessages()
  {
     return advisoryMessages == null ? null : advisoryMessages.toString();
  }

  public void addAdvisoryMessage(String mess)
  {
     if (advisoryMessages == null)
     {
        advisoryMessages = new StringBuilder(mess);
     }
     else
     {
        advisoryMessages.append(String.format("<p>%s", mess));
     }
  }

  public void addErrorMessage(String mess)
  {
     if (errorMessages == null)
     {
        errorMessages = new StringBuilder(mess);
     }
     else
     {
        errorMessages.append(String.format("%n%s", mess));
     }
  }

  public String getErrorMessages()
  {
     return errorMessages == null ? null : errorMessages.toString();
  }

  public static int getNumFields()
  {
     return fields.length;
  }

  public static String getFieldTag(int i)
  {
     return fields[i];
  }

  public static Element getFieldElement(HTMLDocument doc, int i)
  {
     return doc.getElement(fields[i]);
  }

  public static Element getFieldLabelElement(HTMLDocument doc, int i)
  {
     return doc.getElement(fields[i]+"label");
  }

  public String getFieldLabel(int i)
  {
     return invoker.getLabel("main", fields[i]);
  }

  public String getField(int i)
  {
     switch (i)
     {
        case AUX: return invoker.getFileName();
        case ORDER: return getOrder();
        case IST: return getIstName();
        case INDEXER: return displayFormat();
        case GLOSSARIES: return displayGlossaryList();
        case ENCODING: return displayEncoding();
        case DOC_ENCODING: return displayDocumentEncoding();
     }

     return null;
  }

  public String getFieldError(int i)
  {
     switch (i)
     {
        case AUX: return invoker.getFileName() == null ?
           invoker.getLabel("error.no_such_file") :
           null;
        case ORDER: return getOrderError();
        case IST: return getIstNameError();
        case INDEXER: return getIndexerError();
        case GLOSSARIES: return getDisplayGlossaryListError();
     }

     return null;
  }

  private Vector<Glossary> glossaryList;
  private String istName;

  private Vector<String> extraMakeIndexOpts;

  private boolean noidx = false;

  private String order;

  private boolean requiresBib2Gls = false;

  private StringBuilder errorMessages, diagnosticMessages, advisoryMessages;

  private static final Pattern newGlossaryPattern
     = Pattern.compile("\\\\@newglossary\\{([^\\}]+)\\}\\{([^\\}]+)\\}\\{([^\\}]+)\\}\\{([^\\}]+)\\}");

  private static final Pattern istFilePattern
     = Pattern.compile("\\\\@istfilename\\{([^\\}]+)\\}");

  private static final Pattern bib2glsPattern
     = Pattern.compile("\\\\glsxtr@resource\\{(.*)\\}\\{(.*?)\\}");

  private static final Pattern selectAllPattern
     = Pattern.compile(".*selection\\s*=\\s*(all|\\{all\\}).*");

  private static final Pattern orderPattern
     = Pattern.compile("\\\\@glsorder\\{([^\\}]+)\\}");

  private static final Pattern languagePattern
     = Pattern.compile("\\\\@xdylanguage\\{([^\\}]+)\\}\\{([^\\}]*)\\}");

  private static final Pattern codepagePattern
     = Pattern.compile("\\\\@gls@codepage\\{([^\\}]+)\\}\\{([^\\}]*)\\}");

  private static final Pattern glsreferencePattern
     = Pattern.compile("\\\\@gls@reference\\{.*?\\}\\{.*?\\}\\{.*\\}");

  private static final Pattern glsrecordPattern
     = Pattern.compile("\\\\glsxtr@record\\{.*?\\}\\{.*?\\}\\{.*\\}");

  private static final Pattern extraMakeIndexOptsPattern
     = Pattern.compile("\\\\@gls@extramakeindexopts\\{(.*)\\}");

  private static final Pattern glossariesStyPattern
     = Pattern.compile("Package: glossaries (\\d+)\\/(\\d+)\\/(\\d+) v([\\d\\.a-z]+) \\(NLCT\\).*");

  private static final Pattern glossariesXtrStyPattern
     = Pattern.compile("Package: glossaries-extra (\\d+)\\/(\\d+)\\/(\\d+) v([\\d\\.a-z]+) \\(NLCT\\).*");

  private static final Pattern wrongGloTypePattern
     = Pattern.compile("No file (.*?)\\.\\\\@glotype@(.*?)@in\\s*\\.");

  private static final Pattern docDefsPattern
     = Pattern.compile("\\\\openout\\d+\\s*=\\s*`.*\\.glsdefs'.");

  private static final Pattern missingLangPattern
     = Pattern.compile("Package glossaries Warning: No language module detected for `(.*)'.\\s*");

  private static final Pattern missingStyPattern
     = Pattern.compile(".*`(.*?)\\.sty' not found.*");

  private static final Pattern missingGlsTeXPattern
     = Pattern.compile(".*No file `(.*?\\.glstex)'.*");

  private static final Pattern warningPattern
     = Pattern.compile("Package glossaries(-extra)? Warning: .*");

  private static final Pattern emptyNoIdxGlossaryPattern
     = Pattern.compile(".*Empty glossary for \\\\printnoidxglossary\\[type=\\{(.*?)\\}\\]\\..*");

  private static final Pattern systemPattern
     = Pattern.compile("runsystem\\(.*");

  private static final Pattern disabledSystemPattern
     = Pattern.compile("runsystem\\(([^ ]+)(.*)\\)\\.\\.\\.disabled(\\s+\\(restricted\\))?\\.");

  private static final Pattern undefControlSequencePattern
     = Pattern.compile(".* Undefined control sequence.");

  private static final Pattern fragileBrokenPattern
     = Pattern.compile(".*\\\\in@ #1#2->\\\\begingroup \\\\def \\\\in@@\\s*");

  private static final Pattern unknownOptPattern
     = Pattern.compile(".*Unknown option `(.*)' for package `glossaries'.*");

  private static final Pattern infoPattern
     = Pattern.compile("Package glossaries Info:\\s*(.*)");

  private static final Pattern wrglossaryPattern
     = Pattern.compile("wrglossary\\((.*?)\\)\\((.*)\\) on input line (\\d+).*");

  private static final Pattern inputPattern
     = Pattern.compile("\\\\@input\\{(.*)\\.aux\\}");

  private static final Pattern formatPattern
     = Pattern.compile(".* format=(xelatex|lualatex|pdflatex|latex) .*");

  private static final Pattern inputEncPattern
     = Pattern.compile("File: (utf8|latin[0-9]+|cp[0-9]+(?:de)?|decmulti|applemac|macce|next|ansinew|ascii)\\.def.*");

  private static final Pattern glsGroupHeadingPattern
     = Pattern.compile(".*\\\\glsgroupheading\\{(.+?)\\}.*");

  private static final Pattern makeglossDisabledPattern = Pattern.compile(
     "Package glossaries Info: \\\\makeglossaries and \\\\makenoidxglossaries have been disabled on input line ([0-9]+)\\.");

  private static final Pattern makeglossRestoredPattern = Pattern.compile(
     "Package glossaries Info: \\\\makeglossaries and \\\\makenoidxglossaries have been restored on input line ([0-9]+)\\.");

  private Vector<String> problemGroupLabels = new Vector<String>();

  private static final String[] fields =
  {
     "aux",
     "order",
     "ist",
     "indexer",
     "list",
     "encoding",
     "docencoding"
  };

  public static final int AUX=0, ORDER=1, IST=2, INDEXER=3, GLOSSARIES=4,
    ENCODING=5, DOC_ENCODING=6;

  private String latexFormat = null;

  private String inputEnc = null;

  private Vector<Charset> supportedEncodings;

  private MakeGlossariesInvoker invoker;

  private boolean hasRecords = false;
  private boolean selectionAllFound = false;
}