# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import numpy as np
from astropy import units as u

from itur.utils import (prepare_input_array,
                        prepare_output_array,
                        prepare_quantity)


class __ITU835__():
    """Class to model the ITU-R P.835 recommendation.

    The procedures to compute the reference standard atmosphere parameters
    pressented in these versions are identical to those included in version
    ITU_T P.835.

    Available versions:
       * P.835-6 (12/17) (Current version)
       * P.835-5 (02/12) (Superseded)

    Not available versions:
       * P.835-1 (08/94) (Superseded)
       * P.835-2 (08/97) (Superseded)
       * P.835-3 (10/99) (Superseded)
       * P.835-4 (03/05) (Superseded)
    """

    # This is an abstract class that contains an instance to a version of the
    # ITU-R P.835 recommendation.

    def __init__(self, version=6):
        if version == 6:
            self.instance = _ITU835_6()
        elif version == 5:
            self.instance = _ITU835_5()
        else:
            raise ValueError(
                'Version ' +
                str(version) +
                ' is not implemented' +
                ' for the ITU-R P.835 model.')

    @property
    def __version__(self):
        return self.instance.__version__

    def temperature(self, lat, h, season='summer'):
        #
        return self.instance.temperature(lat, h, season)

    def pressure(self, lat, h, season='summer'):
        return self.instance.pressure(lat, h, season)

    def water_vapour_density(self, lat, h, season='summer'):
        return self.instance.water_vapour_density(lat, h, season)

    def standard_temperature(self, h, T_0):
        return self.instance.standard_temperature(h, T_0)

    def standard_pressure(self, h, T_0, P_0):
        return self.instance.standard_pressure(h, T_0, P_0)

    def standard_water_vapour_density(self, h, h_0, rho_0):
        return self.instance.standard_water_vapour_density(h, h_0, rho_0)

    def standard_water_vapour_pressure(self, h, h_0=2, rho_0=7.5):
        return self.instance.standard_water_vapour_pressure(h, h_0, rho_0)


