"""
Methods for importing data from the Ulysses spacecraft.
"""
from collections import OrderedDict
from datetime import datetime, timedelta
import pathlib
import urllib.error

import astropy.units as u
import pandas as pd

from heliopy.data import util

ulysses_url = 'http://ufa.esac.esa.int/ufa-sl-server/data-action?'
url_options = {'PROTOCOL': 'HTTP',
               'PRODUCT_TYPE': 'ALL'}


def swics_heavy_ions(starttime, endtime):
    """
    Import swics heavy ion data.

    The variables in this dataset are:

      - DENS_ALPHA: alpha to oxygen 6+ density ratio
      - VEL_ALPHA:  alpha velocity
      - TEMP_ALPHA: alpha temperature
      - DENS_C6:    carbon 6+ to oxygen 6+ density ratio
      - VEL_C6:     carbon 6+ velocity
      - TEMP_C6:    carbon 6+ temperature
      - DENS_O6:    oxygen 6+ density in cm^-3
      - VEL_O6:     oxygen 6+ velocity
      - TEMP_O6:    oxygen 6+ temperature
      - DENS_NE8:   neon 8+ to oxygen 6+ density ratio
      - VEL_NE8:    neon 8+ velocity
      - TEMP_NE8:   neon 8+ temperature
      - DENS_MG10:  magnesium 10+ to oxygen 6+ density ratio
      - VEL_MG10:   magnesium 10+ velocity
      - TEMP_MG10:  magnesium 10+ temperature
      - DENS_SI9:   silicon 9+ to oxygen 6+ density ratio
      - VEL_SI9:    silicon 9+ velocity
      - TEMP_SI9:   silicon 9+ temperature
      - DENS_S10:   sulphur 10+ to oxygen 6+ density ratio
      - VEL_S10:    sulphur 10+ velocity
      - TEMP_S10:   sulphur 10+ temperature
      - DENS_FE11:  iron 11+ to oxygen 6+ density ratio
      - VEL_FE11:   iron 11+ velocity
      - TEMP_FE11:  iron 11+ temperature

    See http://ufa.esac.esa.int/ufa-sl-server/data-action?PROTOCOL=HTTP&PRODUCT_TYPE=ALL&FILE_NAME=readme.txt&FILE_PATH=/ufa/HiRes/data/swics
    for more information.

    Parameters
    ----------
    starttime : datetime
        Start of interval
    endtime : datetime
        End of interval

    Returns
    -------
    data : :class:`~sunpy.timeseries.TimeSeries`
        Requested data
    """
    names = ['year', 'doy', 'hour', 'minute', 'second']
    for ion in ['ALPHA', 'C6', 'O6', 'NE8', 'MG10', 'SI9', 'SI10', 'FE11']:
        names += ['DENS_' + ion, 'VEL_' + ion, 'TEMP_' + ion]
    product = 'uswimatb'
    units = OrderedDict([('VEL_ALPHA', u.km / u.s), ('TEMP_ALPHA', u.K),
                        ('VEL_C6', u.km / u.s), ('TEMP_C6', u.K),
                        ('VEL_O6', u.km / u.s), ('TEMP_O6', u.K),
                        ('VEL_NE8', u.km / u.s), ('TEMP_NE8', u.K),
                        ('VEL_MG10', u.km / u.s), ('TEMP_MG10', u.K),
                        ('VEL_SI9', u.km / u.s), ('TEMP_SI9', u.K),
                        ('VEL_SI10', u.km / u.s), ('TEMP_SI10', u.K),
                        ('VEL_FE11', u.km / u.s), ('TEMP_FE11', u.K),
                        ('DENS_O6', u.cm**-3),
                        ('DENS_ALPHA', u.dimensionless_unscaled),
                        ('DENS_C6', u.dimensionless_unscaled),
                        ('DENS_O6', u.dimensionless_unscaled),
                        ('DENS_NE8', u.dimensionless_unscaled),
                        ('DENS_MG10', u.dimensionless_unscaled),
                        ('DENS_SI9', u.dimensionless_unscaled),
                        ('DENS_SI10', u.dimensionless_unscaled),
                        ('DENS_FE11', u.dimensionless_unscaled)])
    return _swics(starttime, endtime, names, product, units)


