/*
* IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
*
* http://izpack.org/
* http://izpack.codehaus.org/
*
* Copyright (c) 2008, 2009 Anthonin Bonnefoy
* Copyright (c) 2008, 2009 David Duponchel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.izforge.izpack.adaptator.impl;

import com.izforge.izpack.adaptator.IXMLElement;
import org.w3c.dom.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;
import java.util.regex.Pattern;

/**
 * Implementation of the adaptator between nanoXml and javax
 *
 * @author Anthonin Bonnefoy
 * @author David Duponchel
 */
public class XMLElementImpl implements IXMLElement
{

    /**
     * The dom element embedded by the XMLElement
     */
    private Element element;

    /**
     * A flag to notice any changement made to the element.
     * It is used to generate the childrenVector.
     */
    private boolean hasChanged = true;

    /**
     * Vector of the children elements.
     * It is generated as it is called.
     */
    private Vector<IXMLElement> childrenVector;

    /**
     * Create a new root element in a new document.
     *
     * @param name Name of the root element
     */
    public XMLElementImpl(String name)
    {
        Document document;
        try
        {
            // Création d'un nouveau DOM
            DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder constructeur = documentFactory.newDocumentBuilder();
            document = constructeur.newDocument();
            // Propriétés du DOM
            document.setXmlVersion("1.0");
            element = document.createElement(name);
            document.appendChild(element);
        } catch (ParserConfigurationException e)
        {
            e.printStackTrace();
        }
    }

    /**
     * Constructor which create a root element in the given document
     *
     * @param name       Name of the root element
     * @param inDocument The document in which to create the xml
     */
    public XMLElementImpl(String name, Document inDocument)
    {
        element = inDocument.createElement(name);
    }

    /**
     * Create a element in the same document of the given element
     *
     * @param name             Name of the element
     * @param elementReference Reference of an existing xml. It is used to generate an xmlElement on the same document.
     */
    public XMLElementImpl(String name, IXMLElement elementReference)
    {
        element = elementReference.getElement().getOwnerDocument().createElement(name);
    }

    /**
     * Constructor saving the passed node
     *
     * @param node Node to save inside the XMLElement
     */
    public XMLElementImpl(Node node)
    {
        if (!(node instanceof Element))
        {
            throw new IllegalArgumentException("The node should be an instance of Element");
        }
        this.element = (Element) node;
    }

    public String getName()
    {
        return element.getNodeName();
    }

    public void addChild(IXMLElement child)
    {
        hasChanged = true;
        element.appendChild(child.getElement());
    }

    public void removeChild(IXMLElement child)
    {
        hasChanged = true;
        element.removeChild(child.getElement());
    }

    public boolean hasChildren()
    {
        for (Node child = element.getFirstChild(); child != null; child = child.getNextSibling())
        {
            if (child.getNodeType() == Node.ELEMENT_NODE)
            {
                return true;
            }
        }
        return false;
    }

    private void initChildrenVector()
    {
        if (hasChanged)
        {
            hasChanged = false;
            childrenVector = new Vector<IXMLElement>();
            for (Node child = element.getFirstChild(); child != null; child = child.getNextSibling())
            {
                if (child.getNodeType() == Node.ELEMENT_NODE)
                {
                    childrenVector.add(new XMLElementImpl(child));
                }
            }
        }
    }

    public int getChildrenCount()
    {
        initChildrenVector();
        return childrenVector.size();
    }

    public Vector<IXMLElement> getChildren()
    {
        initChildrenVector();
        return childrenVector;
    }

    public IXMLElement getChildAtIndex(int index)
    {
        initChildrenVector();
        return childrenVector.get(index);
    }

    public IXMLElement getFirstChildNamed(String name)
    {
        XMLElementImpl res = null;
        NodeList nodeList = element.getElementsByTagName(name);
        if (nodeList.getLength() > 0)
        {
            res = new XMLElementImpl(nodeList.item(0));
        }
        return res;
    }