class _ITU835_6():

    def __init__(self):
        self.__version__ = 6
        self.year = 2017
        self.month = 12
        self.link = 'https://www.itu.int/rec/R-REC-P.835-6-201712-I/en'

    @staticmethod
    def standard_temperature(h, T_0=288.15):
        """

        """
        h_p = 6356.766 * h / (6356.766 + h)
        T = np.where(h_p <= 11, 288.15 - 6.5 * h_p,
            np.where(np.logical_and(11 <= h_p, h_p < 20),
                     216.65,
            np.where(np.logical_and(20 <= h_p, h_p < 32),
                     216.65 + (h_p - 20),
            np.where(np.logical_and(32 <= h_p, h_p < 47),
                     216.65 + 2.8 * (h_p - 32),
            np.where(np.logical_and(47 <= h_p, h_p < 51),
                     270.65,
            np.where(np.logical_and(51 <= h_p, h_p < 71),
                     270.65 - 2.8 * (h_p - 51),
            np.where(np.logical_and(71 <= h_p, h_p < 84.852),
                     214.65 - 2.0 * (h_p - 71),
            np.where(np.logical_and(86 <= h, h < 91),
                     186.8673,
            np.where(np.logical_and(91 <= h, h < 100),
                     263.1905 - 76.3232 * np.sqrt((1 - ((h - 91)/19.9429)**2)),
                     195.08134433524688)))))))))

        return T

    @staticmethod
    def standard_pressure(h, T_0=None, P_0=None):
        """

        """
        h_p = 6356.766 * h / (6356.766 + h)
        P = np.where(h_p <= 11,
                     1013.25 * np.power((288.15 / (288.15 - 6.5 * h_p)),
                     (-34.1632 / 6.5)),
            np.where(np.logical_and(11 <= h_p, h_p < 20),
                     226.3226 * np.exp(-34.1632 * (h_p - 11) / 216.65),
            np.where(np.logical_and(20 <= h_p, h_p < 32),
                     54.74980 * (216.65 / (216.65 + (h_p - 20))) ** 34.1632,
            np.where(np.logical_and(32 <= h_p, h_p < 47),
                     8.680422 * (228.65 / (228.65 - 2.8 * (h_p - 32))) **
                     (34.1632 / 2.8),
            np.where(np.logical_and(47 <= h_p, h_p < 51),
                     1.109106 * np.exp(-34.1632 * (h_p - 47) / 270.65),
            np.where(np.logical_and(51 <= h_p, h_p < 71),
                     0.6694167 * np.power((270.65 / (270.65 - 2.8 * (h_p - 51))),
                     (-34.1632 / 2.8)),
            np.where(np.logical_and(71 <= h_p, h_p < 84.852),
                     0.03956649 * np.power((214.65 / (214.65 - 2.0 * (h_p - 71))),
                     (-34.1632 / 2.0)),
            np.where(np.logical_and(86 <= h, h < 100),
                     np.exp(95.571899 -4.011801 * h + 6.424731e-2 * h**2 -
                            4.789660e-4 * h**3 + 1.1340543e-6 * h**4),
                     1e-62)))))))).astype(float)

        return P

    @staticmethod
    def standard_water_vapour_density(h, h_0=2, rho_0=7.5):
        """

        """
        return rho_0 * np.exp(-h / h_0)

    def standard_water_vapour_pressure(self, h, h_0=2, rho_0=7.5):
        """

        """
        rho_h = self.standard_water_vapour_density(h, h_0, rho_0)
        T_h = self.standard_temperature(h)
        return rho_h * T_h / 216.7

    #  Low latitude standard atmosphere functions  (Section ITU-R P.835-6 2)  #
    @staticmethod
    def low_latitude_temperature(h):
        """Section 2 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and((0 <= h), (h < 17)),
                        300.4222 - 6.3533 * h + 0.005886 * h**2,
               np.where(np.logical_and((17 <= h), (h < 47)),
                        194 + (h - 17) * 2.533,
               np.where(np.logical_and((47 <= h), (h < 52)), 270,
               np.where(np.logical_and((52 <= h), (h < 80)),
                        270 - (h - 52) * 3.0714,
               np.where(np.logical_and((80 <= h), (h <= 100)), 184, 184)))))

    def low_latitude_pressure(self, h):
        """Section 2 of Recommendation ITU-R P.835-6."""
        P10 = self.standard_pressure(10)
        P72 = self.standard_pressure(72)
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                        1012.0306 - 109.0338 * h + 3.6316 * h**2,
               np.where(np.logical_and((10 < h), (h <= 72)),
                        P10 * np.exp(-0.147 * (h - 10)),
               np.where(np.logical_and((72 < h), (h <= 100)),
                    P72 * np.exp(-0.165 * (h - 72)), np.nan)))

    @staticmethod
    def low_latitude_water_vapour(h):
        """Section 3.1 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and((0 <= h), (h <= 15)), 19.6542 *
                        np.exp(- 0.2313 * h - 0.1122 * h**2 + 0.01351 * h**3 -
                               0.0005923 * h**4), 0)

    # Mid latitude standard atmosphere functions  (Section ITU-R P.835-6 3)
    @staticmethod
    def mid_latitude_temperature_summer(h):
        """Section 3.1 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and((0 <= h), (h < 13)),
                        294.9838 - 5.2159 * h - 9.07109 * h**2,
               np.where(np.logical_and((13 <= h), (h < 17)), 215,
               np.where(np.logical_and((17 <= h), (h < 47)),
                        215 * np.exp((h - 17) * 0.008128),
               np.where(np.logical_and((47 <= h), (h < 53)), 275,
               np.where(np.logical_and((53 <= h), (h < 80)),
                        275 + 20 * (1 - np.exp((h - 53) * 0.06)),
               np.where(np.logical_and((80 <= h), (h <= 100)),
                        175, np.nan))))))

    def mid_latitude_pressure_summer(self, h):
        """Section 3.1 of Recommendation ITU-R P.835-6."""
        P10 = self.standard_pressure(10)
        P72 = self.standard_pressure(72)
        return np.where(
            np.logical_and((0 <= h), (h <= 10)),
            1012.8186 - 111.5569 * h + 3.8646 * h**2, np.where(
                np.logical_and((10 < h), (h <= 72)),
                P10 * np.exp(-0.147 * (h - 10)),
                np.where(
                    np.logical_and((72 < h), (h <= 100)),
                    P72 * np.exp(-0.165 * (h - 72)),
                    np.nan)))

    @staticmethod
    def mid_latitude_water_vapour_summer(h):
        """Section 3.1 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and((0 <= h), (h <= 15)),
                        14.3542 * np.exp(- 0.4174 * h - 0.02290 * h**2 +
                                         0.001007 * h**3), 0)

    @staticmethod
    def mid_latitude_temperature_winter(h):
        """Section 3.2 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and((0 <= h), (h < 10)),
                        272.7241 - 3.6217 * h - 0.1759 * h**2,
               np.where(np.logical_and((10 <= h), (h < 33)), 218,
               np.where(np.logical_and((33 <= h), (h < 47)),
                        218 + (h - 33) * 3.3571,
               np.where(np.logical_and((47 <= h), (h < 53)), 265,
               np.where(np.logical_and((53 <= h), (h < 80)),
                        265 - (h - 53) * 2.0370,
               np.where(np.logical_and((80 <= h), (h <= 100)),
                        210, np.nan))))))

    def mid_latitude_pressure_winter(self, h):
        """Section 3.2 of Recommendation ITU-R P.835-6."""
        P10 = self.standard_pressure(10)
        P72 = self.standard_pressure(72)
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                    1018.8627 - 124.2954 * h + 4.8307 * h**2,
               np.where(np.logical_and((10 < h), (h <= 72)),
                        P10 * np.exp(-0.147 * (h - 10)),
               np.where(np.logical_and((72 < h), (h <= 100)),
                        P72 * np.exp(-0.155 * (h - 72)), np.nan)))

    @staticmethod
    def mid_latitude_water_vapour_winter(h):
        """Section 3.2 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and(0 <= h, h <= 10),
                        3.4742 * np.exp(- 0.2697 * h - 0.03604 * h**2 +
                                        0.0004489 * h**3), 0)

    #  High latitude standard atmosphere functions  (Section ITU-R P.835-6 4)  #
    @staticmethod
    def high_latitude_temperature_summer(h):
        """Section 4.1 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and((0 <= h), (h < 10)),
                        286.8374 - 4.7805 * h - 0.1402 * h**2,
               np.where(np.logical_and((10 <= h), (h < 23)), 225,
               np.where(np.logical_and((23 <= h), (h < 48)),
                        225 * np.exp((h - 23) * 0.008317),
               np.where(np.logical_and((48 <= h), (h < 53)), 277,
               np.where(np.logical_and((53 <= h), (h < 79)),
                        277 - (h - 53) * 4.0769,
               np.where(np.logical_and((79 <= h), (h <= 100)),
                        171, np.nan))))))

    def high_latitude_pressure_summer(self, h):
        """Section 4.1 of Recommendation ITU-R P.835-6."""
        P10 = self.standard_pressure(10)
        P72 = self.standard_pressure(72)
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                        1008.0278 - 113.2494 * h + 3.9408 * h**2,
               np.where(np.logical_and((10 < h), (h <= 72)),
                        P10 * np.exp(-0.140 * (h - 10)),
               np.where(np.logical_and((72 < h), (h <= 100)),
                        P72 * np.exp(-0.165 * (h - 72)), np.nan)))

    @staticmethod
    def high_latitude_water_vapour_summer(h):
        """Section 4.1 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and((0 <= h), (h <= 15)),
                        8.988 * np.exp(- 0.3614 * h - 0.005402 * h**2 -
                                       0.001955 * h**3), 0)

    @staticmethod
    def high_latitude_temperature_winter(h):
        """Section 4.2 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and((0 <= h), (h < 8.5)),
                        257.4345 + 2.3474 * h - 1.5479 * h**2 +
                        0.08473 * h**3,
               np.where(np.logical_and((8.5 <= h), (h < 30)), 217.5,
               np.where(np.logical_and((30 <= h), (h < 50)),
                        217.5 + (h - 30) * 2.125,
               np.where(np.logical_and((50 <= h), (h < 54)), 260,
               np.where(np.logical_and((54 <= h), (h <= 100)),
                        260 - (h - 54) * 1.667, np.nan)))))

    def high_latitude_pressure_winter(self, h):
        """Section 4.2 of Recommendation ITU-R P.835-6."""
        P10 = self.standard_pressure(10)
        P72 = self.standard_pressure(72)
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                        1010.8828 - 122.2411 * h + 4.554 * h**2,
               np.where(np.logical_and((10 < h), (h <= 72)),
                        P10 * np.exp(-0.147 * (h - 10)),
               np.where(np.logical_and((72 < h), (h <= 100)),
                        P72 * np.exp(-0.150 * (h - 72)), np.nan)))

    @staticmethod
    def high_latitude_water_vapour_winter(h):
        """Section 4.2 of Recommendation ITU-R P.835-6."""
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                        1.2319 * np.exp(0.07481 * h - 0.0981 * h**2 +
                                        0.00281 * h**3), 0)

    def temperature(self, lat, h, season='summer'):
        """ Section 2 of Recommendation ITU-R P.835-6."""
        if season == 'summer':
            return np.where(
                np.abs(lat) < 22, self.low_latitude_temperature(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_temperature_summer(h),
                    self.high_latitude_temperature_summer(h)))
        elif season == 'winter':
            return np.where(
                np.abs(lat) < 22, self.low_latitude_temperature(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_temperature_winter(h),
                    self.high_latitude_temperature_winter(h)))
        else:
            raise ValueError("The value for argument 'season' is not correct."
                             "Valid values are 'summer' and 'winter'.")

    def pressure(self, lat, h, season='summer'):
        """ Section 2 of Recommendation ITU-R P.835-6."""
        if season == 'summer':
            return np.where(
                np.abs(lat) < 22, self.low_latitude_pressure(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_pressure_summer(h),
                    self.high_latitude_pressure_summer(h)))
        elif season == 'winter':
            return np.where(
                np.abs(lat) < 22, self.low_latitude_pressure(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_pressure_winter(h),
                    self.high_latitude_pressure_winter(h)))
        else:
            raise ValueError("The value for argument 'season' is not correct."
                             "Valid values are 'summer' and 'winter'")

    def water_vapour_density(self, lat, h, season='summer'):
        """ Section 2 of Recommendation ITU-R P.835-6."""
        if season == 'summer':
            return np.where(
                np.abs(lat) < 22, self.low_latitude_water_vapour(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_water_vapour_summer(h),
                    self.high_latitude_water_vapour_summer(h)))
        elif season == 'winter':
            return np.where(
                np.abs(lat) < 22, self.low_latitude_water_vapour(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_water_vapour_winter(h),
                    self.high_latitude_water_vapour_winter(h)))
        else:
            raise ValueError("The value for argument 'season' is not correct."
                             "Valid values are 'summer' and 'winter'")


class _ITU835_5():

    def __init__(self):
        self.__version__ = 5
        self.year = 2012
        self.month = 2
        self.link = 'https://www.itu.int/rec/R-REC-P.835-5-201202-I/en'

    @staticmethod
    def standard_temperature(h, T_0=288.15):
        """
        """
        H = np.array([0, 11, 20, 32, 47, 51, 71, 85])
        T = np.array([0, -65, -65, -53, -11, -11, -67, -95]) + T_0

        return np.interp(h, H, T)

    @staticmethod
    def standard_pressure(h, T_0=288.15, P_0=1013.25):
        """
        """
        H = [0, 11, 20, 32, 47, 51, 71, 85]
        L = [-6.5, 0, 1, 2.8, 0, -2.8, -2]
        T = np.array([0, -65, -65, -53, -11, -11, -67, -95]) + T_0

        num_splits = np.minimum(np.searchsorted(H, h), 7)
        if not hasattr(num_splits, '__iter__'):
            num_splits = list([num_splits])

        ret = np.ones_like(h) * P_0
        for ret_i, n in enumerate(num_splits):
            n = n.squeeze()
            P = np.zeros((n + 1))
            P[0] = P_0
            for i in range(n):
                h_p = h[ret_i] if i == (n - 1) else H[i + 1]
                if L[i] != 0:
                    P[i + 1] = P[i] * \
                        (T[i] / (T[i] + L[i] * (h_p - H[i])))**(34.163 / L[i])
                else:
                    P[i + 1] = P[i] * np.exp(-34.162 * (h_p - H[i]) / T[i])

            ret[ret_i] = P[-1]

        return ret

    @staticmethod
    def standard_water_vapour_density(h, h_0=2, rho_0=7.5):
        """

        """
        return rho_0 * np.exp(-h / h_0)

    def standard_water_vapour_pressure(self, h, h_0=2, rho_0=7.5):
        """

        """
        rho_h = self.standard_water_vapour_density(h, h_0, rho_0)
        T_h = self.standard_temperature(h)
        return rho_h * T_h / 216.7

    #  Low latitude standard atmosphere functions  (Section ITU-R P.835 5-2)  #
    @staticmethod
    def low_latitude_temperature(h):
        """Section 2 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and((0 <= h), (h < 17)),
                        300.4222 - 6.3533 * h + 0.005886 * h**2,
               np.where(np.logical_and((17 <= h), (h < 47)),
                        194 + (h - 17) * 2.533,
               np.where(np.logical_and((47 <= h), (h < 52)), 270,
               np.where(np.logical_and((52 <= h), (h < 80)),
                        270 - (h - 52) * 3.0714,
               np.where(np.logical_and((80 <= h), (h <= 100)), 184, np.nan)))))

    def low_latitude_pressure(self, h):
        """Section 2 of Recommendation ITU-R P.835-5."""
        P10 = self.standard_pressure([10])[0]
        P72 = self.standard_pressure([72])[0]
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                        1012.0306 - 109.0338 * h + 3.6316 * h**2,
               np.where(np.logical_and((10 < h), (h <= 72)),
                        P10 * np.exp(-0.147 * (h - 10)),
               np.where(np.logical_and((72 < h), (h <= 100)),
                    P72 * np.exp(-0.165 * (h - 72)), np.nan)))

    @staticmethod
    def low_latitude_water_vapour(h):
        """Section 3.1 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and((0 <= h), (h <= 15)), 19.6542 *
                        np.exp(- 0.2313 * h - 0.1122 * h**2 + 0.01351 * h**3 -
                               0.0005923 * h**4), 0)

    # Mid latitude standard atmosphere functions  (Section ITU-R P.835-6 3)
    @staticmethod
    def mid_latitude_temperature_summer(h):
        """Section 3.1 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and((0 <= h), (h < 13)),
                        294.9838 - 5.2159 * h - 9.07109 * h**2,
               np.where(np.logical_and((13 <= h), (h < 17)), 215,
               np.where(np.logical_and((17 <= h), (h < 47)),
                        215 * np.exp((h - 17) * 0.008128),
               np.where(np.logical_and((47 <= h), (h < 53)), 275,
               np.where(np.logical_and((53 <= h), (h < 80)),
                        275 + 20 * (1 - np.exp((h - 53) * 0.06)),
               np.where(np.logical_and((80 <= h), (h <= 100)),
                        175, np.nan))))))

    def mid_latitude_pressure_summer(self, h):
        """Section 3.1 of Recommendation ITU-R P.835-5."""
        P10 = self.standard_pressure([10])[0]
        P72 = self.standard_pressure([72])[0]
        return np.where(
            np.logical_and((0 <= h), (h <= 10)),
            1012.8186 - 111.5569 * h + 3.8646 * h**2, np.where(
                np.logical_and((10 < h), (h <= 72)),
                P10 * np.exp(-0.147 * (h - 10)),
                np.where(
                    np.logical_and((72 < h), (h <= 100)),
                    P72 * np.exp(-0.165 * (h - 72)),
                    np.nan)))

    @staticmethod
    def mid_latitude_water_vapour_summer(h):
        """Section 3.1 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and((0 <= h), (h <= 15)),
                        14.3542 * np.exp(- 0.4174 * h - 0.02290 * h**2 +
                                         0.001007 * h**3), 0)

    @staticmethod
    def mid_latitude_temperature_winter(h):
        """Section 3.2 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and((0 <= h), (h < 10)),
                        272.7241 - 3.6217 * h - 0.1759 * h**2,
               np.where(np.logical_and((10 <= h), (h < 33)), 218,
               np.where(np.logical_and((33 <= h), (h < 47)),
                        218 + (h - 33) * 3.3571,
               np.where(np.logical_and((47 <= h), (h < 53)), 265,
               np.where(np.logical_and((53 <= h), (h < 80)),
                        265 - (h - 53) * 2.0370,
               np.where(np.logical_and((80 <= h), (h <= 100)),
                        210, np.nan))))))

    def mid_latitude_pressure_winter(self, h):
        """Section 3.2 of Recommendation ITU-R P.835-5."""
        P10 = self.standard_pressure([10])[0]
        P72 = self.standard_pressure([72])[0]
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                    1018.8627 - 124.2954 * h + 4.8307 * h**2,
               np.where(np.logical_and((10 < h), (h <= 72)),
                        P10 * np.exp(-0.147 * (h - 10)),
               np.where(np.logical_and((72 < h), (h <= 100)),
                        P72 * np.exp(-0.155 * (h - 72)), np.nan)))

    @staticmethod
    def mid_latitude_water_vapour_winter(h):
        """Section 3.2 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and(0 <= h, h <= 10),
                        3.4742 * np.exp(- 0.2697 * h - 0.03604 * h**2 +
                                        0.0004489 * h**3), 0)

    #  High latitude standard atmosphere functions  (Section ITU-R P.835-5 4)  #
    @staticmethod
    def high_latitude_temperature_summer(h):
        """Section 4.1 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and((0 <= h), (h < 10)),
                        286.8374 - 4.7805 * h - 0.1402 * h**2,
               np.where(np.logical_and((10 <= h), (h < 23)), 225,
               np.where(np.logical_and((23 <= h), (h < 48)),
                        225 * np.exp((h - 23) * 0.008317),
               np.where(np.logical_and((48 <= h), (h < 53)), 277,
               np.where(np.logical_and((53 <= h), (h < 79)),
                        277 - (h - 53) * 4.0769,
               np.where(np.logical_and((79 <= h), (h <= 100)),
                        171, np.nan))))))

    def high_latitude_pressure_summer(self, h):
        """Section 4.1 of Recommendation ITU-R P.835-5."""
        P10 = self.standard_pressure([10])[0]
        P72 = self.standard_pressure([72])[0]
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                        1008.0278 - 113.2494 * h + 3.9408 * h**2,
               np.where(np.logical_and((10 < h), (h <= 72)),
                        P10 * np.exp(-0.140 * (h - 10)),
               np.where(np.logical_and((72 < h), (h <= 100)),
                        P72 * np.exp(-0.165 * (h - 72)), np.nan)))

    @staticmethod
    def high_latitude_water_vapour_summer(h):
        """Section 4.1 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and((0 <= h), (h <= 15)),
                        8.988 * np.exp(- 0.3614 * h - 0.005402 * h**2 -
                                       0.001955 * h**3), 0)

    @staticmethod
    def high_latitude_temperature_winter(h):
        """Section 4.2 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and((0 <= h), (h < 8.5)),
                        257.4345 + 2.3474 * h - 1.5479 * h**2 +
                        0.08473 * h**3,
               np.where(np.logical_and((8.5 <= h), (h < 30)), 217.5,
               np.where(np.logical_and((30 <= h), (h < 50)),
                        217.5 + (h - 30) * 2.125,
               np.where(np.logical_and((50 <= h), (h < 54)), 260,
               np.where(np.logical_and((54 <= h), (h <= 100)),
                        260 - (h - 54) * 1.667, np.nan)))))

    def high_latitude_pressure_winter(self, h):
        """Section 4.2 of Recommendation ITU-R P.835-5."""
        P10 = self.standard_pressure([10])[0]
        P72 = self.standard_pressure([72])[0]
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                        1010.8828 - 122.2411 * h + 4.554 * h**2,
               np.where(np.logical_and((10 < h), (h <= 72)),
                        P10 * np.exp(-0.147 * (h - 10)),
               np.where(np.logical_and((72 < h), (h <= 100)),
                        P72 * np.exp(-0.150 * (h - 72)), np.nan)))

    @staticmethod
    def high_latitude_water_vapour_winter(h):
        """Section 4.2 of Recommendation ITU-R P.835-5."""
        return np.where(np.logical_and((0 <= h), (h <= 10)),
                        1.2319 * np.exp(0.07481 * h - 0.0981 * h**2 +
                                        0.00281 * h**3), 0)

    def temperature(self, lat, h, season='summer'):
        """ Section 2 of Recommendation ITU-R P.835-5."""
        if season == 'summer':
            return np.where(
                np.abs(lat) < 22, self.low_latitude_temperature(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_temperature_summer(h),
                    self.high_latitude_temperature_summer(h)))
        else:
            return np.where(
                np.abs(lat) < 22, self.low_latitude_temperature(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_temperature_winter(h),
                    self.high_latitude_temperature_winter(h)))

    def pressure(self, lat, h, season='summer'):
        """ Section 2, 3, 4 of Recommendation ITU-R P.835-5."""
        if season == 'summer':
            return np.where(
                np.abs(lat) < 22, self.low_latitude_pressure(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_pressure_summer(h),
                    self.high_latitude_pressure_summer(h)))
        else:
            return np.where(
                np.abs(lat) < 22, self.low_latitude_pressure(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_pressure_winter(h),
                    self.high_latitude_pressure_winter(h)))

    def water_vapour_density(self, lat, h, season='summer'):
        """ Section 2 of Recommendation ITU-R P.835-5."""
        if season == 'summer':
            return np.where(
                np.abs(lat) < 22, self.low_latitude_water_vapour(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_water_vapour_summer(h),
                    self.high_latitude_water_vapour_summer(h)))
        else:
            return np.where(
                np.abs(lat) < 22, self.low_latitude_water_vapour(h),
                np.where(
                    np.abs(lat) < 45, self.mid_latitude_water_vapour_winter(h),
                    self.high_latitude_water_vapour_winter(h)))


