# Copyright (C) 2019 Majormode.  All rights reserved.
#
# This software is the confidential and proprietary information of
# Majormode or one of its subsidiaries.  You shall not disclose this
# confidential information and shall use it only in accordance with the
# terms of the license agreement or other applicable agreement you
# entered into with Majormode.
#
# MAJORMODE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
# OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
# TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
# PURPOSE, OR NON-INFRINGEMENT.  MAJORMODE SHALL NOT BE LIABLE FOR ANY
# LOSSES OR DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
# OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.

from __future__ import annotations

from majormode.perseus.model.date import ISO8601DateTime
from majormode.perseus.model.geolocation import GeoPoint
from majormode.perseus.utils import cast

from majormode.xeberus.constant.device import BatteryStateEventType


class Event:
    """
    Represent an event that occurred on a client application.
    """
    def __eq__(self, other: Event) -> bool:
        return self._event_id == other.event_id

    def __init__(
            self,
            event_id: str,
            event_time: ISO8601DateTime):
        """
        Build a new event.


        @param event_id: The identification of the event generated by the
            client application.  This identification is also known as the
            client identification (CLI).  This identification is generally
            superseded by an identification generated by the server
            application.

             This information is used to detect events that the mobile device would
             report multiple times.  This happens when network outage occurs after
             the client application reported successfully a batch of events, but
             the network connection with the cloud service timed out before the
             client application had the chance to receive the acknowledgement from
             the cloud service.  Therefore, the client application reattempts to
             report one more time these events.

        @param event_time: The time when tis event occured.
        """
        self._event_id = event_id
        self._event_time = event_time

    @property
    def event_id(self) -> str:
        """
        Return the identification of the event generated by the client
        application.


        @return: The identification of the event.
        """
        return self._event_id

    @property
    def event_time(self) -> ISO8601DateTime:
        """
        Return the time when the event occurred.


        @return: The time when the event occurred.
        """
        return self._event_time


