package yacs.net;

import yacs.exceptions.*;
import yacs.solver.*;

import java.util.*;

import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * <p>XML-Parser, der aus der übergebenen Datei die Constraint-Lösungsstrategien
 * ausliest und die entsprechende Objekte (Instanzen der Klasse
 * <code>yacs.net.Strategy</code>) instantiiert.</p>
 *
 * <p>Copyright (C) 2005  Wolfgang Runte
 * <br><br>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <br><br>
 * This library 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
 * Lesser General Public License for more details.
 * <br><br>
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA</p>
 *
 * @author Wolfgang Runte (woru@tzi.org)
 * @version YACS 0.1.1
 */
public class StrategyReader {

// ********** private attributs **********

  /**
   * Liste der verfügbaren Constraint-Lösungsstrategien (enthält Instanzen der
   * Klasse <code>yacs.net.Strategy</code>).
   */
  private List strategies;

  /**
   * Dummy für UML-Diagramm (Borland Together).
   * @supplierCardinality 0..*
   * @clientCardinality 1
   * @link aggregation
   */
  private Strategy lnkStrategy;

  /**
   * HashMap mit den Namen der Strategien als "Keys" und den
   * Constraint-Expressions als "Values".
   */
  //private HashMap subproblemMap = new HashMap();

// ********** public attributs **********

// ********** private methods **********

  /**
   * Löscht unerwünschte Leerzeichen und Zeilenumbrüche aus dem übergebenen
   * <code>String</code>.
   * @param text String
   * @return String
   */
  private String trimTextString(String text) {
    // Trim the value to get rid of NL's at the front
    String t = text.trim();
    int x = t.indexOf("\n");
    if (x >= 0) {
      t = t.substring(0, x);
    }
    return t;
  }

  /**
   * Liest die Solver einer einzelnen Phase einer Strategie aus, instantiiert
   * diese und gibt die Objekte innerhalb einer Liste zurück.
   * @param solvers NodeList
   * @return List
   */
  private List readSolvers(NodeList solvers) {
    Solver newSolver;
    List solverList = new LinkedList();
    // Alle vorhandenen Solver durchgehen:
    for (int i = 0; i < solvers.getLength(); i++) {
      // Kommentare überspringen:
      if (solvers.item(i).getNodeType() == 8) {
        continue;
      }
      // Leere Textfelder ebenfalls überspringen:
      if ((solvers.item(i).getNodeType() == 3) &&
          (this.trimTextString(solvers.item(i).getNodeValue()).equals(""))) {
        continue;
      }
      // Checken ob Node ein !ELEMENT vom Typ "solver" ist:
      if ((solvers.item(i).getNodeType() == 1) &&
          (solvers.item(i).getNodeName().equals("solver"))) {
        NodeList childNodes = solvers.item(i).getChildNodes();
        // Alle vorhandenen Einträge durchgehen:
        for (int j = 0; j < childNodes.getLength(); j++) {
          // Kommentare überspringen:
          if (childNodes.item(j).getNodeType() == 8) {
            continue;
          }
          // Leere Textfelder ebenfalls überspringen:
          if ((childNodes.item(j).getNodeType() == 3) &&
              (this.trimTextString(childNodes.item(j).getNodeValue()).equals(""))) {
            continue;
          }
          // Andere Textfelder instantiieren:
          if (childNodes.item(j).getNodeType() == 3) {
//System.out.println("Instantiate: " + this.trimTextString(childNodes.item(j).getNodeName()) + " -- " + this.trimTextString(childNodes.item(j).getNodeValue()));
            try {
              newSolver = (Solver)Class.forName(this.trimTextString(childNodes.item(j).getNodeValue())).newInstance();
              // ... und in Ergebnsliste merken:
              solverList.add(newSolver);
            } catch (ClassNotFoundException e) {
              System.err.println("Error instantiating solvers in yacs.net.StrategyReader.readSolvers():");
              e.printStackTrace();
            } catch (InstantiationException e) {
              System.err.println("Error instantiating solvers in yacs.net.StrategyReader.readSolvers():");
              e.printStackTrace();
            } catch (IllegalAccessException e) {
              System.err.println("Error instantiating solvers in yacs.net.StrategyReader.readSolvers():");
              e.printStackTrace();
            } catch (ClassCastException e) {
              System.err.println("Error instantiating solvers in yacs.net.StrategyReader.readSolvers():");
              System.err.println("Solvers have to implement the yacs.solver.Solver interface!");
              e.printStackTrace();
            }
          }
        }
      }
    }
//Iterator it = solverList.iterator();
//while (it.hasNext()) { System.out.println("Solver: " + it.next()); }
  return solverList;
  }

