// %pacpus:license{
// This file is part of the PACPUS framework distributed under the
// CECILL-C License, Version 1.0.
// %pacpus:license}
/// @version    $Id: XmlConfigFile.cpp 76 2013-01-10 17:05:10Z kurdejma $

#include <Pacpus/kernel/XmlConfigFile.h>

#include <Pacpus/kernel/Log.h>

#include <QFile>
#include <QTextStream>

////////////////////////////////////////////////////////////////////////////////

using namespace pacpus;
using namespace std;

DECLARE_STATIC_LOGGER("pacpus.core.XmlConfigFile");

XmlConfigFile * XmlConfigFile::m_xmlConfigFile = NULL;

static const char * kXmlConfigFilename = "pacpus_config.xml";

static const char * kRootSection = "pacpus";

static const char * kComponentSection = "components";
static const char * kConnectionSection = "connections";
static const char * kParameterSection = "parameters";
static const char * kPluginSection = "plugins";

static const char * kComponentNode = "component";
static const char * kConnectionNode = "connection";
static const char * kParameterNode = "parameter";
static const char * kPluginNode = "plugin";

static const char * kNameAttribute = "name"; // <component name="MyComponent1" type="MyComponentType"/>
static const char * kLibAttribute = "lib"; // <plugin lib="MyLibrary.dll"/>
static const char * kExtensionAttribute = "extension"; // <parameter extension=".dll">
static const char * kPrefixAttribute = "prefix"; // <parameter prefix="lib">
static const char * kPostfixAttribute = "postfix"; // <parameter postfix="_d">

////////////////////////////////////////////////////////////////////////////////
// helper method
QDomNode getNamedItemFromDomDocument(const QDomDocument & document, const char * itemName);

////////////////////////////////////////////////////////////////////////////////

// CTOR/DTOR
XmlConfigFile::XmlConfigFile()
    : m_numberOfComponents(0)
{
    LOG_TRACE("constructor");

    // create the root of the XML tree
    m_document.appendChild(m_document.createElement(kRootSection));
    // create the sections
    m_document.documentElement().appendChild(m_document.createElement(kParameterSection));
    m_document.documentElement().appendChild(m_document.createElement(kPluginSection));
    m_document.documentElement().appendChild(m_document.createElement(kComponentSection));
    m_document.documentElement().appendChild(m_document.createElement(kConnectionSection));
    m_file.setFileName(kXmlConfigFilename);
    bool isOpenedSuccessfully = m_file.open(QIODevice::ReadWrite); // FIXME: ReadOnly ?
    if (isOpenedSuccessfully) {
        LOG_INFO("XML document " << kXmlConfigFilename << " was created");
    } else {
        LOG_ERROR("cannot open XML document " << kXmlConfigFilename);
        throw "cannot open XML document file";
    }
}

XmlConfigFile::~XmlConfigFile()
{
    LOG_TRACE("destructor");
}

// CREATE/DESTROY
XmlConfigFile * XmlConfigFile::create()
{
    if (NULL ==m_xmlConfigFile) {
        m_xmlConfigFile = new XmlConfigFile();
    }
    return m_xmlConfigFile;
}

void XmlConfigFile::destroy()
{
    delete m_xmlConfigFile;
    m_xmlConfigFile = NULL;
}

// COMPONENTS ADD/REMOVE/CREATE
void XmlConfigFile::addComponent(QDomElement component)
{ 
    QMutexLocker mutexLocker(&m_mutex); // locks mutex, unlocks it in destructor
    (void) mutexLocker; // unused

    // TODO: change .tagName => .attribute(kPropertyComponentName)
    QDomNode componentSectionNode = getNamedItemFromDomDocument(m_document, kComponentSection);
    if (componentSectionNode.namedItem(component.attribute(kNameAttribute)/*.tagName()*/).isNull()) {
        LOG_WARN("component " << component.attribute(kNameAttribute)/*tagName()*/ << " exists already in the document");
    } else {
        QDomNode node = getNamedItemFromDomDocument(m_document, kComponentSection).appendChild(component);
        ++m_numberOfComponents;
        LOG_INFO("component " << node.nodeName() << " has been added to the section "
            << getNamedItemFromDomDocument(m_document, kComponentSection).nodeName());
    }
}

