'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;

};