package org.jboss.seam.maven.helper;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.maven.plugin.logging.Log;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;

/**
 * 
 * Generates JSF taglib descriptor xml
 * 
 * @author Rafael Benevides <https://community.jboss.org/people/rafabene>
 * @author Marek Novotny <https://community.jboss.org/people/manaRH>
 *
 */
public class XMLGenerator
{

   private Log log;

   public XMLGenerator(Log log)
   {
      this.log = log;
   }

   public Element getFaceletsTagElementFromFacesconfig(File xml, String tagName, String converterOrValidator) throws Exception
   {
      log.info("Generating taglib from " + xml);

      DocumentBuilder dstDB = DocumentBuilderFactory.newInstance().newDocumentBuilder();
      DocumentBuilder srcDB = DocumentBuilderFactory.newInstance().newDocumentBuilder();
      Document destDocument = dstDB.newDocument();
      Document srcDocument = srcDB.parse(xml);

      XPath xpath = XPathFactory.newInstance().newXPath();

      Element tagElement = destDocument.createElement("tag");
      Node tagNode = destDocument.appendChild(tagElement);
      Element tagNameElement = destDocument.createElement("tag-name");
      tagNameElement.setTextContent(tagName);
      tagNode.appendChild(tagNameElement);

      Element component = destDocument.createElement(converterOrValidator);
      Node componentNode = tagNode.appendChild(component);

      Element description = destDocument.createElement("description");
      String descriptionContent = xpath.evaluate("//" + converterOrValidator + "/description/text()", srcDocument);
      description.setTextContent(descriptionContent);

      componentNode.appendChild(description);

      Element componentType = destDocument.createElement(converterOrValidator + "-id");
      String componentTypeContent = xpath.evaluate("//" + converterOrValidator + "-id/text()", srcDocument);
      componentType.setTextContent(componentTypeContent);

      componentNode.appendChild(componentType);

      NodeList propertyNodes = (NodeList) xpath.evaluate("//property", srcDocument, XPathConstants.NODESET);

      for (int i = 0; i < propertyNodes.getLength(); i++)
      {
         Node nNode = propertyNodes.item(i);
         if (nNode.getNodeType() == Node.ELEMENT_NODE)
         {

            Element eElement = (Element) nNode;
            String propDescription = getTagValue("description", eElement);
            String propName = getTagValue("property-name", eElement);
            String propClass = getTagValue("property-class", eElement);

            Element attribute = destDocument.createElement("attribute");
            Element atributeDescription = destDocument.createElement("description");
            atributeDescription.setTextContent(propDescription);
            attribute.appendChild(atributeDescription);

            Element attributeName = destDocument.createElement("name");
            attributeName.setTextContent(propName);
            attribute.appendChild(attributeName);

            Element attributeClass = destDocument.createElement("type");
            attributeClass.setTextContent(propClass);
            attribute.appendChild(attributeClass);

            tagElement.appendChild(attribute);
         }

      }

      return tagElement;

   }

   private static String getTagValue(String sTag, Element eElement)
   {
      NodeList nlList = eElement.getElementsByTagName(sTag).item(0).getChildNodes();

      Node nValue = (Node) nlList.item(0);

      return nValue.getNodeValue();
   }

   private void writeFileContent(File outXML, String content) throws IOException
   {
      log.info("Updating " + outXML);
      FileOutputStream fos = new FileOutputStream(outXML);
      fos.write(content.getBytes());
      fos.close();
   }

   public void updateFile(File outXML, List<Element> tags) throws Exception
   {
      DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
      Document doc = docBuilder.parse(outXML);

      for (Element tag : tags)
      {
         Comment comment = doc.createComment("Converter added by seam-cdk-helper plugin");
         Node firstchild = doc.getFirstChild();
         firstchild.appendChild(comment);
         firstchild.appendChild(doc.importNode(tag, true));
      }

      // ///////////////
      // Output the XML
      TransformerFactory tf = TransformerFactory.newInstance();
      tf.setAttribute("indent-number", new Integer(4));

      Transformer transformer = tf.newTransformer();
      transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
      transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
      transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

      DOMImplementation domImplementation = doc.getImplementation();
      if (domImplementation.hasFeature("LS", "3.0") && domImplementation.hasFeature("Core", "2.0"))
      {
         DOMImplementationLS domImplementationLS = (DOMImplementationLS) domImplementation.getFeature("LS", "3.0");
         LSSerializer lsSerializer = domImplementationLS.createLSSerializer();
         DOMConfiguration domConfiguration = lsSerializer.getDomConfig();
         if (domConfiguration.canSetParameter("format-pretty-print", true))
         {
            lsSerializer.getDomConfig().setParameter("format-pretty-print", true);
            LSOutput lsOutput = domImplementationLS.createLSOutput();
            lsOutput.setEncoding("UTF-8");
            StringWriter stringWriter = new StringWriter();
            lsOutput.setCharacterStream(stringWriter);
            lsSerializer.write(doc, lsOutput);
            writeFileContent(outXML, stringWriter.toString());
         }
         else
         {
            throw new RuntimeException("DOMConfiguration 'format-pretty-print' parameter isn't settable.");
         }
      }
      else
      {
         throw new RuntimeException("DOM 3.0 LS and/or DOM 2.0 Core not supported.");
      }
   }
}
