
"""
Get PSM3 TMY
see https://developer.nrel.gov/docs/solar/nsrdb/psm3_data_download/
"""

import io
import requests
import pandas as pd
from json import JSONDecodeError

NSRDB_API_BASE = "https://developer.nrel.gov"
PSM_URL = NSRDB_API_BASE + "/api/solar/nsrdb_psm3_download.csv"
TMY_URL = NSRDB_API_BASE + "/api/nsrdb_api/solar/nsrdb_psm3_tmy_download.csv"

# 'relative_humidity', 'total_precipitable_water' are not available
ATTRIBUTES = [
    'air_temperature', 'dew_point', 'dhi', 'dni', 'ghi', 'surface_albedo',
    'surface_pressure', 'wind_direction', 'wind_speed']
PVLIB_PYTHON = 'pvlib python'


def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
             leap_day=False, full_name=PVLIB_PYTHON, affiliation=PVLIB_PYTHON,
             timeout=30):
    """
    Retrieve NSRDB PSM3 timeseries weather data from the PSM3 API.  The NSRDB
    is described in [1]_ and the PSM3 API is described in [2]_ and [3]_.

    Parameters
    ----------
    latitude : float or int
        in decimal degrees, between -90 and 90, north is positive
    longitude : float or int
        in decimal degrees, between -180 and 180, east is positive
    api_key : str
        NREL Developer Network API key
    email : str
        NREL API uses this to automatically communicate messages back
        to the user only if necessary
    names : str, default 'tmy'
        PSM3 API parameter specifing year or TMY variant to download, see notes
        below for options
    interval : int, default 60
        interval size in minutes, can only be either 30 or 60.  Only used for
        single-year requests (i.e., it is ignored for tmy/tgy/tdy requests).
    leap_day : boolean, default False
        include leap day in the results.  Only used for single-year requests
        (i.e., it is ignored for tmy/tgy/tdy requests).
    full_name : str, default 'pvlib python'
        optional
    affiliation : str, default 'pvlib python'
        optional
    timeout : int, default 30
        time in seconds to wait for server response before timeout

    Returns
    -------
    headers : dict
        metadata from NREL PSM3 about the record, see
        :func:`pvlib.iotools.parse_psm3` for fields
    data : pandas.DataFrame
        timeseries data from NREL PSM3

    Raises
    ------
    requests.HTTPError
        if the request response status is not ok, then the ``'errors'`` field
        from the JSON response or any error message in the content will be
        raised as an exception, for example if the `api_key` was rejected or if
        the coordinates were not found in the NSRDB

    Notes
    -----
    The required NREL developer key, `api_key`, is available for free by
    registering at the `NREL Developer Network <https://developer.nrel.gov/>`_.

    .. warning:: The "DEMO_KEY" `api_key` is severely rate limited and may
        result in rejected requests.

    The PSM3 API `names` parameter must be a single value from the following
    list::

        ['1998', '1999', '2000', '2001', '2002', '2003', '2004', '2005',
         '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013',
         '2014', '2015', '2016', '2017', '2018', 'tmy', 'tmy-2016', 'tmy-2017',
         'tdy-2017', 'tgy-2017', 'tmy-2018', 'tdy-2018', 'tgy-2018']

    .. warning:: PSM3 is limited to data found in the NSRDB, please consult the
        references below for locations with available data

    See Also
    --------
    pvlib.iotools.read_psm3, pvlib.iotools.parse_psm3

    References
    ----------

    .. [1] `NREL National Solar Radiation Database (NSRDB)
       <https://nsrdb.nrel.gov/>`_
    .. [2] `NREL Developer Network - Physical Solar Model (PSM) v3
       <https://developer.nrel.gov/docs/solar/nsrdb/psm3_data_download/>`_
    .. [3] `NREL Developer Network - Physical Solar Model (PSM) v3 TMY
       <https://developer.nrel.gov/docs/solar/nsrdb/psm3_tmy_data_download/>`_
    """
    # The well know text (WKT) representation of geometry notation is strict.
    # A POINT object is a string with longitude first, then the latitude, with
    # four decimals each, and exactly one space between them.
    longitude = ('%9.4f' % longitude).strip()
    latitude = ('%8.4f' % latitude).strip()
    # TODO: make format_WKT(object_type, *args) in tools.py

    # required query-string parameters for request to PSM3 API
    params = {
        'api_key': api_key,
        'full_name': full_name,
        'email': email,
        'affiliation': affiliation,
        'reason': PVLIB_PYTHON,
        'mailing_list': 'false',
        'wkt': 'POINT(%s %s)' % (longitude, latitude),
        'names': names,
        'attributes':  ','.join(ATTRIBUTES),
        'leap_day': str(leap_day).lower(),
        'utc': 'false',
        'interval': interval
    }
    # request CSV download from NREL PSM3
    if any(prefix in names for prefix in ('tmy', 'tgy', 'tdy')):
        URL = TMY_URL
    else:
        URL = PSM_URL
    response = requests.get(URL, params=params, timeout=timeout)
    if not response.ok:
        # if the API key is rejected, then the response status will be 403
        # Forbidden, and then the error is in the content and there is no JSON
        try:
            errors = response.json()['errors']
        except JSONDecodeError:
            errors = response.content.decode('utf-8')
        raise requests.HTTPError(errors, response=response)
    # the CSV is in the response content as a UTF-8 bytestring
    # to use pandas we need to create a file buffer from the response
    fbuf = io.StringIO(response.content.decode('utf-8'))
    return parse_psm3(fbuf)


