#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re
import inspect
from os.path import abspath, dirname, join

path = dirname(abspath(__file__))
root = abspath(join(path, "..", ".."))

from thrift.Thrift import TType
from thriftgen.pyload import ttypes
from thriftgen.pyload import Pyload

from pyload import __version_info__

type_map = {
    TType.BOOL: 'bool',
    TType.DOUBLE: 'float',
    TType.I16: 'int',
    TType.I32: 'int',
    TType.I64: 'int',
    TType.STRING: 'basestring',
    TType.MAP: 'dict',
    TType.LIST: 'list',
    TType.SET: 'set',
    TType.VOID: 'None',
    TType.STRUCT: 'BaseObject',
    TType.UTF8: 'unicode',
}

def get_spec(spec, optional=False):
    """ analyze the generated spec file and writes information into file """
    if spec[1] == TType.STRUCT:
        return spec[3][0].__name__
    elif spec[1]  == TType.LIST:
        if spec[3][0] == TType.STRUCT:
            ttype = spec[3][1][0].__name__
        else:
            ttype = type_map[spec[3][0]]
        return "(list, %s)" % ttype
    elif spec[1] == TType.MAP:
        if spec[3][2] == TType.STRUCT:
            ttype = spec[3][3][0].__name__
        else:
            ttype = type_map[spec[3][2]]

        return "(dict, %s, %s)" % (type_map[spec[3][0]], ttype)
    else:
        return type_map[spec[1]]

optional_re = "%d: +optional +[a-z0-9<>_-]+ +%s"

def main():

    enums = []
    classes = []
    tf = open(join(path, "pyload.thrift"), "rb").read()

    print "generating apitypes.py"

    for name in dir(ttypes):
        klass = getattr(ttypes, name)

        if name in ("TBase", "TExceptionBase") or name.startswith("_") or not (issubclass(klass, ttypes.TBase) or issubclass(klass, ttypes.TExceptionBase)):
            continue

        if hasattr(klass, "thrift_spec"):
            classes.append(klass)
        else:
            enums.append(klass)


    f = open(join(path, "apitypes.py"), "wb")
    f.write(
        """#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Autogenerated by pyload
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING

class BaseObject(object):
\t__version__ = {0}
\t__slots__ = []

\tdef __str__(self):
\t\treturn "<%s %s>" % (self.__class__.__name__, ", ".join("%s=%s" % (k,getattr(self,k)) for k in self.__slots__))

class ExceptionObject(Exception):
\t__version__ = {0}
\t__slots__ = []

""".format(__version_info__))

    dev = open(join(path, "apitypes_debug.py"), "wb")
    dev.write("""#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Autogenerated by pyload
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n
from apitypes import *\n
""")

    dev.write("enums = [\n")

    ## generate enums
    for enum in enums:
        name = enum.__name__
        f.write("class %s:\n" % name)

        for attr in sorted(dir(enum), key=lambda x: getattr(enum, x)):
            if attr.startswith("_") or attr in ("read", "write"): continue
            f.write("\t%s = %s\n" % (attr, getattr(enum, attr)))

        dev.write('\t"%s",\n' % name)
        f.write("\n")

    dev.write("]\n\n")

    dev.write("classes = {\n")

    for klass in classes:
        name = klass.__name__
        base = "ExceptionObject" if issubclass(klass, ttypes.TExceptionBase) else "BaseObject"
        f.write("class %s(%s):\n" % (name,  base))

        # No attributes, don't write further info
        if not klass.__slots__:
            f.write("\tpass\n\n")
            continue

        f.write("\t__slots__ = %s\n\n" % klass.__slots__)
        dev.write("\t'%s' : [" % name)

        #create init
        args = ["self"] + ["%s=None" % x for x in klass.__slots__]
        specs = []

        f.write("\tdef __init__(%s):\n" % ", ".join(args))
        for i, attr in enumerate(klass.__slots__):
            f.write("\t\tself.%s = %s\n" % (attr, attr))

            spec = klass.thrift_spec[i+1]
            # assert correct order, so the list of types is enough for check
            assert spec[2] == attr
            # dirty way to check optional attribute, since it is not in the generated code
            # can produce false positives, but these are not critical
            optional = re.search(optional_re % (i+1, attr), tf, re.I)
            if optional:
                specs.append("(None, %s)" % get_spec(spec))
            else:
                specs.append(get_spec(spec))

        f.write("\n")
        dev.write(", ".join(specs) + "],\n")

    dev.write("}\n\n")

    f.write("class Iface(object):\n")
    dev.write("methods = {\n")

    for name in dir(Pyload.Iface):
        if name.startswith("_"): continue

        func = inspect.getargspec(getattr(Pyload.Iface, name))

        f.write("\tdef %s(%s):\n\t\tpass\n" % (name, ", ".join(func.args)))

        spec = getattr(Pyload, "%s_result" % name).thrift_spec
        if not spec or not spec[0]:
            dev.write("\t'%s': None,\n" % name)
        else:
            spec = spec[0]
            dev.write("\t'%s': %s,\n" % (name, get_spec(spec)))

    f.write("\n")
    dev.write("}\n")

    f.close()
    dev.close()

if __name__ == "__main__":
    main()