/* Copyright (c) 2020-2022 Skyward Experimental Rocketry
 * Authors: Luca Conterio, Alberto Nidasio
 *
 * 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.
 */

#pragma once

#include <Eigen/Core>
#include <ostream>

namespace Boardcore
{

/**
 * @brief Generic error codes that a sensor can generate.
 *
 * Sensors can extend this enum by defining a new set of errors,
 * starting from END_OF_BASE_ERRORS.
 */
enum SensorErrors : uint8_t
{
    NO_ERRORS          = 0,
    INVALID_WHOAMI     = 1,
    INIT_FAIL          = 2,
    NOT_INIT           = 3,  // if some method called before init()
    ALREADY_INIT       = 4,  // if init() called multiple times
    SELF_TEST_FAIL     = 5,
    BUS_FAULT          = 6,
    NO_NEW_DATA        = 7,  // no new data available from the sensor
    INVALID_FIFO_INDEX = 8,
    DMA_ERROR          = 9,
    COMMAND_FAILED     = 10,
    END_OF_BASE_ERRORS = 11  // used to extend this enum
};

struct TimestampData
{
    uint64_t timestamp;
};

struct LoadCellData
{
    uint64_t loadTimestamp = 0;
    float load             = 0;

    static std::string header() { return "loadTimestamp,load\n"; }

    void print(std::ostream& os) const
    {
        os << loadTimestamp << "," << load << "\n";
    }
};

struct TemperatureData
{
    uint64_t temperatureTimestamp = 0;
    float temperature             = 0;

    static std::string header() { return "timestamp,temperature\n"; }

    void print(std::ostream& os) const
    {
        os << temperatureTimestamp << "," << temperature << "\n";
    }
};

struct PressureData
{
    uint64_t pressureTimestamp = 0;
    float pressure             = 0;

    static std::string header() { return "timestamp,pressure\n"; }

    void print(std::ostream& os) const
    {
        os << pressureTimestamp << "," << pressure << "\n";
    }
};

/**
 * @brief Structure to handle humidity data.
 */
struct HumidityData
{
    uint64_t humidityTimestamp = 0;
    float humidity             = 0;
};

/**
 * @brief Structure to handle accelerometer data.
 */
struct AccelerometerData
{
    uint64_t accelerationTimestamp = 0;
    float accelerationX            = 0;
    float accelerationY            = 0;
    float accelerationZ            = 0;

    AccelerometerData() {}

    AccelerometerData(uint64_t timestamp, float x, float y, float z)
        : accelerationTimestamp(timestamp), accelerationX(x), accelerationY(y),
          accelerationZ(z)
    {
    }

    AccelerometerData(const AccelerometerData& data) = default;

    explicit AccelerometerData(const Eigen::Vector3f& acc)
        : accelerationX(acc(0)), accelerationY(acc(1)), accelerationZ(acc(2))
    {
    }

    static std::string header()
    {
        return "timestamp,accelerationX,accelerationY,accelerationZ\n";
    }

    void print(std::ostream& os) const
    {
        os << accelerationTimestamp << "," << accelerationX << ","
           << accelerationY << "," << accelerationZ << "\n";
    }

    operator Eigen::Vector3f() const
    {
        return {accelerationX, accelerationY, accelerationZ};
    }
};

/**
 * @brief Structure to handle quaternion data.
 */
struct QuaternionData
{
    uint64_t quaternionTimestamp = 0;
    float quaternionX            = 0;
    float quaternionY            = 0;
    float quaternionZ            = 0;
    float quaternionW            = 0;

    QuaternionData() {}

    QuaternionData(uint64_t timestamp, float x, float y, float z, float w)
        : quaternionTimestamp(timestamp), quaternionX(x), quaternionY(y),
          quaternionZ(z), quaternionW(w)
    {
    }

    QuaternionData(const QuaternionData& data) = default;

    explicit QuaternionData(const Eigen::Vector4f& quat)
        : quaternionX(quat(0)), quaternionY(quat(1)), quaternionZ(quat(2)),
          quaternionW(quat(3))
    {
    }

    static std::string header()
    {
        return "timestamp,quaternionX,quaternionY,quaternionZ,quaterionW\n";
    }

    void print(std::ostream& os) const
    {
        os << quaternionTimestamp << "," << quaternionX << "," << quaternionY
           << "," << quaternionZ << "," << quaternionW << "\n";
    }

