'use strict';



var Doctype = require('../common/doctype');



//Conversion tables for DOM Level1 structure emulation

var nodeTypes = {

    element: 1,

    text: 3,

    cdata: 4,

    comment: 8

};



var nodePropertyShorthands = {

    tagName: 'name',

    childNodes: 'children',

    parentNode: 'parent',

    previousSibling: 'prev',

    nextSibling: 'next',

    nodeValue: 'data'

};



//Node

var Node = function (props) {

    for (var key in props) {

        if (props.hasOwnProperty(key))

            this[key] = props[key];

    }

};



Node.prototype = {

    get firstChild() {

        var children = this.children;

        return children && children[0] || null;

    },



    get lastChild() {

        var children = this.children;

        return children && children[children.length - 1] || null;

    },



    get nodeType() {

        return nodeTypes[this.type] || nodeTypes.element;

    }

};



Object.keys(nodePropertyShorthands).forEach(function (key) {

    var shorthand = nodePropertyShorthands[key];



    Object.defineProperty(Node.prototype, key, {

        get: function () {

            return this[shorthand] || null;

        },

        set: function (val) {

            this[shorthand] = val;

            return val;

        }

    });

});





//Node construction

exports.createDocument =

exports.createDocumentFragment = function () {

    return new Node({

        type: 'root',

        name: 'root',

        parent: null,

        prev: null,

        next: null,

        children: []

    });

};



exports.createElement = function (tagName, namespaceURI, attrs) {

    var attribs = {},

        attribsNamespace = {},

        attribsPrefix = {};



    for (var i = 0; i < attrs.length; i++) {

        var attrName = attrs[i].name;



        attribs[attrName] = attrs[i].value;

        attribsNamespace[attrName] = attrs[i].namespace;

        attribsPrefix[attrName] = attrs[i].prefix;

    }



    return new Node({

        type: tagName === 'script' || tagName === 'style' ? tagName : 'tag',

        name: tagName,

        namespace: namespaceURI,

        attribs: attribs,

        'x-attribsNamespace': attribsNamespace,

        'x-attribsPrefix': attribsPrefix,

        children: [],

        parent: null,

        prev: null,

        next: null

    });

};



exports.createCommentNode = function (data) {

    return new Node({

        type: 'comment',

        data: data,

        parent: null,

        prev: null,

        next: null

    });

};



var createTextNode = function (value) {

    return new Node({

        type: 'text',

        data: value,

        parent: null,

        prev: null,

        next: null

    });

};





//Tree mutation

exports.setDocumentType = function (document, name, publicId, systemId) {

    var data = Doctype.serializeContent(name, publicId, systemId),

        doctypeNode = null;



    for (var i = 0; i < document.children.length; i++) {

        if (document.children[i].type === 'directive' && document.children[i].name === '!doctype') {

            doctypeNode = document.children[i];

            break;

        }

    }



    if (doctypeNode) {

        doctypeNode.data = data;

        doctypeNode['x-name'] = name;

        doctypeNode['x-publicId'] = publicId;

        doctypeNode['x-systemId'] = systemId;

    }



    else {

        appendChild(document, new Node({

            type: 'directive',

            name: '!doctype',

            data: data,

            'x-name': name,

            'x-publicId': publicId,

            'x-systemId': systemId

        }));

    }



};



exports.setQuirksMode = function (document) {

    document.quirksMode = true;

};



exports.isQuirksMode = function (document) {

    return document.quirksMode;

};



var appendChild = exports.appendChild = function (parentNode, newNode) {

    var prev = parentNode.children[parentNode.children.length - 1];



    if (prev) {

        prev.next = newNode;

        newNode.prev = prev;

    }



    parentNode.children.push(newNode);

    newNode.parent = parentNode;

};



var insertBefore = exports.insertBefore = function (parentNode, newNode, referenceNode) {

    var insertionIdx = parentNode.children.indexOf(referenceNode),

        prev = referenceNode.prev;



    if (prev) {

        prev.next = newNode;

        newNode.prev = prev;

    }



    referenceNode.prev = newNode;

    newNode.next = referenceNode;



    parentNode.children.splice(insertionIdx, 0, newNode);

    newNode.parent = parentNode;

};



exports.detachNode = function (node) {

    if (node.parent) {

        var idx = node.parent.children.indexOf(node),

            prev = node.prev,

            next = node.next;



        node.prev = null;

        node.next = null;



        if (prev)

            prev.next = next;



        if (next)

            next.prev = prev;



        node.parent.children.splice(idx, 1);

        node.parent = null;

    }

};



exports.insertText = function (parentNode, text) {

    var lastChild = parentNode.children[parentNode.children.length - 1];



    if (lastChild && lastChild.type === 'text')

        lastChild.data += text;

    else

        appendChild(parentNode, createTextNode(text));

};



exports.insertTextBefore = function (parentNode, text, referenceNode) {

    var prevNode = parentNode.children[parentNode.children.indexOf(referenceNode) - 1];



    if (prevNode && prevNode.type === 'text')

        prevNode.data += text;

    else

        insertBefore(parentNode, createTextNode(text), referenceNode);

};



exports.adoptAttributes = function (recipientNode, attrs) {

    for (var i = 0; i < attrs.length; i++) {

        var attrName = attrs[i].name;



        if (typeof recipientNode.attribs[attrName] === 'undefined') {

            recipientNode.attribs[attrName] = attrs[i].value;

            recipientNode['x-attribsNamespace'][attrName] = attrs[i].namespace;

            recipientNode['x-attribsPrefix'][attrName] = attrs[i].prefix;

        }

    }

};





//Tree traversing

exports.getFirstChild = function (node) {

    return node.children[0];

};



exports.getChildNodes = function (node) {

    return node.children;

};



exports.getParentNode = function (node) {

    return node.parent;

};



exports.getAttrList = function (node) {

    var attrList = [];



    for (var name in node.attribs) {

        if (node.attribs.hasOwnProperty(name)) {

            attrList.push({

                name: name,

                value: node.attribs[name],

                namespace: node['x-attribsNamespace'][name],

                prefix: node['x-attribsPrefix'][name]

            });

        }

    }



    return attrList;

};





//Node data

exports.getTagName = function (element) {

    return element.name;

};



exports.getNamespaceURI = function (element) {

    return element.namespace;

};



exports.getTextNodeContent = function (textNode) {

    return textNode.data;

};



exports.getCommentNodeContent = function (commentNode) {

    return commentNode.data;

};



exports.getDocumentTypeNodeName = function (doctypeNode) {

    return doctypeNode['x-name'];

};



exports.getDocumentTypeNodePublicId = function (doctypeNode) {

    return doctypeNode['x-publicId'];

};



exports.getDocumentTypeNodeSystemId = function (doctypeNode) {

    return doctypeNode['x-systemId'];

};





//Node types

exports.isTextNode = function (node) {

    return node.type === 'text';

};



exports.isCommentNode = function (node) {

    return node.type === 'comment';

};



exports.isDocumentTypeNode = function (node) {

    return node.type === 'directive' && node.name === '!doctype';

};



exports.isElementNode = function (node) {

    return !!node.attribs;

};