#ifndef INFINITIMEWEATHERSERVICE_H
#define INFINITIMEWEATHERSERVICE_H

#include <QObject>
#include "qble/qbleservice.h"
#include "devices/abstractdevice.h"

/** 00040000-78fc-48fe-8e23-433b3a1942d0 --Weather Service
 * "00040001-78fc-48fe-8e23-433b3a1942d0", --Data
 * "00040002-78fc-48fe-8e23-433b3a1942d0", --Control
 */
class InfiniTimeWeatherService : public QBLEService
{
    Q_OBJECT
public:
    static const char *UUID_SERVICE_WEATHER;
    static const char *UUID_CHARACTERISTIC_WEATHER_DATA;
    static const char *UUID_CHARACTERISTIC_WEATHER_CONTROL;

    explicit InfiniTimeWeatherService(const QString &path, QObject *parent);

    void sendWeather(CurrentWeather *weather);

private:
    Q_SLOT void characteristicChanged(const QString &characteristic, const QByteArray &value);

    QPoint degreesToEPSG3875(const QPointF& location);

    // From firmware
    /**
     * Visibility obscuration types
     */
    enum class obscurationtype {
        /** No obscuration */
        None = 0,
        /** Water particles suspended in the air; low visibility; does not fall */
        Fog = 1,
        /** Tiny, dry particles in the air; invisible to the eye; opalescent */
        Haze = 2,
        /** Small fire-created particles suspended in the air */
        Smoke = 3,
        /** Fine rock powder, from for example volcanoes */
        Ash = 4,
        /** Fine particles of earth suspended in the air by the wind */
        Dust = 5,
        /** Fine particles of sand suspended in the air by the wind */
        Sand = 6,
        /** Water particles suspended in the air; low-ish visibility; temperature is near dewpoint */
        Mist = 7,
        /** This is SPECIAL in the sense that the thing raining down is doing the obscuration */
        Precipitation = 8,
        Length
    };

    /**
     * Types of precipitation
     */
    enum class precipitationtype {
        /**
       * No precipitation
       *
       * Theoretically we could just _not_ send the event, but then
       * how do we differentiate between no precipitation and
       * no information about precipitation
       */
        None = 0,
        /** Drops larger than a drizzle; also widely separated drizzle */
        Rain = 1,
        /** Fairly uniform rain consisting of fine drops */
        Drizzle = 2,
        /** Rain that freezes upon contact with objects and ground */
        FreezingRain = 3,
        /** Rain + hail; ice pellets; small translucent frozen raindrops */
        Sleet = 4,
        /** Larger ice pellets; falling separately or in irregular clumps */
        Hail = 5,
        /** Hail with smaller grains of ice; mini-snowballs */
        SmallHail = 6,
        /** Snow... */
        Snow = 7,
        /** Frozen drizzle; very small snow crystals */
        SnowGrains = 8,
        /** Needles; columns or plates of ice. Sometimes described as "diamond dust". In very cold regions */
        IceCrystals = 9,
        /** It's raining down ash, e.g. from a volcano */
        Ash = 10,
        Length
    };

    /**
     * These are special events that can "enhance" the "experience" of existing weather events
     */
    enum class specialtype {
        /** Strong wind with a sudden onset that lasts at least a minute */
        Squall = 0,
        /** Series of waves in a water body caused by the displacement of a large volume of water */
        Tsunami = 1,
        /** Violent; rotating column of air */
        Tornado = 2,
        /** Unplanned; unwanted; uncontrolled fire in an area */
        Fire = 3,
        /** Thunder and/or lightning */
        Thunder = 4,
        Length
    };

    /**
     * These are used for weather timeline manipulation
     * that isn't just adding to the stack of weather events
     */
    enum class controlcodes {
        /** How much is stored already */
        GetLength = 0,
        /** This wipes the entire timeline */
        DelTimeline = 1,
        /** There's a currently valid timeline event with the given type */
        HasValidEvent = 3,
        Length
    };

    /**
     * Events have types
     * then they're easier to parse after sending them over the air
     */
    enum class eventtype : uint8_t {
        /** @see obscuration */
        Obscuration = 0,
        /** @see precipitation */
        Precipitation = 1,
        /** @see wind */
        Wind = 2,
        /** @see temperature */
        Temperature = 3,
        /** @see airquality */
        AirQuality = 4,
        /** @see special */
        Special = 5,
        /** @see pressure */
        Pressure = 6,
        /** @see location */
        Location = 7,
        /** @see cloud */
        Clouds = 8,
        /** @see humidity */
        Humidity = 9,
        Length
    };

    /**
     * Valid event query
     *
     * NOTE: Not currently available, until needs are better known
     */
    class ValidEventQuery {
    public:
        static constexpr controlcodes code = controlcodes::HasValidEvent;
        eventtype eventType;
    };

    /** The header used for further parsing */
    class TimelineHeader {
    public:
        /**
       * UNIX timestamp
       * TODO: This is currently WITH A TIMEZONE OFFSET!
       * Please send events with the timestamp offset by the timezone.
       **/
        uint64_t timestamp;
        /**
       * Time in seconds until the event expires
       *
       * 32 bits ought to be enough for everyone
       *
       * If there's a newer event of the same type then it overrides this one, even if it hasn't expired
       */
        uint32_t expires;
        /**
       * What type of weather-related event
       */
        eventtype eventType;