class BatteryStateChangeEvent(Event):
    """
    Event that notifies from the change of the state of the internal
    battery of a mobile device, such as:

    - The device is connected to or disconnected from the power source of
      the vehicle this device is mounted on;

    - The level of the device's battery increases or decreases.

    Only devices equipped with an internal battery can post this
    notification.  A device not equipped with an internal battery is
    indeed less secure as it is not able to send any notification when it
    is disconnected from the power source of the vehicle this device is
    mounted on; the device is immediately switched off.
    """
    def __init__(
            self,
            event_id: str,
            event_type: BatteryStateEventType,
            event_time: ISO8601DateTime,
            battery_level: float,
            location: GeoPoint = None):
        """
        Build a new object `BatteryStateChangeEvent`.


        @param event_id: The identification of the event generated by the
            client application running on the mobile device.

        @param event_type: An item of the enumeration `BatteryStateEventType`
            that indicates the type of event that occurred.

        @param event_time: The time when this event occurred.

        @param battery_level: The level in percentage of the device's internal
            battery when this even occurred.

        @param location: The last known location of the device when this event
            occurred.
        """
        super().__init__(event_id, event_time)
        self._event_type = event_type
        self._battery_level = battery_level
        self._location = location

    @property
    def battery_level(self) -> float:
        """
        Return the level in percentage of the device's internal battery when
        this even occurred.


        @return: The level in percentage of the device's internal
            battery when this even occurred. 
        """
        return self._battery_level

    @property
    def event_type(self) -> BatteryStateEventType:
        """
        Return the type of event that occurred.


        @return: An item of the enumeration `BatteryStateEventType` that
            indicates the type of event that occurred.
        """
        return self._event_type

    @staticmethod
    def from_json(payload: any) -> BatteryStateChangeEvent:
        """
        Build an object `BatteryStateChangeEvent` from the specified JSON
        expression.


        @param payload: JSON representation of a battery state change event:

            {
              "battery_level": decimal,
              "event_id": string,
              "event_time": timestamp,
              "event_type": string,
              "location": {
                "accuracy": decimal,
                "altitude": decimal,
                "bearing": decimal,
                "fix_time": timestamp,
                "latitude": decimal,
                "longitude": decimal,
                "provider": string,
                "speed": decimal
              }
            }

            where:

            - `battery_level` (required): The level in percentage of the mobile
              device's battery (expressed between `0.0` and `1.1`) when this
              event occurred.

            - `event_id` (required): The identification of the event generated by
              the client application.

            - `event_time` (required): The time when this event occurred, which
              could be slightly different from the time when the device sent this
              event to the platform, for instance, in case of limited network
              connectivity, the device decided to cache locally this event before
              sending it eventually to the platform.

            - `event_type` (required): Indicate the type of event that occurred:

              - `battery_charging`: The level of the device's battery has
                increased significantly;

              - `battery_discharging`: The level of the device's battery has
                decreased significantly;

              - `battery_plugged`: The battery of the device has been plugged to
                a power source;

              - `battery_unplugged`: The battery of the device has been unplugged
                from a power source.

            - `location` (optional): Last known location of the device when this
              event occurred:

              - `accuracy` (required): The accuracy in meters of the location.

              - `altitude` (required): The altitude in meters of the location.

              - `bearing` (optional): The bearing in degrees of the device.  Bearing
                is the horizontal direction of travel of the device, and is not
                related to the device orientation.  It is guaranteed to be in the
                range `[0.0, 360.0]`.

              - `fix_time` (required): The time when the device determined the
                information of this location fix.

              - `latitude` (required): The latitude-angular distance, expressed in
                decimal degrees (WGS84 datum), measured from the center of the Earth,
                of a point north or south of the Equator corresponding to the
                location.

              - `longitude` (required): The longitude-angular distance, expressed in
                decimal degrees (WGS84 datum), measured from the center of the Earth,
                of a point east or west of the Prime Meridian corresponding to the
                location.

              - `provider` (required): The code name of the location provider that
                reported the location.

                - `fused`: The location API in Google Play services that combines
                  different signals to provide the location information.

                - `gps`: indicate that the location has been provided by a Global
                  Positioning System (GPS).

                - `network`: indicate that the location has been provided by an hybrid
                  positioning system, which uses different positioning technologies,
                  such as Global Positioning System (GPS), Wi-Fi hotspots, cell tower
                  signals.

                - `passive`: A special location provider for receiving locations without
                  actually initiating a location fix.  This provider can be used to
                  passively receive location updates when other applications or services
                  request them without actually requesting the locations yourself.  This
                  provider will return locations generated by other providers.

              - `speed` (optional): The speed in meters/second over the ground.


        @return: An event object.
        """
        return payload and BatteryStateChangeEvent(
            payload['event_id'],
            cast.string_to_enum(payload['event_type'], BatteryStateEventType),
            cast.string_to_timestamp(payload['event_time']),
            payload['battery_level'],
            location=GeoPoint.from_json(payload.get('location'))
        )

    @property
    def location(self) -> GeoPoint:
        """
        Return the last known location of the device when this event occurred.


        @return: The last known location of the device when this event
            occurred.
        """
        return self._location


