# Copyright 2015 Shannon Lucas
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License.  You may obtain a copy
# of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations under
# the License.


class AltimeterModel:
    """Models altimeter data.

    .. sidebar:: Sources:
        - NOAA/FAA AC 00-45F, Change 2, Section 3.1.3.11
        - WMO FM 15-XIV METAR/FM 16-XIV SPECI, Regulation 15.12
    """
    __slots__ = ("pressure", "units")

    def __init__(self, *, pressure=None, units=None):
        """Models the pressure data from a METAR.

        :param int|float pressure: the pressure.
        :param units: a pressure unit indicating the unit of measure used by
            this model.
        :type units: aerodata.units.temperature.Temperature or None
        """
        self.pressure = pressure
        self.units = units


class RemarksModel:
    """Models the remarks section.

    This model exists to accommodate future versions of the METAR parser that
    process the tokens that may be present in the remarks section of NOAA/FAA
    METARs.
    """
    __slots__ = ("text", )

    def __init__(self, *, text=None):
        """Creates a new remarks model.

        :param str text: the source text of the remarks section.
        """
        self.text = text


class RunwayVisualRangeModel:
    """Models runway visual range.

    .. sidebar:: Sources:
        - NOAA/FAA AC 00-45F, Change 2, Section 3.1.3.7
        - WMO FM 15-XIV METAR/FM 16-XIV SPECI, Regulation 15.7
    """
    __slots__ = ("runway_id", "mean_range", "mean_extrema", "max_range",
                 "max_extrema", "tendency", "units")

    def __init__(self, *, runway_id, mean_range=None, mean_extrema=None,
                 max_range=None, max_extrema=None, tendency=None, units=None):
        """Creates a new runway visibility model.

        :param str runway_id: the runway's identifier consisting of a number
            and (optional) position indicator ("L", "C", or "R").
        :param int mean_range: the mean distance a pilot may see down the
            runway measured over a 10 minute period.
        :param str mean_extrema: indicates if the actual mean range is below
            the minimum ("M") or above the maximum ("P") reportable range.
        :param int max_range: indicates the maximum distance a pilot may see
            down the runway if the range varies significantly in a 10 minut
             period.
        :param str max_extrema: indicates if the actual maximum range is below
            the minimum ("M") or above the maximum ("P") reportable range.
        :param str tendency: indicates a trend in runway visibility over time.
            Valid values are "U" for an upward trend, "D" for a downward trend,
            and "N" for no change.
        :param units: a distance unit indicating the unit of measure used by
            distances in this model.
        :type units: aerodata.units.distance.Distance
        """
        self.runway_id = runway_id
        self.mean_range = mean_range
        self.mean_extrema = mean_extrema
        self.max_range = max_range
        self.max_extrema = max_extrema
        self.tendency = tendency
        self.units = units


class SeaSurfaceModel:
    """Models sea-surface temperature and state of the sea.

    ..note::
        As this is a WMO-specific section, the temperature units are always in
        celsius.

    .. sidebar:: Sources:
        - WMO FM 15-XIV METAR/FM 16-XIV SPECI, Regulation 15.13.5
    """
    __slots__ = ("temperature", "sea_state", "units")

    def __init__(self, *, temperature, sea_state):
        """Creates a new sea-surface model.

        :param int|float temperature: the ambient temperature in degrees
            celsius.
        :param int sea_state: the state of the sea per Code Table 3700; must be
            from ``0`` to ``9``.
        """
        self.temperature = temperature
        self.sea_state = sea_state
        self.units = None


class SkyConditionModel:
    """Models the sky condition component of a METAR.

    .. note::
        Currently, both NOAA/FAA and WMO METARs report height in feet. NOAA/FAA
        METARs will use units of 100 feet, and WMO METARs will use units of 30
        meters (equivalent to 100 feet per WMO FM 15-XIV METAR/FM 16-XIV SPECI,
        Regulation 15.9.1.5).
    """
    __slots__ = ("wmo_clear", "cloud_cover", "height", "cloud_type", "units")

    def __init__(self, *, wmo_clear=None, cloud_cover=None, height=None,
                 cloud_type=None, units=None):
        """Creates a new sky condition model.

        :param str|None wmo_clear: the no cloud cover code from WMO compliant
            METARs.  Must be one of 'CAVOK', 'NCD', or 'NSC'.
        :param str|None cloud_cover: cloud coverage code. Must be one of 'VV',
            'SKC', 'CLR', 'FEW', 'SCT', 'BKN', 'OVC', or '///'.
        :param int|None height: the height of the cloud layer in actual unit
            values. For example, a value of 25,000 feet *must* be expressed as
            the integer ``25000`` and not as ``250``.  A negative value
            indicates that height was reported as undetermined ('///'), and a
            value of ``None`` indicates that no height was reported in the
            METAR.
        :param str|None cloud_type: the type of clouds observed. Must be one of
            'CB', 'TCU', or '///'.
        :param units: a distance unit indicating the unit of measure used by
            distances in this model.
        :type units: aerodata.units.distance.Distance or None
        """
        self.wmo_clear = wmo_clear
        self.cloud_cover = cloud_cover
        self.height = height
        self.cloud_type = cloud_type
        self.units = units