        virtual QByteArray toCBOR() const = 0;
    };

    /** Specifies how cloudiness is stored */
    class Clouds : public TimelineHeader {
    public:
        /** Cloud coverage in percentage, 0-100% */
        uint8_t amount;

        QByteArray toCBOR() const override;
    };

    /** Specifies how obscuration is stored */
    class Obscuration : public TimelineHeader {
    public:
        /** Type of precipitation */
        obscurationtype type;
        /**
       * Visibility distance in meters
       * 65535 is reserved for unspecified
       */
        uint16_t amount;

        QByteArray toCBOR() const override;
    };

    /** Specifies how precipitation is stored */
    class Precipitation : public TimelineHeader {
    public:
        /** Type of precipitation */
        precipitationtype type;
        /**
       * How much is it going to rain? In millimeters
       * 255 is reserved for unspecified
       **/
        uint8_t amount;

        QByteArray toCBOR() const override;
    };

    /**
     * How wind speed is stored
     *
     * In order to represent bursts of wind instead of constant wind,
     * you have minimum and maximum speeds.
     *
     * As direction can fluctuate wildly and some watchfaces might wish to display it nicely,
     * we're following the aerospace industry weather report option of specifying a range.
     */
    class Wind : public TimelineHeader {
    public:
        /** Meters per second */
        uint8_t speedMin;
        /** Meters per second */
        uint8_t speedMax;
        /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
        uint8_t directionMin;
        /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
        uint8_t directionMax;

        QByteArray toCBOR() const override;
    };

    /**
     * How temperature is stored
     *
     * As it's annoying to figure out the dewpoint on the watch,
     * please send it from the companion
     *
     * We don't do floats, picodegrees are not useful. Make sure to multiply.
     */
    class Temperature : public TimelineHeader {
    public:
        /**
       * Temperature °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
       * -32768 is reserved for "no data"
       */
        int16_t temperature;
        /**
       * Dewpoint °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
       * -32768 is reserved for "no data"
       */
        int16_t dewPoint;

        QByteArray toCBOR() const override;
    };

    /**
     * How location info is stored
     *
     * This can be mostly static with long expiration,
     * as it usually is, but it could change during a trip for ex.
     * so we allow changing it dynamically.
     *
     * Location info can be for some kind of map watchface
     * or daylight calculations, should those be required.
     *
     */
    class Location : public TimelineHeader {
    public:
        /** Location name */
        std::string location;
        /** Altitude relative to sea level in meters */
        int16_t altitude;
        /** Latitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
        int32_t latitude;
        /** Longitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
        int32_t longitude;

        QByteArray toCBOR() const override;
    };

    /**
     * How humidity is stored
     */
    class Humidity : public TimelineHeader {
    public:
        /** Relative humidity, 0-100% */
        uint8_t humidity;

        QByteArray toCBOR() const override;
    };

    /**
     * How air pressure is stored
     */
    class Pressure : public TimelineHeader {
    public:
        /** Air pressure in hectopascals (hPa) */
        int16_t pressure;

        QByteArray toCBOR() const override;
    };

    /**
     * How special events are stored
     */
    class Special : public TimelineHeader {
    public:
        /** Special event's type */
        specialtype type;

        QByteArray toCBOR() const override;
    };

    /**
     * How air quality is stored
     *
     * These events are a bit more complex because the topic is not simple,
     * the intention is to heavy-lift the annoying preprocessing from the watch
     * this allows watchface or watchapp makers to generate accurate alerts and graphics
     *
     * If this needs further enforced standardization, pull requests are welcome
     */
    class AirQuality : public TimelineHeader {
    public:
        /**
       * The name of the pollution
       *
       * for the sake of better compatibility with watchapps
       * that might want to use this data for say visuals
       * don't localize the name.
       *
       * Ideally watchapp itself localizes the name, if it's at all needed.
       *
       * E.g.
       * For generic ones use "PM0.1", "PM5", "PM10"
       * For chemical compounds use the molecular formula e.g. "NO2", "CO2", "O3"
       * For pollen use the genus, e.g. "Betula" for birch or "Alternaria" for that mold's spores
       */
        std::string polluter;
        /**
       * Amount of the pollution in SI units,
       * otherwise it's going to be difficult to create UI, alerts
       * and so on and for.
       *
       * See more:
       * https://ec.europa.eu/environment/air/quality/standards.htm
       * http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
       *
       * Example units:
       * count/m³ for pollen
       * µgC/m³ for micrograms of organic carbon
       * µg/m³ sulfates, PM0.1, PM1, PM2, PM10 and so on, dust
       * mg/m³ CO2, CO
       * ng/m³ for heavy metals
       *
       * List is not comprehensive, should be improved.
       * The current ones are what watchapps assume!
       *
       * Note: ppb and ppm to concentration should be calculated on the companion, using
       * the correct formula (taking into account temperature and air pressure)
       *
       * Note2: The amount is off by times 100, for two decimal places of precision.
       * E.g. 54.32µg/m³ is 5432
       *
       */
        uint32_t amount;

        QByteArray toCBOR() const override;
    };

};

#endif // INFINITIMEWEATHERSERVICE_H