void XmlConfigFile::delComponent(QDomElement component)
{
    removeComponent(component);
}

void XmlConfigFile::removeComponent(QDomElement component)
{
    QMutexLocker mutexLocker(&m_mutex); // locks mutex, unlocks it in destructor
    (void) mutexLocker; // unused

    QDomNode node = getNamedItemFromDomDocument(m_document, kComponentSection).removeChild(component);
    if (node.isNull()) {
        LOG_WARN("component " << component.attribute(kNameAttribute)/*tagName()*/ << " doesn't exist in the document.");
    } else {
        LOG_INFO("component " << node.nodeName() << " has been removed from the section "
            << getNamedItemFromDomDocument(m_document, kComponentSection).nodeName());
        --m_numberOfComponents;
    }
}

QDomElement XmlConfigFile::createComponent(QString name)
{
    LOG_DEBUG("creating component " << name);

    QMutexLocker mutexLocker(&m_mutex); // locks mutex, unlocks it in destructor
    (void) mutexLocker; // unused
    return m_document.createElement(name);
}

// FILE I/O
void XmlConfigFile::saveFile(QString fileName)
{
    QMutexLocker mutexLocker(&m_mutex);
    (void) mutexLocker; // unused

    m_file.close();
    assert(!m_file.isOpen());
    m_file.setFileName(fileName);
    {
        // open file
        bool isOpenedSuccessfully = m_file.open(QIODevice::WriteOnly);
        if (!isOpenedSuccessfully) {
            LOG_ERROR("cannot open file '" << m_file.fileName() << "' for writing");
            return;
        }
        QTextStream ts(&m_file);
        ts << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n";
        ts << m_document.toString();
        m_file.close();
    }
    LOG_DEBUG("file \"" << m_file.fileName() << "\" has been saved");
}

int XmlConfigFile::loadFile(QString fileName)
{
    // lock access
    QMutexLocker mutexLocker(&m_mutex);
    (void) mutexLocker; // unused

    // check if there are components already
    if (0 != m_numberOfComponents) {
        LOG_WARN("XML document contained " << m_numberOfComponents << " components that will be lost!");
    }

    m_file.close();
    assert(!m_file.isOpen());
    m_file.setFileName(fileName);
    {
        // open file
        bool isOpenedSuccessfully = m_file.open(QIODevice::ReadOnly);
        if (!isOpenedSuccessfully) {
            LOG_ERROR("cannot open file '" << m_file.fileName() << "' for reading");
            return 0;
        }
        // read and parse input XML file
        QString errorMsg;
        int errorLine = 0;
        int errorColumn = 0;
        if (!m_document.setContent(&m_file, /*namespaceProcessing=*/true, &errorMsg, &errorLine, &errorColumn)) {
            LOG_ERROR("cannot parse XML file " << m_file.fileName());
            LOG_ERROR(errorMsg << " at " << errorLine << ":" << errorColumn << " (line:col)");
            m_file.close();
            return 0;
        }
        // close file
        m_file.close();
    }

    // get the number of components in the loaded tree
    m_numberOfComponents = getAllComponents().count();

    LOG_INFO("XML file \"" << m_file.fileName() << "\" has been loaded. Number of components = " << m_numberOfComponents);
    LOG_DEBUG("XML file content:\n"
        << "BEGIN============================================================================\n"
        << m_document.toString()
        << "END==============================================================================\n"
        );

    return m_numberOfComponents;
}

// XML
QDomElement XmlConfigFile::getSection(const char * name) const
{
    QDomElement sectionElement = getNamedItemFromDomDocument(m_document, name).toElement();
    if (sectionElement.isNull()) {
        LOG_WARN("there is no '" << name << "' section in the XML");
        return QDomElement();
    }
    return sectionElement;
}

QDomNodeList XmlConfigFile::getNodesInSection(const char * sectionName, const char * nodeName) const
{
    QDomElement sectionElement = getSection(sectionName);
    return sectionElement.elementsByTagName(nodeName);
}