class LocationUpdate(Event):
    """
    A Geographical location report that a mobile device sent to the
    Location-Based Service (LBS) platform.
    """
    def __eq__(self, other: LocationUpdate) -> bool:
        """
        Indicate whether this location report object is the same as another
        location passed to this function.


        @param other: an instance `LocationUpdate`.


        @return: `True` if the two objects represent the same geographical
            location report (same identification and same fix time); `False`
            otherwise.
        """
        return 0 if self._event_id == other.event_id \
            else self._location.fix_time == other.location.fix_time

    def __init__(
            self,
            event_id: str,
            event_time: ISO8601DateTime,
            location: GeoPoint,
            mileage: float = None,
            network_type: str = None,
            payload: any = None,
            satellites: str = None):
        """
        Build a new instance `LocationUpdate`.


        @param event_id: The reference of this event as given by the client
             application running on the mobile device that reported this event.

             This information is used to detect events that the mobile device would
             report multiple times.  This happens when network outage occurs after
             the client application reported successfully a batch of events, but
             the network connection with the cloud service timed out before the
             client application had the chance to receive the acknowledgement from
             the cloud service.  Therefore, the client application reattempts to
             report one more time these events.

        @param event_time: The time when this event occurred, which
             could be slightly different from the time when the device sent this
             event to the platform, for instance, in case of limited network
             connectivity, the device decided to cache locally this event before
             sending it eventually to the platform.

        @param location: The location fix reported by the mobile device.

        @param mileage: The total distance travelled by the mobile device
            since the application has been installed and executed on this
            device.  This mileage is calculated by the device itself based on
            the location changes its internal GPS determined over the time.

            This mileage might not correspond to the mileage displayed by the
            odometer of the vehicle this device is mounted on.

        @param network_type: A string representation of the network connection
            at the time this location fix has been determined by the mobile
            device of this location fix:

               MNCMCC:type[:subtype]

            where:

            - MNCMCC:string` (required): A Mobile Network Code (MNC) used in
              combination with a Mobile Country Code (MCC) (also known as a "MCC/MNC
              tuple") to uniquely identify the telephony operator of the mobile
              device.

            - `type:string` (required): A human-readable name that describes the
              type of the network that the device is connected to, such as `wifi`,
              `mobile`, `unknown`.

            - `subtype:string` (optional): A human-readable name that describes the
              subtype of this network when applicable.  Network of type `wifi` has
              no subtype.  Network of type `mobile` can have a subtype such as
              `egde`, `hsdpa`, etc.

        @param payload: A JSON expression of additional information that the
            mobile device provided within this report.

        @param satellites: A string representation of the information about
            the satellites that were used to calculate this location fix:

                (azimuth:elevation:PRN:SNR)[, ...]

            where:

            - `azimuth:float`: Azimuth of the satellite in degrees.

            - `elevation:float`: Elevation of the satellite in degrees.

            - `PRN:integer`: Pseudo-Random Number (PRN) for the satellite.

            - `SNR:float`: Signal to Noise Ratio (SNR) for the satellite.
        """
        super().__init__(event_id, event_time)
        self._location = location
        self._network_type = network_type
        self._payload = payload
        self._satellites = satellites

    @staticmethod
    def from_json(payload: any) -> LocationUpdate:
        """
        Build a `LocationUpdate` object from the specified JSON object.


        @param payload: A JSON representation of a location report:

                {
                  "accuracy": decimal,
                  "altitude": decimal,
                  "bearing": decimal,
                  "event_id": string,
                  "event_time": timestamp,
                  "fix_time": timestamp,
                  "latitude": decimal,
                  "longitude": decimal,
                  "mileage": decimal,
                  "network_type": string,
                  "provider": string,
                  "satellites": string,
                  "speed": decimal
                }

            where:

            - `accuracy` (required): The accuracy in meters of the location.

            - `altitude` (required): The altitude in meters of the location.

            - `bearing` (optional): The bearing in degrees of the mobile device.
              Bearing is the horizontal direction of travel of the mobile device,
              and is not related to the device orientation.  It is guaranteed to be
              in the range `[0.0, 360.0]`.

            - `event_id` (required): The identification of this event generated
              by the client application running on the mobile device that reported
              this event.

              This identification is used to detect events that the mobile device
              would report multiple times.  This happens when network outage occurs
              after the client application reported successfully a batch of events,
              but the network connection with the cloud service timed out before the
              client application had the chance to receive the acknowledgement from
              the cloud service.  Therefore, the client application reattempts to
              report one more time these events.

              @note: This identification is generally stored in an 'event_cid` on
                  the server persistent storage.

            - `event_time` (required): The time when this event occurred, which
              could be slightly different from the time when the device sent this
              event to the platform, for instance, in case of limited network
              connectivity, the device decided to cache locally this event before
              sending it eventually to the platform.

            - `fix_time` (required): The time when the mobile device determined
              the information of this fix.

            - `latitude` (required): The latitude-angular distance, expressed in
              decimal degrees (WGS84 datum), measured from the center of the Earth,
              of a point north or south of the Equator.

            - `longitude` (required): The longitude-angular distance, expressed in
              decimal degrees (WGS84 datum), measured from the center of the Earth,
              of a point east or west of the Prime Meridian.

            - `network_type` (optional): String representation of the network
              connection at the time this geographical location fix has been
              determined by the mobile device:

                  MNCMCC:type[:subtype]

              where:

              - `MNCMCC:string` (required): A Mobile Network Code (MNC) used in
                combination with a Mobile Country Code (MCC) (also known as a "MCC/MNC
                tuple") to uniquely identify the telephony operator of the mobile
                device.

              - `type:string` (required): A human-readable name that describes the
                type of the network that the device is connected to, such as `wifi`,
                `mobile`, `unknown`.

              - `subtype:string` (optional): A human-readable name that describes the
                subtype of this network when applicable.  Network of type `wifi` has
                not subtype.  Network of type `mobile` can have a subtype such as
                `egde`, `hsdpa`, etc.

            - `payload` (optional): JSON expression of additional information that
              the mobile device provided within this report.

            - `provider` (required): The type of the location provider that reported
              the location such as:

              - `fused`: The location API in Google Play services that combines
                different signals to provide the location information.

              - `gps`: This provider determines location using satellites.

              - `network`: This provider determines location based on availability of
                cell towers and Wi-Fi access points.  Results are retrieved by means
                of a network lookup.

              - `passive`: A special location provider for receiving locations without
                actually initiating a location fix.  This provider can be used to
                passively receive location updates when other applications or services
                request them without actually requesting the locations yourself.  This
                provider will return locations generated by other providers.

            - `satellites` (optional): A string representation of the information
              about the satellites that were used to calculate this location fix:

                  (azimuth:elevation:PRN:SNR)[, ...]

              where:

              - `azimuth:float`: The azimuth of the satellite in degrees.

              - `elevation:float`: The elevation of the satellite in degrees.

              - `PRN:integer`: The Pseudo-Random Number (PRN) for the satellite.

              - `SNR:float`: The signal to Noise Ratio (SNR) for the satellite.

            - `speed` (optional): Speed in meters/second over the ground.


        @return: A `LocationUpdate` object.
        """
        return payload and LocationUpdate(
            payload['event_id'],
            cast.string_to_timestamp(payload['event_time']),
            GeoPoint(
                payload['latitude'],
                payload['longitude'],
                accuracy=payload['accuracy'],  # Required for tracked location
                altitude=payload['altitude'],  # Required for tracked location
                bearing=payload.get('bearing'),
                fix_time=cast.string_to_timestamp(payload['fix_time']),  # Required for tracked location
                provider=payload['provider'],  # Required for tracked location
                speed=payload.get('speed')),
            network_type=payload.get('network_type'),
            payload=payload.get('payload'),
            satellites=payload.get('satellites'))

    @property
    def location(self) -> GeoPoint:
        """
        Return the location fix reported by the mobile device.


        @return: The location fix reported by the mobile device.
        """
        return self._location

    @property
    def network_type(self) -> str:
        """
        Return a string representation of the network connection at the time
        this geographical location fix has been determined by the mobile
        device:

            MNCMCC:type[:subtype]

        where:

        - `MNCMCC:string` (required): A Mobile Network Code (MNC) used in
          combination with a Mobile Country Code (MCC) (also known as a "MCC/MNC
          tuple") to uniquely identify the telephony operator of the mobile
          device.

        - `type:string` (required): A human-readable name that describes the
          type of the network that the device is connected to, such as `wifi`,
          `mobile`, `unknown`.

        - `subtype:string` (optional): A human-readable name that describes the
          subtype of this network when applicable.  Network of type `wifi` has
          not subtype.  Network of type `mobile` can have a subtype such as
          `egde`, `hsdpa`, etc.


        @return: A string representation of a network connection.
        """
        return  self._network_type

    @property
    def payload(self) -> any or None:
        """
        Return a JSON expression of additional information that the mobile
        device provided within this report.


        @return: A JSON expression.
        """
        return self._payload

    @property
    def satellites(self) -> str:
        """
        Return a string representation of the information about the satellites
        that were used to calculate this location fix:

            (azimuth:elevation:PRN:SNR)[, ...]

        where:

        - `azimuth:float`: Azimuth of the satellite in degrees.

        - `elevation:float`: Elevation of the satellite in degrees.

        - `PRN:integer`: Pseudo-Random Number (PRN) for the satellite.

        - `SNR:float`: Signal to Noise Ratio (SNR) for the satellite.


        @return: Information about the satellites that were used to calculate
            this location fix:
        """
        return self._satellites
