/**
 * IXmlAttribute is just the standard Attr
 */
export type IXmlAttribute = Attr;

/**
 * Just a wrapper for an XML Element object.
 * It facilitates handling of XML elements by OSMD
 */
export class IXmlElement {
    public name: string;
    public value: string;
    public hasAttributes: boolean = false;
    public firstAttribute: IXmlAttribute;
    public hasElements: boolean;

    private attrs: IXmlAttribute[];
    private elem: Element;

    /**
     * Wraps 'elem' Element in a IXmlElement
     * @param elem
     */
    constructor(elem: Element) {
        if (!elem) {
            throw new Error("IXmlElement: expected Element, got undefined");
        }
        this.elem = elem;
        this.name = elem.nodeName.toLowerCase();

        if (elem.hasAttributes()) {
            this.hasAttributes = true;
            this.firstAttribute = elem.attributes[0];
        }
        this.hasElements = elem.hasChildNodes();
        // Look for a value
        if (elem.childNodes.length === 1 && elem.childNodes[0].nodeType === Node.TEXT_NODE) {
            this.value = elem.childNodes[0].nodeValue;
        } else {
            this.value = "";
        }
    }

    /**
     * Get the attribute with the given name
     * @param attributeName
     * @returns {Attr}
     */
    public attribute(attributeName: string): IXmlAttribute {
        return this.elem.attributes.getNamedItem(attributeName);
    }

    /**
     * Get all attributes
     * @returns {IXmlAttribute[]}
     */
    public attributes(): IXmlAttribute[] {
        if (!this.attrs) {
            const attributes: NamedNodeMap = this.elem.attributes;
            const attrs: IXmlAttribute[] = [];
            for (let i: number = 0; i < attributes.length; i += 1) {
                attrs.push(attributes[i]);
            }
            this.attrs = attrs;
        }
        return this.attrs;
    }

    /**
     * Get the first child element with the given node name
     * @param elementName
     * @returns {IXmlElement}
     */
    public element(elementName: string): IXmlElement {
        const nodes: NodeList = this.elem.childNodes;
        for (let i: number = 0, length: number = nodes.length; i < length; i += 1) {
            const node: Node = nodes[i];
            if (node.nodeType === Node.ELEMENT_NODE && node.nodeName.toLowerCase() === elementName) {
                return new IXmlElement(node as Element);
            }
        }
    }

    /**
     * Get the children with the given node name (if given, otherwise all child elements)
     * @param nodeName
     * @returns {IXmlElement[]}
     */
    public elements(nodeName?: string): IXmlElement[] {
        const nodes: NodeList = this.elem.childNodes;
        const ret: IXmlElement[] = [];
        const nameUnset: boolean = !nodeName;
        if (!nameUnset) {
            nodeName = nodeName.toLowerCase();
        }
        for (let i: number = 0; i < nodes.length; i += 1) {
            const node: Node = nodes[i];
            if (node.nodeType === Node.ELEMENT_NODE &&
                (nameUnset || node.nodeName.toLowerCase() === nodeName)
            ) {
                ret.push(new IXmlElement(node as Element));
            }
        }
        return ret;
    }

    /**
     * Get the first child element with the given node name
     * with all the children of consequent child elements with the same node name.
     * for example two <notations> tags will be combined for better processing
     * @param elementName
     * @returns {IXmlElement}
     */
    public combinedElement(elementName: string): IXmlElement {
        const nodes: NodeList = this.elem.childNodes;
        if (nodes.length > 0) {
            let firstNode: Node;
            for (let i: number = 0, length: number = nodes.length; i < length; i += 1) {
                const otherNode: Node = nodes[i];
                if (otherNode.nodeType === Node.ELEMENT_NODE && otherNode.nodeName.toLowerCase() === elementName) {
                    if (firstNode) {
                        const childNodes: NodeList = otherNode.childNodes;
                        for (let j: number = 0, numChildNodes: number = childNodes.length; j < numChildNodes; j += 1) {
                            const childNode: Node = childNodes[j];
                            firstNode.appendChild(childNode.cloneNode(true));
                        }
                    } else {
                        firstNode = otherNode;
                    }
                }
            }
            if (firstNode) {
                return new IXmlElement(firstNode as Element);
            }
        }
    }
}
