"""
NIST Downloader
"""
from robobrowser import RoboBrowser
import bs4
import re
import unicodedata
import pyfant

__all__ = ["get_nist_webbook_constants", "NIST_URL"]


NIST_URL = "http://webbook.nist.gov/chemistry/form-ser.html"


def get_nist_webbook_constants(formula, flag_unicode=True, flag_parse_state=False):
    """
    Navigates through NIST webbook pages to retrieve a table of molecular constants

    Args:
        formula: example: "OH"
        flag_unicode:
        flag_parse_state: parses NIST 'State' column and extends each data row with three extra
                          columns: 'label', 'mult', 'spdf'

    Returns: tuple: table (list of lists), header (list of strings), name of molecule

    **Disclaimer** This scraper matches a specific version of the Chemistry Web Book. Therefore,
    it may stop working if the NIST web site is updated.
    """

    # Callables to be used in different conversion situations
    _conv_formula = lambda x: _html_formula_to_plain(x, flag_unicode)
    f_header = _conv_formula
    f_state = _conv_formula
    f_float_or_none = lambda x: _nonify(_floatify(_conv_formula(x)))
    f_str_or_none = lambda x: _nonify(_conv_formula(x))
    functions = [f_state] + [f_float_or_none] * 10 + [f_str_or_none, f_float_or_none]

    browser = RoboBrowser(history=True, parser="lxml")
    browser.open("http://webbook.nist.gov/chemistry/form-ser.html")

    # # Page
    form = browser.get_form()
    form["Formula"].value = formula
    browser.submit_form(form)

    # # Molecule park page
    text = browser.find(text="Constants of diatomic molecules")

    if text is None:
        # # Probably fell in this page showing possible choices
        # In this case, clicks on the first choice

        ol = browser.find("ol")

        if not ol:
            raise RuntimeError("Constants of diatomic molecules not found for '{}'".format(formula))

        link0 = ol.find("a")

        if not link0:
            raise RuntimeError("Constants of diatomic molecules not found for '{}'".format(formula))
        browser.follow_link(link0)

        # # Molecule park page
        text = browser.find(text="Constants of diatomic molecules")
        link_ctes = text.parent
        browser.follow_link(link_ctes)
    else:
        link_ctes = text.parent
        browser.follow_link(link_ctes)

    # # Page

    title = browser.find("title").text

    table = browser.find("table", class_="small data")

    rows = table.find_all("tr")
    n = 0
    header = None
    data = []  # list of lists
    for row in rows:
        cols = row.find_all("td")
        flag_header = False
        if len(cols) == 0:
            if n > 0:
                continue
            # If no columns found, we assume it is the header row
            cols = row.find_all("th")
            header = [f_header(ele) for ele in cols]
            header.append("A")

        elif len(cols) == 13:

            row = [function(ele) for function, ele in zip(functions, cols)]
            row.append(_parse_A(browser, cols[1]))
            data.append(row)
            n += 1

    if flag_parse_state:
        _extend_nist_table(data, header)

    return data, header, title


def _extend_nist_table(data, header):
    """Parses 'State' and adds 3 extra columns to table **in place**

    If parsing fails, (label, mult, spdf) columns for that row will be blank
    """

    header.extend(["label", "mult", "spdf"])

    for row in data:
        trio = ("", "", "")
        try:
            trio = _nist_str_to_state(row[0])
        except ValueError:
            pass
        row.extend(trio)


def _nist_str_to_state(s):
    """Parses NIST state string into tuple

    **Note** This method will deal with unicode superscript characters

    Args:
        s: NIST state string such as "E 1Sigma_g+" or "d ¹Sigma_u⁺"

    Return:
        (label, mult, spdf)
    """

    s_ = unicodedata.normalize('NFKD', s).encode('ascii', 'replace').decode()

    match = re.match("([A-Za-z])\s*(\d)\s*([A-Za-z]+)", s_)

    if match is None:
        raise ValueError("Nist 'State' string not recognized: {}".format(s))

    label, _mult, _spdf = match.groups()
    mult = int(_mult)
    spdf = pyfant.greek_to_spdf(_spdf)

    return label, mult, spdf


def _parse_A(browser, tag):
    """
    Parses the "A" information (A is the "coupling constant")

    The cell in column "Te" sometimes cites a reference; the anchor tag has an Id like "Dia19";
    the value we are looking for is in (roughly in the form "A = some_number"

    """
    for item in tag.children:
        if item.name == "a":
            try:
                id_ = re.match("#(Dia\d+)", item.get("href")).groups()[0]
                a = browser.find("a", id=id_)
                if a is not None:
                    td = a.next_element.next_element
                    stemp = re.match("<td>\s*A.*?=\s*([\(\)\+\-]*[0-9]+\.[0-9]+)", str(td)).groups()[0]
                    if stemp is None:
                        return None
                    try:
                        return float(stemp.replace("(", "").replace(")", ""))
                    except AttributeError:
                        # no match
                        return None
                    except ValueError:
                        # match, but no valid float
                        return None

            except AttributeError:
                pass


    return None

_conv_unicode_sup = {
    "i": "\u2071",
    "1": "\u00b9",
    "2": "\u00b2",
    "3": "\u00b3",
    "4": "\u2074",
    "+": "\u207a",
    "-": "\u207b"}

_conv_ascii_sup = {"1": "1",
                   "2": "2",
                   "3": "3",
                   "4": "4",
                   "+": "+",
                   "-": "-",}


_conv_unicode_img = {"larrow": "←",
                     "rarrow": "→",
                     "lrarrow": "↔"}

_conv_ascii_img = {"larrow": "<-",
                   "rarrow": "->",
                   "lrarrow": "<->"}


def _html_formula_to_plain(tag, flag_unicode):
    """
    Returns a string from a bs4.element.Tag representing a formula in NIST web book pages.

    Some HTML elements are converted to unicode characters
    """

    conv_sup = _conv_unicode_sup if flag_unicode else _conv_ascii_sup
    conv_img = _conv_unicode_img if flag_unicode else _conv_ascii_img

    parts = []
    for item in tag.children:
        if isinstance(item, bs4.element.NavigableString):
            parts.append(str(item))
        elif item.name == "sub":
            parts.append("_" + item.text)
        elif item.name == "sup":
            parts.append(conv_sup.get(item.text, "^" + item.text))
        elif item.name == "img":
            # parts.append("\\"+item.attrs["alt"])
            alt = item.attrs["alt"]
            parts.append(conv_img.get(alt, alt))
    return ("".join(parts)).strip().replace("  ", " ").replace("  ", " ")


def _floatify(str_):
    """Tries to convert to float, stripping brackets if it is the case"""
    try:
        ret = float(str_)
    except:
        # Note: It seems that NIST Chemistry WebBook has some OCR behind.
        #       I am making some effort to go around some cases I bumped into, for example, : "4.7E-_7"

        gg = re.match("[\[(]*([0-9\.\-E]+)", str_.replace("_", ""))
        if gg is None:
            ret = str_
        else:
            ret = float(gg.groups()[0])
    return ret


def _nonify(x):
    """Returns None if len(x) == 0, else x"""
    if isinstance(x, str) and len(x.strip()) == 0:
        return None
    return x