class TemperatureModel:
    """Models the temperature and dew point data from a METAR.

    .. sidebar:: Sources:
        - NOAA/FAA AC 00-45F, Change 2, Section 3.1.3.10
        - WMO FM 15-XIV METAR/FM 16-XIV SPECI, Regulation 15.11
    """
    __slots__ = ("temperature", "dew_point", "units")

    def __init__(self, *, temperature=None, dew_point=None, units=None):
        """Creates a new temperature model.

        :param float temperature: the temperature.
        :param float dew_point: the dew point.
        :param units: a temperature unit indicating the unit of measure used by
            this model.
        :type units: aerodata.units.temperature.Temperature or None
        :return:
        """
        self.temperature = temperature
        self.dew_point = dew_point
        self.units = units


class VisibilityModel:
    """Models station visibility.

    .. sidebar:: Sources:
        - NOAA/FAA AC 00-45F, Change 2, Section 3.1.3.6
        - WMO FM 15-XIV METAR/FM 16-XIV SPECI, Regulation 15.6
    """
    __slots__ = ("distance", "units", "direction", "min_visibility",
                 "min_direction", "is_small", "is_cavok")

    def __init__(self, *, distance=None, units=None, direction=None,
                 min_visibility=None, min_direction=None, is_small=False,
                 is_cavok=False):
        """Creates a new visibility model.

        :param distance: the greatest distance visible through at least half
            the horizon circle.
        :type distance: int or float
        :param units: a distance unit indicating the unit of measure used by
            distances in this model.
        :type units: aerodata.units.distance.Distance
        :param str direction: the direction of the prevailing visibility on WMO
            METARs expressed as either a compass direction ("N", "NE", E",
            "SE", "S", "SW", "W", "NW") or as ``None`` to indicate no
            directional variation.
        :param int min_visibility: the minimum visibility; this is used on WMO
            METARs when visibility is variable.
        :param str min_direction: the direction of the minimum visibility on WMO
            METARs expressed as either a compass direction ("N", "NE", E",
            "SE", "S", "SW", "W", "NW") or as `NDV` to indicate no directional
            variation.
        :param bool is_small: if ``True`` on a NOAA/FAA METAR, the prevailing
            visibility is actually less than the specified visibility.  This
            flag is set by automated stations when the recorded visibility is
            less than the smallest fractional distance that can be reported
            (1/4SM).
        :param bool is_cavok: if ``True``, this visibility reading was derived
            from a report that specified the visibility as "CAVOK."
        """
        self.distance = distance
        self.units = units
        self.direction = direction
        self.min_visibility = min_visibility
        self.min_direction = min_direction
        self.is_small = is_small
        self.is_cavok = is_cavok


class PresentWeatherModel:
    """Models present weather information.

    .. sidebar:: Sources:
        - NOAA/FAA AC 00-45F, Change 2, Section 3.1.3.8
        - WMO FM 15-XIV METAR/FM 16-XIV SPECI, Regulation 15.8
    """
    __slots__ = ("intensity", "descriptor", "precipitation", "obscuration",
                 "other")

    def __init__(self, *, intensity=None, descriptor=None, precipitation=None,
                 obscuration=None, other=None):
        """Creates a new present weather model

        :param str|None intensity: the intensity or proximity of the weather
            phenomena.  Must be one of ("-", "+", "VC") or ``None``.
        :param str|None descriptor: the phenomena descriptor. Must be one of
            ("MI", "PR", "BC", "DR", "BL", "SH", "TS", "FZ") or ``None``.
        :param Sequence[str] precipitation: present precipitation.  May
            contain up to three string elements from the set ("DZ", "RA", "SN",
            "SG", "IC", "PL", "GR", "GS", "UP") or may be empty.
        :param str|None obscuration: present obscuration. Must be one of
            ("BR", "FG", "FU", "VA", "DU", "SA", "HZ", "PY") or ``None``.
        :param str|None other: other present phenomena. Must be one of (`"PO",
            "SQ", "FC", "SS", "DS") or `None``.
        """
        self.intensity = intensity
        self.descriptor = descriptor
        self.precipitation = precipitation
        self.obscuration = obscuration
        self.other = other


