/* Copyright (C) 2019-2020 Stephan Kreutzer
 *
 * This file is part of dimgra-1.
 *
 * dimgra-1 is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 or any later
 * version of the license, as published by the Free Software Foundation.
 *
 * dimgra-1 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License 3 for more details.
 *
 * You should have received a copy of the GNU Affero General Public License 3
 * along with dimgra-1. If not, see <http://www.gnu.org/licenses/>.
 */

"use strict";

function GraphEngine()
{
    let that = this;

    // Pass the ID of the node, which is a string. Empty string to get the first,
    // default node. Returns null if the node can't be obtained.
    // TODO: When requesting the default, the actual ID of the first node should
    // be in the returned Node object.
    that.getNodeById = function(id) {
        if (typeof id !== 'string' && !(id instanceof String))
        {
            id = "";
        }

        if (_loaded == false)
        {
            _load();
        }

        if (_loaded == false)
        {
            throw "Loading failed."
        }

        let nodeSource = null;

        if ("querySelectorAll" in document)
        {
            if (id.length <= 0)
            {
                // Evil browsers/web/W3C: no support for namespaces in querySelectorAll().
                // See https://www.w3.org/TR/selectors-api/#resolving-namespaces
                let result = document.querySelectorAll("node");

                for (let i = 0; i < result.length; i++)
                {
                    if (result[i].namespaceURI == "http://graphml.graphdrawing.org/xmlns")
                    {
                        nodeSource = result[i];
                        break;
                    }
                }
            }
            else
            {
                // Evil browsers/web/W3C: no support for namespaces in querySelectorAll().
                // See https://www.w3.org/TR/selectors-api/#resolving-namespaces
                let result = document.querySelectorAll("node[id='" + id + "']");
                let nodes = new Array();

                for (let i = 0; i < result.length; i++)
                {
                    if (result[i].namespaceURI == "http://graphml.graphdrawing.org/xmlns")
                    {
                        nodes.push(result[i]);
                    }
                }

                if (nodes.length <= 0)
                {
                    
                }
                else if (nodes.length > 1)
                {
                    throw "'id' attribute value '" + id + "' of a node found more than once.";
                }

                nodeSource = nodes[0];
            }
        }
        else
        {
            if (id.length <= 0)
            {
                if (_nodeMapping.length > 0)
                {
                    for (let item in _nodeMapping)
                    {
                        nodeSource = _nodeMapping[item];
                        break;
                    }
                }
                else
                {
                    return null;
                }
            }
            else
            {
                if (!(id in _nodeMapping))
                {
                    return null;
                }

                nodeSource = _nodeMapping[id];
            }
        }

        if (nodeSource == null)
        {
            return null;
        }

        if (nodeSource.hasAttribute("id") != true)
        {
            throw "node is missing its 'id' attribute.";
        }

        let title = null;
        let description = null;

        for (let i = 0; i < nodeSource.children.length; i++)
        {
            let child = nodeSource.children[i];

            if (child.namespaceURI != "http://graphml.graphdrawing.org/xmlns")
            {
                continue;
            }

            if (child.localName != "data")
            {
                continue;
            }

            if (child.hasAttribute("key") != true)
            {
                throw "data is missing its 'key' attribute.";
            }

            if (child.getAttribute("key") == "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/node-title.20200601T122500Z")
            {
                if (title != null)
                {
                    throw "node contains more than one title.";
                }

                title = child.textContent;
            }
            else if (child.getAttribute("key") == "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/node-text.20200601T122500Z")
            {
                if (description != null)
                {
                    throw "node contains more than one text.";
                }

                description = child.textContent;
            }
        }


        let parents = new Array();
        let children = new Array();

        if ("querySelectorAll" in document)
        {
            // TODO: Check nodeSource.getAttribute("id") for injection here and elsewhere.
            // Evil browsers/web/W3C: no support for namespaces in querySelectorAll().
            // See https://www.w3.org/TR/selectors-api/#resolving-namespaces
            let result = document.querySelectorAll("edge[source='" + nodeSource.getAttribute("id") + "']");

            for (let i = 0; i < result.length; i++)
            {
                if (result[i].namespaceURI != "http://graphml.graphdrawing.org/xmlns")
                {
                    continue;
                }

                if (result[i].hasAttribute("source") != true)
                {
                    throw "edge is missing its 'source' attribute.";
                }

                if (result[i].hasAttribute("target") != true)
                {
                    throw "edge is missing its 'target' attribute.";
                }

                let dimension = null;
                let dimensionEstablishing = null;

                for (let j = 0; j < result[i].children.length; j++)
                {
                    let child = result[i].children[j];

                    if (child.namespaceURI != "http://graphml.graphdrawing.org/xmlns")
                    {
                        continue;
                    }

                    if (child.localName != "data")
                    {
                        continue;
                    }

                    if (child.hasAttribute("key") != true)
                    {
                        throw "data is missing its 'key' attribute.";
                    }

                    if (child.getAttribute("key") == "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/dimension-target.20200601T122500Z")
                    {
                        if (dimension != null)
                        {
                            throw "edge contains more than one dimension.";
                        }

                        dimension = child.textContent;
                    }
                    else if (child.getAttribute("key") == "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/dimension-establishing.20200601T122500Z")
                    {
                        if (dimensionEstablishing != null)
                        {
                            throw "edge contains more than one setting about if it is establishing a dimension.";
                        }

                        if (child.textContent == "true")
                        {
                            dimensionEstablishing = true;
                        }
                        else if (child.textContent == "false")
                        {
                            dimensionEstablishing = false;
                        }
                        else
                        {
                            throw "edge contains unknown setting value '" + child.textContent + "' regarding if it's establishing a dimension, 'true' or 'false' (bool) expected.";
                        }
                    }

                    if (dimension != null &&
                        dimensionEstablishing != null)
                    {
                        break;
                    }
                }

                if (dimension === null)
                {
                    dimension = "";
                    dimensionEstablishing = false;
                }

                if (dimensionEstablishing === null)
                {
                    dimensionEstablishing = false;
                }

                children.push(new Edge(result[i].getAttribute("target"), dimension, dimensionEstablishing));
            }


            // TODO: Check nodeSource.getAttribute("id") for injection here and elsewhere.
            // Evil browsers/web/W3C: no support for namespaces in querySelectorAll().
            // See https://www.w3.org/TR/selectors-api/#resolving-namespaces
            result = document.querySelectorAll("edge[target='" + nodeSource.getAttribute("id") + "']");

            for (let i = 0; i < result.length; i++)
            {
                if (result[i].namespaceURI != "http://graphml.graphdrawing.org/xmlns")
                {
                    continue;
                }

                if (result[i].hasAttribute("source") != true)
                {
                    throw "edge is missing its 'source' attribute.";
                }

                if (result[i].hasAttribute("target") != true)
                {
                    throw "edge is missing its 'target' attribute.";
                }

                let dimension = null;
                let dimensionEstablishing = null;

                for (let j = 0; j < result[i].children.length; j++)
                {
                    let child = result[i].children[j];

                    if (child.namespaceURI != "http://graphml.graphdrawing.org/xmlns")
                    {
                        continue;
                    }

                    if (child.localName != "data")
                    {
                        continue;
                    }

                    if (child.hasAttribute("key") != true)
                    {
                        throw "data is missing its 'key' attribute.";
                    }

                    if (child.getAttribute("key") == "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/dimension-target.20200601T122500Z")
                    {
                        if (dimension != null)
                        {
                            throw "edge contains more than one dimension.";
                        }

                        dimension = child.textContent;
                    }
                    else if (child.getAttribute("key") == "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/dimension-establishing.20200601T122500Z")
                    {
                        if (dimensionEstablishing != null)
                        {
                            throw "edge contains more than one setting about if it is establishing a dimension.";
                        }

                        if (child.textContent == "true")
                        {
                            dimensionEstablishing = true;
                        }
                        else if (child.textContent == "false")
                        {
                            dimensionEstablishing = false;
                        }
                        else
                        {
                            throw "edge contains unknown setting value '" + child.textContent + "' regarding if it's establishing a dimension, 'true' or 'false' (bool) expected.";
                        }
                    }

                    if (dimension != null &&
                        dimensionEstablishing != null)
                    {
                        break;
                    }
                }

                /*
                if (dimensionEstablishing !== true)
                {
                    dimension = null;
                }
                */

                if (dimension === null)
                {
                    dimension = "";
                    dimensionEstablishing = false;
                }

                if (dimensionEstablishing === null)
                {
                    dimensionEstablishing = false;
                }

                parents.push(new Edge(result[i].getAttribute("source"), dimension, dimensionEstablishing));
            }
        }
        else
        {
            // TODO: Get nodes from the native DOM XML loader.
        }

        return new Node(nodeSource.getAttribute("id"), title, description, parents, children);
    }

    function _load()
    {
        if (_loaded != false)
        {
            return true;
        }

        if ("querySelectorAll" in document)
        {
            _loaded = true;
            return true;
        }
        else
        {
            //TODO Implement native DOM XML loader.
        }
    }

    let _loaded = false;
}

function Node(id, title, description, parents, children)
{
    // TODO: Properly check if parents, children is an Array,
    // and throw exception if not.

    let that = this;

    that.getId = function()
    {
        return _id;
    }

    that.getTitle = function()
    {
        return _title;
    }

    that.getText = function()
    {
        return _description;
    }

    that.getParents = function()
    {
        return _parents;
    }

    that.getChildren = function()
    {
        return _children;
    }

    let _id = id;
    let _title = title;
    let _description = description;
    // Edge objects.
    let _parents = parents;
    // Edge objects.
    let _children = children;
}

function Edge(id, dimension, dimensionEstablishing)
{
    let that = this;

    that.getId = function()
    {
        return _id;
    }

    that.getDimension = function()
    {
        return _dimension;
    }

    that.isDimensionEstablishing = function()
    {
        return _dimensionEstablishing;
    }

    let _id = id;
    let _dimension = dimension;
    let _dimensionEstablishing = dimensionEstablishing;
}