def swics_abundances(starttime, endtime):
    """
    Import swics abundance data.

    The variables in this dataset are:

      - VEL_ALPHA:  alpha velocity
      - RAT_C6_C5:  ratio of carbon 6+ to 5+
      - RAT_O7_O6:  ratio of oxygen 7+ to 6+
      - RAT_FE_O:   abundance ratio of iron to oxygen
      - CHARGE_FE:  average charge state of iron
      - N_CYC:      number of instrument cycles in average

    See http://ufa.esac.esa.int/ufa-sl-server/data-action?PROTOCOL=HTTP&PRODUCT_TYPE=ALL&FILE_NAME=readme.txt&FILE_PATH=/ufa/HiRes/data/swics
    for more information.

    Parameters
    ----------
    starttime : datetime
        Start of interval
    endtime : datetime
        End of interval

    Returns
    -------
    data : :class:`~sunpy.timeseries.TimeSeries`
        Requested data
    """
    names = ['year', 'doy', 'hour', 'minute', 'second',
             'VEL_ALPHA', 'RAT_C6_C5', 'RAT_O7_O6', 'RAT_FE_O', 'CHARGE_FE',
             'N_CYC']
    product = 'uswichst'
    units = OrderedDict([('VEL_ALPHA', u.km / u.s),
                        ('RAT_C6_C5', u.dimensionless_unscaled),
                        ('RAT_O7_O6', u.dimensionless_unscaled),
                        ('RAT_FE_O', u.dimensionless_unscaled),
                        ('CHARGE_FE', u.dimensionless_unscaled),
                        ('N_CYC', u.dimensionless_unscaled)])
    return _swics(starttime, endtime, names, product, units)


class _swicsDownloader(util.Downloader):
    def __init__(self, product, names, units):
        self.product = product
        self.names = names
        self.units = units

    def intervals(self, starttime, endtime):
        return self.intervals_yearly(starttime, endtime)

    def fname(self, interval):
        yearstr = interval.start.strftime('%Y')[-2:]
        return f'{self.product}{yearstr}.dat'

    def local_dir(self, interval):
        return pathlib.Path('ulysses') / 'swics'

    def download(self, interval):
        local_dir = self.local_path(interval).parent
        local_dir.mkdir(parents=True, exist_ok=True)
        fname = self.fname(interval)

        swics_options = url_options
        swics_options['FILE_NAME'] = fname
        swics_options['FILE_PATH'] = '/ufa/HiRes/data/swics'
        _download_ulysses(swics_options, fname, local_dir)
        return self.local_path(interval)

    def load_local_file(self, interval):
        readargs = {'names': self.names,
                    'delim_whitespace': True,
                    'na_values': ['******']}
        thisdata = pd.read_csv(self.local_path(interval), **readargs)
        thisdata = _convert_ulysses_time(thisdata)
        return thisdata


def _swics(starttime, endtime, names, product, units=None):
    downloader = _swicsDownloader(product, names, units)
    return downloader.load(starttime, endtime)


class _fgmDownloader(util.Downloader):
    def __init__(self, units):
        self.units = units

    def intervals(self, starttime, endtime):
        return self.intervals_daily(starttime, endtime)

    def fname(self, interval):
        dtime = interval.start
        yearstr = self.yearstr(interval)
        filename = ('U' + yearstr[-2:] + dtime.strftime('%j') + 'SH')
        return f'{filename}.ASC'

    @staticmethod
    def yearstr(interval):
        return interval.start.strftime('%Y')

    def local_dir(self, interval):
        return pathlib.Path('ulysses') / 'fgm' / 'hires'

    def download(self, interval):
        local_dir = self.local_path(interval).parent
        local_dir.mkdir(parents=True, exist_ok=True)
        fname = self.fname(interval)
        yearstr = self.yearstr(interval)

        fgm_options = url_options
        fgm_options['FILE_NAME'] = fname
        fgm_options['FILE_PATH'] = '/ufa/HiRes/VHM-FGM/' + yearstr
        _download_ulysses(fgm_options, fname, local_dir)
        return self.local_path(interval)

    def load_local_file(self, interval):
        readargs = {'names': ['year', 'doy', 'hour', 'minute', 'second',
                              'Bx', 'By', 'Bz', '|B|'],
                    'delim_whitespace': True}
        thisdata = pd.read_csv(self.local_path(interval), **readargs)
        thisdata = _convert_ulysses_time(thisdata)
        return thisdata