__model = __ITU835__()


def change_version(new_version):
    """
    Change the version of the ITU-R P.835 recommendation currently being used.

    This function changes the model used for the ITU-R P.835 recommendation
    to a different version.

    Parameters
    ----------
    new_version : int
        Number of the version to use.
        Valid values are:
          *  6: Activates recommendation ITU-R P.835-6 (12/17) (Current version)
          *  5: Activates recommendation ITU-R P.835-5 (02/12) (Superseded)
    """
    global __model
    __model = __ITU835__(new_version)


def get_version():
    """The version of the model currently in use for the ITU-R P.835 recommendation.

    Obtain the version of the ITU-R P.835 recommendation currently being used.

    Returns
    -------
    version: int
       The version of the ITU-R P.835 recommendation being used.
    """
    global __model
    return __model.__version__


def temperature(lat, h, season='summer'):
    """ Determine the temperature at a given latitude and height.

    Method to determine the temperature as a function of altitude and latitude,
    for calculating gaseous attenuation along an Earth-space path. This method
    is recommended when more reliable local data are not available.


    Parameters
    ----------
    lat : number, sequence, or numpy.ndarray
        Latitudes of the receiver points
    h : number or Quantity
        Height (km)
    season : string
        Season of the year (available values, 'summer', and 'winter').
        Default 'summer'


    Returns
    -------
    T: Quantity
        Temperature (K)


    References
    ----------
    [1] Reference Standard Atmospheres
    https://www.itu.int/rec/R-REC-P.835/en

    """
    global __model
    type_output = type(lat)
    lat = prepare_input_array(lat)
    h = prepare_quantity(h, u.km, 'Height')
    val = __model.temperature(lat, h, season)
    return prepare_output_array(val, type_output) * u.Kelvin


