// bytran -|- spectral calculations for portable devices using the HITRAN database
//
// Developed by Dzianis Pliutau (Denis Plutov) and Kostya Roslyakov
// www.bytran.org | bytran@bytran.org
// Copyright (c) 2016 Dzianis Pliutau
// Distributed under the MIT license
//
// The bytran logo is distributed under the Creative Commons 3.0 Attribution License: https://creativecommons.org/licenses/by/3.0/us/
//
// The MIT License (MIT)
// Copyright (c) 2016 Dzianis Pliutau
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.



// Object to retrieve values from sensors (pressure, temperature, humidity)
#include "sensors.h"
#include <QMessageBox>
#include <QDebug>
#include <QSensor>
#include <QList>

Sensors::Sensors(QObject *parent, OneSlantLayer *inHorizPathParams) : QObject(parent)
{
    // Transfering in the horizontal path parameters
    horizParams = inHorizPathParams;

    // SensorTag object to get the readings from the SensorTag object
    sensorTag = new SensorTag();

    // Disabling the use of the SensorTag
    this->useSensorTag = false;
    emit useSensorTagStateChanged();

    // Enabling the sensor usage states
    this->totalPressureSensorEnabled = false;
    this->temperatureSensorEnabled = false;
    this->humiditySensorEnabled = false;

    // Object to acquire readings from built in sensors
    sensorsCellPhone = new SensorsCellPhone();

    // Object to get the sensor input from the Sense HAT board
    senseHat = new SenseHat();

    // Initializing the accumulation time and the number of summed samples
    this->numSummedSamples = 0;
    // Values used to calculate averaged total pressure, temperature and humidity
    this->summedTotalPressureAtm = 0;
    this->summedTemperatureK = 0;
    this->summedHumidityPpmv = 0;

    // Object used to convert pressure from the specified units to the default (atm)
    pressConverter = new ConcPressUnitsConvert();
    // Object used to convert temperature from specified units to the default (Kelvin)
    tempConverter = new TemperatureUnitsConvert();
    // Object to convert the humidity from the sensor units to the default units
    humidityConverter = new HumidityConvert();

    // Initializing the timer object
    timer = new QTimer(this);
    timer->start();

    // Connections
    connect(this->timer, SIGNAL(timeout()), this, SLOT(getUpdatedReadings()));
}

// Method to get a pointer to the SensorTag object
SensorTag *Sensors::getSensorTag() { return this->sensorTag; }

// Method to get a pointer to the Cell Phone Sensors object
SensorsCellPhone *Sensors::getSensorsCellPhone() { return this->sensorsCellPhone; }

// Method to get a reference to the Sense HAT class
SenseHat *Sensors::getSenseHat() { return this->senseHat; }

bool Sensors::getUseSensorTag() { return this->useSensorTag; }
void Sensors::setUseSensorTag(bool useSensorTagNew)
{
    // Only available if the sensortag is connected
    if (this->getSensorTag()->getSensorTagConnected())
    {
        // Updating the SensorTag enabled state
        this->useSensorTag = useSensorTagNew;

        // If the sensor tag is switched off and no built in sensors are available, then turn off the sensors
        if (this->useSensorTag == false)
        {
            if (this->getSensorsCellPhone()->getHumiditySensorIsPresent() == false) setEnableHumiditySensor(false);
            if (this->getSensorsCellPhone()->getTemperatureSensorIsPresent() == false) setEnableTemperatureSensor(false);
            if (this->getSensorsCellPhone()->getTotalPressureSensorIsPresent() == false) setEnableTotalPressureSensor(false);
        }
        emit useSensorTagStateChanged();
    }
    else
    {
        this->useSensorTag = false;
        emit useSensorTagStateChanged();
    }

    // If the SensorTag is not to be used, then maybe disable the sensortag temporarily
    // Or else for now keep it on for as long as it is connected, maybe change this later
}

// Methods to get the last recorded values from sensors
double Sensors::getTotalPressureAtm() { return this->totalPressureAtm; }
double Sensors::getTemperatureK() { return this->temperatureK; }
double Sensors::getHumidityPpmv() { return this->humidityPpmv; }

// Methods to get and set the saved total pressure, temperature and humidity values
double Sensors::getSavedTotalPressure() { return this->savedTotalPressureAtm; }
void Sensors::setSavedTotalPressure(double newSavedTotalPressureAtm) { this->savedTotalPressureAtm = newSavedTotalPressureAtm; }
double Sensors::getSavedTemperature() { return this->savedTemperatureK; }
void Sensors::setSavedTemperature(double newSavedTemperatureK) { this->savedTemperatureK = newSavedTemperatureK; }
double Sensors::getSavedHumidity() { return this->savedHumidityPpmv; }
void Sensors::setSavedHumidity(double newSavedHumidityPpmv) { this->savedHumidityPpmv = newSavedHumidityPpmv; }

