# Licensed under a 3-clause BSD style license - see LICENSE.rst


import html
import re

import astropy.units as u
from astropy.table import Table

from ..query import BaseQuery
from ..utils import async_to_sync, prepend_docstr_nosections
from . import conf
from ..exceptions import TableParseError

__all__ = ['Nist', 'NistClass']


def _strip_blanks(table):
    """
    Remove blank lines from table (included for "human readability" but
    useless to us...
    returns a single string joined by \n newlines

    Parameters
    ----------
    table : str
       table to strip as a string

    Returns
    -------
    single string joined by newlines.
    """
    numbersletters = re.compile("[0-9A-Za-z]")
    if isinstance(table, str):
        table = table.split('\n')
    table = [line for line in table if numbersletters.search(line)]
    return "\n".join(table)


@async_to_sync
class NistClass(BaseQuery):
    URL = conf.server
    TIMEOUT = conf.timeout
    unit_code = {'Angstrom': 0,
                 'nm': 1,
                 'um': 2}
    energy_level_code = {'cm-1': 0, 'invcm': 0, 'cm': 0,
                         'ev': 1, 'eV': 1, 'EV': 1, 'electronvolt': 1,
                         'R': 2, 'Rydberg': 2, 'rydberg': 2}
    order_out_code = {'wavelength': 0,
                      'multiplet': 1}
    wavelength_unit_code = {'vacuum': 3,
                            'vac+air': 4}

    def _args_to_payload(self, *args, **kwargs):
        """
        Serves the same purpose as `~NistClass.query` but returns
        the raw HTTP response rather than a `~astropy.table.Table` object.

        Parameters
        ----------
        minwav : `astropy.units.Quantity` object
            The lower wavelength for the spectrum in appropriate units.
        maxwav : `astropy.units.Quantity` object
            The upper wavelength for the spectrum in appropriate units.
        linename : str or iterable of str, optional
            The spectrum/spectra to fetch. Defaults to "H I"
        energy_level_unit : str, optional
            The energy level units must be one of the following:
            'R', 'Rydberg', 'rydberg', 'cm', 'cm-1', 'EV', 'eV',
            'electronvolt', 'ev', 'invcm' Defaults to 'eV'.
        output_order : str, optional
            Decide ordering of output. Must be one of following:
            ['wavelength', 'multiplet']. Defaults to 'wavelength'.
        wavelength_type : str, optional
            Must be one of 'vacuum' or 'vac+air'. Defaults to 'vacuum'.
        get_query_payload : bool, optional
            If true then returns the dictionary of query parameters, posted to
            remote server. Defaults to `False`.

        Returns
        -------
        request_payload : dict
            The dictionary of parameters sent with the HTTP request

        """
        request_payload = {}
        linename = kwargs["linename"]
        request_payload["spectra"] = linename if isinstance(linename, str) else "; ".join(linename)
        (min_wav, max_wav, wav_unit) = _parse_wavelength(args[0], args[1])
        request_payload["low_w"] = min_wav
        request_payload["upp_w"] = max_wav
        request_payload["unit"] = wav_unit
        request_payload["submit"] = "Retrieve Data"
        request_payload["format"] = 1  # ascii
        request_payload["line_out"] = 0  # All lines
        request_payload["en_unit"] = Nist.energy_level_code[
            kwargs["energy_level_unit"]]
        request_payload["output_type"] = 0  # entirely rather than pagewise
        request_payload["bibrefs"] = 1
        request_payload["show_obs_wl"] = 1
        request_payload["show_calc_wl"] = 1
        request_payload["order_out"] = Nist.order_out_code[
            kwargs['output_order']]
        request_payload["max_low_enrg"] = ""
        request_payload["show_av"] = Nist.wavelength_unit_code[
            kwargs['wavelength_type']]
        request_payload["max_upp_enrg"] = ""
        request_payload["tsb_value"] = 0
        request_payload["min_str"] = ""
        request_payload["A_out"] = 0
        request_payload["f_out"] = "on"
        request_payload["intens_out"] = "on"
        request_payload["max_str"] = ""
        request_payload["allowed_out"] = 1
        request_payload["forbid_out"] = 1
        request_payload["min_accur"] = ""
        request_payload["min_intens"] = ""
        request_payload["conf_out"] = "on"
        request_payload["term_out"] = "on"
        request_payload["enrg_out"] = "on"
        request_payload["J_out"] = "on"
        request_payload["g_out"] = "on"
        request_payload["page_size"] = 15
        request_payload["remove_js"] = "on"
        request_payload["show_wn"] = 1
        return request_payload

    @prepend_docstr_nosections("\n" + _args_to_payload.__doc__)
    def query_async(self, minwav, maxwav, *, linename="H I",
                    energy_level_unit='eV', output_order='wavelength',
                    wavelength_type='vacuum', get_query_payload=False):
        """
        Returns
        -------
        response : `requests.Response` object
            The response of the HTTP request.
        """
        request_payload = self._args_to_payload(
            minwav, maxwav, linename=linename,
            energy_level_unit=energy_level_unit, output_order=output_order,
            wavelength_type=wavelength_type)
        if get_query_payload:
            return request_payload

        response = self._request("GET", url=Nist.URL, params=request_payload,
                                 timeout=Nist.TIMEOUT)
        return response

    def _parse_result(self, response, *, verbose=False):
        """
        Parses the results from the HTTP response to `astropy.table.Table`.

        Parameters
        ----------
        response : `requests.Response`
            The HTTP response object

        Returns
        -------
        table : `astropy.table.Table`
        """

        pre_re = re.compile("<pre>(.*)</pre>", flags=re.DOTALL)
        links_re = re.compile(r"<a.*?>\s*(\w+)\s*</a>")
        content = str(response.text)

        try:
            pre = pre_re.findall(content)[0]
        except IndexError:
            raise Exception("Result did not contain a table")
        try:
            table = _strip_blanks(pre)
            table = links_re.sub(r'\1', table)
            table = html.unescape(table)
            table = Table.read(table, format='ascii.fixed_width', data_start=3, delimiter='|')
            return table
        except Exception as ex:
            self.response = response
            self.table_parse_error = ex
            raise TableParseError("Failed to parse asciitable! The raw "
                                  "response can be found in self.response, "
                                  "and the error in self.table_parse_error.")


Nist = NistClass()


def _parse_wavelength(min_wav, max_wav):
    """
    Helper function to return wavelength and units in form accepted by NIST

    Parameters
    ----------
    min_wav : `astropy.units.Quantity` object
        The lower wavelength in proper units.
    max_wav : `astropy.units.Quantity` object
        The upper wavelength in proper units.

    Returns
    -------
    tuple : (number, number, number)
        The value of lower, upper wavelength and their unit.
    """
    max_wav = max_wav.to(min_wav.unit)
    wav_unit = min_wav.unit.to_string()
    if wav_unit not in Nist.unit_code:
        max_wav = max_wav.to(u.angstrom).value
        min_wav = min_wav.to(u.angstrom).value
        wav_unit = Nist.unit_code['Angstrom']
    else:
        max_wav = max_wav.value
        min_wav = min_wav.value
        wav_unit = Nist.unit_code[wav_unit]
    return (min_wav, max_wav, wav_unit)