  /**
   * Einlesen einer einzelnen Strategie und Rückgabe des entsprechenden Objekts
   * (Instanz der Klasse <code>yacs.net.Strategy</code>).
   * @param strategy Node
   * @return Strategy
   */
  private Strategy readStrategy(Node strategy) {
    List preprocessingSolvers = new LinkedList();
    List consistencySolvers = new LinkedList();
    List searchSolvers = new LinkedList();
    // Den Namen der Strategie holen:
    String strategyName = strategy.getAttributes().getNamedItem("name").getNodeValue();
    // Die Strategie instantiieren:
    Strategy newStrategy = new Strategy(strategyName);
    // XML-Elemente der Strategie aus Datei auslesen:
    NodeList childNodes = strategy.getChildNodes();
    for (int i = 0; i < childNodes.getLength(); i++) {
      // Kommentare überspringen:
      if (childNodes.item(i).getNodeType() == 8) {
        continue;
      }
      // Leere Textfelder ebenfalls überspringen:
      if ((childNodes.item(i).getNodeType() == 3) &&
          (this.trimTextString(childNodes.item(i).getNodeValue()).equals(""))) {
        continue;
      }
      // Checken ob Node ein !ELEMENT vom Typ "preprocessing" ist:
      if ((childNodes.item(i).getNodeType() == 1) &&
          (childNodes.item(i).getNodeName().equals("preprocessing"))) {
        preprocessingSolvers.addAll(this.readSolvers(childNodes.item(i).getChildNodes()));
        continue;
      }
      // Checken ob Node ein !ELEMENT vom Typ "consistency" ist:
      if ((childNodes.item(i).getNodeType() == 1) &&
          (childNodes.item(i).getNodeName().equals("consistency"))) {
        consistencySolvers.addAll(this.readSolvers(childNodes.item(i).getChildNodes()));
        continue;
      }
      // Checken ob Node ein !ELEMENT vom Typ "search" ist:
      if ((childNodes.item(i).getNodeType() == 1) &&
          (childNodes.item(i).getNodeName().equals("search"))) {
        searchSolvers.addAll(this.readSolvers(childNodes.item(i).getChildNodes()));
        continue;
      }
    }
    // Eingelesenen Solver zur Strategie hinzufügen:
    Iterator it;
    try {
      // Solver für Preprozessing:
      it = preprocessingSolvers.iterator();
      while (it.hasNext()) {
        newStrategy.addPreprocessingSolver((PreprocessingSolver)it.next());
      }
      // Solver zur Konsistenzherstellung:
      it = consistencySolvers.iterator();
      while (it.hasNext()) {
        newStrategy.addConsistencySolver((ConsistencySolver)it.next());
      }
      // Solver zur Lösungssuche:
      it = searchSolvers.iterator();
      while (it.hasNext()) {
        newStrategy.addSearchSolver((SearchSolver)it.next());
      }
    } catch (OnlyPreprocessingSolversAllowedException e) {
      e.printStackTrace();
    } catch (OnlyConsistencySolversAllowedException e) {
      e.printStackTrace();
    } catch (OnlySearchSolversAllowedException e) {
      e.printStackTrace();
    } catch (ClassCastException e) {
      System.err.println("Error instantiating solvers in yacs.net.StrategyReader.readStrategy():");
      System.err.println("Solvers have to implement the corresponding interface classes!");
      e.printStackTrace();
    }
//System.out.println("Strategie -- " + newStrategy);
    return newStrategy;
  }