def pressure(lat, h, season='summer'):
    """ Determine the atmospheric pressure at a given latitude and height.

    Method to determine the pressure as a function of altitude and latitude,
    for calculating gaseous attenuation along an Earth-space path.
    This method is recommended when more reliable local data are not available.

    Parameters
    ----------
    lat : number, sequence, or numpy.ndarray
        Latitudes of the receiver points
    h : number or Quantity
        Height (km)
    season : string
        Season of the year (available values, 'summer', and 'winter').
        Default 'summer'


    Returns
    -------
    P: Quantity
        Pressure (hPa)


    References
    ----------
    [1] Reference Standard Atmospheres
    https://www.itu.int/rec/R-REC-P.835/en
    """
    global __model
    type_output = type(lat)
    lat = prepare_input_array(lat)
    h = prepare_quantity(h, u.km, 'Height')
    val = __model.pressure(lat, h, season)
    return prepare_output_array(val, type_output) * u.hPa


def water_vapour_density(lat, h, season='summer'):
    """ Determine the water vapour density at a given latitude and height.

    Method to determine the water-vapour density as a
    function of altitude and latitude, for calculating gaseous attenuation
    along an Earth-space path. This method is recommended when more reliable
    local data are not available.


    Parameters
    ----------
    lat : number, sequence, or numpy.ndarray
        Latitudes of the receiver points
    h : number or Quantity
        Height (km)
    season : string
        Season of the year (available values, 'summer', and 'winter').
        Default 'summer'


    Returns
    -------
    rho: Quantity
        Water vapour density (g/m^3)


    References
    ----------
    [1] Reference Standard Atmospheres
    https://www.itu.int/rec/R-REC-P.835/en
    """
    global __model
    type_output = type(lat)
    lat = prepare_input_array(lat)
    h = prepare_quantity(h, u.km, 'Height')
    val = __model.water_vapour_density(lat, h, season)
    return prepare_output_array(val, type_output) * u.g / u.m**3


