/*
 * SmartHome server: Base of the system
 *
 * Copyright (C) 2010 Daniel Pisi <dan.pisi@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License
 * as published by the Free Software Foundation.
 *
 */

/**
 * @file shbase.cpp
 * @brief Base of the SmartHome server
 * @author Daniel Pisi
 */

#include <QtSql>
#include "shbase.h"

// -----------------------------------------------------------------------------

/**
 * \brief Constructor of ShBase, the main modular subsystem class
 * \param parent Pointer to the parent object
 */
ShBase::ShBase(QObject *parent) : QObject(parent)
{
    reload();
}
// -----------------------------------------------------------------------------

/**
 * \brief Updates a structure of plug-ins to reflect changes made in database
 */
void ShBase::reload()
{
    int id;
    QPluginLoader pluginLoader;
    QString path;
    QObject *plugin;
    ShObject *shObject;

    qDebug("Updating system structure");

    QDir pluginsDir(qApp->applicationDirPath());
    pluginsDir.cd("plugins");

    QSqlQuery query;
    query.exec("SELECT id,class,params FROM smarthome_objects");

    QList<int> oldIds = mObjects.keys();
    QList<int> newItems;

    // Find out differences between old and new objects
    while (query.next())
    {
        if (!oldIds.removeOne(query.value(0).toInt()))
            newItems.append(query.at());
    }

    // Delete no longer needed objects
    foreach (int id, oldIds)
    {
        delete mObjects[id];
        mObjects.remove(id);
    }

    // Load and initialize new objects
    foreach (int idx, newItems)
    {
        query.seek(idx);
        path = pluginsDir.absoluteFilePath(QString("lib%1.so").arg(query.value(1).toString()));

        pluginLoader.setFileName(path);
        plugin = pluginLoader.instance();

        shObject = NULL;
        if (plugin)
        {
            shObject = qobject_cast<ShObject*>(plugin);
            if (shObject)
            {
                qDebug(QString("Plugin %1 loaded").arg(query.value(1).toString()).toLatin1());

                id = query.value(0).toInt();
                mObjects[id] = shObject;

                connect(plugin, SIGNAL(newMessage(ShMessagePtr)), this, SLOT(routeMessage(ShMessagePtr)));

                shObject->setObjectId(id);
                shObject->init(query.value(2).toString());
            }
            else
                delete plugin;
        }
        if (!shObject)
            qDebug(QString("Failed to load plugin %1: %2").arg(query.value(1).toString(), pluginLoader.errorString()).toLatin1());
    }

    // Update all pins
    QMapIterator<int,ShObject*> objectsI(mObjects);
    while (objectsI.hasNext())
    {
        objectsI.next();
        objectsI.value()->updatePins(queryPins(objectsI.key()));
    }
}
// -----------------------------------------------------------------------------

/**
 * \brief Finds out the pin ID belonging to the given object and signal
 * \param object Plug-in ID
 * \param signal Name of the signal
 * \return Pin id
 */
int ShBase::queryPinId(int object, QString signal)
{
    QSqlQuery query;

    query.prepare("SELECT id FROM smarthome_pins WHERE object=? AND signal=?");
    query.addBindValue(object);
    query.addBindValue(signal);
    query.exec();

    return query.next() ? query.value(0).toInt() : 0;
}
// -----------------------------------------------------------------------------

/**
 * \brief Assembles a list of all pins belonging to given object
 * \param object Plug-in id
 * \return List of found pins
 */
QList<ShPin> ShBase::queryPins(int objectId)
{
    QList<ShPin> ret;
    QSqlQuery query;

    query.prepare("SELECT signal,direction,type FROM smarthome_pins WHERE object=?");
    query.addBindValue(objectId);
    query.exec();

    ShPin pin;
    pin.setObjectId(objectId);

    while (query.next())
    {
        pin.setSignal(query.value(0).toString());
        pin.setDirection(query.value(1).toString());
        pin.setType(query.value(2).toString());
        ret.append(pin);
    }

    return ret;
}
// -----------------------------------------------------------------------------

/**
 * \brief Checks if the given pin ID belongs to the given object
 * \param object Pin ID
 * \param object Plug-in ID
 * \return true if the pin belongs to the object
 */
bool ShBase::pinBelongsTo(int pin, int object)
{
    QSqlQuery query;

    query.prepare("SELECT * FROM smarthome_pins WHERE id=? AND object=?");
    query.addBindValue(pin);
    query.addBindValue(object);
    query.exec();

    return query.next();
}
// -----------------------------------------------------------------------------

/**
 * \brief Query the pin ID details
 * \param object Pin ID
 * \return Object describing the pin
 */
ShPin ShBase::queryPin(int pinId)
{
    ShPin pin;
    QSqlQuery query;

    query.prepare("SELECT object,signal,direction,type FROM smarthome_pins WHERE id=?");
    query.addBindValue(pinId);
    query.exec();

    if (query.next())
    {
        pin.setObjectId(query.value(0).toInt());
        pin.setSignal(query.value(1).toString());
        pin.setDirection(query.value(2).toString());
        pin.setType(query.value(3).toString());
    }
    return pin;
}
// -----------------------------------------------------------------------------

/**
 * \brief Routes messages to the plug-ins according to configuration stored in the SQL database
 * \param message Shared pointer to the message
 */
void ShBase::routeMessage(ShMessagePtr message)
{
    QSqlQuery destinations;
    int pin, id;

    qDebug(QString("Routing message: sender = %1, signal = %2, value = %3, type = %4").arg(message->senderId()).arg(message->signal()).arg(message->value().toString()).arg(message->msgTypeStr()).toLatin1());

    if (!mObjects.contains(message->senderId()))
        return;

    if (message->msgType() == ShMessage::mtReload)
    {
        reload();
        return;
    }

    pin = (mObjects[message->senderId()]->idApi()) ? message->signal().toInt()
                                                   : queryPinId(message->senderId(), message->signal());

    if (pin <= 0 || !pinBelongsTo(pin, message->senderId()))
        return;


    QString sqlCmd;

    if (( message->msgType() == ShMessage::mtValue) ||
        ((message->msgType() == ShMessage::mtError) && (message->pin().isOutput())) )
    {
        sqlCmd = "SELECT destination FROM smarthome_links WHERE source=%1";
    }
    else if (( message->msgType() == ShMessage::mtGet) ||
             ( message->msgType() == ShMessage::mtAck) ||
             ((message->msgType() == ShMessage::mtError) && (message->pin().isInput())) )
    {
        sqlCmd = "SELECT source FROM smarthome_links WHERE destination=%1";
    }

    ShMessagePtr destMsg;

    destinations.exec(sqlCmd.arg(pin));

    if (!destinations.next())
    {
        qDebug("Destination not found, pin = %d", pin);
    }

    do
    {
        pin = destinations.value(0).toInt();

        ShPin destPin = queryPin(pin);
        id = destPin.objectId();

        if (mObjects.contains(id))
        {
            destMsg = ShMessagePtr(new ShMessage);

            destMsg->setSenderId(message->senderId());
            destMsg->setMsgType(message->msgType());
            destMsg->setPin(destPin);
            destMsg->setSignal(mObjects[id]->idApi() ? QString::number(pin) : destPin.signal());
            destMsg->setValue(message->value());

            mObjects[id]->processMessage(destMsg);
        }
    } while (destinations.next());
}
// -----------------------------------------------------------------------------