  /**
   * Auslesen und Instantiieren der Constraint-Lösungsstrategien. Der Parameter
   * <code>strategyPath</code> ist der Pfad zur der XML-Datei, in der die
   * Constraint-Lösungsstrategien spezifiziert sind.
   * @param strategyPath String
   * @return List
   */
  private List readStrategies(String strategyPath) {
    List newStrategies = new LinkedList();
    Document document;
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(true);
    //factory.setNamespaceAware(true);
    try {
      DocumentBuilder builder = factory.newDocumentBuilder();
      // Fehlerbehandlung (durch anonyme innere Klasse):
      builder.setErrorHandler(new org.xml.sax.ErrorHandler() {
        // ignore fatal errors (an exception is guaranteed)
        public void fatalError(SAXParseException exception) throws SAXException {}
        // treat validation errors as fatal
        public void error(SAXParseException e) throws SAXParseException {
          throw e;
        }
        // dump warnings too
        public void warning(SAXParseException err) throws SAXParseException {
          System.err.println("** Warning"
                             + ", line " + err.getLineNumber()
                             + ", uri " + err.getSystemId());
          System.err.println("   " + err.getMessage());
        }
      });
      // Dokument "parsen":
      document = builder.parse(new File(strategyPath));
      // ... und die einzelnen Bestandteile aus der DOM-Struktur auslesen:
      if (document.hasChildNodes()) {
        NodeList childNodes, strategyChildNodes;
        childNodes = document.getChildNodes();
        // Äußere Schleife - Dokument lesen:
        for (int i = 0; i < childNodes.getLength(); i++) {
          // Kommentare überspringen:
          if (childNodes.item(i).getNodeType() == 8) {
            continue;
          }
          // Leere Textfelder ebenfalls überspringen:
          if ((childNodes.item(i).getNodeType() == 3) &&
              (this.trimTextString(childNodes.item(i).getNodeValue()).equals(""))) {
            continue;
          }
          // Checken ob Node ein !DOCTYPE (DTD) vom Typ "strategies" ist:
          if (childNodes.item(i).getNodeType() == 10) {
            if (childNodes.item(i).getNodeName().equals("strategies")) {
              continue;
            } else {
              throw new StrategyParserException("XML parsing error: wrong or missing DTD.");
            }
          }
          // Checken ob Node ein !ELEMENT vom Typ "strategies" ist:
          if ((childNodes.item(i).getNodeType() == 1) &&
              (childNodes.item(i).getNodeName().equals("strategies"))) {
            strategyChildNodes = childNodes.item(i).getChildNodes();
            for (int j = 0; j < strategyChildNodes.getLength(); j++) {
              // Kommentare überspringen:
              if (strategyChildNodes.item(j).getNodeType() == 8) {
                continue;
              }
              // Leere Textfelder ebenfalls überspringen:
              if ((strategyChildNodes.item(j).getNodeType() == 3) &&
                  (this.trimTextString(strategyChildNodes.item(j).getNodeValue()).equals(""))) {
                continue;
              }
              // Einzelne Strategien einlesen und hinzufügen:
//System.out.println("Node: " + strategyChildNodes.item(j).getNodeName() + " -- " + strategyChildNodes.item(j).getNodeValue() + " -- " + strategyChildNodes.item(j).getNodeType());
              newStrategies.add(this.readStrategy(strategyChildNodes.item(j)));
            }
          } else {
            throw new StrategyParserException("XML parsing error: element of type 'strategies' expected.");
          }
        }
      } else {
        throw new StrategyParserException("XML parsing error: empty document.");
      }
    } catch (SAXException sxe) {
      // Error generated during parsing)
      Exception x = sxe;
      if (sxe.getException() != null) {
        x = sxe.getException();
      }
      x.printStackTrace();
    } catch (ParserConfigurationException pce) {
      // Parser with specified options can't be built
      pce.printStackTrace();
    } catch (IOException ioe) {
      // I/O error
      ioe.printStackTrace();
    } catch (StrategyParserException e) {
      e.printStackTrace();
    }
    return newStrategies;
  }

//  /**
//   * Testen ob es Teilprobleme (Instanzen der Klasse
//   * <code>yacs.parser.Expression</code>) gibt, die eine Strategie benötigen,
//   * welche in der XML-Datei nicht definiert wurde.
//   * @throws StrategyParserException
//   */
/*  private void undefinedStrategies() throws StrategyParserException {
    Iterator it = subproblemMap.keySet().iterator();
    String subproblemName;
    boolean hit;
    // Für jedes übergebene Teilproblem ...
    while (it.hasNext()) {
      subproblemName = (String)it.next();
      hit = false;
      // ... sämtliche instantiierten Strategien durchgehen:
      for (int i = 0; i < this.strategies.size(); i++) {
        // Wenn es für das Teilproblem eine instantiierte Strategie gibt ...
        if (subproblemName.equals(((Strategy)this.strategies.get(i)).getStrategyName())) {
          // ... Treffer merken und Schleife beenden:
          hit = true;
          continue;
        }
      }
      // Wenn für ein Teilproblem kein Treffer bei den instantiierten
      // Strategien vorliegt, wird eine Exception ausgelöst:
      if (hit == false) {
        throw new StrategyParserException("Strategy parser error: no XML strategy definition found for specified subproblem '" + subproblemName + "'.");
      }
    }
  }/*

// ********** public methods **********

  /**
   * Konstruktor -- Der Parameter <code>strategyPath</code> ist der Pfad zur der
   * XML-Datei, in der die Constraint-Lösungsstrategien spezifiziert sind.
   * @param strategyPath String
   */
  public StrategyReader(String strategyPath) {
    this.strategies = this.readStrategies(strategyPath);
/*    try {
      this.undefinedStrategies();
    } catch (StrategyParserException e) {
      e.printStackTrace();
    }*/
  }

  /**
   * Liefert die eingelesenen Strategien als Liste (enthält Instanzen der Klasse
   * <code>yacs.net.Strategy</code>).
   * @return List
   */
  public List getStrategies() {
    return this.strategies;
  }

}