// COMPONENTS
QDomNodeList XmlConfigFile::getAllComponents() const
{
    return getNodesInSection(kComponentSection, kComponentNode);
}

QStringList XmlConfigFile::getAllComponentsNames() const
{
    // get component nodes
    QDomNodeList componentNodes = getAllComponents();
    // get component names
    QStringList componentNameList;
    for (int i = 0; i < componentNodes.size(); ++i) {
        QDomElement componentElement = componentNodes.at(i).toElement();
        componentNameList.append(componentElement.attribute(kNameAttribute));
    }
    return componentNameList;
}

QDomElement XmlConfigFile::getComponent(QString componentName) const
{
    LOG_DEBUG("getting component " << componentName);

    QDomNodeList componentNodes = getAllComponents();
    for (int i = 0; i < componentNodes.size(); ++i) {
        QDomElement componentElement = componentNodes.at(i).toElement();
        if (componentName == componentElement.attribute(kNameAttribute)) {
            return componentElement;
        }
    }
    LOG_WARN("cannot get component " << componentName << ": document does not contain a component with this name");

    return QDomElement();
} 

// CONNECTIONS
QDomNodeList XmlConfigFile::getAllConnections() const
{
    return getNodesInSection(kConnectionSection, kConnectionNode);
}

QDomElement XmlConfigFile::getConnection(QString name) const
{
    LOG_DEBUG("getting connection " << name);

    QDomNodeList connectionNodes = getAllConnections();
    for (int i = 0; i < connectionNodes.size(); ++i) {
        QDomElement connectionElement = connectionNodes.at(i).toElement();
        // TODO: name by attribute 'name'
        if (name == connectionElement.attribute(kNameAttribute)) {
            return connectionElement;
        }
    }
    LOG_WARN("cannot get connection " << name << ": document does not contain a connection with this name");

    return QDomElement();
}

// PARAMETERS
QDomNodeList XmlConfigFile::getAllParameters() const
{
    return getNodesInSection(kParameterSection, kParameterNode);
}

// PLUGINS
QDomNodeList XmlConfigFile::getAllPlugins()
{
    // get section
    QDomElement pluginsElement = getSection(kPluginSection);

    // get attributes
    m_libraryExtension = pluginsElement.attribute(kExtensionAttribute);
    if (!m_libraryExtension.isEmpty()) {
        // prefix with a dot '.' if there is no one
        if ('.' != m_libraryExtension.at(0)) {
            m_libraryExtension = '.' + m_libraryExtension;
        }
    }
    m_libraryPrefix = pluginsElement.attribute(kPrefixAttribute);
    m_libraryPostfix = pluginsElement.attribute(kPostfixAttribute);
    
    // get nodes
    return getNodesInSection(kPluginSection, kPluginNode);
}

QStringList XmlConfigFile::getAllPluginsNames()
{
    QDomNodeList pluginList = getAllPlugins();
    if (0 == pluginList.size()) {
        LOG_ERROR("no plugins were specified");
        return QStringList();
    }
    LOG_INFO("there are " << pluginList.size() << " plugins");

    // get plugin library paths
    QStringList pluginLibraryNames;
    for (int i = 0; i < pluginList.size(); ++i) {
        QDomElement pluginElement = pluginList.at(i).toElement();
        QString libraryFileName = libraryPrefix() + pluginElement.attribute(kLibAttribute) + libraryPostfix() + libraryExtension();
        pluginLibraryNames.append(libraryFileName);
    }
    return pluginLibraryNames;
}

QString XmlConfigFile::libraryExtension() const
{
    return m_libraryExtension;
}

QString XmlConfigFile::libraryPrefix() const
{
    return m_libraryPrefix;
}

QString XmlConfigFile::libraryPostfix() const
{
    return m_libraryPostfix;
}

////////////////////////////////////////////////////////////////////////////////
// HELPER METHODS
QDomNode getNamedItemFromDomDocument(const QDomDocument & document, const char * itemName)
{
    return document.documentElement().namedItem(itemName);
}