// Method to get and set the sampling period
int Sensors::getSamplingPeriod() { return timer->interval(); }
void Sensors::setSampligPeriod(int mSec)
{
    timer->setInterval(mSec);
    sensorTag->setSamplingPeriod( mSec);
}

// Methods to start and stop the timer (may be used to temporarily disable sample acquisition)
void Sensors::stopTimer() { timer->stop(); }
void Sensors::startTimer() { timer->start(); }

// Methods to get and set the averaging time
int Sensors::getNumSamplesToAverage() { return this->numSamplesToAverage; }
void Sensors::setNumSamplesToAverage(int number) { this->numSamplesToAverage = number; }

// Methods to enable the sensors to record data
bool Sensors::getTotalPressureSensorIsEnabled() { return this->totalPressureSensorEnabled; }  // Modify to include the SensorTag
void Sensors::setEnableTotalPressureSensor(bool enable)
{
    // Only do this if the sensor tag sensor is present and connected or the cell phone sensor is present
    if (this->sensorsCellPhone->getTotalPressureSensorIsPresent() || (this->sensorTag->getSensorTagConnected()) || (this->senseHat->getSenseHatIsPresent()))
    {
        if (enable == true) {
            // Only do this if the pressure sensor was in an inactive state
            if ((this->totalPressureSensorEnabled) == false)
            {
                this->savedTotalPressureAtm = horizParams->getTotalPress();
                this->totalPressureSensorEnabled = true;
            }
        }
        else {
            // Only do this if the sensor was previously active, otherwise the saved garbage value will be copied
            if ((this->totalPressureSensorEnabled) == true) {
                horizParams->setTotalPress(this->savedTotalPressureAtm);
                this->totalPressureSensorEnabled = false;
            }
        }
        this->sensorsCellPhone->setEnableTotalPressureSensor(this->totalPressureSensorEnabled);
        emit totalPressureUpdated();
    }
    else
    {
        // Only do this if the sensor was previously active, otherwise the saved garbage value will be copied
        if ((this->totalPressureSensorEnabled) == true) {
            horizParams->setTotalPress(this->savedTotalPressureAtm);
            this->totalPressureSensorEnabled = false;
        }
    }
}

bool Sensors::getTemperatureSensorIsEnabled() { return this->temperatureSensorEnabled; }  // Modify to include the SensorTag
void Sensors::setEnableTemperatureSensor(bool enable)
{
    // Only do this if the sensor tag sensor is present and connected or the cell phone sensor is present
    if (this->sensorsCellPhone->getTemperatureSensorIsPresent() || (this->sensorTag->getSensorTagConnected()) || (this->senseHat->getSenseHatIsPresent()))
    {
        if (enable == true) {
            // Only do this if the sensor was in an inactive state
            if ((this->temperatureSensorEnabled) == false)
            {
                this->savedTemperatureK = horizParams->getTemp();
                this->temperatureSensorEnabled = true;
            }
        }
        else {
            // Only do this if the sensor was previously active, otherwise the saved garbage value will be copied
            if ((this->temperatureSensorEnabled) == true) {
                horizParams->setTemp(this->savedTemperatureK);
                this->temperatureSensorEnabled = false;
            }
        }
        this->sensorsCellPhone->setEnableTemperatureSensor(this->temperatureSensorEnabled);
        emit temperatureUpdated();
    }
    else
    {
        // Only do this if the sensor was previously active, otherwise the saved garbage value will be copied
        if ((this->temperatureSensorEnabled) == true) {
            horizParams->setTemp(this->savedTemperatureK);
            this->temperatureSensorEnabled = false;
        }
    }
}

bool Sensors::getHumiditySensorIsEnabled() { return this->humiditySensorEnabled; }  // Modify to include the SensorTag
void Sensors::setEnableHumiditySensor(bool enable)
{
    // Modify to include the SensorTag

    // Only do this if the sensor tag sensor is present and connected or the cell phone sensor is present
    if (this->sensorsCellPhone->getHumiditySensorIsPresent() || (this->sensorTag->getSensorTagConnected()) || (this->senseHat->getSenseHatIsPresent()))
    {
        if (enable == true)
        {
            // Only do this if the sensor was in an inactive state
            if ((this->humiditySensorEnabled) == false)
            {
                this->savedHumidityPpmv = horizParams->getPartPress(0);
                this->humiditySensorEnabled = true;
            }
        }
        else
        {
            // Only do this if the sensor was previously active, otherwise the saved garbage value will be copied
            if ((this->humiditySensorEnabled) == true)
            {
                horizParams->setPartPress(0, this->savedHumidityPpmv);
                this->humiditySensorEnabled = false;
            }
        }
        this->sensorsCellPhone->setEnableHumiditySensor(this->humiditySensorEnabled);
        emit humidityUpdated();
    }
    else
    {
        // Only do this if the sensor was previously active, otherwise the saved garbage value will be copied
        if ((this->humiditySensorEnabled) == true)
        {
            horizParams->setPartPress(0, this->savedHumidityPpmv);
            this->humiditySensorEnabled = false;
        }
    }
}


