/**
 * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.web.bundle;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.util.ArgumentChecker;

/**
 * Parses a bundle XML file into a bundle manager.
 */
public class BundleParser {

  /** Logger. */
  private static final Logger s_logger = LoggerFactory.getLogger(BundleParser.class);

  /** The bundle element tag name. */
  private static final String BUNDLE_ELEMENT = "bundle";
  /** The fragment element tag name. */
  private static final String FRAGMENT_ELEMENT = "fragment";
  /** The ID attribute name. */
  private static final String ID_ATTR = "id";

  /**
   * The base directory for the fragments.
   */
  private File _baseDir;
  /**
   * The bundle manager to populate.
   */
  private final BundleManager _bundleManager = new BundleManager();
  /**
   * The cache of elements.
   */
  private final Map<String, Element> _elementsByIdMap = new HashMap<String, Element>(); 

  /**
   * Creates a parser
   * 
   */
  public BundleParser() {
  }
    
  /**
   * Gets the baseDir.
   * @return the baseDir
   */
  public File getBaseDir() {
    return _baseDir;
  }

  /**
   * Sets the baseDir.
   * @param baseDir  the baseDir
   */
  public void setBaseDir(File baseDir) {
    validateFile(baseDir);
    _bundleManager.setBaseDir(baseDir);
    _baseDir = baseDir;
  }

  private void validateFile(File baseDir) {
    if (baseDir != null) {
      ArgumentChecker.isTrue(baseDir.exists(), baseDir + "does not exists");
    }
  }

  //-------------------------------------------------------------------------
  /**
   * Parses the XML file, returning the bundle manager.
   * 
   * @param xmlStream the XML input stream, not null
   * @return the parsed bundle manager, not null
   */
  public BundleManager parse(InputStream xmlStream) {
    ArgumentChecker.notNull(xmlStream, "xml inputstream");
    DocumentBuilder builder = getDocumentBuilder();
    if (builder != null) {
      try {
        Document document = builder.parse(xmlStream);
        processXMLDocument(document);
      } catch (SAXException ex) {
        throw new OpenGammaRuntimeException("unable to parse xml file", ex);
      } catch (IOException ex) {
        throw new OpenGammaRuntimeException("unable to read xml file", ex);
      }
    }
    return _bundleManager;
  }

  private void processXMLDocument(Document document) {
    buildAllElements(document);
    for (Element element : _elementsByIdMap.values()) {
      addToManager(element);
    }
  }

  private void addToManager(Element element) {
    String idAttr = element.getAttribute(ID_ATTR);
    Bundle bundle = new Bundle(idAttr);
    NodeList childNodes = element.getChildNodes();
    for (int i = 0; i < childNodes.getLength(); i++) {
      Node node = childNodes.item(i);
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        Element childElement = (Element) node;
        if (childElement.getNodeName().equals(BUNDLE_ELEMENT)) {
          processRefBundle(bundle, childElement);
        }
        if (childElement.getNodeName().equals(FRAGMENT_ELEMENT)) {
          processFragment(bundle, childElement);
        } 
      }
    }
    _bundleManager.addBundle(bundle);
  }

  private void processFragment(Bundle bundle, Element element) {
    String fragment = element.getTextContent();
    if (isValidFragment(fragment)) {
      bundle.addChildNode(createBundleFragment(fragment));
    }
  }

  private boolean isValidFragment(String fragment) {
    if (StringUtils.isNotBlank(fragment)) {
      return true;
    }
    throw new OpenGammaRuntimeException("invalid fragment value while parsing bundle xml file");
  }


  private BundleNode createBundleFragment(String fragment) {
    return new Fragment(new File(_baseDir, fragment));
  }

  private void processRefBundle(Bundle bundle, Element element) {
    String idRef = element.getAttribute("idref");
    if (isValidIdRef(idRef)) {
      Bundle refBundle = _bundleManager.getBundle(idRef);
      if (refBundle == null) {
        Element refElement = _elementsByIdMap.get(idRef);
        // this can cause infinite loop if we have circular reference
        addToManager(refElement);
        refBundle = _bundleManager.getBundle(idRef);
      }
      bundle.addChildNode(refBundle);
    }
  }

  private boolean isValidIdRef(String idRef) {
    if (StringUtils.isNotBlank(idRef) && idRefExists(idRef)) {
      return true;
    }
    throw new OpenGammaRuntimeException(" invalid idref ["  + idRef + "]");
  }

  private boolean idRefExists(String idRef) {
    return _elementsByIdMap.get(idRef) != null;
  }

  private void buildAllElements(Document document) {
    Element rootElement = document.getDocumentElement();
    if (isValidRootElement(rootElement)) {
      rootElement.normalize();
      NodeList childNodes = rootElement.getChildNodes();
      for (int i = 0; i < childNodes.getLength(); i++) {
        Node node = childNodes.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
          Element element = (Element) node;
          if (isValidBundleElement(element)) {
            String idAttr = element.getAttribute(ID_ATTR);
            if (_elementsByIdMap.get(idAttr) == null) {
              _elementsByIdMap.put(idAttr, element);
            } else {
              throw new OpenGammaRuntimeException("parsing bundle XML : duplicate id attribute in " + node.getNodeName());
            }
          }
        }
      }
    }
  }

  private boolean isValidRootElement(Element rootElement) {
    if (rootElement.getNodeName().equals("uiResourceConfig")) {
      return true;
    }
    throw new OpenGammaRuntimeException("parsing bundle XML : invalid root element " + rootElement.getNodeName());
  }

  private boolean isValidBundleElement(Element element) {
    return isBundleElement(element) && hasChildren(element) && hasValidId(element);
  }

  private boolean hasValidId(Element element) {
    if (element.hasAttribute(ID_ATTR) && StringUtils.isNotBlank(element.getAttribute(ID_ATTR))) {
      return true;
    } 
    throw new OpenGammaRuntimeException("parsing bundle XML : bundle element needs id attribute");
  }

  private boolean hasChildren(Element element) {
    if (element.hasChildNodes()) {
      return true;
    } 
    throw new OpenGammaRuntimeException("parsing bundle XML : missing children elements in bundle");
  }

  private boolean isBundleElement(Element element) {
    if (element.getNodeName().equals(BUNDLE_ELEMENT)) {
      return true;
    } 
    throw new OpenGammaRuntimeException("parsing bundle XML : element not a bundle");
  }

  private DocumentBuilder getDocumentBuilder() {
    DocumentBuilder builder = null;
    DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
    try {
      builder = builderFactory.newDocumentBuilder();
    } catch (ParserConfigurationException e) {
      s_logger.warn("Unable to create a DOM parser", e);
    }
    return builder;
  }

}

@@ -2095,16 +2095,17 @@
 eDir + %22
+ 
 does not
@@ -2110,17 +2110,16 @@
 ot exist
-s
 %22);%0A    

Correct error message when folder doesn't exist.