# AUTOGENERATED FILE! PLEASE DON'T EDIT
import re, logging, warnings
from typing import Union, Tuple, List
from .atom import Atom, mo, NoFreeElectrons, OctetFull
from .substance import alkane, alcohol
import k1lib.cli as cli
__all__ = ["parse"]
mainNumsDict = {"meth": 1, "eth": 2, "prop": 3, "but": 4, "pent": 5, "hex": 6, "hept": 7, "oct": 8, "non": 9, "dec": 10, "undec": 11, "dodec": 12, "tridec": 13, "tetradec": 14, "pentadec": 15, "hexadec": 16, "heptadec": 17, "octadec": 18, "nonadec": 19}
bondsDict = {"ane": 1, "ene": 2, "yne": 3}
cis_trans = "(?P<cis_trans>[, \-]?((cis)|(trans))$)"
def join(a): return "(" + "|".join(f"({p})" for p in a) + ")"
pres1 = ["n-", "o-", "p-"]
pres2 = ["mono", "di", "tri", "tetra", "penta", "hexa"]
pres3 = ["iso", "cyclo", "sec-", "tert-"]
pres = f"{join(pres1)}?[\-]?{join(pres2)}?[\-]?{join(pres3)}?"
main_pre = f"(?P<main_pre>(((cis-)|(trans-))?[0-9,\-])*{pres})"
#main_pre = "(?P<main_pre>(n-)?(cyclo))"
mids = list(mainNumsDict.keys()) + ["benz", "naphthal", "pyridine", "xylenol", "ether", "amine"]
main_mid = "(?P<main_mid>" + "|".join(f"({m})" for m in mids) + ")"
sufs = list(bondsDict.keys()) + ["anol", "ate", "adiene", "aldehyde", "anone"]
main_suf = f"(?P<main_suf>{join(sufs)})"
main = f"(?P<main>{main_pre}?{main_mid}{main_suf}?{cis_trans}?)$"; mainC = re.compile(f"^{main}")
branch_pre = f"(?P<branch_pre>(((cis-)|(trans-))?[0-9,\-])*{pres})"
bMids = mids + ["fluoro", "chloro", "bromo", "iodo", "nitro", "phenyl", "perfluoro"]
branch_mid = "(?P<branch_mid>" + join(bMids) + ")"
branch_suf = "(?P<branch_suf>yl)"
branch = f"(?P<branch>{branch_pre}?{branch_mid}{branch_suf}?[ ]?)"; branchC = re.compile(f"^{branch}$")
fullStrict = f"^{branch}*{main}"
fullStrictC = re.compile(fullStrict)
full = f"{branch}*{main}"
fullC = re.compile(full)
def recognizeInit(ogS:str) -> Union[Tuple[List[str], List[str]], str]:
    """Tries to recognize the input sequence. Returns the sequence if can't understand, else
returns tuple (mainParts, branches)."""
    s = ogS; m = re.fullmatch(fullStrictC, s)
    if m is None: return ogS
    s = s[:m.start("main")]
    sMain = m.group("main")
    sMains = [m.group("main_pre") or "", m.group("main_mid") or "", m.group("main_suf") or ""]
    sMains = ["".join(sMains), *sMains]

    groups = []
    for _ in range(int(10)):
        m = re.match(branch, s)
        if m is None or m.end() == 0: break
        groups.append(s[:m.end()].strip("-,"))
        s = s[m.end():]
    if len(s) != 0: return None
    return [sMains, groups]
def preLocs(sPre, *defaults) -> List[int]:
    """If ``sPre`` looks like "3,1,4-" or sth like that, then extract all the location infos."""
    answer = []
    try: answer = sPre.strip("-,").split(",") | cli.replace("-", "") | cli.toInt() | cli.deref()
    except: pass
    return defaults if len(answer) == 0 else answer
def detectModifiers(sPre:str, has:set) -> str:
    if "cyclo" in sPre: has.add("cyclo"); sPre = sPre.replace("cyclo", "")
    if "iso" in sPre: has.add("iso"); sPre = sPre.replace("iso", "")
    if "sec" in sPre: has.add("sec"); sPre = sPre.replace("sec", "")
    if "tert" in sPre: has.add("tert"); sPre = sPre.replace("tert", "")
    return sPre
def fixTridec(sPre:str, sMid:str) -> Tuple[str, str]:
    """Handles special case for tridec, tetradec, ..., as "tri" leaks into sPre and not sMid for detection"""
    if sMid == "dec": # special case for tridec, tetradec and whatnot
        for prefix in ["tri", "tetra", "penta", "hexa"]:
            if sPre.endswith(prefix):
                sPre = sPre[:-len(prefix)]
                sMid = prefix + sMid; break
    return sPre, sMid