// Slot is called to get the updated readings from the active sensors
void Sensors::getUpdatedReadings()
{
    double totalPressure;  // Variable to hold the total pressure value acquired from a cell phone or SensorTag sensor
    double temperature;    // Variable to hold the temperature value recorded from a cell phone or SensorTag sensor
    double humidity;       // Variable to hold the humidity value recorded from a cell phone or the SensorTag sensor


    // Getting the updated pressure reading from the pressure sensor
    if (this->totalPressureSensorEnabled)
    {
        // Get the total pressure reading from the cell phone sensor or the SensorTag
        if ((sensorTag->getSensorTagConnected()) && (this->useSensorTag)) { totalPressure = sensorTag->getTotalPressure(); }
        else
        {
            if (this->senseHat->getSenseHatIsPresent() == false) { totalPressure = sensorsCellPhone->getTotalPressure(); }
            else { totalPressure = this->senseHat->getTotalPressure() * 100; }  // 100 is to convert mBar to Pa
        }

        if (this->numSummedSamples < this->numSamplesToAverage) {
            summedTotalPressureAtm = summedTotalPressureAtm + pressConverter->fromSpecifiedToDefaultPressUnits(3, totalPressure);
        }
        else {
            this->totalPressureAtm = this->summedTotalPressureAtm / this->numSamplesToAverage;
            this->summedTotalPressureAtm = 0;
            horizParams->setTotalPress(this->totalPressureAtm);
            emit totalPressureUpdated();
        }
    }

    // Getting the updated temperature reading from the temperature sensor
    if (this->temperatureSensorEnabled)
    {
        if ((sensorTag->getSensorTagConnected()) && (this->useSensorTag)) { temperature = sensorTag->getTemperature(); }
        else
        {
            if (this->senseHat->getSenseHatIsPresent() == false) { temperature = sensorsCellPhone->getTemperature(); }
            else { temperature = this->senseHat->getTemperature(); }
        }

        if (this->numSummedSamples < this->numSamplesToAverage) {
            summedTemperatureK = summedTemperatureK + tempConverter->fromSpecifiedToCurrent(1, temperature);
        }
        else {
            this->temperatureK = this->summedTemperatureK / this->numSamplesToAverage;
            this->summedTemperatureK = 0;
            horizParams->setTemp(this->temperatureK);
            emit temperatureUpdated();
        }
    }

    // Getting the updated humidity reading from the sensor
    if (this->humiditySensorEnabled)
    {
        if ((sensorTag->getSensorTagConnected()) && (this->useSensorTag)) { humidity = sensorTag->getHumidity(); }
        else {
            if (this->senseHat->getSenseHatIsPresent() == false) { humidity = sensorsCellPhone->getHumidity(); }
            else { humidity = this->senseHat->getHumidity(); }
        }

        if (this->numSummedSamples < this->numSamplesToAverage) {
            summedHumidityPpmv = summedHumidityPpmv + humidityConverter->fromRelativeHumidityToPpmv(humidity, tempConverter->fromDefaultToSpecified(1, horizParams->getTemp()), pressConverter->fromDefaultToSpecifiedPressUnits(horizParams->getTotalPress(), 3));
        }
        else
        {
            this->humidityPpmv = this->summedHumidityPpmv / this->numSamplesToAverage;
            this->summedHumidityPpmv = 0;
            horizParams->setPartPress(0, this->humidityPpmv);
            emit humidityUpdated();
        }
    }

    // If at least one of the sensors is active, then updating the number of summed samples
    if ((this->totalPressureSensorEnabled == true) || (this->temperatureSensorEnabled == true) || (this->humiditySensorEnabled == true))
    {
        // Reinitializing the number of summed samples
        if (this->numSummedSamples < this->numSamplesToAverage) { this->numSummedSamples++; }
                                                           else { this->numSummedSamples = 0; }
    }
}

// Methods to establish if one of the below sensors is present
bool Sensors::getTotalPressureSensorIsPresent()
{
    if (this->sensorTag->getSensorTagConnected() || this->sensorsCellPhone->getTotalPressureSensorIsPresent()) { return true; }
                                                                                                          else { return false; }
}
bool Sensors::getTemperatureSensorIsPresent()
{
    if (this->sensorTag->getSensorTagConnected() || this->sensorsCellPhone->getTemperatureSensorIsPresent()) { return true; }
                                                                                                        else { return false; }
}
bool Sensors::getHumiditySensorIsPresent()
{
    if (this->sensorTag->getSensorTagConnected() || this->sensorsCellPhone->getHumiditySensorIsPresent()) { return true; }
                                                                                                     else { return false; }
}

// Method to connect and disconnect a SensorTag device
void Sensors::connectSensorTagDevice() { this->getSensorTag()->connectToDevice(); }
void Sensors::disconnectSensorTagDevice()
{
    // Disabling sensortag
    setUseSensorTag(false);
    emit useSensorTagStateChanged();

    // Disconnecting from the SensorTag device
    this->getSensorTag()->disconnectFromDevice();
}
