import os, io, sys, re
from .utils import CodeGenerator, camel_to_snake_case, camel_to_screaming_snake_case, delegate_type, which
from .registry import BaseTypeElem, Member, Typed, TypeRef, DispatchTable

PREDEFINED_TYPES = {
    'int8_t': 'i8',
    'uint8_t': 'u8',
    'int16_t': 'i16',
    'uint16_t': 'u16',
    'int32_t': 'i32',
    'uint32_t': 'u32',
    'int64_t': 'i64',
    'uint64_t': 'u64',
    'float': 'f32',
    'double': 'f64',
    'void': 'c_void',
    'char': 'c_char',
    'int': 'c_int',
    'size_t': 'usize',
}

PREDEFINED_UTILS = {
    'VK_MAKE_VERSION': ('vk_make_version', 'u32'),
}

IGNORED = set([
    'VK_TRUE', 'VK_FALSE', 'VK_NULL_HANDLE'
])

RESERVED_KEYWORDS = set([
    '_', 'abstract', 'alignof', 'as', 'become',
    'box', 'break', 'const', 'continue', 'crate',
    'do', 'else', 'enum', 'extern', 'false',
    'final', 'fn', 'for', 'if', 'impl',
    'in', 'let', 'loop', 'macro', 'match',
    'mod', 'move', 'mut', 'offsetof', 'override',
    'priv', 'proc', 'pub', 'pure', 'ref',
    'return', 'Self', 'self', 'sizeof', 'static',
    'struct', 'super', 'trait', 'true', 'type',
    'typeof', 'unsafe', 'unsized', 'use', 'virtual',
    'where', 'while', 'yield', 
])

INCLUDE_TO_MODULE = {
    'X11/Xlib.h':                 'wsi::xlib',
    'X11/extensions/Xrandr.h':    'wsi::xlib',
    'android/native_window.h':    'wsi::android',
    'mir_toolkit/client_types.h': 'wsi::mir',
    'wayland-client.h':           'wsi::wayland',
    'windows.h':                  'wsi::win32',
    'xcb/xcb.h':                  'wsi::xcb',
}


STRUCT_TYPES = set([BaseTypeElem.STRUCT, BaseTypeElem.UNION])

_rustfmt_config = None
_rustfmt_exe = None

def rustfmt(filename):
    global _rustfmt_config, _rustfmt_exe
    if _rustfmt_exe is None:
        _rustfmt_exe = which('rustfmt')
        if _rustfmt_exe is None:
            _rustfmt_exe = False
            print('`rustfmt` not found in PATH. The file `%s` will not be formatted!' % filename)
            return
    if _rustfmt_exe is False:
        return
    import subprocess
    rustfmt_cmdline = [_rustfmt_exe]
    if _rustfmt_config is None:
        if os.path.isfile('./rustfmt.toml'):
            _rustfmt_config = './rustfmt.toml'
        else:
            _rustfmt_config = False
    if _rustfmt_config is not False:
        rustfmt_cmdline.append('--config-path')
        rustfmt_cmdline.append(str(_rustfmt_config))
    rustfmt_cmdline.append(filename)
    res = subprocess.run(rustfmt_cmdline)
    if res.returncode == 3:
        print('`rustfmt` was unable to format `%s` properly, but the code was valid: the error was ignored' % filename)
    elif res.returncode != 0:
        raise subprocess.CalledProcessError('Command %s returned %d', res.args, res.returncode)

def _lifetime_diamond(lifetimes, with_subtyping=False):
    if not lifetimes:
        return ''
    else:
        lifetime_args = list()
        prev = None
        for l in reversed(sorted(lifetimes)):
            if prev is not None and with_subtyping:
                arg = '\'%s: \'%s' % (l, prev)
            else:
                arg = '\'%s' % l
            lifetime_args.append(arg)
            prev = l
        return '<%s>' % ','.join(lifetime_args)