class RecentWeatherModel:
    """Models present weather information.

    .. sidebar:: Sources:
        - WMO FM 15-XIV METAR/FM 16-XIV SPECI, Regulation 15.13.2
    """
    __slots__ = ("descriptor", "precipitation", "obscuration", "other")

    def __init__(self, *, descriptor=None, precipitation=None,
                 obscuration=None, other=None):
        """Creates a new recent weather model

        :param str|None descriptor: the phenomena descriptor. Must be one of
            ("BL", "SH", "TS", "FZ") or ``None``.
        :param Sequence[str] precipitation: present precipitation.  May
            contain up to three string elements from the set ("DZ", "RA", "SN",
            "SG", "IC", "PL", "GR", "GS", "UP") or may be empty.
        :param str|None obscuration: present obscuration. Must be one of
            ("VA", "DU", "SA") or ``None``.
        :param str|None other: other present phenomena. Must be one of ("FC",
            "SS", "DS") or `None``.
        """
        self.descriptor = descriptor
        self.precipitation = precipitation
        self.obscuration = obscuration
        self.other = other


class WindModel:
    """Models station visibility.

    .. sidebar:: Sources:
        - NOAA/FAA AC 00-45F, Change 2, Section 3.1.3.5
        - WMO FM 15-XIV METAR/FM 16-XIV SPECI, Regulation 15.5
    """
    __slots__ = ("direction", "speed", "units", "gust", "is_variable",
                 "variable_from", "variable_to")

    def __init__(self, *, direction=None, speed=None, units=None, gust=None,
                 is_variable=False, variable_from=None, variable_to=None):
        """Creates a new wind model.

        :param int|None direction: the direction the wind is coming from in
            degrees relative to true north.  For variable winds less than 6
            knots (6 km/h on WMO reports), this field should be ``0``.
        :param int|None speed: the wind speed.
        :param int|None gust: the speed of wind gusts, or ``0`` if there are no
            gusts.
        :param units: a velocity unit indicating the unit of measure used by
            velocities in this model.
        :type units: aerodata.units.velocity.Velocity
        :param bool is_variable: indicates if the wind direction is variable.
        :param int|None variable_from: for winds greater than 6 knots (6 km/h
            on WMO reports), the inner bounds of the variable wind direction in
            degrees relative to true north.  For variable winds less than 6
            knots, this field should be ``0``.
        :param int|None variable_to: for winds greater than 6 knots (6 km/h on
            WMO reports), the outer bounds of the variable wind direction in
            degrees relative to true north.  For variable winds less than 6
            knots, this value should be ``0``.
        """
        self.direction = direction
        self.speed = speed
        self.units = units
        self.gust = gust
        self.is_variable = is_variable
        self.variable_from = variable_from
        self.variable_to = variable_to


class METARModel:
    """Models the METAR itself.

    .. sidebar:: Sources:
        - NOAA/FAA AC 00-45F, Change 2
        - WMO FM 15-XIV METAR/FM 16-XIV SPECI
    """
    __slots__ = ("type", "station_id", "timestamp", "modifier", "wind",
                 "visibility", "runway", "present_weather", "sky_condition",
                 "temperature", "altimeter", "recent_weather", "wind_shear",
                 "sea_surface", "remarks", "metar")

    def __init__(self, *, type=None, station_id=None, timestamp=None,
                 modifier=None, wind=None, visibility=None, runway=None,
                 present_weather=None, sky_condition=None, temperature=None,
                 altimeter=None, recent_weather=None, wind_shear=None,
                 sea_surface=None, remarks=None, metar=None):
        """Creates a new METAR model.

        :param str type: the type of report (routine or special).
        :param str station_id: ICAO station identifier.
        :param datetime timestamp: report date and time.
        :param str modifier: report modifier.
        :param WindModel wind: wind information.
        :param VisibilityModel visibility: visibility information.
        :param RunwayVisualRangeModel runway: runway visual range.
        :param PresentWeatherModel present_weather: present weather phenomena.
        :param SkyConditionModel sky_condition: sky conditions.
        :param TemperatureModel temperature: temperature and dew point.
        :param AltimeterModel altimeter: atmospheric pressure reading.
        :param RecentWeatherModel recent_weather: recent weather phenomena.
        :param WindShearModel wind_shear: per-runway wind shear.
        :param SeaSurfaceModel sea_surface: sea-surface conditions.
        :param RemarksModel remarks: comments and additional data.
        :param str metar: the original METAR text.
        """
        self.type = type
        self.station_id = station_id
        self.timestamp = timestamp
        self.modifier = modifier
        self.wind = wind
        self.visibility = visibility
        self.runway = runway
        self.present_weather = present_weather
        self.sky_condition = sky_condition
        self.temperature = temperature
        self.altimeter = altimeter
        self.recent_weather = recent_weather
        self.wind_shear = wind_shear
        self.sea_surface = sea_surface
        self.remarks = remarks
        self.metar = metar
