# -*- Mode: python; tab-width: 4; indent-tabs-mode: nil -*-
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright (C) 2012 Lanedo GmbH
# Copyright (C) 2012-2022 Aleksander Morgado <aleksander@aleksander.es>
#

import string

import utils
from FieldResult import FieldResult
from Field import Field

"""
The Container class takes care of handling collections of Input or
Output fields
"""
class Container:

    """
    Constructor
    """
    def __init__(self, service, prefix, container_type, dictionary, common_objects_dictionary, static, since, compat):
        # The current QMI service
        self.service = service
        # The field container prefix usually contains the name of the Message,
        # e.g. "Qmi Message Ctl Something"
        self.prefix = prefix

        # We may have 'Input' or 'Output' containers
        if container_type == 'Input':
            self.readonly = False
        elif container_type == 'Output':
            self.readonly = True
        else:
            raise ValueError('Cannot handle container type \'%s\'' % container_type)

        self.name = container_type

        self.static = static
        self.since = since
        self.compat = compat

        # Create the composed full name (prefix + name),
        #  e.g. "Qmi Message Ctl Something Output"
        self.fullname = self.prefix + ' ' + self.name

        self.fields = None
        if dictionary is not None:
            self.fields = []

            new_dict = []
            # First, look for references to common types
            for field_dictionary in dictionary:
                if 'common-ref' in field_dictionary:
                    for common in common_objects_dictionary:
                        if common['type'] == 'TLV' and \
                           common['common-ref'] == field_dictionary['common-ref']:
                           # Replace the reference with a copy of the common dictionary
                           # If the source reference has prerequisites, add them to the copy
                           copy = dict(common)
                           if 'prerequisites' in field_dictionary:
                               copy['prerequisites'] = field_dictionary['prerequisites']
                           # If an explicit 'since' is given in the field, prefer it over any other one
                           if 'since' in field_dictionary:
                               copy['since'] = field_dictionary['since']
                           # If the common type does not have any explicit 'since', take the one from the message
                           # If the common type has a 'since', take it only if it is newer than the one from the message
                           elif not 'since' in copy or utils.version_compare(copy['since'],self.since) > 0:
                               copy['since'] = self.since
                           new_dict.append(copy)
                           break
                    else:
                        raise RuntimeError('Common type \'%s\' not found' % field_dictionary['name'])
                else:
                    new_dict.append(field_dictionary)
            dictionary = new_dict
            # We need to sort the fields, so that the ones with prerequisites are
            # include after the prerequisites themselves. Note: we don't currently
            # support complex setups yet.
            sorted_dictionary = []
            for field_dictionary in dictionary:
                if 'prerequisites' in field_dictionary:
                    sorted_dictionary.append(field_dictionary)
                else:
                    sorted_dictionary.insert(0, field_dictionary)

            # Then, really parse each field
            for field_dictionary in sorted_dictionary:
                if field_dictionary['type'] == 'TLV':
                    if field_dictionary['format'] == 'sequence' and \
                       field_dictionary['name'] == 'Result':
                        self.fields.append(FieldResult(self.service, self.fullname, field_dictionary, common_objects_dictionary, container_type, static))
                    else:
                        self.fields.append(Field(self.service, self.fullname, field_dictionary, common_objects_dictionary, container_type, static))


    """
    Emit enumeration of TLVs in the container
    """
    def __emit_tlv_ids_enum(self, f):
        if self.fields is None:
            return

        f.write('\n')
        for tlv in self.fields:
            translations = { 'enum_name'  : tlv.id_enum_name,
                             'enum_value' : tlv.id }
            template = (
                '#define ${enum_name} ${enum_value}\n')
            f.write(string.Template(template).substitute(translations))


    """
    Emit new container types
    """
    def __emit_types(self, hfile, cfile, translations):
        translations['type_macro'] = 'QMI_TYPE_' + utils.remove_prefix(utils.build_underscore_uppercase_name(self.fullname), 'QMI_')
        # Emit types header
        template = '\n'
        if not self.static and self.service != 'CTL':
            template += (
                '/**\n'
                ' * ${camelcase}:\n'
                ' *\n'
                ' * The #${camelcase} structure contains private data and should only be accessed\n'
                ' * using the provided API.\n'
                ' *\n'
                ' * Since: ${since}\n'
                ' */\n')
        template += (
            'typedef struct _${camelcase} ${camelcase};\n'
            '${static}GType ${underscore}_get_type (void) G_GNUC_CONST;\n'
            '#define ${type_macro} (${underscore}_get_type ())\n')
        if self.compat:
            template += (
                'G_GNUC_INTERNAL\n'
                'gpointer ${underscore}_get_compat_context (${camelcase} *self);\n'
                'G_GNUC_INTERNAL\n'
                'void ${underscore}_set_compat_context (\n'
                '    ${camelcase} *self,\n'
                '    gpointer compat_context,\n'
                '    GDestroyNotify compat_context_free);\n')
        hfile.write(string.Template(template).substitute(translations))

        # Emit types source
        template = (
            '\n'
            'struct _${camelcase} {\n'
            '    volatile gint ref_count;\n')
        if self.compat:
            template += (
                '\n'
                '    gpointer compat_context;\n'
                '    GDestroyNotify compat_context_free;\n')
        cfile.write(string.Template(template).substitute(translations))

        if self.fields is not None:
            for field in self.fields:
                if field.variable is not None:
                    translations['field_variable_name'] = field.variable_name
                    translations['field_name'] = field.name
                    template = (
                        '\n'
                        '    /* ${field_name} */\n'
                        '    gboolean ${field_variable_name}_set;\n')
                    cfile.write(string.Template(template).substitute(translations))
                    field.emit_variable_declaration(cfile)

        cfile.write(
            '};\n')


    """
    Emit container handling core implementation
    """
    def __emit_core(self, hfile, cfile, translations):
        # Emit container core header
        template = '\n'
        if not self.static and self.service != 'CTL':
            template += (
                '\n'
                '/**\n'
                ' * ${underscore}_ref:\n'
                ' * @self: a #${camelcase}.\n'
                ' *\n'
                ' * Atomically increments the reference count of @self by one.\n'
                ' *\n'
                ' * Returns: the new reference to @self.\n'
                ' *\n'
                ' * Since: ${since}\n'
                ' */\n')
        template += (
            '${static}${camelcase} *${underscore}_ref (${camelcase} *self);\n'
            '\n')
        if not self.static and self.service != 'CTL':
            template += (
                '/**\n'
                ' * ${underscore}_unref:\n'
                ' * @self: a #${camelcase}.\n'
                ' *\n'
                ' * Atomically decrements the reference count of @self by one.\n'
                ' * If the reference count drops to 0, @self is completely disposed.\n'
                ' *\n'
                ' * Since: ${since}\n'
                ' */\n')
        template += (
            '${static}void ${underscore}_unref (${camelcase} *self);\n'
            'G_DEFINE_AUTOPTR_CLEANUP_FUNC (${camelcase}, ${underscore}_unref)\n')
        if not self.readonly:
            if not self.static:
                template += (
                    '\n'
                    '/**\n'
                    ' * ${underscore}_new:\n'
                    ' *\n'
                    ' * Allocates a new #${camelcase}.\n'
                    ' *\n'
                    ' * Returns: the newly created #${camelcase}. The returned value should be freed with ${underscore}_unref().\n'
                    ' *\n'
                    ' * Since: ${since}\n'
                    ' */\n')
            template += (
                '${static}${camelcase} *${underscore}_new (void);\n')

        if self.static:
            cfile.write(string.Template(template).substitute(translations))
        else:
            hfile.write(string.Template(template).substitute(translations))

        # Emit container core source
        template = (
            '\n'
            '${static}GType\n'
            '${underscore}_get_type (void)\n'
            '{\n'
            '    static gsize g_define_type_id_initialized = 0;\n'
            '\n'
            '    if (g_once_init_enter (&g_define_type_id_initialized)) {\n'
            '        GType g_define_type_id =\n'
            '            g_boxed_type_register_static (g_intern_static_string ("${camelcase}"),\n'
            '                                          (GBoxedCopyFunc) ${underscore}_ref,\n'
            '                                          (GBoxedFreeFunc) ${underscore}_unref);\n'
            '\n'
            '        g_once_init_leave (&g_define_type_id_initialized, g_define_type_id);\n'
            '    }\n'
            '\n'
            '    return g_define_type_id_initialized;\n'
            '}\n'
            '\n'
            '${static}${camelcase} *\n'
            '${underscore}_ref (${camelcase} *self)\n'
            '{\n'
            '    g_return_val_if_fail (self != NULL, NULL);\n'
            '\n'
            '    g_atomic_int_inc (&self->ref_count);\n'
            '    return self;\n'
            '}\n'
            '\n'
            '${static}void\n'
            '${underscore}_unref (${camelcase} *self)\n'
            '{\n'
            '    g_return_if_fail (self != NULL);\n'
            '\n'
            '    if (g_atomic_int_dec_and_test (&self->ref_count)) {\n')

        if self.compat:
            template += (
                '        if (self->compat_context && self->compat_context_free)\n'
                '            self->compat_context_free (self->compat_context);\n')

        if self.fields is not None:
            for field in self.fields:
                if field.variable is not None and field.variable.needs_dispose:
                    template += field.variable.build_dispose('        ', 'self->' + field.variable_name)
                    if field.variable.needs_compat_gir and self.service != 'CTL':
                        template += field.variable.build_dispose_gir('        ', 'self->' + field.variable_name)

        template += (
            '        g_slice_free (${camelcase}, self);\n'
            '    }\n'
            '}\n')

        if self.compat:
            template += (
                'gpointer\n'
                '${underscore}_get_compat_context (${camelcase} *self)\n'
                '{\n'
                '    g_return_val_if_fail (self != NULL, NULL);\n'
                '\n'
                '    return self->compat_context;\n'
                '}\n'
                '\n'
                'void\n'
                '${underscore}_set_compat_context (\n'
                '    ${camelcase} *self,\n'
                '    gpointer compat_context,\n'
                '    GDestroyNotify compat_context_free)\n'
                '{\n'
                '    g_return_if_fail (self != NULL);\n'
                '\n'
                '    if (self->compat_context && self->compat_context_free)\n'
                '        self->compat_context_free (self->compat_context);\n'
                '\n'
                '    self->compat_context = compat_context;\n'
                '    self->compat_context_free = compat_context_free;\n'
                '}\n')
        cfile.write(string.Template(template).substitute(translations))

        # _new() is only generated if the container is not readonly
        if self.readonly:
            return

        template = (
            '\n'
            '${static}${camelcase} *\n'
            '${underscore}_new (void)\n'
            '{\n'
            '    ${camelcase} *self;\n'
            '\n'
            '    self = g_slice_new0 (${camelcase});\n'
            '    self->ref_count = 1;\n'
            '    return self;\n'
            '}\n')
        cfile.write(string.Template(template).substitute(translations))


    """
    Emit container implementation
    """
    def emit(self, hfile, cfile):
        translations = { 'name'       : self.name,
                         'camelcase'  : utils.build_camelcase_name (self.fullname),
                         'underscore' : utils.build_underscore_name (self.fullname),
                         'since'      : self.since,
                         'static'     : 'static ' if self.static else '' }

        auxfile = cfile if self.static else hfile

        if self.fields is None:
            template = ('\n'
                        '/* Note: no fields in the ${name} container */\n')
            auxfile.write(string.Template(template).substitute(translations))
            cfile.write(string.Template(template).substitute(translations))
            return

        # Emit the container and field  types
        # Emit field getter/setter
        if self.fields is not None:
            for field in self.fields:
                field.emit_types(auxfile, cfile)
        self.__emit_types(auxfile, cfile, translations)

        # Emit TLV enums
        self.__emit_tlv_ids_enum(cfile)

        # Emit fields
        if self.fields is not None:
            for field in self.fields:
                field.emit_getter(auxfile, cfile)
                if not self.readonly:
                    field.emit_setter(auxfile, cfile)

        # Emit the container core
        self.__emit_core(auxfile, cfile, translations)


    """
    Add sections
    """
    def add_sections(self, sections):
        if self.fields is None:
            return

        translations = { 'name'       : self.name,
                         'camelcase'  : utils.build_camelcase_name (self.fullname),
                         'underscore' : utils.build_underscore_name (self.fullname),
                         'type_macro' : 'QMI_TYPE_' + utils.remove_prefix(utils.build_underscore_uppercase_name(self.fullname), 'QMI_') }

        # Standard
        template = (
            '${underscore}_get_type\n'
            '${type_macro}\n')
        sections['standard'] += string.Template(template).substitute(translations)

        # Private
        if self.compat:
            template = (
                '${underscore}_get_compat_context\n'
                '${underscore}_set_compat_context\n')
            sections['private'] += string.Template(template).substitute(translations)

        # Public types
        template = (
            '${camelcase}\n')
        sections['public-types'] += string.Template(template).substitute(translations)

        # Public methods
        template = '<SUBSECTION ${camelcase}Methods>\n'
        if not self.readonly:
            template += (
                '${underscore}_new\n')
        template += (
            '${underscore}_ref\n'
            '${underscore}_unref\n')
        sections['public-methods'] += string.Template(template).substitute(translations)

        for field in self.fields:
            field.add_sections(sections)