    public Vector<IXMLElement> getChildrenNamed(String name)
    {
        Vector<IXMLElement> res = new Vector<IXMLElement>();
        NodeList nodeList = element.getElementsByTagName(name);
        Element child;
        for (int i = 0; i < nodeList.getLength(); i++)
        {
            child = (Element) nodeList.item(i);
            res.add(new XMLElementImpl(child));
        }
        return res;
    }

    public String getAttribute(String name)
    {
        return this.getAttribute(name, null);
    }

    public String getAttribute(String name, String defaultValue)
    {
        Node attribute = element.getAttributes().getNamedItem(name);
        if (attribute != null)
        {
            return attribute.getNodeValue();
        }
        return defaultValue;
    }

    public void setAttribute(String name, String value)
    {
        NamedNodeMap attributes = element.getAttributes();
        Attr attribute = element.getOwnerDocument().createAttribute(name);
        attribute.setValue(value);
        attributes.setNamedItem(attribute);
    }

    public void removeAttribute(String name)
    {
        this.element.getAttributes().removeNamedItem(name);
    }

    public Enumeration enumerateAttributeNames()
    {
        NamedNodeMap namedNodeMap = element.getAttributes();
        Properties properties = new Properties();
        for (int i = 0; i < namedNodeMap.getLength(); i++)
        {
            Node node = namedNodeMap.item(i);
            properties.put(node.getNodeName(), node.getNodeValue());
        }
        return properties.keys();
    }

    public boolean hasAttribute(String name)
    {
        return (this.element.getAttributes().getNamedItem(name) != null);
    }

    public Properties getAttributes()
    {
        Properties properties = new Properties();
        NamedNodeMap namedNodeMap = this.element.getAttributes();
        for (int i = 0; i < namedNodeMap.getLength(); ++i)
        {
            properties.put(namedNodeMap.item(i).getNodeName(), namedNodeMap.item(i).getNodeValue());
        }
        return properties;
    }

    public int getLineNr()
    {
        Object ln = element.getUserData("ln");
        if (ln == null)
        {
            return NO_LINE;
        }
        try
        {
            return (Integer) element.getUserData("ln");
        }
        catch (ClassCastException e)
        {
            return NO_LINE;
        }
    }

    public String getContent()
    {
        StringBuilder sb = new StringBuilder();
        String content;
        Node child = this.element.getFirstChild();

        // no error if there are children
        boolean err = (child == null);

        // pattern : only whitespace characters
        Pattern p = Pattern.compile("^\\s+$");

        while (!err && child != null)
        {
            content = child.getNodeValue();
            if (child.getNodeType() == Node.TEXT_NODE)
            {
                // text node : nanoXML ignores it if it's only whitespace characters.
                if (content != null && !p.matcher(content).matches())
                {
                    sb.append(content);
                }
            }
            else if (child.getNodeType() == Node.CDATA_SECTION_NODE)
            {
                sb.append(content);
            }
            // neither CDATA nor text : real nested element !
            else
            {
                err = true;
            }
            child = child.getNextSibling();
        }
        return (err) ? null : sb.toString().trim();
    }

    public void setContent(String content)
    {
        Node child;
        while ((child = this.element.getFirstChild()) != null)
        {
            this.element.removeChild(child);
        }
        element.appendChild(element.getOwnerDocument().createTextNode(content));
    }

    public Node getElement()
    {
        return element;
    }

    @Override
    public String toString()
    {
        return element.getNodeName() + " " + element.getNodeValue();
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj instanceof IXMLElement)
        {
            IXMLElement o = (IXMLElement) obj;
            Element elem = (Element) o.getElement();
            Node child2 = elem.getFirstChild();
            for (Node child = element.getFirstChild(); child != null && child2 != null; child = child.getNextSibling())
            {
                if (!child.equals(child2))
                {
                    return false;
                }
                child2.getNextSibling();
            }
            return true;
        }
        return false;
    }
}
