/*
 * $Id: ErrorCodeSpec.java,v 1.35 2012/03/15 21:07:39 agoubard Exp $
 *
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
package org.xins.common.spec;

import java.io.IOException;
import java.io.Reader;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Element;

import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.xml.ElementList;
import org.xins.common.xml.ElementFormatter;
import org.xml.sax.SAXException;

/**
 * Specification of a error code (also known as result code).
 *
 * @version $Revision: 1.35 $ $Date: 2012/03/15 21:07:39 $
 * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
 *
 * @since XINS 1.3.0
 */
public final class ErrorCodeSpec {

   /**
    * Type indicating that the error code is functional.
    *
    * @since XINS 1.4.0
    */
   public static final Type FUNCTIONAL = new Type();

   /**
    * Type indicating that the error code is technical.
    *
    * @since XINS 1.4.0
    */
   public static final Type TECHNICAL = new Type();

   /**
    * Name of the function.
    */
   private final String _errorCodeName;

   /**
    * Description of the function.
    */
   private String _description;

   /**
    * The output parameters of the function.
    */
   private Map<String, ParameterSpec> _outputParameters = new LinkedHashMap<String, ParameterSpec>();

   /**
    * The output data section elements of the function.
    */
   private Map _outputDataSectionElements;

   /**
    * The type of the error code.
    */
   private Type _type;

   /**
    * Creates a new instance of <code>ErrorCodeSpec</code>.
    *
    * @param name
    *    the name of the error code, cannot be <code>null</code>.
    *
    * @param reference
    *    the reference class used to get the type of the parameters, cannot be <code>null</code>.
    *
    * @param baseURL
    *    the base URL used to located the specifications, cannot be <code>null</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>name == null || reference == null || baseURL == null</code>.
    *
    * @throws InvalidSpecificationException
    *    if the result code file cannot be found or is incorrect.
    */
   public ErrorCodeSpec(String name, Class reference, String baseURL)
   throws IllegalArgumentException, InvalidSpecificationException {

      MandatoryArgumentChecker.check("name", name, "reference", reference, "baseURL", baseURL);
      _errorCodeName = name;
      try {
         Reader reader = APISpec.getReader(baseURL, name + ".rcd");
         parseErrorCode(reader, reference);
      } catch (IOException ioe) {
         throw new InvalidSpecificationException("[ErrorCode: " + name + "] Cannot read error code.", ioe);
      }
   }

   /**
    * Gets the name of the error code.
    *
    * @return
    *    The name of the error code, never <code>null</code>.
    */
   public String getName() {

      return _errorCodeName;
   }

   /**
    * Gets the description of the error code.
    *
    * @return
    *    The description of the error code, never <code>null</code>.
    */
   public String getDescription() {

      return _description;
   }

   /**
    * Gets the output parameter for the specified name.
    *
    * @param parameterName
    *    the name of the parameter, cannot be <code>null</code>.
    *
    * @return
    *    the parameter, never <code>null</code>.
    *
    * @throws EntityNotFoundException
    *    if the error code does not contain any output parameter with the specified name.
    *
    * @throws IllegalArgumentException
    *    if <code>parameterName == null</code>.
    */
   public ParameterSpec getOutputParameter(String parameterName)
   throws EntityNotFoundException, IllegalArgumentException {

      MandatoryArgumentChecker.check("parameterName", parameterName);
      ParameterSpec parameter = _outputParameters.get(parameterName);

      if (parameter == null) {
         throw new EntityNotFoundException("Output parameter \"" + parameterName
               + "\" not found in the error code \"" + _errorCodeName +"\".");
      }

      return parameter;
   }

   /**
    * Gets the output parameter specifications defined in the error code.
    * The key is the name of the parameter, the value is the {@link ParameterSpec} object.
    *
    * @return
    *    The output parameters specifications, never <code>null</code>.
    */
   public Map<String, ParameterSpec> getOutputParameters() {

      return _outputParameters;
   }

