# Copyright 2016 Free Software Foundation, Inc.
# This file is part of GNU Radio
#
# GNU Radio Companion is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# GNU Radio Companion 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 General Public License for more details.
#
# You should have received a copy of the GNU 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

from __future__ import absolute_import

import collections
import itertools
import re

from ..Constants import ADVANCED_PARAM_TAB
from ..utils import to_list

from .block import Block
from ._flags import Flags
from ._templates import MakoTemplates


def build(id, label='', category='', flags='', documentation='',
          value=None, asserts=None,
          parameters=None, inputs=None, outputs=None, templates=None, **kwargs):
    block_id = id

    cls = type(str(block_id), (Block,), {})
    cls.key = block_id

    cls.label = label or block_id.title()
    cls.category = [cat.strip() for cat in category.split('/') if cat.strip()]

    cls.flags = Flags(to_list(flags))
    if re.match(r'options$|variable|virtual', block_id):
        cls.flags.set(Flags.NOT_DSP, Flags.DISABLE_BYPASS)

    cls.documentation = {'': documentation.strip('\n\t ').replace('\\\n', '')}

    cls.asserts = [_single_mako_expr(a, block_id) for a in to_list(asserts)]

    cls.inputs_data = _build_ports(inputs, 'sink') if inputs else []
    cls.outputs_data = _build_ports(outputs, 'source') if outputs else []
    cls.parameters_data = _build_params(parameters or [],
                                        bool(cls.inputs_data), bool(cls.outputs_data), cls.flags)
    cls.extra_data = kwargs

    templates = templates or {}
    cls.templates = MakoTemplates(
        imports=templates.get('imports', ''),
        make=templates.get('make', ''),
        callbacks=templates.get('callbacks', []),
        var_make=templates.get('var_make', ''),
    )
    # todo: MakoTemplates.compile() to check for errors

    cls.value = _single_mako_expr(value, block_id)

    return cls


def _build_ports(ports_raw, direction):
    ports = []
    port_ids = set()
    stream_port_ids = itertools.count()

    for i, port_params in enumerate(ports_raw):
        port = port_params.copy()
        port['direction'] = direction

        port_id = port.setdefault('id', str(next(stream_port_ids)))
        if port_id in port_ids:
            raise Exception('Port id "{}" already exists in {}s'.format(port_id, direction))
        port_ids.add(port_id)

        ports.append(port)
    return ports


def _build_params(params_raw, have_inputs, have_outputs, flags):
    params = []

    def add_param(**data):
        params.append(data)

    add_param(id='id', name='ID', dtype='id', hide='part')

    if not flags.not_dsp:
        add_param(id='alias', name='Block Alias', dtype='string',
                  hide='part', category=ADVANCED_PARAM_TAB)

        if have_outputs or have_inputs:
            add_param(id='affinity', name='Core Affinity', dtype='int_vector',
                      hide='part', category=ADVANCED_PARAM_TAB)

        if have_outputs:
            add_param(id='minoutbuf', name='Min Output Buffer', dtype='int',
                      hide='part', value='0', category=ADVANCED_PARAM_TAB)
            add_param(id='maxoutbuf', name='Max Output Buffer', dtype='int',
                      hide='part', value='0', category=ADVANCED_PARAM_TAB)

    base_params_n = {}
    for param_data in params_raw:
        param_id = param_data['id']
        if param_id in params:
            raise Exception('Param id "{}" is not unique'.format(param_id))

        base_key = param_data.get('base_key', None)
        param_data_ext = base_params_n.get(base_key, {}).copy()
        param_data_ext.update(param_data)

        add_param(**param_data_ext)
        base_params_n[param_id] = param_data_ext

    add_param(id='comment', name='Comment', dtype='_multiline', hide='part',
              value='', category=ADVANCED_PARAM_TAB)
    return params


def _single_mako_expr(value, block_id):
    if not value:
        return None
    value = value.strip()
    if not (value.startswith('${') and value.endswith('}')):
        raise ValueError('{} is not a mako substitution in {}'.format(value, block_id))
    return value[2:-1].strip()