class RustCodeGenerator(CodeGenerator):
    def __init__(self, out, *args, **kwargs):
        if isinstance(out, str):
            self._filename = out
            self._old_out = None
        else:
            from tempfile import NamedTemporaryFile
            self._old_out = out
            out = NamedTemporaryFile('w', delete=False)
            self._filename = out.name
        super(RustCodeGenerator, self).__init__(out, *args, **kwargs)
    def close(self):
        super(RustCodeGenerator, self).close()
        self.out.close()
        rustfmt(self._filename)
        if self._old_out is not None:
            self.out = self._old_out
            with open(self._filename, "r") as input:
                for line in iter(input.readlines()):
                    self.out.write(line)
            os.unlink(self._filename)
            self._old_out = None


class ImportGenerator(CodeGenerator):
    def __init__(self, *args, **kwargs):
        super(ImportGenerator, self).__init__(*args, **kwargs)
        self.imports = set()
        self._target_out = self.out
        self.out = io.StringIO()
    
    def close(self):
        if self._target_out is not None:
            contents = self.out.getvalue()
            self.out.close()
            self.out = self._target_out
            self._target_out = None
            if len(self.imports) > 0:
                for imp in sorted(self.imports):
                    self('use ', imp, ';').nl()
                self.nl()
            self.imports = None
            self.out.write(contents)
        super(ImportGenerator, self).close()