def parse_psm3(fbuf):
    """
    Parse an NSRDB PSM3 weather file (formatted as SAM CSV).  The NSRDB
    is described in [1]_ and the SAM CSV format is described in [2]_.

    Parameters
    ----------
    fbuf: file-like object
        File-like object containing data to read.

    Returns
    -------
    headers : dict
        metadata from NREL PSM3 about the record, see notes for fields
    data : pandas.DataFrame
        timeseries data from NREL PSM3

    Notes
    -----
    The return is a tuple with two items. The first item is a header with
    metadata from NREL PSM3 about the record containing the following fields:

    * Source
    * Location ID
    * City
    * State
    * Country
    * Latitude
    * Longitude
    * Time Zone
    * Elevation
    * Local Time Zone
    * Clearsky DHI Units
    * Clearsky DNI Units
    * Clearsky GHI Units
    * Dew Point Units
    * DHI Units
    * DNI Units
    * GHI Units
    * Solar Zenith Angle Units
    * Temperature Units
    * Pressure Units
    * Relative Humidity Units
    * Precipitable Water Units
    * Wind Direction Units
    * Wind Speed
    * Cloud Type -15
    * Cloud Type 0
    * Cloud Type 1
    * Cloud Type 2
    * Cloud Type 3
    * Cloud Type 4
    * Cloud Type 5
    * Cloud Type 6
    * Cloud Type 7
    * Cloud Type 8
    * Cloud Type 9
    * Cloud Type 10
    * Cloud Type 11
    * Cloud Type 12
    * Fill Flag 0
    * Fill Flag 1
    * Fill Flag 2
    * Fill Flag 3
    * Fill Flag 4
    * Fill Flag 5
    * Surface Albedo Units
    * Version

    The second item is a dataframe with the PSM3 timeseries data.

    Examples
    --------
    >>> # Read a local PSM3 file:
    >>> with open(filename, 'r') as f:  # doctest: +SKIP
    ...     metadata, df = iotools.parse_psm3(f)  # doctest: +SKIP

    See Also
    --------
    pvlib.iotools.read_psm3, pvlib.iotools.get_psm3

    References
    ----------
    .. [1] `NREL National Solar Radiation Database (NSRDB)
       <https://nsrdb.nrel.gov/>`_
    .. [2] `Standard Time Series Data File Format
       <https://rredc.nrel.gov/solar/old_data/nsrdb/2005-2012/wfcsv.pdf>`_
    """
    # The first 2 lines of the response are headers with metadata
    header_fields = fbuf.readline().split(',')
    header_fields[-1] = header_fields[-1].strip()  # strip trailing newline
    header_values = fbuf.readline().split(',')
    header_values[-1] = header_values[-1].strip()  # strip trailing newline
    header = dict(zip(header_fields, header_values))
    # the response is all strings, so set some header types to numbers
    header['Local Time Zone'] = int(header['Local Time Zone'])
    header['Time Zone'] = int(header['Time Zone'])
    header['Latitude'] = float(header['Latitude'])
    header['Longitude'] = float(header['Longitude'])
    header['Elevation'] = int(header['Elevation'])
    # get the column names so we can set the dtypes
    columns = fbuf.readline().split(',')
    columns[-1] = columns[-1].strip()  # strip trailing newline
    # Since the header has so many columns, excel saves blank cols in the
    # data below the header lines.
    columns = [col for col in columns if col != '']
    dtypes = dict.fromkeys(columns, float)  # all floats except datevec
    dtypes.update(Year=int, Month=int, Day=int, Hour=int, Minute=int)
    dtypes['Cloud Type'] = int
    dtypes['Fill Flag'] = int
    data = pd.read_csv(
        fbuf, header=None, names=columns, usecols=columns, dtype=dtypes,
        delimiter=',', lineterminator='\n')  # skip carriage returns \r
    # the response 1st 5 columns are a date vector, convert to datetime
    dtidx = pd.to_datetime(
        data[['Year', 'Month', 'Day', 'Hour', 'Minute']])
    # in USA all timezones are integers
    tz = 'Etc/GMT%+d' % -header['Time Zone']
    data.index = pd.DatetimeIndex(dtidx).tz_localize(tz)

    return header, data


def read_psm3(filename):
    """
    Read an NSRDB PSM3 weather file (formatted as SAM CSV).  The NSRDB
    is described in [1]_ and the SAM CSV format is described in [2]_.

    Parameters
    ----------
    filename: str
        Filename of a file containing data to read.

    Returns
    -------
    headers : dict
        metadata from NREL PSM3 about the record, see
        :func:`pvlib.iotools.parse_psm3` for fields
    data : pandas.DataFrame
        timeseries data from NREL PSM3

    See Also
    --------
    pvlib.iotools.parse_psm3, pvlib.iotools.get_psm3

    References
    ----------
    .. [1] `NREL National Solar Radiation Database (NSRDB)
       <https://nsrdb.nrel.gov/>`_
    .. [2] `Standard Time Series Data File Format
       <https://rredc.nrel.gov/solar/old_data/nsrdb/2005-2012/wfcsv.pdf>`_
    """
    with open(str(filename), 'r') as fbuf:
        content = parse_psm3(fbuf)
    return content