def standard_temperature(h, T_0=288.15):
    """ Determine the standard temperature at a given height.

    Method to compute the temperature of an standard atmosphere at
    a given height. The reference standard atmosphere is based on the United
    States Standard Atmosphere, 1976, in which the atmosphere is divided into
    seven successive layers showing linear variation with temperature.


    Parameters
    ----------
    h : number or Quantity
        Height (km)
    T_0 : number or Quantity
        Surface temperature (K)


    Returns
    -------
    T: Quantity
        Temperature (K)


    References
    ----------
    [1] Reference Standard Atmospheres
    https://www.itu.int/rec/R-REC-P.835/en
    """
    global __model

    h = prepare_quantity(h, u.km, 'Height')
    T_0 = prepare_quantity(T_0, u.Kelvin, 'Surface temperature')
    return __model.standard_temperature(h, T_0) * u.Kelvin


def standard_pressure(h, T_0=288.15, P_0=1013.25):
    """ Determine the standard pressure at a given height.

    Method to compute the total atmopsheric pressure of an standard atmosphere
    at a given height.

    The reference standard atmosphere is based on the United States Standard
    Atmosphere, 1976, in which the atmosphere is divided into seven successive
    layers showing linear variation with temperature.


    Parameters
    ----------
    h : number or Quantity
        Height (km)
    T_0 : number or Quantity
        Surface temperature (K)
    P_0 : number or Quantity
        Surface pressure (hPa)


    Returns
    -------
    P: Quantity
        Pressure (hPa)


    References
    ----------
    [1] Reference Standard Atmospheres
    https://www.itu.int/rec/R-REC-P.835/en
    """
    global __model

    type_output = type(h)
    h = prepare_quantity(h, u.km, 'Height')
    h = np.atleast_1d(h)
    T_0 = prepare_quantity(T_0, u.Kelvin, 'Surface temperature')
    P_0 = prepare_quantity(P_0, u.hPa, 'Surface pressure')
    val = __model.standard_pressure(h, T_0, P_0)
    return prepare_output_array(val, type_output) * u.hPa


