/**
 * GuL_NovaFitness
 * Copyright (c) 2023 Guido Lehne
 *
 * 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.
 *
 * Developed for Arduino-ESP32
 * Created by Guido Lehne
 *
 */
#pragma once

#include "UARTInterface.h"

namespace GuL
{

    class NovaFitness
    {
    public:
        enum FailureReason
        {
            NONE,
            CHECKSUM_ERROR,
            INVALID_FRAME,
            UNKNOWN_COMMAND_ID,
            INCOMPLETE_FRAME,
            UART_INTERFACE_NOT_AVAILABLE,
            TOO_MUCH_DATA
        };
        struct FirmwareVersion
        {
            uint8_t year = 0;
            uint8_t month = 0;
            uint8_t day = 0;
        };

        enum ReportingMode
        {
            ACTIVE,
            PASSIVE
        };
        enum WorkMode
        {
            SLEEP,
            WAKEUP
        };

        NovaFitness(UARTInterface &stream, uint16_t id = 0xFFFF);
#if defined(ARDUINO)
        NovaFitness(HardwareSerial &stream, uint16_t id = 0xFFFF);
        NovaFitness(Stream &stream, uint16_t id = 0xFFFF);
#endif
        const char *getSensorName() { return _name; }

        bool sleep();
        bool wakeup();
        bool setToActiveReporting();
        bool setToPassiveReporting();
        bool setWorkingPeriod(uint8_t minutes);

        bool poll();
        bool pollFirmwareVersion();
        bool pollReportingMode();
        bool pollWorkingMode();
        bool pollWorkingPeriod();
        bool read();

        FirmwareVersion getVersion();
        uint16_t getID();
        float getPM2_5();
        float getPM10();
        float getPM100();
        ReportingMode getReportingMode();
        WorkMode getWorkMode();

        FailureReason getLastFailureReason() { return _lastFailureReason; }

    protected:
        enum ParsingSteps
        {
            WAIT_FOR_NEW_FRAME,
            RECEIVE_COMMAND_ID,
            RECEIVE_PAYLOAD,
            RECEIVE_TAIL
        };

        enum CommandID
        {
            SETTINGS_COMMAND_ID = 0xC5,
            DATA_COMMAND_ID = 0xC0,
            DATA100_COMMAND_ID = 0xCF
        };

        enum SettingsType
        {
            REPORTING_MODE = 0x02,
            ID_SETTING = 0x05,
            WORK_MODE = 0x06,
            WORKING_PERIOD = 0x08,
            FIRMWARE_SETTING = 0x07,
            QUERY_DATA = 0x04
        };

        enum SettingsAction
        {
            Query = 0x00,
            Set = 0x01
        };

        FirmwareVersion _firmwareVersion;

        ParsingSteps _parseStep = WAIT_FOR_NEW_FRAME;
        ReportingMode _reportingMode;
        WorkMode _workMode;
        uint16_t _workingPeriodSeconds;
        float _pm2_5 = -1, _pm10 = -1, _pm100 = -1;

        bool send(uint8_t *cmd, size_t cmdSize);
        void unpackFrame();
        void handleDataPayload();
        void handleData100Payload();
        void handleSettingsPayload();
        const char *_name = "NOVAFITNESS";

    private:
#ifdef ARDUINO
        ArduinoHardwareSerialWrapper _hardwareSerialWrapper;
        StreamWrapper _streamWrapper;
#endif
        UARTInterface *_uart = nullptr;
        static constexpr size_t _expected_incoming_frame_length = 10; // Default incoming frame length (header + command ID + payload + checksum + tail), at the moment it is constant but maybe in future there will be frames with different lengths, so this is just a default value for buffer size
        static constexpr size_t MAX_INCOMING_FRAME_LENGTH = 10;       // Maximum possible frame length (e.g. for data100 frames)
        uint8_t _payload[MAX_INCOMING_FRAME_LENGTH];                  // Buffer to store payload of incoming frame (max frame length minus header, command ID and checksum)
        uint8_t _payloadSize = 0;

        uint8_t calcChecksum(uint8_t *cmd, size_t cmdSize);
        void setIdIntoFrame(uint8_t *cmd);
        bool tryParseFrame();
        void shiftBufferToNextStartByte();

    private:
        uint16_t _id;
        FailureReason _lastFailureReason = NONE;

    public:
    };

}; // namespace GuL
