# -*- Mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
#
# 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-2017 Aleksander Morgado <aleksander@aleksander.es>
#

import string
import utils
from Variable import Variable
import VariableFactory

"""
Variable type for Arrays ('array' format)
"""
class VariableArray(Variable):

    """
    Constructor
    """
    def __init__(self, dictionary, array_element_type, container_type):

        # Call the parent constructor
        Variable.__init__(self, dictionary)

        self.private_format  = 'GArray *'
        self.public_format = self.private_format
        self.element_type = 'GArray'
        self.fixed_size = 0
        self.name = dictionary['name']

        # The array and its contents need to get disposed
        self.needs_dispose = True

        # We need to know whether the variable comes in an Input container or in
        # an Output container, as we should not dump the element clear() helper method
        # if the variable is from an Input container.
        self.container_type = container_type

        # Load variable type of this array
        if 'name' in dictionary['array-element']:
            self.array_element = VariableFactory.create_variable(dictionary['array-element'], array_element_type + ' ' + dictionary['array-element']['name'], self.container_type)
        else:
            self.array_element = VariableFactory.create_variable(dictionary['array-element'], '', self.container_type)

        # Load variable type for the array size prefix
        if 'size-prefix-format' in dictionary:
            # We do NOT allow 64-bit types as array sizes (GArray won't support them)
            if dictionary['size-prefix-format'] not in [ 'guint8', 'guint16', 'guint32' ]:
                raise ValueError('Invalid size prefix format (%s): not guint8 or guint16 or guint32' % dictionary['size-prefix-format'])
            default_array_size = { 'format' : dictionary['size-prefix-format'] }
            self.array_size_element = VariableFactory.create_variable(default_array_size, '', self.container_type)
        elif 'fixed-size' in dictionary:
            # fixed-size arrays have no size element, obviously
            self.fixed_size = dictionary['fixed-size']
            if int(self.fixed_size) == 0 or int(self.fixed_size) > 512:
                raise ValueError('Fixed array size %s out of bounds (not between 0 and 512)' % self.fixed_size)
        else:
            # Default to 'guint8' if no explicit array size given
            default_array_size = { 'format' : 'guint8' }
            self.array_size_element = VariableFactory.create_variable(default_array_size, '', self.container_type)

        # Load variable type for the sequence prefix
        if 'sequence-prefix-format' in dictionary:
            sequence = { 'format' : dictionary['sequence-prefix-format'] }
            self.array_sequence_element = VariableFactory.create_variable(sequence, '', self.container_type)
        else:
            self.array_sequence_element = ''


    """
    Emit the type for the array element
    """
    def emit_types(self, f, since, static):
        self.array_element.emit_types(f, since, static)


    """
    Constructs the name of the array clear function
    """
    def clear_func_name(self):
        # element public format might be a base type like 'gchar *' rather
        # than a structure name like QmiFooBar
        elt_name = self.array_element.public_format.replace('*', 'pointer')
        return utils.build_underscore_name(self.name) + \
             '_' + \
             utils.build_underscore_name_from_camelcase(utils.build_camelcase_name(elt_name))


    """
    Emits the code to clear the element of the array
    """
    def emit_helper_methods(self, hfile, cfile):
        self.array_element.emit_helper_methods(hfile, cfile)

        # No need for the clear func if no need to dispose the contents
        if self.array_element.needs_dispose == False:
            return

        # No need for the clear func if we were not the ones who created
        # the array
        if self.container_type == "Input":
            return

        translations = { 'element_format'   : self.array_element.public_format,
                         'underscore'       : self.clear_func_name(),
                         'dispose_contents' : self.array_element.build_dispose('    ', '(*p)') }

        template = (
            '\n'
            'static void\n'
            '${underscore}_clear (${element_format} *p)\n'
            '{\n'
            '$dispose_contents'
            '}\n')
        cfile.write(string.Template(template).substitute(translations))


    """
    Reading an array from the raw byte buffer is just about providing a loop to
    read every array element one by one.
    """
    def emit_buffer_read(self, f, line_prefix, tlv_out, error, variable_name):
        common_var_prefix = utils.build_underscore_name(self.name)
        translations = { 'lp'                          : line_prefix,
                         'variable_name'               : variable_name,
                         'private_format'              : self.private_format,
                         'public_array_element_format' : self.array_element.public_format,
                         'underscore'                  : self.clear_func_name(),
                         'common_var_prefix'           : common_var_prefix }

        template = (
            '${lp}{\n'
            '${lp}    guint ${common_var_prefix}_i;\n')
        f.write(string.Template(template).substitute(translations))

        if self.fixed_size:
            translations['fixed_size'] = self.fixed_size

            template = (
                '${lp}    guint16 ${common_var_prefix}_n_items = ${fixed_size};\n'
                '\n')
            f.write(string.Template(template).substitute(translations))
        else:
            translations['array_size_element_format'] = self.array_size_element.public_format
            template = (
                '${lp}    ${array_size_element_format} ${common_var_prefix}_n_items;\n')

            if self.array_sequence_element != '':
                translations['array_sequence_element_format'] = self.array_sequence_element.public_format
                template += (
                    '${lp}    ${array_sequence_element_format} ${common_var_prefix}_sequence;\n')

            template += (
                '\n'
                '${lp}    /* Read number of items in the array */\n')
            f.write(string.Template(template).substitute(translations))
            self.array_size_element.emit_buffer_read(f, line_prefix + '    ', tlv_out, error, common_var_prefix + '_n_items')

            if self.array_sequence_element != '':
                template = (
                    '\n'
                    '${lp}    /* Read sequence in the array */\n')
                f.write(string.Template(template).substitute(translations))
                self.array_size_element.emit_buffer_read(f, line_prefix + '    ', tlv_out, error, common_var_prefix + '_sequence')

                template = (
                    '\n'
                    '${lp}    ${variable_name}_sequence = ${common_var_prefix}_sequence;\n')
                f.write(string.Template(template).substitute(translations))

        template = (
            '\n'
            '${lp}    ${variable_name} = g_array_sized_new (\n'
            '${lp}        FALSE,\n'
            '${lp}        FALSE,\n'
            '${lp}        sizeof (${public_array_element_format}),\n'
            '${lp}        (guint)${common_var_prefix}_n_items);\n'
            '\n')

        if self.array_element.needs_dispose == True:
            template += (
                '${lp}    g_array_set_clear_func (${variable_name},\n'
                '${lp}                            (GDestroyNotify)${underscore}_clear);\n'
                '\n')

        template += (
            '${lp}    for (${common_var_prefix}_i = 0; ${common_var_prefix}_i < ${common_var_prefix}_n_items; ${common_var_prefix}_i++) {\n'
            '${lp}        ${public_array_element_format} ${common_var_prefix}_aux;\n'
            '\n')
        f.write(string.Template(template).substitute(translations))

        self.array_element.emit_buffer_read(f, line_prefix + '        ', tlv_out, error, common_var_prefix + '_aux')

        template = (
            '${lp}        g_array_insert_val (${variable_name}, ${common_var_prefix}_i, ${common_var_prefix}_aux);\n'
            '${lp}    }\n'
            '${lp}}\n')
        f.write(string.Template(template).substitute(translations))


    """
    Writing an array to the raw byte buffer is just about providing a loop to
    write every array element one by one.
    """
    def emit_buffer_write(self, f, line_prefix, tlv_name, variable_name):
        common_var_prefix = utils.build_underscore_name(self.name)
        translations = { 'lp'                : line_prefix,
                         'variable_name'     : variable_name,
                         'common_var_prefix' : common_var_prefix }

        template = (
            '${lp}{\n'
            '${lp}    guint ${common_var_prefix}_i;\n')
        f.write(string.Template(template).substitute(translations))

        if self.fixed_size == 0:
            translations['array_size_element_format'] = self.array_size_element.private_format

            template = (
                '${lp}    ${array_size_element_format} ${common_var_prefix}_n_items;\n'
                '\n'
                '${lp}    /* Write the number of items in the array first */\n'
                '${lp}    ${common_var_prefix}_n_items = (${array_size_element_format}) ${variable_name}->len;\n')
            f.write(string.Template(template).substitute(translations))

            self.array_size_element.emit_buffer_write(f, line_prefix + '    ', tlv_name, common_var_prefix + '_n_items')

        if self.array_sequence_element != '':
            self.array_sequence_element.emit_buffer_write(f, line_prefix + '    ', tlv_name, variable_name + '_sequence')


        template = (
            '\n'
            '${lp}    for (${common_var_prefix}_i = 0; ${common_var_prefix}_i < ${variable_name}->len; ${common_var_prefix}_i++) {\n')
        f.write(string.Template(template).substitute(translations))

        self.array_element.emit_buffer_write(f, line_prefix + '        ', tlv_name, 'g_array_index (' + variable_name + ', ' + self.array_element.public_format + ',' + common_var_prefix + '_i)')

        template = (
            '${lp}    }\n'
            '${lp}}\n')
        f.write(string.Template(template).substitute(translations))


    """
    The array will be printed as a list of fields enclosed between curly
    brackets
    """
    def emit_get_printable(self, f, line_prefix):
        common_var_prefix = utils.build_underscore_name(self.name)
        translations = { 'lp'                : line_prefix,
                         'common_var_prefix' : common_var_prefix }

        template = (
            '${lp}{\n'
            '${lp}    guint ${common_var_prefix}_i;\n')
        f.write(string.Template(template).substitute(translations))

        if self.fixed_size:
            translations['fixed_size'] = self.fixed_size

            template = (
                '${lp}    guint16 ${common_var_prefix}_n_items = ${fixed_size};\n'
                '\n')
            f.write(string.Template(template).substitute(translations))
        else:
            translations['array_size_element_format'] = self.array_size_element.public_format
            template = (
                '${lp}    ${array_size_element_format} ${common_var_prefix}_n_items;\n')

            if self.array_sequence_element != '':
                translations['array_sequence_element_format'] = self.array_sequence_element.public_format
                template += (
                    '${lp}    ${array_sequence_element_format} ${common_var_prefix}_sequence;\n')

            template += (
                '\n'
                '${lp}    /* Read number of items in the array */\n')
            f.write(string.Template(template).substitute(translations))
            self.array_size_element.emit_buffer_read(f, line_prefix + '    ', 'out', '&error', common_var_prefix + '_n_items')

            if self.array_sequence_element != '':
                template = (
                    '\n'
                    '${lp}    /* Read sequence */\n')
                f.write(string.Template(template).substitute(translations))
                self.array_sequence_element.emit_buffer_read(f, line_prefix + '    ', 'out', '&error', common_var_prefix + '_sequence')
                template = (
                    '\n'
                    '${lp}    g_string_append_printf (printable, "[[Seq:%u]] ", ${common_var_prefix}_sequence);\n')
                f.write(string.Template(template).substitute(translations))

        template = (
            '\n'
            '${lp}    g_string_append (printable, "{");\n'
            '\n'
            '${lp}    for (${common_var_prefix}_i = 0; ${common_var_prefix}_i < ${common_var_prefix}_n_items; ${common_var_prefix}_i++) {\n'
            '${lp}        g_string_append_printf (printable, " [%u] = \'", ${common_var_prefix}_i);\n')
        f.write(string.Template(template).substitute(translations))

        self.array_element.emit_get_printable(f, line_prefix + '        ');

        template = (
            '${lp}        g_string_append (printable, " \'");\n'
            '${lp}    }\n'
            '\n'
            '${lp}    g_string_append (printable, "}");\n'
            '${lp}}')
        f.write(string.Template(template).substitute(translations))


    """
    Variable declaration
    """
    def build_variable_declaration(self, public, line_prefix, variable_name):
        translations = { 'lp'   : line_prefix,
                         'name' : variable_name }

        template = ''
        if self.array_sequence_element != '':
            translations['array_sequence_element_format'] = self.array_sequence_element.public_format
            template += (
                '${lp}${array_sequence_element_format} ${name}_sequence;\n')

        template += (
            '${lp}GArray *${name};\n')
        return string.Template(template).substitute(translations)


    """
    Getter for the array type
    """
    def build_getter_declaration(self, line_prefix, variable_name):
        if not self.visible:
            return ""

        translations = { 'lp'   : line_prefix,
                         'name' : variable_name }

        template = ''
        if self.array_sequence_element != '':
            translations['array_sequence_element_format'] = self.array_sequence_element.public_format
            template += (
                '${lp}${array_sequence_element_format} *${name}_sequence,\n')

        template += (
            '${lp}GArray **${name},\n')
        return string.Template(template).substitute(translations)


    """
    Documentation for the getter
    """
    def build_getter_documentation(self, line_prefix, variable_name):
        if not self.visible:
            return ""

        translations = { 'lp'                          : line_prefix,
                         'public_array_element_format' : self.array_element.public_format,
                         'public_array_element_type'   : self.array_element.element_type,
                         'name'                        : variable_name }

        template = ''
        if self.array_sequence_element != '':
            template += (
                '${lp}@${name}_sequence: (out)(optional): a placeholder for the output sequence number, or %NULL if not required.\n')

        template += (
            '${lp}@${name}: (out)(optional)(element-type ${public_array_element_type})(transfer none): a placeholder for the output #GArray of #${public_array_element_format} elements, or %NULL if not required. Do not free it, it is owned by @self.\n')
        return string.Template(template).substitute(translations)


    """
    Builds the array getter implementation
    """
    def build_getter_implementation(self, line_prefix, variable_name_from, variable_name_to, to_is_reference):
        if not self.visible:
            return ""

        translations = { 'lp'   : line_prefix,
                         'from' : variable_name_from,
                         'to'   : variable_name_to }

        template = ''
        if self.array_sequence_element != '':
            template += (
                '${lp}if (${to}_sequence)\n'
                '${lp}    *${to}_sequence = ${from}_sequence;\n')

        if to_is_reference:
            template += (
                '${lp}if (${to})\n'
                '${lp}    *${to} = ${from};\n')
        else:
            template += (
                '${lp}${to} = ${from};\n')

        return string.Template(template).substitute(translations)


    """
    Setter for the array type
    """
    def build_setter_declaration(self, line_prefix, variable_name):
        if not self.visible:
            return ""

        translations = { 'lp'   : line_prefix,
                         'name' : variable_name }

        template = ''
        if self.array_sequence_element != '':
            translations['array_sequence_element_format'] = self.array_sequence_element.public_format
            template += (
                '${lp}${array_sequence_element_format} ${name}_sequence,\n')

        template += (
            '${lp}GArray *${name},\n')
        return string.Template(template).substitute(translations)


    """
    Documentation for the setter
    """
    def build_setter_documentation(self, line_prefix, variable_name):
        if not self.visible:
            return ""

        translations = { 'lp'                          : line_prefix,
                         'public_array_element_format' : self.array_element.public_format,
                         'public_array_element_type'   : self.array_element.element_type,
                         'name'                        : variable_name }

        template = ''
        if self.array_sequence_element != '':
            template += (
                '${lp}@${name}_sequence: the sequence number.\n')

        template += (
            '${lp}@${name}: (in)(element-type ${public_array_element_type}): a #GArray of #${public_array_element_format} elements. A new reference to @${name} will be taken.\n')
        return string.Template(template).substitute(translations)


    """
    Builds the array setter implementation
    """
    def build_setter_implementation(self, line_prefix, variable_name_from, variable_name_to):
        if not self.visible:
            return ""

        translations = { 'lp'   : line_prefix,
                         'from' : variable_name_from,
                         'to'   : variable_name_to }

        template = ''
        if self.array_sequence_element != '':
            template += (
                '${lp}${to}_sequence = ${from}_sequence;\n')

        template += (
            '${lp}if (${to})\n'
            '${lp}    g_array_unref (${to});\n'
            '${lp}${to} = g_array_ref (${from});\n')
        return string.Template(template).substitute(translations)


    """
    Documentation for the struct field
    """
    def build_struct_field_documentation(self, line_prefix, variable_name):
        translations = { 'lp'                          : line_prefix,
                         'public_array_element_format' : self.array_element.public_format,
                         'name'                        : variable_name }

        template = ''
        if self.array_sequence_element != '':
            template += (
                '${lp}@${name}_sequence: the sequence number.\n')

        template += (
            '${lp}@${name}: a #GArray of #${public_array_element_format} elements.\n')
        return string.Template(template).substitute(translations)


    """
    Dispose the array just with an unref
    """
    def build_dispose(self, line_prefix, variable_name):
        translations = { 'lp'            : line_prefix,
                         'variable_name' : variable_name }

        template = (
            '${lp}if (${variable_name})\n'
            '${lp}    g_array_unref (${variable_name});\n')
        return string.Template(template).substitute(translations)


    """
    Add sections
    """
    def add_sections(self, sections):
        self.array_element.add_sections(sections)
