/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                         *
 *  Copyright (C) 2015 Simon St√ºrz <simon.stuerz@guh.guru>                 *
 *                                                                         *
 *  This file is part of nymea.                                            *
 *                                                                         *
 *  nymea is free software: you can redistribute it and/or modify          *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation, version 2 of the License.                *
 *                                                                         *
 *                                                                         *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with nymea. If not, see <http://www.gnu.org/licenses/>.          *
 *                                                                         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <QFile>
#include <QFileSystemWatcher>
#include <QDebug>
#include <sys/time.h>

#include "radio433receiver.h"
#include "loggingcategories.h"

Radio433Receiver::Radio433Receiver(QObject *parent, int gpio) :
    QThread(parent),m_gpioPin(gpio)
{
    m_timings.clear();
    m_available = false;

    m_mutex.lock();
    m_enabled = false;
    m_mutex.unlock();

    connect(this, &Radio433Receiver::timingReady, this, &Radio433Receiver::handleTiming, Qt::DirectConnection);
}

Radio433Receiver::~Radio433Receiver()
{
    quit();
    wait();
    deleteLater();
}

bool Radio433Receiver::stopReceiver()
{
    m_mutex.lock();
    m_enabled = false;
    m_mutex.unlock();
    return true;
}

bool Radio433Receiver::available()
{
    return m_available;
}

void Radio433Receiver::run()
{
    struct pollfd fdset[2];
    int gpio_fd = m_gpio->openGpio();
    char buf[1];

    bool enabled = true;
    m_mutex.lock();
    m_enabled = true;
    m_mutex.unlock();

    int lastTime = micros();
    // poll the gpio file, if something changes...emit the signal with the current pulse length
    while(enabled){
        memset((void*)fdset, 0, sizeof(fdset));
        fdset[0].fd = STDIN_FILENO;
        fdset[0].events = POLLIN;

        fdset[1].fd = gpio_fd;
        fdset[1].events = POLLPRI;
        int rc = poll(fdset, 2, 1000);

        if (rc < 0) {
            qCWarning(dcHardware) << "ERROR: poll failed";
            return;
        }
        if(rc == 0){
            //timeout
        }
        if (fdset[1].revents & POLLPRI){
            if(read(fdset[1].fd, buf, 1) != 1){
                qCWarning(dcHardware) << "could not read GPIO";
            }
            int currentTime = micros();
            int duration = currentTime - lastTime;
            lastTime = currentTime;
            emit timingReady(duration);
        }

        m_mutex.lock();
        enabled = m_enabled;
        m_mutex.unlock();
    }
}

bool Radio433Receiver::setUpGpio()
{
    if(!m_gpio){
        m_gpio = new Gpio(this,m_gpioPin);
    }

    if(!m_gpio->exportGpio() || !m_gpio->setDirection(INPUT) || !m_gpio->setEdgeInterrupt(EDGE_BOTH)){
        return false;
    }
    return true;
}

bool Radio433Receiver::startReceiver()
{
    if(!setUpGpio()){
        m_available = false;
        return false;
    }

    m_mutex.lock();
    m_enabled = true;
    m_mutex.unlock();
    m_available = true;

    start();
    return true;
}

int Radio433Receiver::micros()
{
    struct timeval tv ;
    int now ;
    gettimeofday (&tv, NULL) ;
    now  = (int)tv.tv_sec * (int)1000000 + (int)tv.tv_usec ;

    return (int)(now - m_epochMicro) ;
}

bool Radio433Receiver::valueInTolerance(int value, int correctValue)
{
    // in in range of +- 200 [us] of sollValue return true, eles return false
    if(value >= (double)correctValue - 200 && value <= (double)correctValue + 200){
        return true;
    }
    return false;
}

bool Radio433Receiver::checkValue(int value)
{
    if(valueInTolerance(value,m_pulseProtocolOne) || valueInTolerance(value,2*m_pulseProtocolOne) || valueInTolerance(value,3*m_pulseProtocolOne) || valueInTolerance(value,4*m_pulseProtocolOne) || valueInTolerance(value,8*m_pulseProtocolOne)){
        return true;
    }
    if(valueInTolerance(value,m_pulseProtocolTwo) || valueInTolerance(value,2*m_pulseProtocolTwo) || valueInTolerance(value,3*m_pulseProtocolTwo) || valueInTolerance(value,4*m_pulseProtocolTwo) || valueInTolerance(value,8*m_pulseProtocolTwo)){
        return true;
    }
    return false;
}