    operator Eigen::Vector4f() const
    {
        return {quaternionX, quaternionY, quaternionZ, quaternionW};
    }
};

/**
 * @brief Structure to handle gyroscope data.
 */
struct GyroscopeData
{
    uint64_t angularSpeedTimestamp = 0;
    float angularSpeedX            = 0;
    float angularSpeedY            = 0;
    float angularSpeedZ            = 0;

    GyroscopeData() {}

    GyroscopeData(uint64_t timestamp, float x, float y, float z)
        : angularSpeedTimestamp(timestamp), angularSpeedX(x), angularSpeedY(y),
          angularSpeedZ(z)
    {
    }

    GyroscopeData(const GyroscopeData& data) = default;

    explicit GyroscopeData(const Eigen::Vector3f& vel)
        : angularSpeedX(vel(0)), angularSpeedY(vel(1)), angularSpeedZ(vel(2))
    {
    }

    static std::string header()
    {
        return "timestamp,angularSpeedX,angularSpeedY,angularSpeedZ\n";
    }

    void print(std::ostream& os) const
    {
        os << angularSpeedTimestamp << "," << angularSpeedX << ","
           << angularSpeedY << "," << angularSpeedZ << "\n";
    }

    operator Eigen::Vector3f() const
    {
        return {angularSpeedX, angularSpeedY, angularSpeedZ};
    }
};

/**
 * @brief Structure to handle magnetometer data.
 */
struct MagnetometerData
{
    uint64_t magneticFieldTimestamp = 0;
    float magneticFieldX            = 0;
    float magneticFieldY            = 0;
    float magneticFieldZ            = 0;

    MagnetometerData() {}

    MagnetometerData(uint64_t timestamp, float x, float y, float z)
        : magneticFieldTimestamp(timestamp), magneticFieldX(x),
          magneticFieldY(y), magneticFieldZ(z)
    {
    }

    MagnetometerData(const MagnetometerData& data) = default;

    explicit MagnetometerData(const Eigen::Vector3f& mag)
        : magneticFieldX(mag(0)), magneticFieldY(mag(1)), magneticFieldZ(mag(2))
    {
    }

    static std::string header()
    {
        return "timestamp,magneticFieldX,magneticFieldY,magneticFieldZ\n";
    }

    void print(std::ostream& os) const
    {
        os << magneticFieldTimestamp << "," << magneticFieldX << ","
           << magneticFieldY << "," << magneticFieldZ << "\n";
    }

    operator Eigen::Vector3f() const
    {
        return {magneticFieldX, magneticFieldY, magneticFieldZ};
    }
};

/**
 * @brief Structure to handle GPS data.
 */
struct GPSData
{
    uint64_t gpsTimestamp = 0;
    float latitude        = 0;  // [deg]
    float longitude       = 0;  // [deg]
    float height          = 0;  // [m]
    float velocityNorth   = 0;  // [m/s]
    float velocityEast    = 0;  // [m/s]
    float velocityDown    = 0;  // [m/s]w
    float speed           = 0;  // [m/s]
    float track           = 0;  // [deg]
    float positionDOP     = 0;  // [?]
    uint8_t satellites    = 0;  // [1]
    uint8_t fix           = 0;  // 0 = no fix

    static std::string header()
    {
        return "timestamp,latitude,longitude,height,velocityNorth,velocityEast,"
               "velocityDown,speed,track,positionDOP,satellites,fix\n";
    }

    void print(std::ostream& os) const
    {
        os << gpsTimestamp << "," << longitude << "," << latitude << ","
           << height << "," << velocityNorth << "," << velocityEast << ","
           << velocityDown << "," << speed << "," << track << "," << positionDOP
           << "," << (int)satellites << "," << (int)fix << "\n";
    }
};

/**
 * @brief Structure to handle ADC data.
 */
struct ADCData
{
    uint64_t voltageTimestamp = 0;
    uint8_t channelId         = 0;
    float voltage             = 0;
};

/**
 * @brief Structure to handle current data.
 */
struct CurrentData
{
    uint64_t currentTimestamp = 0;
    float current             = 0;

    static std::string header() { return "timestamp,current\n"; }

    void print(std::ostream& os) const
    {
        os << currentTimestamp << "," << current << "\n";
    }
};

}  // namespace Boardcore