def standard_water_vapour_density(h, h_0=2, rho_0=7.5):
    """ Determine the standard water vapour density at a given height.

    The reference standard atmosphere is based on the United States Standard
    Atmosphere, 1976, in which the atmosphere is divided into seven successive
    layers showing linear variation with temperature.


    Parameters
    ----------
    h : number or Quantity
        Height (km)
    h_0 : number or Quantity
        Scale height (km)
    rho_0 : number or Quantity
        Surface water vapour density (g/m^3)


    Returns
    -------
    rho: Quantity
        Water vapour density (g/m^3)


    References
    ----------
    [1] Reference Standard Atmospheres
    https://www.itu.int/rec/R-REC-P.835/en
    """
    global __model

    h = prepare_quantity(h, u.km, 'Height')
    h_0 = prepare_quantity(h_0, u.km, 'Scale height')
    rho_0 = prepare_quantity(
        rho_0,
        u.g / u.m**3,
        'Surface water vapour density')
    return __model.standard_water_vapour_density(h, h_0, rho_0) * u.g / u.m**3


def standard_water_vapour_pressure(h, h_0=2, rho_0=7.5):
    """Determine the standard water vapour pressure at a given height.

    The reference standard atmosphere is based on the United States Standard
    Atmosphere, 1976, in which the atmosphere is divided into seven successive
    layers showing linear variation with temperature.


    Parameters
    ----------
    h : number or Quantity
        Height (km)
    h_0 : number or Quantity
        Scale height (km)
    rho_0 : number or Quantity
        Surface water vapour density (g/m^3)


    Returns
    -------
    e: Quantity
        Water vapour pressure (hPa)


    References
    ----------
    [1] Reference Standard Atmospheres
    https://www.itu.int/rec/R-REC-P.835/en
    """
    global __model

    h = prepare_quantity(h, u.km, 'Height')
    h_0 = prepare_quantity(h_0, u.km, 'Scale height')
    rho_0 = prepare_quantity(
        rho_0,
        u.g / u.m**3,
        'Surface water vapour density')
    return __model.standard_water_vapour_pressure(h, h_0, rho_0) * u.hPa