def recognizeMain(a:Union[List[str], str]) -> Union[Atom, str]:
    """Tries to recognize main part of the molecule (eg. n-cyclohexane). Returns
:class:`~k1lib.mo.Atom` if recognized, else the input string"""
    s = ""
    try:
        if isinstance(a, str):
            m = re.fullmatch(mainC, a)
            a = [a, m.group("main_pre"), m.group("main_mid"), m.group("main_suf")]
        s, sPre, sMid, sSuf = a
        sPre, sMid, sSuf = (sPre or "").strip().lstrip("-"), (sMid or "").strip(), (sSuf or "").strip()
        has = set(); sPre = detectModifiers(sPre, has)
        if sMid in mainNumsDict:
            sPre, sMid = fixTridec(sPre, sMid)
            carbons = mainNumsDict[sMid]
            if sSuf in bondsDict:
                a = alkane(carbons); bonds = bondsDict[sSuf] # main atom
                if bonds > 1: b = a.next(times=preLocs(sPre, 1)[0]-1); b.bond(b.next(), bonds-1)
            elif sSuf == "anol":
                a = alcohol(carbons, preLocs(sPre, 1)[0]) # main atom
            elif sSuf == "adiene":
                a = alkane(carbons)
                for loc in preLocs(sPre, 1, 2):
                    b = a.next(times=loc-1); b(b.next())
            else: return s
        elif sMid == "xylenol" and sSuf.strip() == "":
            a = mo.benzene; a(mo.H2O)
            for loc in preLocs(sPre): a.next(times=loc-1)(mo.CH4)
        else: return s
        if "cyclo" in has: a.endChain.bond(a)
        if "iso" in has: a.moveLastCTo2ndC()
        if "tert" in has: a.moveLastCTo2ndC().moveLastCTo2ndC()
        return a
    except Exception as e:
        print(s); raise e
diTriTetra = {term: i+1 for i, term in enumerate(pres2)}
def detectDiTriTetra(sPre:str) -> Tuple[str, str]:
    """If exists, returns (sPre, num), else return (sPre, None)"""
    for k, v in diTriTetra.items():
        if sPre.endswith(k):
            return sPre.replace(k, ""), v
    return sPre, None
def detectCisTrans(sPre:str, has) -> str:
    for term in ["cis", "trans"]:
        if term in sPre:
            has.add(term); sPre = sPre.replace(term, "")
    return sPre
def recognizeGroup(s:str, mainA:Atom) -> Union[Atom, str]:
    """Tries to recognize a group (eg. "1-cyclopentyl"), and adds it to the main atom ``mainA``.

:return: if can't recognize, then returns the group string, else returns the main atom"""
    if mainA is None: mainA = alkane(10)
    m = re.fullmatch(branchC, s); ops = [] # operations to generate the branch
    sPre, sMid, sSuf = [m.group("branch_pre"), m.group("branch_mid"), m.group("branch_suf")]
    has = set(); sPre = detectCisTrans(detectModifiers(sPre, has), has)
    if sMid in mainNumsDict:
        if sSuf != "yl": return s
        carbons = mainNumsDict[sMid]
        ops.append(lambda _: alkane(carbons))
        if "cyclo" in has: ops.append(lambda a: a.endChain.bond(a))
        if "iso" in has: ops.append(lambda a: a.moveLastCTo2ndC())
        if "tert" in has: ops.append(lambda a: a.moveLastCTo2ndC().moveLastCTo2ndC())
    elif sMid == "chloro": ops.append(lambda _: mo.Cl(mo.H))
    elif sMid == "fluoro": ops.append(lambda _: mo.F (mo.H))
    elif sMid == "bromo":  ops.append(lambda _: mo.Br(mo.H))
    elif sMid == "iodo":   ops.append(lambda _: mo.I (mo.H))
    elif sMid == "phenyl": ops.append(lambda _: mo.benzene)
    else: return s#, sPre, sMid, sSuf
    def gen(a=None):
        for op in ops: a = op(a)
        return a
    # now attaching group to main chain...
    sPre, locWord = detectDiTriTetra(sPre); locs = preLocs(sPre)
    logging.debug(f"locs: {locs}, locWord: {locWord}, sPre: {sPre}")
    if len(locs) > 0 and locWord is not None: # both available, check consistency
        logging.debug(f"locs and locWord both available")
        assert locWord == len(locs), f"s: {s}, locWord: {locWord}, locs: {locs}"
        for loc in locs: mainA.next(times=loc-1)(gen())
        return mainA
    elif len(locs) > 0 and locWord is None:
        if len(locs) > 1: warnings.warn(f"Group {s} has attachment positions, but not di,tri,tetra words!")
        for loc in locs: mainA.next(times=loc-1)(gen())
        return mainA
    else:
        logging.debug(f"Can't detect any position, will be adding stuff randomly to the molecule")
        a = mainA
        for _ in range(max(len(locs), locWord or 0, 1)):
            try:
                while a.availableBonds == 0: a = a.next()
            except RecursionError: raise RuntimeError(f"Molecule has no more free carbons to attach group {s} to")
            a(gen())
            pass
        return mainA
def parse(s:str, quiet:bool=False) -> Union[Atom, str]:
    """Tries to recognize molecule. Returns molecule :class:`~k1lib.mo.Atom`
if recognized, else the molecule's string."""
    def p(s):
        if not quiet: print(s)
    try:
        # initial recognition
        out = recognizeInit(s); has = set()
        if isinstance(out, str): return s
        mainParts, groups = out
        # form main chain
        mainA = recognizeMain(mainParts)
        if isinstance(mainA, str):
            p(f"Recognized {s}, but couldn't form the main chain {mainParts[0]}"); return s
        # check for special perfluoro branch case
        gs = []
        for g in groups:
            if "perfluoro" in g:
                if "perfluoro" != g: p(f"For some reason, group {g} is not exactly 'perfluoro'."); return s
                has.add("perfluoro")
            else: gs.append(g)
        for g in gs:
            mainA = recognizeGroup(g, mainA)
            if isinstance(mainA, str):
                p(f"Recognized {s}, the main chain too, but don't recognize group {g}")
        if "perfluoro" in has: mainA.perfluoro_ize()
        return mainA
    except OctetFull:
        p("Some of the atoms can't form new bonds, as their octet is full. Please revise your structure!"); return s
    except NoFreeElectrons:
        p("Some of the atoms can't form new bonds, as they have no free electrons left to contribute to the bond. Please revise your structure!"); return s