   /**
    * Gets the specification of the element of the output data section with the
    * specified name.
    *
    * @param elementName
    *    the name of the element, cannot be <code>null</code>.
    *
    * @return
    *   The specification of the output data section element, never <code>null</code>.
    *
    * @throws EntityNotFoundException
    *    if the error code does not define any output data element with the specified name.
    *
    * @throws IllegalArgumentException
    *    if <code>elementName == null</code>.
    */
   public DataSectionElementSpec getOutputDataSectionElement(String elementName)
   throws EntityNotFoundException, IllegalArgumentException {

      MandatoryArgumentChecker.check("elementName", elementName);

      DataSectionElementSpec element = (DataSectionElementSpec) _outputDataSectionElements.get(elementName);

      if (element == null) {
         throw new EntityNotFoundException("Output data section element \"" + elementName
               + "\" not found in the error code \"" + _errorCodeName +"\".");
      }

      return element;
   }

   /**
    * Gets the specification of the elements of the output data section.
    * The key is the name of the element, the value is the {@link DataSectionElementSpec} object.
    *
    * @return
    *   the specification of the output data section, never <code>null</code>.
    */
   public Map getOutputDataSectionElements() {

      return _outputDataSectionElements;
   }

   /**
    * Gets the type of the error code.
    *
    * @return
    *    the type of the error code.
    *
    * @since XINS 1.4.0
    */
   public Type getType() {
      return _type;
   }

   /**
    * Parses the result code file.
    *
    * @param reader
    *    the reader that contains the content of the result code file, cannot be <code>null</code>.
    *
    * @param reference
    *    the reference class used to get the type of the parameters, cannot be <code>null</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>reader == null || reference == null</code>.
    *
    * @throws IOException
    *    if the parser cannot read the content.
    *
    * @throws InvalidSpecificationException
    *    if the result code file is incorrect.
    */
   private void parseErrorCode(Reader reader, Class reference)
   throws IllegalArgumentException, IOException, InvalidSpecificationException {

      MandatoryArgumentChecker.check("reader", reader, "reference", reference);
      Element errorCode;
      try {
         errorCode = ElementFormatter.parse(reader);
      } catch (SAXException pe) {
         throw new InvalidSpecificationException("[ErrorCode: " + _errorCodeName + "] Cannot parse error code.",  pe);
      }

      // Get the type of the error code.
      String typeAsString = errorCode.getAttribute("type");
      if (typeAsString.equals("") || typeAsString.equals("technical")) {
         _type = TECHNICAL;
      } else if (typeAsString.equals("functional")) {
         _type = FUNCTIONAL;
      } else {
         throw new InvalidSpecificationException("[ErrorCode: " + _errorCodeName
               + "] Incorrect type specified.");
      }

      // Get the result from the parsed error code specification.
      ElementList descriptionElementList = new ElementList(errorCode, "description");
      if (descriptionElementList.isEmpty()) {
         throw new InvalidSpecificationException("[ErrorCode: " + _errorCodeName
               + "] No definition specified.");
      }
      Element descriptionElement = descriptionElementList.get(0);
      _description = descriptionElement.getTextContent();
      if (_description == null) {
         _description = "";
      }

      ElementList output = new ElementList(errorCode, "output");
      if (!output.isEmpty()) {

         // Output parameters
         Element outputElement = output.get(0);
         _outputParameters = FunctionSpec.parseParameters(reference, outputElement);

         // Data section
         ElementList dataSections = new ElementList(outputElement, "data");
         if (!dataSections.isEmpty()) {
            Element dataSection = dataSections.get(0);
            _outputDataSectionElements = FunctionSpec.parseDataSectionElements(reference, dataSection, dataSection);
         }
      }
   }
   /**
    * Type of an error code.
    *
    * @version $Revision: 1.35 $ $Date: 2012/03/15 21:07:39 $
    * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
    *
    * @since XINS 1.4.0
    */
   public static class Type {

      /**
       * Creates a new type.
       */
      private Type() {
      }
   }
}