/*
 * SmartHome server: Plug-in x10
 *
 * 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 x10interface.cpp
 * @brief Interfaces the CM12U controller via serial port
 * @author Daniel Pisi
 */

#include "x10interface.h"
#include <QWeakPointer>
// -----------------------------------------------------------------------------

/**
 * \brief Constructor of ShX10, initialization and opening the CM12U controller port
 * \param device Path to the CM12U controller port
 * \param parent Pointer to the parent object
 */
X10Interface::X10Interface(QString device, QObject *parent) : QObject(parent)
{
    mX10Port = new QextSerialPort(device, QextSerialPort::EventDriven);
    mX10Port->setBaudRate(BAUD4800);
    mX10Port->setFlowControl(FLOW_OFF);
    mX10Port->setDataBits(DATA_8);
    mX10Port->setStopBits(STOP_1);
    mX10Port->setParity(PAR_NONE);

    if (mX10Port->open(QIODevice::ReadWrite) == true)
    {
        connect(mX10Port, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
    }
    else
        qWarning(QString("X10 device failed to open: %1").arg(mX10Port->errorString()).toLatin1());

    mTimer = new QTimer(this);
    mTimer->setSingleShot(true);
    mTimer->setInterval(20000);

    connect(mTimer, SIGNAL(timeout()), this, SLOT(onTimeout()));

    mX10Port->write("X10Ready ");
    mState = stReady;
}
// -----------------------------------------------------------------------------

/**
 * \brief Send the enqueued packet, if all of packets has been sent emits a signal transmissionDone
 */
void X10Interface::sendNextPacket()
{
    if (mPacketQueue.isEmpty())
    {
        mTimer->stop();
        mState = stReady;
        emit transmissionDone();
    }
    else
    {
        QByteArray arr = mPacketQueue.head()->frameData();
        qDebug(QString("Sending packet %1 %2").arg(QString::number(arr[0], 16), QString::number(arr[1], 16)).toAscii());
        mTimer->start();
        mX10Port->write(mPacketQueue.head()->frameData());
        mState = stCmdSent;
    }
}
// -----------------------------------------------------------------------------

/**
 * \brief Updates the CM12U controller time
 */
void X10Interface::sendTime()
{
    char data[7];
    QDateTime dateTime = QDateTime::currentDateTime();

    data[0]  =  0x9B;
    data[1]  =  dateTime.time().second();
    data[2]  =  dateTime.time().minute();
    data[3]  =  dateTime.time().hour() % 12;
    data[4]  =  dateTime.date().dayOfYear() & 0xFF;
    data[5]  = (dateTime.date().dayOfYear() & 0x100) >> 8;
    data[5] |=  0x80 >> (dateTime.date().dayOfWeek() % 7);
    data[6]  =  0x00;

    mX10Port->write(data, 7);
    qDebug("Time sent");
}
// -----------------------------------------------------------------------------

/**
 * \brief Enqueue X-10 addressing packet(s)
 * \param devices Addressed device(s)
 * \return true if the packet(s) has been enqueued successfully
 */
bool X10Interface::enqueueAddressing(QString devices)
{
    QRegExp rxn("([A-P])(1[0-5]|[0-9])((?:\\s*,\\s*)(1[0-5]|[0-9]))*");
    if (!rxn.exactMatch(devices))
        return false;

    QStringList deviceList = devices.split(QRegExp("[^0-9]+"), QString::SkipEmptyParts);

    X10PacketPtr packet;
    foreach (QString device, deviceList)
    {
        packet = X10PacketPtr(new X10Packet);
        packet->setHousecode(devices[0].toAscii());
        packet->setDevicecode(device.toInt());
        mPacketQueue.enqueue(packet);
    }
    return true;
}
// -----------------------------------------------------------------------------

/**
 * \brief Enqueue X-10 packet turning the addressed device(s) on/off
 * \param devices Addressed device(s), only housecode part is relevant
 * \param on true for turning devices on, false for turning them off
 * \return true if the packet(s) has been enqueued successfully
 */
bool X10Interface::onOffDevices(QString devices, bool on)
{
    if ((mState != stReady) || (!enqueueAddressing(devices)))
        return false;

    X10PacketPtr packet;

    packet = X10PacketPtr(new X10Packet);
    packet->setHousecode(devices[0].toAscii());
    packet->setFunction(on ? X10Packet::fcOn : X10Packet::fcOff);
    mPacketQueue.enqueue(packet);

    sendNextPacket();

    qDebug("on/off");
    return true;
}
// -----------------------------------------------------------------------------

/**
 * \brief Enqueue X-10 packet brightening/dimming the addressed device(s) on/off
 * \param devices Addressed device(s), only housecode part is relevant
 * \param dim Number of dimming steps (1 to 24)
 * \return true if the packet(s) has been enqueued successfully
 */
bool X10Interface::dimLights(QString devices, int dim)
{
    if ((mState != stReady) || (!enqueueAddressing(devices)))
        return false;

    X10Packet::X10Function fce;

    fce = (dim >= 0) ? X10Packet::fcBright : X10Packet::fcDim;
    dim = qMin(qAbs(dim), 22);

    X10PacketPtr packet;
    packet = X10PacketPtr(new X10Packet);
    packet->setHousecode(devices[0].toAscii());
    packet->setFunction(fce);
    packet->setDimAmount(dim);
    mPacketQueue.enqueue(packet);

    sendNextPacket();
    qDebug("dimming");
    return true;
}
// -----------------------------------------------------------------------------

/**
 * \brief Enqueue X-10 packet turning on/off all lights within given house
 * \param house Housecode
 * \param on true for turning lights on, false for turning them off
 * \return true if the packet(s) has been enqueued successfully
 */
bool X10Interface::onOffAllLights(char house, bool on)
{
    if (mState != stReady)
        return false;

    X10PacketPtr packet;

    packet = X10PacketPtr(new X10Packet);
    packet->setHousecode(house);
    packet->setFunction(on ? X10Packet::fcAllLightsOn : X10Packet::fcAllLightsOff);
    mPacketQueue.enqueue(packet);

    sendNextPacket();
    return true;
}
// -----------------------------------------------------------------------------

/**
 * \brief Slot reading incoming data
 */
void X10Interface::onReadyRead()
{
    char b;

    if (mX10Port->bytesAvailable())
    {
        mX10Port->read(&b, 1);

        switch (mState)
        {
        case stCmdSent:
            if ((quint8)b == mPacketQueue.head()->checksum())
            {
                qDebug("Checksum OK");
                mX10Port->putChar(0x00);
                mPacketQueue.dequeue();
                mState = stChecksumOk;
            }
            else
                sendNextPacket();
            break;

        case stChecksumOk:
            sendNextPacket();
            break;

        case stReady:
            if ((quint8)b == 0xA5)
                sendTime();
            break;
        }
    }
    else
        qWarning("Nothing was available");
}
// -----------------------------------------------------------------------------

/**
 * \brief Slot invoked on sent packet response timeout
 */
void X10Interface::onTimeout()
{
    mTimer->stop();

    mPacketQueue.clear();
    mState = stReady;

    emit transmissionFailed("timeout");
}
// -----------------------------------------------------------------------------