bool Radio433Receiver::checkValues(Protocol protocol)
{
    switch (protocol) {
    case Protocol48:
        for(int i = 1; i < m_timings.count(); i++){
            if(!(valueInTolerance(m_timings.at(i),m_pulseProtocolOne) || valueInTolerance(m_timings.at(i),2*m_pulseProtocolOne) || valueInTolerance(m_timings.at(i),3*m_pulseProtocolOne) || valueInTolerance(m_timings.at(i),4*m_pulseProtocolOne) || valueInTolerance(m_timings.at(i),8*m_pulseProtocolOne))){
                break;
            }
        }
        return true;
    case Protocol64:
        for(int i = 1; i < m_timings.count(); i++){
            if(!(valueInTolerance(m_timings.at(i),m_pulseProtocolTwo) || valueInTolerance(m_timings.at(i),2*m_pulseProtocolTwo) || valueInTolerance(m_timings.at(i),3*m_pulseProtocolTwo) || valueInTolerance(m_timings.at(i),4*m_pulseProtocolTwo) || valueInTolerance(m_timings.at(i),8*m_pulseProtocolTwo))){
                break;
            }
        }
        return true;
    default:
        break;
    }
    return false;
}

void Radio433Receiver::changeReading(bool reading)
{
    if(reading != m_reading){
        m_reading = reading;

        //TODO: create colission detection

        //emit readingChanged(reading);
    }
}

void Radio433Receiver::handleTiming(int duration)
{
    // to short...
    if(duration < 60){
        changeReading(false);
        m_timings.clear();
        return;
    }

    // could by a sync signal...
    bool sync = false;
    if(duration > 2400 && duration < 14000){
        changeReading(false);
        sync = true;
    }

    // got sync signal and list is not empty...
    if(!m_timings.isEmpty() && sync){
        // 1 sync bit + 48 data bit
        if(m_timings.count() == 49 && checkValues(Protocol48)){
            //qCWarning(dcHardware) << "48 bit ->" << m_timings << "\n--------------------------";
            changeReading(false);
            emit dataReceived(m_timings);
        }

        // 1 sync bit + 64 data bit
        if(m_timings.count() == 65 && checkValues(Protocol64)){
            //qCWarning(dcHardware) << "64 bit ->" << m_timings << "\n--------------------------";
            changeReading(false);
            emit dataReceived(m_timings);
        }
        m_timings.clear();
        m_pulseProtocolOne = 0;
        m_pulseProtocolTwo = 0;
        changeReading(false);
    }

    // got sync signal and list is empty...
    if(m_timings.isEmpty() && sync){
        m_timings.append(duration);
        m_pulseProtocolOne = (int)((double)m_timings.first()/31);
        m_pulseProtocolTwo = (int)((double)m_timings.first()/10);
        changeReading(false);
        return;
    }

    // list not empty and this is a possible value
    if(!m_timings.isEmpty() && checkValue(duration)){
        m_timings.append(duration);

        // check if it could be a signal -> if we have a sync and 15 valid values
        // set reading true to prevent a collision from own transmitter
        if(m_timings.count() == 20 && (checkValues(Protocol48) || checkValues(Protocol64))){
            changeReading(true);
        }

        // check if we have already a vallid protocol
        // 1 sync bit + 48 data bit
        if(m_timings.count() == 49 && checkValues(Protocol48)){
            //qCWarning(dcHardware) << "48 bit -> " << m_timings << "\n--------------------------";
            emit dataReceived(m_timings);
        }

        // 1 sync bit + 64 data bit
        if(m_timings.count() == 65 && checkValues(Protocol64)){
            //qCWarning(dcHardware) << "64 bit -> " << m_timings << "\n--------------------------";
            changeReading(false);
            emit dataReceived(m_timings);
            m_timings.clear();
        }
    }
}
