/*
 * SmartHome server: Plug-in xbee
 *
 * 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 shxbee.cpp
 * @brief Plug-in implementing an interface to Digi XBee series 2 module
 * @author Daniel Pisi
 */

#include "shxbee.h"
#include "xbpackets.h"

#include <QRegExp>

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

XbModuleData::XbModuleData()
{
    lastDigitalSample = 0;
    inputs = outputs = 0;
}
// -----------------------------------------------------------------------------

ShXbee::ShXbee(QObject *parent) : ShObjectHelper(parent)
{
}
// -----------------------------------------------------------------------------

bool ShXbee::init(QString param)
{
    mLastFrameId = 1;

    mInterface = new XbInterface(param);

    connect(mInterface, SIGNAL(newPacketReceived(QByteArray)),
            this, SLOT(processPacket(QByteArray)));

    qDebug("XBee initialized");
    return true;
}
// -----------------------------------------------------------------------------

void ShXbee::updatePins(const QList<ShPin> &pins)
{
    const char *cfgCmd[13] = {"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
                              "",   "",   "P0", "P1", "P2"};
    QRegExp rx("0X([0-9A-F]{16}):([0-9A-F])");

    QMap<quint64,XbModuleData> newModules;

    quint64 addr;
    quint8  io;

    // Create newModules list
    foreach (ShPin pin, pins)
    {
        if (rx.exactMatch(pin.signal().toUpper()))
        {
            addr = rx.cap(1).toULongLong(0, 16);
            io   = rx.cap(2).toULongLong(0, 16);

            if (pin.isInput())  newModules[addr].inputs  |= (1 << io);
            if (pin.isOutput()) newModules[addr].outputs |= (1 << io);
        }
    }

    // Update individual pins settings
    XbRemoteCommand packet;
    packet.setFrameId(0);
    packet.setOptions(0x02);

    quint16 newInputs, newOutputs, msk;
    QMapIterator<quint64,XbModuleData> newModI(newModules);

    while (newModI.hasNext())
    {
        newModI.next();
        addr = newModI.key();

        newInputs  = newModI.value().inputs  & ~mModules[addr].inputs;
        newOutputs = newModI.value().outputs & ~mModules[addr].outputs;

        for (int i = 0; i < 16; i++)
        {
            msk = 1 << i;
            if (newInputs & msk || newOutputs & msk)
            {
                packet.setAddr64(addr);
                packet.setCommand(cfgCmd[i], newInputs & msk ? 3 : 4);
                mInterface->sendPacket(packet);
            }
        }
        newModules[addr].lastDigitalSample = mModules[addr].lastDigitalSample;
    }

    mModules = newModules;

    // Update configuration and query input values
    XbRemoteCommand prCmd, icCmd, isCmd;

    foreach (addr, mModules.keys())
    {
        prCmd.setFrameId(0);
        prCmd.setAddr64(addr);
        prCmd.setOptions(0x02);
        prCmd.setCommand("PR", new XbUint16Block(0x1FFF));
        mInterface->sendPacket(prCmd);

        icCmd.setFrameId(0);
        icCmd.setAddr64(addr);
        icCmd.setOptions(0x02);
        icCmd.setCommand("IC", new XbUint16Block(0x1FFF));
        mInterface->sendPacket(icCmd);

        isCmd.setFrameId(nextFrameId());
        isCmd.setAddr64(addr);
        isCmd.setOptions(0x02);
        isCmd.setCommand("IS");
        mInterface->sendPacket(isCmd);
    }
}
// -----------------------------------------------------------------------------

quint8 ShXbee::nextFrameId()
{
    mLastFrameId = (mLastFrameId == 255) ? 1 : mLastFrameId+1;
    return mLastFrameId;
}
// -----------------------------------------------------------------------------

void ShXbee::processMessage(ShMessagePtr message)
{
    const char *cfgCmd[13] = {"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
                              "",   "",   "P0", "P1", "P2"};

    qDebug(QString("Xbee: Processing message from object %1, signal = %2").arg(message->senderId()).arg(message->signal()).toLatin1());

    QRegExp rx1("0X([0-9A-F]{16}):([0-9A-F])");
    QRegExp rx2("0X([0-9A-F]{16})");

    if (message->msgType() == ShMessage::mtValue)
    {
        if (rx1.exactMatch(message->signal().toUpper()))
        {
            XbRemoteCommand *packet = new XbRemoteCommand;

            packet->setFrameId(nextFrameId());
            packet->setAddr64(rx1.cap(1).toULongLong(0, 16));
            packet->setOptions(0x02);
            packet->setCommand(QByteArray(cfgCmd[rx1.cap(2).toInt(0,16)]), message->value().toBool() ? 4 : 5);

            sendPacket(packet->frameId(), message, packet);
            qDebug(QString("Value of %1 set to %2").arg(message->signal(), message->value().toString()).toLatin1());
        }
        else if (rx2.exactMatch(message->signal().toUpper()))
        {
            XbTransmitRequest *packet = new XbTransmitRequest;

            packet->setFrameId(nextFrameId());
            packet->setAddr64(rx2.cap(1).toULongLong(0, 16));
            packet->setRfData(message->value().toByteArray());
            sendPacket(packet->frameId(), message, packet);
        }
    }
    else if (message->msgType() == ShMessage::mtGet)
    {
        if (rx1.exactMatch(message->signal().toUpper()))
        {
            XbRemoteCommand *packet = new XbRemoteCommand;

            packet->setFrameId(nextFrameId());
            packet->setAddr64(rx1.cap(1).toULongLong(0, 16));
            packet->setOptions(0x02);
            packet->setCommand("IS");
            sendPacket(packet->frameId(), message, packet);
        }
        else if (rx2.exactMatch(message->signal().toUpper()))
        {
            XbTransmitRequest *packet = new XbTransmitRequest;

            packet->setFrameId(nextFrameId());
            packet->setAddr64(rx2.cap(1).toULongLong(0, 16));
            packet->setRfData("get");
            sendPacket(packet->frameId(), message, packet);
        }
    }
}
// -----------------------------------------------------------------------------