_RE_SUB_ = re.compile(r'\-[0-9]+$')
class RustGenerator:
    def __init__(self, registry):
        self.registry = registry
        self.target = 'src'
        self._import_generator = None

    def manage_imports(self, gen):
        if self._import_generator is not None and self._import_generator.imports is not None:
            raise ValueError('canonly open one import-generator at a time')
        self._import_generator = ImportGenerator(gen)
        return self._import_generator

    def add_import(self, imp):
        if self._import_generator is not None and self._import_generator.imports is not None:
            self._import_generator.imports.add(imp)
        return imp

    def rust_value(self, value, **kwargs):
        if value is None:
            return 'None', 'Option<c_void>'
        if value.startswith('(') and value.endswith(')'):
            value = value[1:-1]
        if value.startswith('"'):
            return '"%s\\0"' % value[1:-1], '&str'
        if value[0].isalpha():
            item = self.registry.enum_items[value]
            name = self.rust_enum_item_name(item)
            if item.enum_group.type is None:
                _, ty = self.rust_enum_item_value(item, **kwargs)
                if not kwargs.get('no_imports', False):
                    self.add_import('enums')
                return 'enums::' + name, ty
            else:
                return item.enum_group.name + '::' + name, item.enum_group.name
        ty = 'u32'
        sub = _RE_SUB_.search(value)
        if sub is not None:
            sub = sub.group(0)
            value = value[:-len(sub)]
        else:
            sub = ''
        if value.endswith('f'):
            ty = 'f32'
            value = value + '32'
        elif value.endswith('ULL'):
            ty = 'u64'
            value = value[:-3] + 'u64'
        elif value.endswith('LL'):
            ty = 'i64'
            value = value[:-2] + 'i64'
        elif value.endswith('U'):
            ty = 'u32'
            value = value[:-1] + 'u32'
        if value.startswith('~'):
            value = '!' + value[1:]
        return value+sub, ty

    def rust_dimension_value(self, value, **kwargs):
        value, ty = self.rust_value(value, **kwargs)
        if value is not None and value[0].isalpha() and ty != 'usize':
            # requires a cast
            return '%s as usize' % value
        return value

    def rust_enum_item_value(self, item, **kwargs):
        if item.bitpos is not None:
            return '1<<%s' % item.bitpos, 'u32'
        v, t = self.rust_value(item.value, **kwargs)
        if item.enum_group.type is not None:
            if v.startswith('-'):
                i = int(v[1:])
                v = '!%s' % (i-1)
            return v, 'u32'
        return v, t

    def rust_enum_item_name(self, item):
        if item.enum_group.type is None:
            return item.name
        name = item.shortname
        if name[0].isnumeric() or name in RESERVED_KEYWORDS:
            if item.enum_group.type == 'bitmask':
                if name.endswith('_BIT'):
                    name = name[:-4]
                name = 'BIT_' + name
            else:
                name = 'E_' + name
        return name

    def rust_member_name(self, member):
        if isinstance(member, str):
            name = member
        else:
            name = member.name
        if name in RESERVED_KEYWORDS:
            name = 'e' + name[0].upper() + name[1:]
        return name
    rust_param_name = rust_member_name

    def rust_member_function(self, member, keyword_prefix='_'):
        if isinstance(member, str):
            name = member
        else:
            name = member.name
        name = camel_to_snake_case(name)
        if name.startswith('pp_'):
            name = name[3:]
        elif name.startswith('p_'):
            name = name[2:]
        if name in RESERVED_KEYWORDS:
            name = keyword_prefix + name
        return name
    rust_param_name = rust_member_name

    def rust_composed_lifetimes(self, ty, **kwargs):
        lifetimes = set()
        for m in ty.members:
            if m.name == 'pNext' and m.type == TypeRef.VOID_PTR:
                lifetimes.add('l')
            else:
                (_, _, _, member_lifetimes) = self.rust_type_details(m, no_imports=True, **kwargs)
                lifetimes.update(member_lifetimes)
        return lifetimes

    def rust_type_details(self, ty, **kwargs):
        if isinstance(ty, str):
            ty = self.registry.types[ty]
        elif isinstance(ty, Member):
            m = ty
            ty = m.type
            if 'optional' not in kwargs:
                kwargs['optional'] = m.optional
            if 'len' not in kwargs:
                kwargs['len'] = m.len
        as_raw_conv = False
        lifetimes = set()
        if isinstance(ty, BaseTypeElem):
            type_name = ty.name
            raw_type_name = None
            if type_name in PREDEFINED_TYPES:
                type_name = PREDEFINED_TYPES[type_name]
                return (type_name, type_name, as_raw_conv, lifetimes)
            elif type_name in RESERVED_KEYWORDS:
                type_name = 'T' + type_name
            if ty.name == 'VkBool32':
                as_raw_conv = True
                raw_type_name = 'VkBool32'
                type_name = 'bool'
            elif ty.__class__ is BaseTypeElem.PROVIDED:
                self.add_import('platform::*')
                if len(ty.requires) == 1:
                    mod = INCLUDE_TO_MODULE.get(ty.requires[0], None)
                    if mod is not None:
                        type_name = mod + '::' + type_name
            elif ty.__class__ is BaseTypeElem.FUNCTIONPOINTER and kwargs.pop('optional', None):
                type_name = 'Option<%s>' % type_name
            elif ty.__class__ in STRUCT_TYPES:
                if not kwargs.get('as_param', None):
                    lifetimes.update(self.rust_composed_lifetimes(ty))
                    type_name += _lifetime_diamond(lifetimes)
            elif ty.__class__ is BaseTypeElem.HANDLE:
                raw_type_name = ty.non_dispatchable and 'u64' or 'usize'
                as_raw_conv = True
                if not kwargs.get('as_param', None):
                    lifetimes.add('h')
                    type_name += '<\'h>'
                if kwargs.pop('optional', None):
                    type_name = 'Option<%s>' % type_name
            if raw_type_name is None:
                raw_type_name = type_name
            return (type_name, raw_type_name, as_raw_conv, lifetimes)
        elif not isinstance(ty, TypeRef):
            raise ValueError('unable to hadle arg', ty)
        elif ty.is_named():
            return self.rust_type_details(ty.name, **kwargs)
        elif ty.is_function():
            params = ', '.join([self.rust_raw_type(p.type, as_param=True) for p in ty.arg.params])
            res = ''
            if ty.arg.returns != TypeRef.VOID:
                res = ' -> %s' % self.rust_raw_type(ty.arg.returns, as_param=True, optional=True)
            type_name = 'extern "system" fn (%s)%s' % (params, res)
            if kwargs.pop('optional', None):
                type_name = 'Option<%s>' % type_name
            return (type_name, type_name, as_raw_conv, lifetimes)
        elif ty.is_ptr():
            length = kwargs.pop('len', None)
            opt = kwargs.pop('optional', None)
            if ty.is_const():
                arg = ty.arg.arg
                mut = ''
            else:
                arg = ty.arg
                mut = 'mut '
            (type_name, raw_typename, as_raw_conv, arg_lifetimes) = self.rust_type_details(arg, **kwargs)
            lifetimes.update(arg_lifetimes)
            raw_typename = '*%s%s' % (mut or 'const ', raw_typename)
            if not mut and arg == TypeRef.CHAR and length == 'null-terminated':
                if not kwargs.get('as_param', None):
                    lt = '\'l '
                    lifetimes.add('l')
                else:
                    lt = ''
                type_name = '&%sAsRef<CStr>' % lt
                lifetimes.add('l')
                as_raw_conv = True
                if opt:
                    type_name = 'Option<%s>' % type_name
            elif length:
                if arg == TypeRef.VOID:
                    type_name = 'u8'
                if not kwargs.get('as_param', None):
                    lt = '\'l '
                    lifetimes.add('l')
                else:
                    lt = ''
                type_name = '&%s%s[%s]' % (lt, mut, type_name)
                lifetimes.add('l')
                as_raw_conv = True
            elif arg == TypeRef.VOID or (arg.is_named() and arg.resolved_type.__class__ is BaseTypeElem.PROVIDED):
                type_name = raw_typename
            else:
                if not kwargs.get('as_param', None):
                    lt = '\'l '
                    lifetimes.add('l')
                else:
                    lt = ''
                type_name = '&%s%s%s' % (lt, mut, type_name)
                if opt:
                    type_name = 'Option<%s>' % type_name
                    as_raw_conv = True
            return (type_name, raw_typename, as_raw_conv, lifetimes)
        elif ty.is_array():
            if ty.is_const():
                arg = ty.arg.arg
            else:
                arg = ty.arg
            (type_name, raw_typename, as_raw_conv, arg_lifetimes) = self.rust_type_details(arg, **kwargs)
            lifetimes.update(arg_lifetimes)
            dim = self.rust_dimension_value(ty.dim, no_imports=kwargs.get('no_imports', False))
            type_name = '[%s; %s]' % (type_name, dim)
            raw_typename = '[%s; %s]' % (raw_typename, dim)
            return (type_name, raw_typename, as_raw_conv, lifetimes)
        raise ValueError('unable to hadle arg', ty)

    def rust_raw_type(self, ty, **kwargs):
        (_typename, raw_typename, _as_raw_conv, _lifetimes) = self.rust_type_details(ty, **kwargs)
        return raw_typename

    def rust_safe_type(self, ty, **kwargs):
        (typename, _raw_typename, _as_raw_conv, _lifetimes) = self.rust_type_details(ty, **kwargs)
        return typename

    def rust_param_as_raw(self, ty, declname=None, **kwargs):
        if isinstance(ty, Member):
            if declname is None:
                declname = self.rust_param_name(ty)
            if ty.is_out and (ty.len_for or ty is getattr(ty.container, 'out_param', None)):
                if ty.len:
                    declname = '%s.as_mut_ptr()' % declname
                else:
                    declname = '&mut %s' % declname
        (typename, raw_typename, as_raw_conv, _lifetimes) = self.rust_type_details(ty, no_imports=True, **kwargs)
        if as_raw_conv:
            if '&' in declname:
                declname = '(%s)' %declname
            declname = '%s.as_raw()' % declname
            if 'u8' in typename and 'c_void' in raw_typename:
                declname = '%s as %s' % (declname,raw_typename)
        return declname

    def is_public(self, ty):
        if isinstance(ty, str):