"""
Generate CPU features tables from CCompilerOpt
"""
from os import sys, path
from numpy.distutils.ccompiler_opt import CCompilerOpt

class FakeCCompilerOpt(CCompilerOpt):
    # disable caching no need for it
    conf_nocache = True

    def __init__(self, arch, cc, *args, **kwargs):
        self.fake_info = (arch, cc, '')
        CCompilerOpt.__init__(self, None, **kwargs)

    def dist_compile(self, sources, flags, **kwargs):
        return sources

    def dist_info(self):
        return self.fake_info

    @staticmethod
    def dist_log(*args, stderr=False):
        # avoid printing
        pass

    def feature_test(self, name, force_flags=None, macros=[]):
        # To speed up
        return True

class Features:
    def __init__(self, arch, cc):
        self.copt = FakeCCompilerOpt(arch, cc, cpu_baseline="max")

    def names(self):
        return self.copt.cpu_baseline_names()

    def serialize(self, features_names):
        result = []
        for f in self.copt.feature_sorted(features_names):
            gather = self.copt.feature_supported.get(f, {}).get("group", [])
            implies = self.copt.feature_sorted(self.copt.feature_implies(f))
            result.append((f, implies, gather))
        return result

    def table(self, **kwargs):
        return self.gen_table(self.serialize(self.names()), **kwargs)

    def table_diff(self, vs, **kwargs):
        fnames = set(self.names())
        fnames_vs = set(vs.names())
        common = fnames.intersection(fnames_vs)
        extra = fnames.difference(fnames_vs)
        notavl = fnames_vs.difference(fnames)
        iextra = {}
        inotavl = {}
        idiff = set()
        for f in common:
            implies = self.copt.feature_implies(f)
            implies_vs = vs.copt.feature_implies(f)
            e = implies.difference(implies_vs)
            i = implies_vs.difference(implies)
            if not i and not e:
                continue
            if e:
                iextra[f] = e
            if i:
                inotavl[f] = e
            idiff.add(f)

        def fbold(f):
            if f in extra:
                return f':enabled:`{f}`'
            if f in notavl:
                return f':disabled:`{f}`'
            return f

        def fbold_implies(f, i):
            if i in iextra.get(f, {}):
                return f':enabled:`{i}`'
            if f in notavl or i in inotavl.get(f, {}):
                return f':disabled:`{i}`'
            return i

        diff_all = self.serialize(idiff.union(extra))
        diff_all += vs.serialize(notavl)
        content = self.gen_table(
            diff_all, fstyle=fbold, fstyle_implies=fbold_implies, **kwargs
        )
        return content

    def gen_table(self, serialized_features, fstyle=None, fstyle_implies=None,
                  **kwargs):

        if fstyle is None:
            fstyle = lambda ft: f'``{ft}``'
        if fstyle_implies is None:
            fstyle_implies = lambda origin, ft: fstyle(ft)

        rows = []
        have_gather = False
        for f, implies, gather in serialized_features:
            if gather:
                have_gather = True
            name = fstyle(f)
            implies = ' '.join([fstyle_implies(f, i) for i in implies])
            gather = ' '.join([fstyle_implies(f, i) for i in gather])
            rows.append((name, implies, gather))
        if not rows:
            return ''
        fields = ["Name", "Implies", "Gathers"]
        if not have_gather:
            del fields[2]
            rows = [(name, implies) for name, implies, _ in rows]
        return self.gen_rst_table(fields, rows, **kwargs)

    def gen_rst_table(self, field_names, rows, tab_size=4):
        assert(not rows or len(field_names) == len(rows[0]))
        rows.append(field_names)
        fld_len = len(field_names)
        cls_len = [max(len(c[i]) for c in rows) for i in range(fld_len)]
        del rows[-1]
        cformat = ' '.join('{:<%d}' % i for i in cls_len)
        border = cformat.format(*['='*i for i in cls_len])

        rows = [cformat.format(*row) for row in rows]
        # header
        rows = [border, cformat.format(*field_names), border] + rows
        # footer
        rows += [border]
        # add left margin
        rows = [(' ' * tab_size) + r for r in rows]
        return '\n'.join(rows)

def wrapper_section(title, content, tab_size=4):
    tab = ' '*tab_size
    if content:
        return (
            f"{title}\n{'~'*len(title)}"
            f"\n.. table::\n{tab}:align: left\n\n"
            f"{content}\n\n"
        )
    return ''

def wrapper_tab(title, table, tab_size=4):
    tab = ' '*tab_size
    if table:
        ('\n' + tab).join((
            '.. tab:: ' + title,
            tab + '.. table::',
            tab + 'align: left',
            table + '\n\n'
        ))
    return ''


if __name__ == '__main__':

    pretty_names = {
        "PPC64": "IBM/POWER big-endian",
        "PPC64LE": "IBM/POWER little-endian",
        "S390X": "IBM/ZSYSTEM(S390X)",
        "ARMHF": "ARMv7/A32",
        "AARCH64": "ARMv8/A64",
        "ICC": "Intel Compiler",
        # "ICCW": "Intel Compiler msvc-like",
        "MSVC": "Microsoft Visual C/C++"
    }
    gen_path = path.join(
        path.dirname(path.realpath(__file__)), "generated_tables"
    )
    with open(path.join(gen_path, 'cpu_features.inc'), 'w') as fd:
        fd.write(f'.. generated via {__file__}\n\n')
        for arch in (
            ("x86", "PPC64", "PPC64LE", "ARMHF", "AARCH64", "S390X")
        ):
            title = "On " + pretty_names.get(arch, arch)
            table = Features(arch, 'gcc').table()
            fd.write(wrapper_section(title, table))

    with open(path.join(gen_path, 'compilers-diff.inc'), 'w') as fd:
        fd.write(f'.. generated via {__file__}\n\n')
        for arch, cc_names in (
            ("x86", ("clang", "ICC", "MSVC")),
            ("PPC64", ("clang",)),
            ("PPC64LE", ("clang",)),
            ("ARMHF", ("clang",)),
            ("AARCH64", ("clang",)),
            ("S390X", ("clang",))
        ):
            arch_pname = pretty_names.get(arch, arch)
            for cc in cc_names:
                title = f"On {arch_pname}::{pretty_names.get(cc, cc)}"
                table = Features(arch, cc).table_diff(Features(arch, "gcc"))
                fd.write(wrapper_section(title, table))


