/*  Copyright (C) 2021 Avamander

    This file is part of InfiniTime.

    InfiniTime 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, either version 3 of the License, or
    (at your option) any later version.

    InfiniTime is distributed in the hope that it will be useful,
    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 this program.  If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once

/**
 * Different weather events, weather data structures used by {@link WeatherService.h}
 *
 * How to upload events to the timeline?
 *
 * All timeline write payloads are simply CBOR-encoded payloads of the structs described below.
 *
 * All payloads have a mandatory header part and the dynamic part that
 * depends on the event type specified in the header. If you don't,
 * you'll get an error returned. Data is relatively well-validated,
 * so keep in the bounds of the data types given.
 *
 * Write all struct members (CamelCase keys) into a single finite-sized map, and write it to the characteristic.
 * Mind the MTU.
 *
 * How to debug?
 *
 * There's a Screen that you can compile into your firmware that shows currently valid events.
 * You can adapt that to display something else. That part right now is very much work in progress
 * because the exact requirements are not yet known.
 *
 *
 * Implemented based on and other material:
 * https://en.wikipedia.org/wiki/METAR
 * https://www.weather.gov/jetstream/obscurationtypes
 * http://www.faraim.org/aim/aim-4-03-14-493.html
 */

namespace Pinetime {
  namespace Controllers {
    class WeatherData {
    public:
      /**
       * 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;
      };

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

      /** 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;
      };

      /** 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;
      };

      /**
       * 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;
      };

      /**
       * 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;
      };

      /**
       * 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;
      };

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

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

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

      /**
       * 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;
      };
    };
  }
}