def fgm_hires(starttime, endtime):
    """
    Import high resolution fluxgate magnetometer data.

    Parameters
    ----------
    starttime : datetime
        Start of interval
    endtime : datetime
        End of interval

    Returns
    -------
    data : :class:`~sunpy.timeseries.TimeSeries`
        Requested data
    """
    units = OrderedDict([('Bx', u.nT), ('By', u.nT),
                         ('Bz', u.nT), ('|B|', u.nT)])
    downloader = _fgmDownloader(units)
    return downloader.load(starttime, endtime)


class _swoopsionDownloader(util.Downloader):
    def __init__(self, units):
        self.units = units

    def intervals(self, starttime, endtime):
        return self.intervals_monthly(starttime, endtime)

    def fname(self, interval):
        dtime = interval.start
        year = dtime.strftime('%Y')
        doy = dtime.strftime('%j')
        return 'u{}{}bam.dat'.format(str(year)[2:], doy)

    @staticmethod
    def yearstr(interval):
        return interval.start.strftime('%Y')

    def local_dir(self, interval):
        return pathlib.Path('ulysses') / 'swoops' / 'ions'

    def download(self, interval):
        local_dir = self.local_path(interval).parent
        local_dir.mkdir(parents=True, exist_ok=True)
        fname = self.fname(interval)

        swoops_options = url_options
        year = fname[1:3]
        # doy = fname[5:8]
        swoops_options['FILE_NAME'] = fname
        swoops_options['FILE_PATH'] =\
            (f'/ufa/stageIngestArea/swoops/ions/bamion{year}.zip_files')
        _download_ulysses(swoops_options, fname, local_dir)
        return self.local_path(interval)

    def load_local_file(self, interval):
        readargs = {'names': ['year', 'doy', 'hour', 'minute', 'second',
                              'r', 'hlat', 'hlon', 'n_p', 'n_a',
                              'T_p_large', 'T_p_small',
                              'v_r', 'v_t', 'v_n', 'iqual'],
                    'delim_whitespace': True}
        thisdata = pd.read_csv(self.local_path(interval), **readargs)
        thisdata = _convert_ulysses_time(thisdata)
        return thisdata


def swoops_ions(starttime, endtime):
    """
    Import SWOOPS ion data.

    Parameters
    ----------
    starttime : datetime
        Start of interval
    endtime : datetime
        End of interval

    Returns
    -------
    data : :class:`~sunpy.timeseries.TimeSeries`
        Requested data
    """

    units = OrderedDict([('T_p_large', u.K), ('T_p_small', u.K),
                        ('v_t', u.km / u.s), ('v_r', u.km / u.s),
                        ('v_n', u.km / u.s), ('r', u.au),
                        ('n_a', u.cm**-3), ('n_p', u.cm**-3),
                        ('hlat', u.deg),
                        ('hlon', u.deg),
                        ('iqual', u.dimensionless_unscaled)])
    downloader = _swoopsionDownloader(units)
    return downloader.load(starttime, endtime)


def _download_ulysses(options, fname, local_dir):
    """Common downloading functionality"""
    dl_url = ulysses_url
    for key in options:
        dl_url += key + '=' + options[key] + '&'
    # Download data
    try:
        util._download_remote(dl_url, fname, local_dir)
    except urllib.error.HTTPError:
        raise util.NoDataError


def _convert_ulysses_time(data):
    """Method to convert timestamps to datetimes"""
    if (data['year'] < 1900).all():
        data.loc[data['year'] > 50, 'year'] += 1900
        data.loc[data['year'] < 50, 'year'] += 2000

    data['Time'] = pd.to_datetime(data['year'].astype(str) + ':' +
                                  data['doy'].astype(str),
                                  format='%Y:%j')
    data['Time'] += (pd.to_timedelta(data['hour'], unit='h') +
                     pd.to_timedelta(data['minute'], unit='m') +
                     pd.to_timedelta(data['second'], unit='s'))
    data = data.drop(['year', 'doy', 'hour', 'minute', 'second'],
                     axis=1)
    return data