/* Copyright (C) 2020-2022 Stephan Kreutzer
 *
 * This file is part of hyperdex_1, a submodule of the
 * digital_publishing_workflow_tools package.
 *
 * hyperdex_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,
 * as published by the Free Software Foundation.
 *
 * hyperdex_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 hyperdex_1. If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * @file $/htx/gui/hyperdex/hyperdex_1/DataFileLoader.java
 * @brief Loads GraphML using additional semantics to organize values into
 *     interrelated dimensions.
 * @author Stephan Kreutzer
 * @since 2020-09-27
 */



import java.io.InputStream;
import java.util.List;
import java.util.LinkedList;
import java.util.Locale;
import java.util.ResourceBundle;
import java.text.MessageFormat;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.events.XMLEvent;
import javax.xml.namespace.QName;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.XMLStreamException;
import java.util.Map;
import java.util.HashMap;
import java.io.UnsupportedEncodingException;



class DataFileLoader
{
    public DataFileLoader()
    {

    }

    public int load(InputStream in)
    {
        this.nodes.clear();
        this.notes.clear();
        this.edges.clear();

        try
        {
            XMLInputFactory inputFactory = XMLInputFactory.newInstance();
            inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
            inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);

            XMLEventReader eventReader = inputFactory.createXMLEventReader(in);

            while (eventReader.hasNext() == true)
            {
                XMLEvent event = eventReader.nextEvent();

                if (event.isStartElement() == true)
                {
                    String tagName = event.asStartElement().getName().getLocalPart();

                    if (tagName.equals("node") == true)
                    {
                        String nodeId = null;

                        {
                            Attribute attributeId = event.asStartElement().getAttributeByName(new QName("id"));

                            if (attributeId == null)
                            {
                                throw constructTermination("messageInputIsMissingAnAttribute", null, null, tagName, "id");
                            }

                            nodeId = attributeId.getValue();
                        }

                        StringBuilder sbTitle = null;
                        StringBuilder sbText = null;
                        boolean inTitle = false;
                        boolean inText = false;

                        while (eventReader.hasNext() == true)
                        {
                            event = eventReader.nextEvent();

                            if (event.isStartElement() == true)
                            {
                                tagName = event.asStartElement().getName().getLocalPart();

                                if (tagName.equals("data") == true)
                                {
                                    StartElement startElement = event.asStartElement();
                                    Attribute attributeKey = startElement.getAttributeByName(new QName("key"));

                                    if (attributeKey == null)
                                    {
                                        throw constructTermination("messageInputIsMissingAnAttribute", null, null, tagName, "key");
                                    }

                                    if (inTitle == true ||
                                        inText == true)
                                    {
                                        throw constructTermination("messageInputEncounteredNestedData", null, null, tagName);
                                    }

                                    if (attributeKey.getValue().equals(HTXSCHEMEID_DIMGRA1_NODETITLE) == true)
                                    {
                                        if (sbTitle != null)
                                        {
                                            throw constructTermination("messageInputDataDuplicate", null, null, tagName, "key", attributeKey.getValue(), "node", nodeId);
                                        }

                                        inTitle = true;
                                        sbTitle = new StringBuilder();
                                    }
                                    else if (attributeKey.getValue().equals(HTXSCHEMEID_DIMGRA1_NODETEXT) == true)
                                    {
                                        if (sbText != null)
                                        {
                                            throw constructTermination("messageInputDataDuplicate", null, null, tagName, "key", attributeKey.getValue(), "node", nodeId);
                                        }

                                        inText = true;
                                        sbText = new StringBuilder();
                                    }
                                }
                            }
                            else if (event.isCharacters() == true)
                            {
                                if (inTitle == true)
                                {
                                    sbTitle.append(event.asCharacters().getData());
                                }
                                else if (inText == true)
                                {
                                    sbText.append(event.asCharacters().getData());
                                }
                            }
                            else if (event.isEndElement() == true)
                            {
                                tagName = event.asEndElement().getName().getLocalPart();

                                if (tagName.equals("data") == true)
                                {
                                    inTitle = false;
                                    inText = false;
                                }
                                else if (tagName.equals("node") == true)
                                {
                                    inTitle = false;
                                    inText = false;

                                    break;
                                }
                            }
                        }

                        if (sbTitle == null)
                        {
                            throw constructTermination("messageInputElementMissing", null, null, "node", "data", "key", HTXSCHEMEID_DIMGRA1_NODETITLE);
                        }

                        this.nodes.add(new Node(nodeId, sbTitle.toString()));

                        if (sbText != null)
                        {
                            if (this.notes.containsKey(nodeId) == true)
                            {
                                throw constructTermination("messageInputNodeTextDuplicate", null, null, "node", "data", "key", HTXSCHEMEID_DIMGRA1_NODETEXT, nodeId);
                            }

                            if (sbText.toString().isEmpty() == false)
                            {
                                this.notes.put(nodeId, sbText.toString());
                            }
                        }
                    }
                    else if (tagName.equals("edge") == true)
                    {
                        Attribute attributeSource = event.asStartElement().getAttributeByName(new QName("source"));

                        if (attributeSource == null)
                        {
                            throw constructTermination("messageInputIsMissingAnAttribute", null, null, tagName, "source");
                        }

                        Attribute attributeTarget = event.asStartElement().getAttributeByName(new QName("target"));

                        if (attributeTarget == null)
                        {
                            throw constructTermination("messageInputIsMissingAnAttribute", null, null, tagName, "target");
                        }

                        Map<String, String> data = new HashMap<String, String>();

                        while (eventReader.hasNext() == true)
                        {
                            event = eventReader.nextEvent();

                            if (event.isStartElement() == true)
                            {
                                tagName = event.asStartElement().getName().getLocalPart();

                                if (tagName.equals("data") == true)
                                {
                                    Attribute attributeKey = event.asStartElement().getAttributeByName(new QName("key"));

                                    if (attributeKey == null)
                                    {
                                        continue;
                                    }

                                    if (attributeKey.getValue().equals(HTXSCHEMEID_DIMGRA1_DIMENSIONTARGET) != true &&
                                        attributeKey.getValue().equals(HTXSCHEMEID_DIMGRA1_DIMENSIONESTABLISHING) != true)
                                    {
                                        continue;
                                    }

                                    StringBuilder sb = null;

                                    while (eventReader.hasNext() == true)
                                    {
                                        event = eventReader.nextEvent();

                                        if (event.isCharacters() == true)
                                        {
                                            if (sb == null)
                                            {
                                                sb = new StringBuilder();
                                            }

                                            sb.append(event.asCharacters().getData());
                                        }
                                        else if (event.isEndElement() == true)
                                        {
                                            tagName = event.asEndElement().getName().getLocalPart();

                                            if (tagName.equals("data") == true)
                                            {
                                                if (sb == null)
                                                {
                                                    throw constructTermination("messageInputEdgeDataIsMissingOrEmpty", null, null, "edge", "data");
                                                }

                                                if (data.containsKey(attributeKey.getValue()) == true)
                                                {
                                                    throw constructTermination("messageInputEdgeDataDuplicateKey", null, null, "edge", "data", "key", attributeKey.getValue());
                                                }

                                                data.put(attributeKey.getValue(), sb.toString());

                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                            else if (event.isEndElement() == true)
                            {
                                tagName = event.asEndElement().getName().getLocalPart();

                                if (tagName.equals("edge") == true)
                                {
                                    break;
                                }
                            }
                        }

                        if (data.containsKey(HTXSCHEMEID_DIMGRA1_DIMENSIONTARGET) != true)
                        {
                            throw constructTermination("messageInputEdgeDataKeyAttributeValueDimensionTargetMissing", null, null, "edge", "data", "key", HTXSCHEMEID_DIMGRA1_DIMENSIONTARGET);
                        }

                        String dimension = data.get(HTXSCHEMEID_DIMGRA1_DIMENSIONTARGET);
                        boolean isDimensionEstablishing = false;

                        if (data.containsKey(HTXSCHEMEID_DIMGRA1_DIMENSIONESTABLISHING) == true)
                        {
                            isDimensionEstablishing = data.get(HTXSCHEMEID_DIMGRA1_DIMENSIONESTABLISHING).equals("true");
                        }

                        Edge edge = new Edge(attributeSource.getValue(),
                                             attributeTarget.getValue(),
                                             dimension,
                                             isDimensionEstablishing);

                        this.edges.add(edge);
                    }
                }
            }
        }
        catch (XMLStreamException ex)
        {
            throw constructTermination("messageInputErrorWhileReading", ex, null);
        }

        return 0;
    }

    public List<Node> GetNodes()
    {
        return this.nodes;
    }

    public Map<String, String> GetNotes()
    {
        return this.notes;
    }

    public List<Edge> GetEdges()
    {
        return this.edges;
    }

    public ProgramTerminationException constructTermination(String id, Exception cause, String message, Object ... arguments)
    {
        if (message == null)
        {
            if (arguments == null)
            {
                message = "hyperdex_1 (DataFileLoader): " + getI10nString(id);
            }
            else
            {
                message = "hyperdex_1 (DataFileLoader): " + getI10nStringFormatted(id, arguments);
            }
        }

        return new ProgramTerminationException(id, cause, message, L10N_BUNDLE, arguments);
    }

    /**
     * @brief This method interprets l10n strings from a .properties file as encoded in UTF-8.
     */
    private String getI10nString(String key)
    {
        if (this.l10n == null)
        {
            this.l10n = ResourceBundle.getBundle(L10N_BUNDLE, this.getLocale());
        }

        try
        {
            return new String(this.l10n.getString(key).getBytes("UTF-8"), "UTF-8");
        }
        catch (UnsupportedEncodingException ex)
        {
            return this.l10n.getString(key);
        }
    }

    private String getI10nStringFormatted(String i10nStringName, Object ... arguments)
    {
        MessageFormat formatter = new MessageFormat("");
        formatter.setLocale(this.getLocale());

        formatter.applyPattern(getI10nString(i10nStringName));
        return formatter.format(arguments);
    }

    public Locale getLocale()
    {
        return Locale.getDefault();
    }

    public static final String HTXSCHEMEID_DIMGRA1_DIMENSIONTARGET       = "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/dimension-target.20200601T122500Z";
    public static final String HTXSCHEMEID_DIMGRA1_DIMENSIONESTABLISHING = "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/dimension-establishing.20200601T122500Z";
    public static final String HTXSCHEMEID_DIMGRA1_NODETITLE             = "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/node-title.20200601T122500Z";
    public static final String HTXSCHEMEID_DIMGRA1_NODETEXT              = "htx-scheme-id://org.hypertext-systems.20180702T071630Z/dimgra.20200601T122500Z/dimgra-1.20200601T122500Z/node-text.20200601T122500Z";

    protected List<Node> nodes = new LinkedList<Node>();
    protected Map<String, String> notes = new HashMap<String, String>();
    protected List<Edge> edges = new LinkedList<Edge>();

    private static final String L10N_BUNDLE = "l10n.l10nDataFileLoader";
    private ResourceBundle l10n;
}