bool ShXbee::sendPacket(int id, ShMessagePtr message, XbPacket *packet)
{
    if (mMsgPool.contains(id))
        return false;

    mMsgPool[id] = XbMessagePtr(new XbMessage(this));

    connect(mMsgPool[id].data(), SIGNAL(timeout()), this, SLOT(messageTimeout()));

    mMsgPool[id]->message = message;
    mMsgPool[id]->packet  = QSharedPointer<XbPacket>(packet);

    mInterface->sendPacket(*packet);
    mMsgPool[id]->timer->start();

    return true;
}
// -----------------------------------------------------------------------------

void ShXbee::messageProcessed(int id)
{
    if (!mMsgPool.contains(id))
        return;

    mMsgPool[id]->timer->stop();

    if (mMsgPool[id]->message->requireAck())
        ackMessage(mMsgPool[id]->message);

    mMsgPool.remove(id);
}
// -----------------------------------------------------------------------------

void ShXbee::messageError(int id, QString reason)
{
    if (!mMsgPool.contains(id))
        return;

    mMsgPool[id]->timer->stop();

    errorMessage(mMsgPool[id]->message, reason);
    mMsgPool.remove(id);
}
// -----------------------------------------------------------------------------

void ShXbee::messageTimeout()
{
    XbMessage *xbMsg = qobject_cast<XbMessage*>(sender());
    if (xbMsg == NULL) return;

    errorMessage(xbMsg->message, "timeout");
    mMsgPool.remove(mMsgPool.key(QWeakPointer<XbMessage>(xbMsg)));
}
// -----------------------------------------------------------------------------

void ShXbee::processPacket(QByteArray data)
{
    if (!XbPacket::valid(data))
        return;

    qDebug("Processing packet " + data.toHex());

    switch ((quint8)data.at(3))
    {
        case 0x8B:
        {
            XbTransmitStatus packet;
            if (packet.parseFrameData(data))
            {
                if ((packet.deliveryStatus() == 0x00) ||
                    (packet.deliveryStatus() == 0x23))
                {
                    messageProcessed(packet.frameId());
                }
                else
                    messageError(packet.frameId(), packet.deliveryStatusString());
            }
            else
                qWarning("Parsing of the transmit status packet failed.");
        }
        break;

        case 0x90:
        {
            XbReceivedData packet;
            if (packet.parseFrameData(data))
            {
                QString signal = "0x" + QString("%1").arg(packet.addr64(), 16, 16, QLatin1Char('0')).toUpper();
                outputUpdated(signal, packet.rfData());
            }
            else
                qWarning("Parsing of the received data packet failed.");
        }
        break;

        case 0x92:
        {
            XbIoSample packet;
            if (packet.parseFrameData(data))
            {
                quint16 sample = packet.ioSamples()->digitalSample();
                quint16 mask = packet.ioSamples()->digitalMask();

                qDebug(QString("new digital sample = %1, mask = %2").arg(sample, 4, 16, QChar('0')).arg(mask, 4, 16, QChar('0')).toLatin1());

                if (sample != mModules[packet.addr64()].lastDigitalSample)
                {
                    bool input, input0;
                    QString signal = "0x" + QString("%1:%2").arg(packet.addr64(), 16, 16, QLatin1Char('0')).toUpper();
                    for (int i = 0; i < 13; i++)
                    {
                        input  = sample & (1 << i);
                        input0 = mModules[packet.addr64()].lastDigitalSample & (1 << i);

                        if (input != input0)
                            outputUpdated(signal.arg(i), input ? 0 : 1);
                    }
                    mModules[packet.addr64()].lastDigitalSample = sample;
                }
            }
            else
                qWarning("Parsing of the received I/O sample packet failed.");
        }
        break;

        case 0x97:
        {
            XbRemoteCommandResponse packet;

            packet.parseFrameData(data);

            if (packet.command() == "IS")
            {
                mModules[packet.addr64()].lastDigitalSample = ((XbIoSamplesBlock*)(packet.cmdParam()))->digitalSample();
                qDebug(QString("digital = %1").arg(mModules[packet.addr64()].lastDigitalSample,4,16).toLatin1());
            }

            messageProcessed(packet.frameId());
        }
        break;
    }
}

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

Q_EXPORT_PLUGIN2(xbee, ShXbee);
