"""
Provides helper functionality specificially for Qt C++/QML code generators
"""
import qface.idl.domain as domain
try:
    from jinja2 import pass_environment
except ImportError:
    from jinja2 import environmentfilter as pass_environment


def upper_first(s):
    s = str(s)
    return s[0].upper() + s[1:]


class Filters(object):
    """provides a set of filters to be used with the template engine"""
    classPrefix = ''

    @staticmethod
    def className(symbol):
        classPrefix = Filters.classPrefix
        return '{0}{1}'.format(classPrefix, symbol.name)

    @staticmethod
    def defaultValue(symbol):
        prefix = Filters.classPrefix
        t = symbol.type
        if t.is_primitive:
            if t.is_int:
                return 'int(0)'
            if t.is_bool:
                return 'bool(false)'
            if t.is_string:
                return 'QString()'
            if t.is_real:
                return 'qreal(0.0)'
            if t.is_var:
                return 'QVariant()'
        elif t.is_void:
            return ''
        elif t.is_enumeration:
            value = next(iter(t.reference.members))
            return '{0}::{0}Enum::{1}'.format(symbol.type, value)
        elif symbol.kind == 'enum':
            value = next(iter(symbol.members))
            return '{0}::{1}'.format(symbol, value)
        elif t.is_flag:
            return '0'
        elif t.is_list:
            nested = Filters.returnType(t.nested)
            return 'QVariantList()'.format(nested)
        elif t.is_struct:
            return '{0}{1}()'.format(prefix, t)
        elif t.is_model:
            return 'nullptr'
        elif t.is_interface:
            return 'nullptr'
        raise Exception("Unknown symbol type" + repr(symbol))

    @staticmethod
    def parameterType(symbol):
        prefix = Filters.classPrefix
        if symbol.type.is_enumeration:
            return '{0}::{0}Enum {1}'.format(symbol.type, symbol)
        if symbol.type.is_void or symbol.type.is_primitive:
            if symbol.type.is_string:
                return 'const QString &{0}'.format(symbol)
            if symbol.type.is_var:
                return 'const QVariant &{0}'.format(symbol)
            if symbol.type.is_real:
                return 'qreal {0}'.format(symbol)
            if symbol.type.is_bool:
                return 'bool {0}'.format(symbol)
            if symbol.type.is_int:
                return 'int {0}'.format(symbol)
            return '{0} {1}'.format(symbol.type, symbol)
        elif symbol.type.is_list:
            nested = Filters.returnType(symbol.type.nested)
            return 'const QVariantList &{1}'.format(nested, symbol)
        elif symbol.type.is_model:
            return 'QAbstractItemModel *{0}'.format(symbol)
        elif symbol.type.is_complex:
            if symbol.type.is_interface:
                return '{0}Base *{1}'.format(symbol.type, symbol)
            else:
                return 'const {0}{1} &{2}'.format(prefix, symbol.type, symbol)
        raise Exception("Unknown symbol type")

    @staticmethod
    def returnType(symbol):
        prefix = Filters.classPrefix
        t = symbol.type
        if t.is_enumeration:
            return '{0}::{0}Enum'.format(symbol.type)
        if symbol.type.is_void or symbol.type.is_primitive:
            if t.is_string:
                return 'QString'
            if t.is_var:
                return 'QVariant'
            if t.is_real:
                return 'qreal'
            if t.is_int:
                return 'int'
            if t.is_bool:
                return 'bool'
            if t.is_void:
                return 'void'
            print(t)
            assert False
        elif symbol.type.is_list:
            nested = Filters.returnType(symbol.type.nested)
            return 'QVariantList'.format(nested)
        elif symbol.type.is_model:
            return 'QAbstractItemModel *'
        elif symbol.type.is_complex:
            if symbol.type.is_interface:
                return '{0}Base *'.format(symbol.type)
            else:
                return '{0}{1}'.format(prefix, symbol.type)
        raise Exception("Unknown symbol type")

    @staticmethod
    def header_dependencies(symbol):
        types = symbol.dependencies
        lines = []
        for t in types:
            if t.is_primitive:
                continue
            if t.is_interface:
                lines.append('class {0};'.format(t))
            if t.is_struct:
                lines.append('#include "{0}.h"'.format(t))
        return "\n".join(lines)

    @staticmethod
    def source_dependencies(symbol):
        types = symbol.dependencies
        lines = []
        module_name = symbol.module.module_name
        if not symbol.kind == 'module':
            lines.append('#include "{0}module.h"'.format(module_name.lower()))
        for t in types:
            if t.is_primitive:
                continue
            if t.is_interface:
                lines.append('#include "{0}.h"'.format(t.name.lower()))
        return "\n".join(lines)

    @staticmethod
    def open_ns(symbol):
        ''' generates a open namespace from symbol namespace x { y { z {'''
        blocks = ['namespace {0} {{'.format(x) for x in symbol.module.name_parts]
        return ' '.join(blocks)

    @staticmethod
    def close_ns(symbol):
        '''generates a closing names statement from a symbol'''
        closing = ' '.join(['}' for x in symbol.module.name_parts])
        name = '::'.join(symbol.module.name_parts)
        return '{0} // namespace {1}'.format(closing, name)

    @staticmethod
    def using_ns(symbol):
        '''generates a using namespace x::y::z statement from a symbol'''
        id = '::'.join(symbol.module.name_parts)
        return 'using namespace {0};'.format(id)

    @staticmethod
    def ns(symbol):
        '''generates a namespace x::y::z statement from a symbol'''
        if symbol.type and symbol.type.is_primitive:
            return ''
        return '{0}::'.format('::'.join(symbol.module.name_parts))

    @staticmethod
    def fqn(symbol):
        '''generates a fully qualified name from symbol'''
        return '{0}::{1}'.format(Filters.ns(symbol), symbol.name)

    @staticmethod
    def signalName(s):
        if isinstance(s, domain.Property):
            return '{0}Changed'.format(s)
        return s

    @staticmethod
    @pass_environment
    def parameters(env, s, filter=None, spaces=True):
        if not filter:
            filter = Filters.parameterType
        elif isinstance(filter, str):
            filter = env.filters[filter]
        args = []
        indent = ', '
        if not spaces:
            indent = ','
        if isinstance(s, domain.Operation):
            args = s.parameters
        elif isinstance(s, domain.Signal):
            args = s.parameters
        elif isinstance(s, domain.Struct):
            args = s.fields
        elif isinstance(s, domain.Property):
            args = [s]
        return indent.join([filter(a) for a in args])

    @staticmethod
    @pass_environment
    def signature(env, s, expand=False, filter=None):
        if not filter:
            filter = Filters.returnType
        elif isinstance(filter, str):
            filter = env.filters[filter]
        if isinstance(s, domain.Operation):
            args = s.parameters
        elif isinstance(s, domain.Signal):
            args = s.parameters
        elif isinstance(s, domain.Property):
            args = [s]  # for <property>Changed(<type>)
        elif isinstance(s, domain.Struct):
            args = s.fields
        else:
            args = []
        if expand:
            return ', '.join(['{0} {1}'.format(filter(a), a.name) for a in args])
        return ','.join([filter(a) for a in args])

    @staticmethod
    def identifier(s):
        return str(s).lower().replace('.', '_')

    @staticmethod
    def path(s):
        return str(s).replace('.', '/')

    @staticmethod
    def get_filters():
        return {
            'qt.defaultValue': Filters.defaultValue,
            'qt.returnType': Filters.returnType,
            'qt.parameterType': Filters.parameterType,
            'qt.open_ns': Filters.open_ns,
            'qt.close_ns': Filters.close_ns,
            'qt.using_ns': Filters.using_ns,
            'qt.ns': Filters.ns,
            'qt.fqn': Filters.fqn,
            'qt.signalName': Filters.signalName,
            'qt.parameters': Filters.parameters,
            'qt.signature': Filters.signature,
            'qt.identifier': Filters.identifier,
            'qt.path': Filters.path,
            'qt.className': Filters.className,
            'qt.source_dependencies': Filters.source_dependencies,
            'qt.header_dependencies': Filters.header_dependencies,
        }
