# -*- coding: utf-8 -*-
# Copyright (c) 2010-2013 by Yaco Sistemas <goinnn@gmail.com>
#               2015 by Pablo Martín <goinnn@gmail.com>
# 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 3 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 programe.  If not, see <http://www.gnu.org/licenses/>.

import json
import numbers
import sys

import django

from copy import deepcopy

from django.conf import settings
from django.core.urlresolvers import reverse
from django.contrib.admin.widgets import AdminDateWidget, AdminSplitDateTime, AdminTimeWidget
from django.forms.models import modelform_factory
from django.template.loader import render_to_string
from django.utils.formats import number_format

from inplaceeditform import settings as inplace_settings
from inplaceeditform.commons import apply_filters, import_module, has_transmeta, get_static_url, get_admin_static_url, get_module_name
from inplaceeditform.perms import SuperUserPermEditInline

if sys.version_info[0] == 2:
    string = basestring
else:
    string = str
    unicode = str

class BaseAdaptorField(object):

    DEFAULT_MIN_HEIGHT = 30

    def __init__(self, request, obj, field_name,
                 filters_to_show=None,
                 config=None):
        self.request = request
        self.obj = obj
        self.field_name = field_name
        self.filters_to_show = filters_to_show and filters_to_show.split('|')

        self.model = obj.__class__
        self.field_name_render = field_name  # To transmeta

        self.config = config or {}
        self.config['obj_id'] = unicode(self.obj.pk)
        self.config['field_name'] = self.field_name_render
        self.config['app_label'] = self.model._meta.app_label
        self.config['module_name'] = get_module_name(self.model)
        self.config['filters_to_show'] = filters_to_show
        self.config['can_auto_save'] = self.config.get('can_auto_save', 1)

        filters_to_edit = self.config.get('filters_to_edit', None)
        self.filters_to_edit = filters_to_edit and filters_to_edit.split('|') or []

        self.class_inplace = self.config.get('class_inplace', '')
        self.tag_name_cover = self.config.get('tag_name_cover', 'span')
        self.min_width = self.config.get('min_width', self.DEFAULT_MIN_HEIGHT)
        font_size = self.config.get('font_size', '12')
        if font_size.endswith('px'):
            self.font_size = float(font_size.replace('px', ''))
        else:
            self.font_size = 12
        loads = self.config.get('loads', None)
        self.loads = loads and loads.split(':') or []
        self.initial = {}
        self._transmeta_processing()

    @property
    def name(self):
        return ''

    @property
    def classes(self):
        return 'inplaceedit %sinplaceedit' % (self.name)

    @classmethod
    def get_config(self, request, **kwargs):
        """
        Get the arguments given to the template tag element and complete these
        with the ones from the settings.py if necessary.
        """
        config = kwargs

        config_from_settings = deepcopy(inplace_settings.DEFAULT_INPLACE_EDIT_OPTIONS)
        config_one_by_one = inplace_settings.DEFAULT_INPLACE_EDIT_OPTIONS_ONE_BY_ONE

        if not config_one_by_one:
            # Solution 1: Using default config only if none specified.
            if not config and config_from_settings:
                config = config_from_settings
        else:
            # Solution 2: Updating the configured config with the default one.
            config = dict(config_from_settings, **config)
        return config

    def get_form_class(self):
        if django.VERSION[0] == 1 and django.VERSION[1] <= 5:
            return modelform_factory(self.model)
        return modelform_factory(self.model, fields='__all__')

    def get_form(self):
        form_class = self.get_form_class()
        return form_class(instance=self.obj,
                          initial=self.initial,
                          prefix=id(form_class))

    def get_field(self):
        field = self.get_form()[self.field_name]
        field = self._adding_size(field)
        return field

    def get_value_editor(self, value):
        return self.get_field().field.clean(value)

    def render_value(self, field_name=None):
        field_name = field_name or self.field_name_render
        value = getattr(self.obj, field_name)
        if callable(value) and not isinstance(self, AdaptorManyToManyField):
            value = value()
        return apply_filters(value, self.filters_to_show, self.loads)

    def render_value_edit(self):
        value = self.render_value()
        if value:
            return value
        return self.empty_value()

    def empty_value(self):
        '''
        Get the text to display when the field is empty.
        '''
        edit_empty_value = self.config.get('edit_empty_value', False)
        if edit_empty_value:
            return edit_empty_value
        else:
            return unicode(inplace_settings.INPLACEEDIT_EDIT_EMPTY_VALUE)

    def render_field(self, template_name="inplaceeditform/render_field.html", extra_context=None):
        extra_context = extra_context or {}
        context = {'form': self.get_form(),
                   'field': self.get_field(),
                   'STATIC_URL': get_static_url(),
                   'class_inplace': self.class_inplace,
                   'inplace_save_url': reverse('inplace_save')}
        context.update(extra_context)
        return render_to_string(template_name, context)

    def render_media_field(self, template_name="inplaceeditform/render_media_field.html", extra_context=None):
        extra_context = extra_context or {}
        context = {'field': self.get_field(),
                   'STATIC_URL': get_static_url(),
                   'ADMIN_MEDIA_PREFIX': get_admin_static_url()}
        context.update(extra_context)

        return render_to_string(template_name, context)

    def render_config(self, template_name="inplaceeditform/render_config.html"):
        return render_to_string(template_name,
                                {'config': self.config})

    def can_edit(self):
        can_edit_adaptor_path = inplace_settings.ADAPTOR_INPLACEEDIT_EDIT
        if can_edit_adaptor_path:
            path_module, class_adaptor = ('.'.join(can_edit_adaptor_path.split('.')[:-1]),
                                          can_edit_adaptor_path.split('.')[-1])
            cls_perm = getattr(import_module(path_module), class_adaptor)
        else:
            cls_perm = SuperUserPermEditInline
        return cls_perm.can_edit(self)

    def loads_to_post(self, request):
        return json.loads(request.POST.get('value'))

    def save(self, value):
        setattr(self.obj, self.field_name, value)
        self.obj.save()

    def get_auto_height(self):
        return self.config.get('auto_height', False)

    def get_auto_width(self):
        return self.config.get('auto_width', False)

    def get_height(self, widget_options):
        if 'height' in self.config:
            height = self.config['height']
        else:
            height = widget_options.get('height', '0')
        return float(height.replace('px', ''))

    def get_width(self, widget_options):
        if 'width' in self.config:
            return float(self.config['width'].replace('px', ''))
        else:
            return max(float(widget_options.get('width', '0').replace('px', '')), self.min_width)

    def get_font_size(self, widget_options):
        if 'font_size' in self.config:
            font_size = self.config['font_size']
        else:
            font_size = widget_options.get('font_size', '12')
        return float(font_size.replace('px', ''))

    def get_line_height(self, widget_options):
        if 'line_height' in self.config:
            return float(self.config['line_height'].replace('px', ''))
        return self.get_font_size(widget_options)

    def treatment_height(self, height, font_size, width=None):
        if isinstance(height, string) and not height.endswith('px') or not isinstance(height, string):
            height = "%spx" % height
        return height

    def treatment_width(self, width, font_size, height=None):
        if isinstance(width, string) and not width.endswith('px') or not isinstance(width, string):
            width = "%spx" % width
        return width

    def _adding_size(self, field):
        attrs = field.field.widget.attrs
        widget_options = self.config and self.config.get('widget_options', {})
        auto_height = self.get_auto_height()
        auto_width = self.get_auto_width()
        if 'style' not in attrs:
            style = ''
            height = self.get_height(widget_options)
            width = self.get_width(widget_options)
            font_size = self.get_font_size(widget_options)
            line_height = self.get_line_height(widget_options)
            if height and not auto_height:
                style += "height: %s; " % self.treatment_height(height, font_size, width)
            if width and not auto_width:
                style += "width: %s; " % self.treatment_width(width, font_size, height)
            if not auto_height or not auto_width:
                style += "font-size: %spx; " % font_size
                style += "line-height: %spx; " % line_height
            for key, value in widget_options.items():
                if key in ('height', 'width'):
                    continue
                style += "%s: %s; " % (key, value)
            if style:
                attrs['style'] = style
        field.field.widget.attrs = attrs
        return field

    def _transmeta_processing(self):
        if has_transmeta:
            import transmeta
            translatable_fields = self._get_translatable_fields(self.model)
            if self.field_name in translatable_fields:
                self.field_name = transmeta.get_real_fieldname(self.field_name)
                self.transmeta = True
                if not self.render_value(self.field_name):
                    message_translation = unicode(inplace_settings.INPLACEEDIT_EDIT_MESSAGE_TRANSLATION)
                    self.initial = {self.field_name: message_translation}
                return
        self.transmeta = False

    def _get_translatable_fields(self, cls):
        classes = cls.mro()
        translatable_fields = []
        [translatable_fields.extend(cl._meta.translatable_fields) for cl in classes
         if getattr(cl, '_meta', None) and getattr(cl._meta, 'translatable_fields', None)]
        return translatable_fields

class AdaptorTextField(BaseAdaptorField):

    INCREASE_HEIGHT = 3
    MULTIPLIER_WIDTH = 1.25

    @property
    def name(self):
        return 'text'

    def treatment_height(self, height, font_size, width=None):
        if 'height' in self.config:
            effective_height = height
        else:
            effective_height = font_size
        return "%spx" % (effective_height + self.INCREASE_HEIGHT)

    def treatment_width(self, width, font_size, height=None):
        return "%spx" % (width * self.MULTIPLIER_WIDTH)

class AdaptorURLField(AdaptorTextField):

    @property
    def name(self):
        return 'url'

    def render_value(self, field_name=None, template_name="inplaceeditform/adaptor_url/render_value.html"):
        value = super(AdaptorURLField, self).render_value(field_name)
        config = deepcopy(self.config)
        context = {'value': value,
                   'value_link': value,
                   'obj': self.obj}
        config.update(context)
        return render_to_string(template_name, config)

class AdaptorEmailField(AdaptorURLField):

    @property
    def name(self):
        return 'email'

    def render_value(self, field_name=None, template_name="inplaceeditform/adaptor_url/render_value.html"):
        value = super(AdaptorURLField, self).render_value(field_name)
        config = deepcopy(self.config)
        context = {'value': value,
                   'value_link': 'mailto:%s' % value,
                   'obj': self.obj}
        config.update(context)
        return render_to_string(template_name, config)

class AdaptorTextAreaField(BaseAdaptorField):

    @property
    def name(self):
        return 'textarea'

class AdaptorBooleanField(BaseAdaptorField):

    @property
    def name(self):
        return 'boolean'

    def __init__(self, *args, **kwargs):
        super(AdaptorBooleanField, self).__init__(*args, **kwargs)
        self.config['can_auto_save'] = 0

    def render_value(self, field_name=None, template_name="inplaceeditform/adaptor_boolean/render_value.html"):
        value = super(AdaptorBooleanField, self).render_value(field_name)
        return render_to_string(template_name, {'value': value, 'STATIC_URL': get_static_url()})

    def render_field(self, template_name="inplaceeditform/adaptor_boolean/render_field.html", extra_context=None):
        return super(AdaptorBooleanField, self).render_field(template_name, extra_context=extra_context)

    def render_media_field(self, template_name="inplaceeditform/adaptor_boolean/render_media_field.html", extra_context=None):
        return super(AdaptorBooleanField, self).render_media_field(template_name, extra_context=extra_context)

class AdaptorNullBooleanField(AdaptorBooleanField):

    DEFAULT_MIN_HEIGHT = 60

    @property
    def name(self):
        return 'nullboolean'

    def get_value_editor(self, value):
        request = self.request
        value = request.POST.get('value', None)
        if value and isinstance(value, string):
            value = json.loads(value)
        value = {'2': True,
                 True: True,
                 'True': True,
                 '3': False,
                 'False': False,
                 False: False}.get(value, None)
        return self.get_field().field.clean(value)

    def render_value(self, field_name=None, template_name="inplaceeditform/adaptor_boolean/render_value.html"):
        value = super(AdaptorBooleanField, self).render_value(field_name)
        if value is None:
            return value
        return super(AdaptorNullBooleanField, self).render_value(field_name=field_name, template_name=template_name)

    def render_media_field(self, template_name="inplaceeditform/render_media_field.html", extra_context=None):
        return super(AdaptorBooleanField, self).render_media_field(template_name, extra_context=extra_context)

class BaseDateField(BaseAdaptorField):

    def __init__(self, *args, **kwargs):
        super(BaseDateField, self).__init__(*args, **kwargs)
        self.config['can_auto_save'] = 0

    def render_media_field(self, template_name="inplaceeditform/adaptor_date/render_media_field.html", extra_context=None):
        extra_context = extra_context or {}
        context = {'javascript_catalog_url': reverse('django.views.i18n.javascript_catalog')}
        context.update(extra_context)
        return super(BaseDateField, self).render_media_field(template_name,
                                                             extra_context=context)

    def render_value(self, field_name=None):
        val = super(BaseDateField, self).render_value(field_name)
        if not isinstance(val, string):
            val = apply_filters(val, [self.filter_render_value])
        return val

class AdaptorDateField(BaseDateField):

    @property
    def name(self):
        return 'date'

    def __init__(self, *args, **kwargs):
        super(AdaptorDateField, self).__init__(*args, **kwargs)
        self.filter_render_value = "date:'%s'" % settings.DATE_FORMAT

    def render_field(self, template_name="inplaceeditform/adaptor_date/render_field.html", extra_context=None):
        return super(AdaptorDateField, self).render_field(template_name, extra_context=extra_context)

    def get_field(self):
        field = super(AdaptorDateField, self).get_field()
        field.field.widget = AdminDateWidget()
        return field

class AdaptorDateTimeField(BaseDateField):

    @property
    def name(self):
        return 'datetime'

    def __init__(self, *args, **kwargs):
        super(AdaptorDateTimeField, self).__init__(*args, **kwargs)
        self.filter_render_value = "date:'%s'" % settings.DATETIME_FORMAT

    def render_field(self, template_name="inplaceeditform/adaptor_datetime/render_field.html", extra_context=None):
        return super(AdaptorDateTimeField, self).render_field(template_name, extra_context=extra_context)

    def render_media_field(self, template_name="inplaceeditform/adaptor_datetime/render_media_field.html", extra_context=None):
        return super(AdaptorDateTimeField, self).render_media_field(template_name, extra_context=extra_context)

    def get_field(self):
        field = super(AdaptorDateTimeField, self).get_field()
        field.field.widget = AdminSplitDateTime()
        return field

class AdaptorTimeField(BaseDateField):

    @property
    def name(self):
        return 'time'

    def __init__(self, *args, **kwargs):
        super(AdaptorTimeField, self).__init__(*args, **kwargs)
        self.filter_render_value = "date:'%s'" % settings.TIME_FORMAT

    def get_field(self):
        field = super(AdaptorTimeField, self).get_field()
        field.field.widget = AdminTimeWidget()
        return field

class BaseNumberField(BaseAdaptorField):

    def render_value(self, field_name=None):
        value = super(BaseNumberField, self).render_value(field_name=field_name)
        if isinstance(value, numbers.Number):
            value = number_format(value)
        return value

class AdaptorIntegerField(BaseNumberField):

    DEFAULT_MIN_HEIGHT = 40

    @property
    def name(self):
        return 'integer'

class AdaptorFloatField(BaseNumberField):

    DEFAULT_MIN_HEIGHT = 50

    @property
    def name(self):
        return 'float'

class AdaptorDecimalField(BaseNumberField):

    DEFAULT_MIN_HEIGHT = 60

    @property
    def name(self):
        return 'decimal'

class AdaptorChoicesField(BaseAdaptorField):

    MULTIPLIER_HEIGHT = 1.75
    INCREASE_WIDTH = 40

    @property
    def name(self):
        return 'choices'

    def treatment_height(self, height, font_size, width=None):
        if 'height' in self.config:
            effective_height = height
        else:
            effective_height = font_size
        return "%spx" % (effective_height * self.MULTIPLIER_HEIGHT)

    def treatment_width(self, width, font_size, height=None):
        return "%spx" % (width + self.INCREASE_WIDTH)

    def render_value(self, field_name=None):
        field_name = field_name or self.field_name
        return super(AdaptorChoicesField, self).render_value('get_%s_display' % field_name)

class AdaptorForeingKeyField(BaseAdaptorField):

    MULTIPLIER_HEIGHT = 1.75
    INCREASE_WIDTH = 40

    @property
    def name(self):
        return 'fk'

    def treatment_height(self, height, font_size, width=None):
        if 'height' in self.config:
            effective_height = height
        else:
            effective_height = font_size
        return "%spx" % (effective_height * self.MULTIPLIER_HEIGHT)

    def treatment_width(self, width, font_size, height=None):
        return "%spx" % (width + self.INCREASE_WIDTH)

    def render_value(self, field_name=None):
        value = super(AdaptorForeingKeyField, self).render_value(field_name)
        if not isinstance(value, string):
            value = unicode(value)
        return value

    def get_value_editor(self, value):
        value = super(AdaptorForeingKeyField, self).get_value_editor(value)
        return value and value.pk

    def save(self, value):
        setattr(self.obj, "%s_id" % self.field_name, value)
        self.obj.save()

class AdaptorManyToManyField(BaseAdaptorField):

    MULTIPLIER_HEIGHT = 6
    INCREASE_WIDTH = 50

    @property
    def name(self):
        return 'm2m'

    def __init__(self, *args, **kwargs):
        super(AdaptorManyToManyField, self).__init__(*args, **kwargs)
        self._filters_to_show = self.filters_to_show
        self.filters_to_show = None

    def treatment_height(self, height, font_size, width=None):
        if 'height' in self.config:
            effective_height = height
        else:
            effective_height = font_size
        return "%spx" % (effective_height * self.MULTIPLIER_HEIGHT)

    def treatment_width(self, width, font_size, height=None):
        return "%spx" % (width + self.INCREASE_WIDTH)

    def get_value_editor(self, value):
        return [item.pk for item in super(AdaptorManyToManyField, self).get_value_editor(value)]

    def render_value(self, field_name=None):
        return super(AdaptorManyToManyField, self).render_value(field_name).all()

class AdaptorCommaSeparatedManyToManyField(AdaptorManyToManyField):

    @property
    def name(self):
        return 'm2mcomma'

    def __init__(self, *args, **kwargs):
        super(AdaptorCommaSeparatedManyToManyField, self).__init__(*args, **kwargs)
        self.config['can_auto_save'] = 0

    def render_value(self, field_name=None, template_name="inplaceeditform/adaptor_m2m/render_commaseparated_value.html"):
        queryset = super(AdaptorCommaSeparatedManyToManyField, self).render_value(field_name)
        value = render_to_string(template_name, {'queryset': queryset})
        return apply_filters(value, self._filters_to_show, self.loads)

class AdaptorFileField(BaseAdaptorField):

    MULTIPLIER_HEIGHT = 2

    def __init__(self, *args, **kwargs):
        super(AdaptorFileField, self).__init__(*args, **kwargs)
        self.config['can_auto_save'] = 0

    def loads_to_post(self, request):
        files = [f for f in request.FILES.values()]
        return files and files[0] or None

    def treatment_height(self, height, font_size, width=None):
        if 'height' in self.config:
            effective_height = height
        else:
            effective_height = font_size
        return "%spx" % (effective_height * self.MULTIPLIER_HEIGHT)

    def render_field(self, template_name="inplaceeditform/adaptor_file/render_field.html", extra_context=None):
        extra_context = extra_context or {}
        try:
            from django.core.context_processors import csrf
            context = csrf(self.request)
        except ImportError:
            context = {}
        context.update(extra_context)
        return super(AdaptorFileField, self).render_field(template_name, context)

    def render_media_field(self, template_name="inplaceeditform/adaptor_file/render_media_field.html", extra_context=None):
        return super(AdaptorFileField, self).render_media_field(template_name, extra_context=extra_context)

    def render_value(self, field_name=None, template_name='inplaceeditform/adaptor_file/render_value.html'):
        field_name = field_name or self.field_name_render
        value = getattr(self.obj, field_name)
        if not value:
            return ''
        config = deepcopy(self.config)
        context = {'value': value,
                   'obj': self.obj}
        config.update(context)
        return render_to_string(template_name, config)

    def save(self, value):
        file_name = value and value.name
        if not file_name:
            super(AdaptorFileField, self).save(value)
        else:
            getattr(self.obj, self.field_name).save(file_name, value)

class AdaptorImageField(AdaptorFileField):

    def render_field(self, template_name="inplaceeditform/adaptor_image/render_field.html", extra_context=None):
        return super(AdaptorImageField, self).render_field(template_name, extra_context=extra_context)

    def render_media_field(self, template_name="inplaceeditform/adaptor_image/render_media_field.html", extra_context=None):
        return super(AdaptorImageField, self).render_media_field(template_name, extra_context=extra_context)

    def render_value(self, field_name=None, template_name='inplaceeditform/adaptor_image/render_value.html'):
        return super(AdaptorImageField, self).render_value(field_name=field_name, template_name=template_name)


# Copyright (c) 2010-2013 by Yaco Sistemas <goinnn@gmail.com> or <pmartin@yaco.es>
#
# 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 3 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 programe.  If not, see <http://www.gnu.org/licenses/>.

import json

from django import template
from django.core.urlresolvers import reverse
from django.template import Library, Variable

from inplaceeditform import settings as inplace_settings
from inplaceeditform.commons import get_adaptor_class, get_static_url, get_admin_static_url
from inplaceeditform.tag_utils import RenderWithArgsAndKwargsNode, parse_args_kwargs

register = Library()

def inplace_js(context, activate_inplaceedit=True, toolbar=False):
    request = context['request']
    request.inplace_js_rendered = True
    return {
        'STATIC_URL': get_static_url(),
        'ADMIN_MEDIA_PREFIX': get_admin_static_url(),
        'activate_inplaceedit': activate_inplaceedit,
        'auto_save': json.dumps(inplace_settings.INPLACEEDIT_AUTO_SAVE),
        'event': inplace_settings.INPLACEEDIT_EVENT,
        'disable_click': json.dumps(inplace_settings.INPLACEEDIT_DISABLE_CLICK),
        'toolbar': toolbar,
        'enable_class': inplace_settings.INPLACE_ENABLE_CLASS,
        'success_text': inplace_settings.INPLACEEDIT_SUCCESS_TEXT,
        'unsaved_changes': inplace_settings.INPLACEEDIT_UNSAVED_TEXT,
        'inplace_get_field_url': inplace_settings.INPLACE_GET_FIELD_URL or reverse('inplace_get_field'),
        'inplace_save_url': inplace_settings.INPLACE_SAVE_URL or reverse('inplace_save'),
        'field_types': inplace_settings.INPLACE_FIELD_TYPES,
        'focus_when_editing': json.dumps(inplace_settings.INPLACE_FOCUS_WHEN_EDITING),
        'inplace_js_extra': getattr(request, 'inplace_js_extra', '')
    }
register.inclusion_tag("inplaceeditform/inplace_js.html", takes_context=True)(inplace_js)

def inplace_css(context, toolbar=False):
    request = context['request']
    request.inplace_css_rendered = True
    return {
        'STATIC_URL': get_static_url(),
        'ADMIN_MEDIA_PREFIX': get_admin_static_url(),
        'toolbar': toolbar,
        'inplace_js_extra': getattr(request, 'inplace_css_extra', '')
    }
register.inclusion_tag("inplaceeditform/inplace_css.html", takes_context=True)(inplace_css)

def inplace_static(context):
    return {
        'STATIC_URL': get_static_url(),
        'ADMIN_MEDIA_PREFIX': get_admin_static_url(),
        'toolbar': False,
        'request': context['request']
    }
register.inclusion_tag("inplaceeditform/inplace_static.html", takes_context=True)(inplace_static)

#to old django versions
def inplace_media(context):
    return inplace_static(context)
register.inclusion_tag("inplaceeditform/inplace_static.html", takes_context=True)(inplace_media)

def inplace_toolbar(context):
    return {
        'STATIC_URL': get_static_url(),
        'ADMIN_MEDIA_PREFIX': get_admin_static_url(),
        'toolbar': True,
        'request': context['request']
    }
register.inclusion_tag("inplaceeditform/inplace_static.html", takes_context=True)(inplace_toolbar)

class InplaceEditNode(RenderWithArgsAndKwargsNode):

    def prepare_context(self, args, kwargs, context):
        expression_to_show = args[0]
        tokens_to_show = expression_to_show.split('|')
        obj_field_name, filters_to_show = tokens_to_show[0], '|'.join(tokens_to_show[1:])
        obj_field_name_split = obj_field_name.split(".")
        obj_context = '.'.join(obj_field_name_split[:-1])
        field_name = obj_field_name_split[-1]
        obj = Variable(obj_context).resolve(context)
        adaptor = kwargs.get('adaptor', None)
        class_adaptor = get_adaptor_class(adaptor, obj, field_name)
        request = context.get('request')

        config = class_adaptor.get_config(request, **kwargs)

        adaptor_field = class_adaptor(request, obj, field_name,
                                      filters_to_show,
                                      config)

        context = {
            'adaptor_field': adaptor_field,
        }
        return context

@register.tag
def inplace_edit(parser, token):
    args, kwargs = parse_args_kwargs(parser, token)
    return InplaceEditNode(args, kwargs, 'inplaceeditform/inplace_edit.html')

@register.tag(name='eval')
def do_eval(parser, token):
    "Usage: {% eval %}1 + 1{% endeval %}"

    nodelist = parser.parse(('endeval',))

    class EvalNode(template.Node):
        def render(self, context):
            return template.Template(nodelist.render(context)).render(template.Context(context))
    parser.delete_first_token()
    return EvalNode()


# -*- coding: utf-8 -*-
# Copyright (c) 2010-2013 by Yaco Sistemas <goinnn@gmail.com>
#               2015 by Pablo Martín <goinnn@gmail.com>
# 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 3 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 programe.  If not, see <http://www.gnu.org/licenses/>.

from django import template
from django.utils.encoding import smart_str
from django.template.loader import render_to_string

def parse_args_kwargs(parser, token):
    """
    Parse uniformly args and kwargs from a templatetag

    Usage::

      For parsing a template like this:

      {% footag my_contents,height=10,zoom=20 as myvar %}

      You simply do this:

      @register.tag
      def footag(parser, token):
          args, kwargs = parse_args_kwargs(parser, token)
    """
    bits = token.contents.split(' ')

    if len(bits) <= 1:
        raise template.TemplateSyntaxError("'%s' takes at least one argument" % bits[0])

    if token.contents[13] == '"':
        end_quote = token.contents.index('"', 14) + 1
        args = [template.Variable(token.contents[13:end_quote])]
        kwargs_start = end_quote
    else:
        try:
            next_space = token.contents.index(' ', 14)
            kwargs_start = next_space + 1
        except ValueError:
            next_space = None
            kwargs_start = None
        args = [template.Variable(token.contents[13:next_space])]

    kwargs = {}
    kwargs_list = token.contents[kwargs_start:].split(',')
    for kwargs_item in kwargs_list:
        if '=' in kwargs_item:
            k, v = kwargs_item.split('=', 1)
            k = k.strip()
            kwargs[k] = template.Variable(v)
    return args, kwargs

def get_args_and_kwargs(args, kwargs, context):
    out_args = [arg.resolve(context) for arg in args]
    out_kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context)) for k, v in kwargs.items()])
    return out_args, out_kwargs

class RenderWithArgsAndKwargsNode(template.Node):
    """
    Node for templatetags which renders templates with parsed args and kwargs

    Usage::

      class FooNode(RenderWithArgsAndKwargsNode):
          def prepare_context(self, context, args, kwargs):
              context['result_list'] = kwargs['result_list']
              return context

      @register.tag
      def footag(parser, token):
          args, kwargs = parse_args_kwargs(parser, token)
          return FooNode(args, kwargs, template='footag.html')
    """

    def __init__(self, args, kwargs, template):
        self.args = args
        self.kwargs = kwargs
        self.template = template

    def prepare_context(self, args, kwargs, context):
        """
        Hook for overriding in subclasses.

        Note that "args" and "kwargs" parameters are already resolved with context
        """
        return context

    def render(self, context):
        args, kwargs = get_args_and_kwargs(self.args, self.kwargs, context)
        context = self.prepare_context(args, kwargs, context)
        return render_to_string(self.template, context)


"""Run a web server providing a Prometheus metrics endpoint."""

import argparse
import asyncio
import logging
import sys
from typing import (
    ClassVar,
    Dict,
    Iterable,
)

from aiohttp.web import Application
from prometheus_client import (
    Metric,
    ProcessCollector,
)
from toolrack.log import setup_logger
from toolrack.script import Script

from .metric import (
    MetricConfig,
    MetricsRegistry,
)
from .web import PrometheusExporter

class PrometheusExporterScript(Script):
    """Expose metrics to Prometheus."""

    # Name of the script, can be set by subsclasses.
    name: ClassVar[str] = 'prometheus-exporter'

    # The defualt port for the exporter, can be changed by subclasses.
    default_port: int = 9090

    registry: MetricsRegistry

    def __init__(self, stdout=None, stderr=None, loop=None):
        super().__init__(stdout=stdout, stderr=stderr)
        self.loop = loop or asyncio.get_event_loop()
        self.registry = MetricsRegistry()

    @property
    def description(self) -> str:
        """Service description.

        By default, return the class docstring.

        """
        return self.__doc__ or ''

    @property
    def logger(self) -> logging.Logger:
        """A logger for the script."""
        return logging.getLogger(name=self.name)

    def configure_argument_parser(self, parser: argparse.ArgumentParser):
        """Add configuration to the ArgumentParser.

        Subclasses can implement this to add options to the ArgumentParser for
        the script.

        """

    async def on_application_startup(self, application: Application):
        """Handler run at Application startup.

        This must be a coroutine.

        Subclasses can implement this to perform operations as part of
        Application.on_startup handler.

        """

    async def on_application_shutdown(self, application: Application):
        """Handler run at Application shutdown.

        This must be a coroutine.

        Subclasses can implement this to perform operations as part of
        Application.on_shutdown handler.

        """

    def configure(self, args: argparse.Namespace):
        """Perform additional confguration steps at script startup.

        Subclasses can implement this.

        """

    def create_metrics(
            self, metric_configs: Iterable[MetricConfig]) -> Dict[str, Metric]:
        """Create and register metrics from a list of MetricConfigs."""
        return self.registry.create_metrics(metric_configs)

    def get_parser(self) -> argparse.ArgumentParser:
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description=self.description)
        parser.add_argument(
            '-H', '--host', default='localhost', help='host address to bind')
        parser.add_argument(
            '-p',
            '--port',
            type=int,
            default=self.default_port,
            help='port to run the webserver on')
        parser.add_argument(
            '-L',
            '--log-level',
            default='WARNING',
            choices=['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'],
            help='minimum level for log messages')
        parser.add_argument(
            '--process-stats',
            action='store_true',
            help='include process stats in metrics')
        self.configure_argument_parser(parser)
        return parser

    def main(self, args: argparse.Namespace):
        self._setup_logging(args.log_level)
        self._configure_registry(include_process_stats=args.process_stats)
        self.configure(args)
        exporter = self._get_exporter(args)
        exporter.run()

    def _setup_logging(self, log_level: str):
        """Setup logging for the application and aiohttp."""
        level = getattr(logging, log_level)
        names = (
            'aiohttp.access', 'aiohttp.internal', 'aiohttp.server',
            'aiohttp.web', self.name)
        for name in names:
            setup_logger(name=name, stream=sys.stderr, level=level)

    def _configure_registry(self, include_process_stats: bool = False):
        """Configure the MetricRegistry."""
        if include_process_stats:
            self.registry.register_additional_collector(
                ProcessCollector(registry=None))

    def _get_exporter(self, args: argparse.Namespace) -> PrometheusExporter:
        """Return a :class:`PrometheusExporter` configured with args."""
        exporter = PrometheusExporter(
            self.name, self.description, args.host, args.port, self.registry)
        exporter.app.on_startup.append(self.on_application_startup)
        exporter.app.on_shutdown.append(self.on_application_shutdown)
        return exporter


"""Helpers around prometheus-client to create and register metrics."""

from collections import namedtuple
from typing import (
    Dict,
    Iterable,
    NamedTuple,
    Union,
)

from prometheus_client import (
    CollectorRegistry,
    Counter,
    Enum,
    Gauge,
    generate_latest,
    Histogram,
    Info,
    Metric,
    Summary,
)

class MetricType(NamedTuple):
    """Details about a metric type."""

    cls: Metric
    options: Dict[str, str] = {}

# Map metric types to their MetricTypes
METRIC_TYPES: Dict[str, MetricType] = {
    'counter': MetricType(cls=Counter, options={'labels': 'labelnames'}),
    'enum': MetricType(
        cls=Enum, options={
            'labels': 'labelnames',
            'states': 'states'
        }),
    'gauge': MetricType(cls=Gauge, options={'labels': 'labelnames'}),
    'histogram': MetricType(
        cls=Histogram, options={
            'labels': 'labelnames',
            'buckets': 'buckets'
        }),
    'info': MetricType(cls=Info, options={'labels': 'labelnames'}),
    'summary': MetricType(cls=Summary, options={'labels': 'labelnames'})
}

class MetricConfig(namedtuple('MetricConfig',
                              ['name', 'description', 'type', 'config'])):
    """Configuration for a metric."""

    def __new__(cls, name, description, typ, config):
        if typ not in METRIC_TYPES:
            raise InvalidMetricType(name, typ)
        return super().__new__(cls, name, description, typ, config)

class InvalidMetricType(Exception):
    """Raised when invalid metric type is found."""

    def __init__(self, name, invalid_type):
        self.name = name
        self.invalid_type = invalid_type
        type_list = ', '.join(sorted(METRIC_TYPES))
        super().__init__(
            f'Invalid type for {self.name}: must be one of {type_list}')

class MetricsRegistry:
    """A registry for metrics."""

    registry: CollectorRegistry

    def __init__(self):
        self.registry = CollectorRegistry(auto_describe=True)
        self._metrics: Dict[str, Metric] = {}

    def create_metrics(self,
                       configs: Iterable[MetricConfig]) -> Dict[str, Metric]:
        """Create Prometheus metrics from a list of MetricConfigs."""
        metrics: Dict[str, Metric] = {
            config.name: self._register_metric(config)
            for config in configs
        }
        self._metrics.update(metrics)
        return metrics

    def get_metric(
            self, name: str,
            labels: Union[Dict[str, str], None] = None) -> Metric:
        """Return a metric, optionally configured with labels."""
        metric = self._metrics[name]
        if labels:
            return metric.labels(**labels)

        return metric

    def get_metrics(self) -> Dict[str, Metric]:
        """Return a dict mapping names to metrics."""
        return self._metrics.copy()

    def register_additional_collector(self, collector):
        """Registrer an additional collector or metric.

        Metric(s) for the collector will not be include in the result of
        get_metrics.

        """
        self.registry.register(collector)

    def generate_metrics(self) -> bytes:
        """Generate text with metrics values from the registry."""
        return bytes(generate_latest(self.registry))

    def _register_metric(self, config: MetricConfig) -> Metric:
        metric_type = METRIC_TYPES[config.type]
        options = {
            metric_type.options[key]: value
            for key, value in config.config.items()
            if key in metric_type.options
        }
        return metric_type.cls(
            config.name, config.description, registry=self.registry, **options)


"""AioHTTP application for exposing metrics to Prometheus."""

from textwrap import dedent
from typing import (
    Awaitable,
    Callable,
    Iterable,
    Union,
)

from aiohttp.web import (
    Application,
    Request,
    Response,
    run_app,
)
from prometheus_client import (
    CONTENT_TYPE_LATEST,
    Metric,
)

from .metric import MetricsRegistry

# Signature for update handler
UpdateHandler = Callable[[Iterable[Metric]], Awaitable[None]]

class PrometheusExporter:
    """Export Prometheus metrics via a web application."""

    name: str
    descrption: str
    host: str
    port: int
    register: MetricsRegistry
    app: Application

    _update_handler: Union[UpdateHandler, None] = None

    def __init__(
            self, name: str, description: str, host: str, port: int,
            registry: MetricsRegistry):
        self.name = name
        self.description = description
        self.host = host
        self.port = port
        self.registry = registry
        self.app = self._make_application()

    def set_metric_update_handler(self, handler: UpdateHandler):
        """Set a handler to update metrics.

        The provided coroutine function is called at every request with a dict
        as argument, mapping metric names to metrics.  The signature is the
        following:

          async def update_handler(metrics):

        """
        self._update_handler = handler

    def run(self):
        """Run the :class:`aiohttp.web.Application` for the exporter."""
        run_app(
            self.app,
            host=self.host,
            port=self.port,
            print=lambda *args, **kargs: None,
            access_log_format='%a "%r" %s %b "%{Referrer}i" "%{User-Agent}i"')

    def _make_application(self) -> Application:
        """Setup an :class:`aiohttp.web.Application`."""
        app = Application()
        app['exporter'] = self
        app.router.add_get('/', self._handle_home)
        app.router.add_get('/metrics', self._handle_metrics)
        app.on_startup.append(self._log_startup_message)
        return app

    async def _log_startup_message(self, app: Application):
        """Log message about application startup."""
        self.app.logger.info(f'Listening on http://{self.host}:{self.port}')

    async def _handle_home(self, request: Request) -> Response:
        """Home page request handler."""
        if self.description:
            title = f'{self.name} - {self.description}'
        else:
            title = self.name

        text = dedent(
            f'''<!DOCTYPE html>

                <title>{title}</title>

{title}

                  Metric are exported at the
                  /metrics endpoint.

            ''')
        return Response(content_type='text/html', text=text)

    async def _handle_metrics(self, request: Request) -> Response:
        """Handler for metrics."""
        if self._update_handler:
            await self._update_handler(self.registry.get_metrics())
        response = Response(body=self.registry.generate_metrics())
        response.content_type = CONTENT_TYPE_LATEST
        return response


# -*- coding: utf-8 -*-
from __future__ import absolute_import

import pmxbot
from pmxbot.core import command

import wolframalpha

@command(aliases='wolframalpha')
def wa(client, event, channel, nick, rest):
	"""
	A free-text query resolver by Wolfram|Alpha. Returns the first
	result, if available.
	"""
	client = wolframalpha.Client(pmxbot.config['Wolfram|Alpha API key'])
	res = client.query(rest)
	return next(res.results).text


import six
from six.moves import http_client

def fix_HTTPMessage():
	"""
	Python 2 uses a deprecated method signature and doesn't provide the
	forward compatibility.
	Add it.
	"""
	if six.PY3:
		return

	http_client.HTTPMessage.get_content_type = http_client.HTTPMessage.gettype
	http_client.HTTPMessage.get_param = http_client.HTTPMessage.getparam


import itertools
import json

from six.moves import urllib, map

import xmltodict
from jaraco.itertools import always_iterable

from . import compat

compat.fix_HTTPMessage()

class Client(object):
    """
    Wolfram|Alpha v2.0 client

    Pass an ID to the object upon instantiation, then
    query Wolfram Alpha using the query method.
    """
    def __init__(self, app_id):
        self.app_id = app_id

    def query(self, input, params=(), **kwargs):
        """
        Query Wolfram|Alpha using the v2.0 API

        Allows for arbitrary parameters to be passed in
        the query. For example, to pass assumptions:

            client.query(input='pi', assumption='*C.pi-_*NamedConstant-')

        To pass multiple assumptions, pass multiple items
        as params:

            params = (
                ('assumption', '*C.pi-_*NamedConstant-'),
                ('assumption', 'DateOrder_**Day.Month.Year--'),
            )
            client.query(input='pi', params=params)

        For more details on Assumptions, see
        https://products.wolframalpha.com/api/documentation.html#6
        """
        data = dict(
            input=input,
            appid=self.app_id,
        )
        data = itertools.chain(params, data.items(), kwargs.items())

        query = urllib.parse.urlencode(tuple(data))
        url = 'https://api.wolframalpha.com/v2/query?' + query
        resp = urllib.request.urlopen(url)
        assert resp.headers.get_content_type() == 'text/xml'
        assert resp.headers.get_param('charset') == 'utf-8'
        return Result(resp)

class ErrorHandler(object):
    def __init__(self, *args, **kwargs):
        super(ErrorHandler, self).__init__(*args, **kwargs)
        self._handle_error()

    def _handle_error(self):
        if 'error' not in self:
            return

        template = 'Error {error[code]}: {error[msg]}'
        raise Exception(template.format(**self))

class Document(dict):
    _attr_types = {}
    "Override the types from the document"

    @classmethod
    def from_doc(cls, doc):
        """
        Load instances from the xmltodict result. Always return
        an iterable, even if the result is a singleton.
        """
        return map(cls, always_iterable(doc))

    def __getattr__(self, name):
        type = self._attr_types.get(name, lambda x: x)
        attr_name = '@' + name
        try:
            val = self[name] if name in self else self[attr_name]
        except KeyError:
            raise AttributeError(name)
        return type(val)

class Assumption(Document):
    @property
    def text(self):
        text = self.template.replace('${desc1}', self.description)
        try:
            text = text.replace('${word}', self.word)
        except Exception:
            pass
        return text[:text.index('. ') + 1]

class Warning(Document):
    pass

class Image(Document):
    """
    Holds information about an image included with an answer.
    """
    _attr_types = dict(
        height=int,
        width=int,
    )

class Subpod(Document):
    """
    Holds a specific answer or additional information relevant to said answer.
    """
    _attr_types = dict(
        img=Image.from_doc,
    )

def xml_bool(str_val):
    """
    >>> xml_bool('true')
    True
    >>> xml_bool('false')
    False
    """
    return bool(json.loads(str_val))

class Pod(ErrorHandler, Document):
    """
    Groups answers and information contextualizing those answers.
    """
    _attr_types = dict(
        position=float,
        numsubpods=int,
        subpod=Subpod.from_doc,
    )

    @property
    def subpods(self):
        return self.subpod

    @property
    def primary(self):
        return '@primary' in self and xml_bool(self['@primary'])

    @property
    def texts(self):
        """
        The text from each subpod in this pod as a list.
        """
        return [subpod.plaintext for subpod in self.subpod]

    @property
    def text(self):
        return next(iter(self.subpod)).plaintext

class Result(ErrorHandler, Document):
    """
    Handles processing the response for the programmer.
    """
    _attr_types = dict(
        pod=Pod.from_doc,
    )

    def __init__(self, stream):
        doc = xmltodict.parse(stream, dict_constructor=dict)['queryresult']
        super(Result, self).__init__(doc)

    @property
    def info(self):
        """
        The pods, assumptions, and warnings of this result.
        """
        return itertools.chain(self.pods, self.assumptions, self.warnings)

    @property
    def pods(self):
        return self.pod

    @property
    def assumptions(self):
        return Assumption.from_doc(self.get('assumptions'))

    @property
    def warnings(self):
        return Warning.from_doc(self.get('warnings'))

    def __iter__(self):
        return self.info

    def __len__(self):
        return sum(1 for _ in self.info)

    @property
    def results(self):
        """
        The pods that hold the response to a simple, discrete query.
        """
        return (
            pod
            for pod in self.pods
            if pod.primary
            or pod.title == 'Result'
        )

    @property
    def details(self):
        """
        A simplified set of answer text by title.
        """
        return {pod.title: pod.text for pod in self.pods}


""" Generic API Client """
from copy import deepcopy
import json
import requests

try:
    from urlparse import urljoin
except ImportError:
    from urllib.parse import urljoin

class ApiClient(object):
    """ Client to interact with a generic Rest API.

    Subclasses should implement functionality accordingly with the provided
    service methods, i.e. ``get``, ``post``, ``put`` and ``delete``.
    """

    accept_type = 'application/json'
    api_base = None

    def __init__(
            self,
            base_url,
            username=None,
            api_key=None,
            status_endpoint=None,
            timeout=60
    ):
        """ Initialise client.

        Args:
            base_url (str): The base URL to the service being used.
            username (str): The username to authenticate with.
            api_key (str): The API key to authenticate with.
            timeout (int): Maximum time before timing out.
        """
        self.base_url = base_url
        self.username = username
        self.api_key = api_key
        self.status_endpoint = urljoin(self.base_url, status_endpoint)
        self.timeout = timeout

    @staticmethod
    def encode(request, data):
        """ Add request content data to request body, set Content-type header.

        Should be overridden by subclasses if not using JSON encoding.

        Args:
            request (HTTPRequest): The request object.
            data (dict, None): Data to be encoded.

        Returns:
            HTTPRequest: The request object.
        """
        if data is None:
            return request

        request.add_header('Content-Type', 'application/json')
        request.data = json.dumps(data)

        return request

    @staticmethod
    def decode(response):
        """ Decode the returned data in the response.

        Should be overridden by subclasses if something else than JSON is
        expected.

        Args:
            response (HTTPResponse): The response object.

        Returns:
            dict or None.
        """
        try:
            return response.json()
        except ValueError as e:
            return e.message

    def get_credentials(self):
        """ Returns parameters to be added to authenticate the request.

        This lives on its own to make it easier to re-implement it if needed.

        Returns:
            dict: A dictionary containing the credentials.
        """
        return {"username": self.username, "api_key": self.api_key}

    def call_api(
            self,
            method,
            url,
            headers=None,
            params=None,
            data=None,
            files=None,
            timeout=None,
    ):
        """ Call API.

        This returns object containing data, with error details if applicable.

        Args:
            method (str): The HTTP method to use.
            url (str): Resource location relative to the base URL.
            headers (dict or None): Extra request headers to set.
            params (dict or None): Query-string parameters.
            data (dict or None): Request body contents for POST or PUT requests.
            files (dict or None: Files to be passed to the request.
            timeout (int): Maximum time before timing out.

        Returns:
            ResultParser or ErrorParser.
        """
        method = method.upper()
        headers = deepcopy(headers) or {}
        headers['Accept'] = self.accept_type
        params = deepcopy(params) or {}
        data = data or {}
        files = files or {}

        if self.username and self.api_key:
            params.update(self.get_credentials())

        url = urljoin(self.base_url, url)

        r = requests.request(
            method,
            url,
            headers=headers,
            params=params,
            files=files,
            data=data,
            timeout=timeout,
        )

        return r, r.status_code

    def get(self, url, params=None, **kwargs):
        """ Call the API with a GET request.

        Args:
            url (str): Resource location relative to the base URL.
            params (dict or None): Query-string parameters.

        Returns:
            ResultParser or ErrorParser.
        """
        return self.call_api(
            "GET",
            url,
            params=params,
            **kwargs
        )

    def delete(self, url, params=None, **kwargs):
        """ Call the API with a DELETE request.

        Args:
            url (str): Resource location relative to the base URL.
            params (dict or None): Query-string parameters.

        Returns:
            ResultParser or ErrorParser.
        """
        return self.call_api(
            "DELETE",
            url,
            params=params,
            **kwargs
        )

    def put(self, url, params=None, data=None, files=None, **kwargs):
        """ Call the API with a PUT request.

        Args:
            url (str): Resource location relative to the base URL.
            params (dict or None): Query-string parameters.
            data (dict or None): Request body contents.
            files (dict or None: Files to be passed to the request.

        Returns:
            An instance of ResultParser or ErrorParser.
        """
        return self.call_api(
            "PUT",
            url,
            params=params,
            data=data,
            files=files,
            **kwargs
        )

    def post(self, url, params=None, data=None, files=None, **kwargs):
        """ Call the API with a POST request.

        Args:
            url (str): Resource location relative to the base URL.
            params (dict or None): Query-string parameters.
            data (dict or None): Request body contents.
            files (dict or None: Files to be passed to the request.

        Returns:
            An instance of ResultParser or ErrorParser.
        """
        return self.call_api(
            "POST",
            url,
            params=params,
            data=data,
            files=files,
            **kwargs
        )

    def service_status(self, **kwargs):
        """ Call the API to get the status of the service.

        Returns:
            An instance of ResultParser or ErrorParser.
        """
        return self.call_api(
            'GET',
            self.status_endpoint,
            params={'format': 'json'},
            **kwargs
        )


import sys

try:
    from urlparse import urljoin
except ImportError:
    from urllib.parse import urljoin

from zenlog import logging

from nerd.client import ApiClient

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
stream = logging.StreamHandler(sys.stdout)
stream.setLevel(logging.DEBUG)
formatter = logging.Formatter(
    '\n\n %(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
stream.setFormatter(formatter)
logger.addHandler(stream)

class NerdClient(ApiClient):
    api_base = "http://nerd.huma-num.fr/nerd/service/"
    max_text_length = 500  # Approximation.
    sentences_per_group = 10  # Number of sentences per group

    def __init__(self, apiBase=api_base):
        super(NerdClient, self).__init__(base_url=apiBase)
        if not apiBase.endswith('/'):
            apiBase = apiBase + '/'
        api_base = apiBase

        self.disambiguate_service = urljoin(api_base, "disambiguate")
        self.concept_service = urljoin(api_base, "kb/concept")
        self.segmentation_service = urljoin(api_base, "segmentation")
        self.language_service = urljoin(api_base, "language")

    def _process_query(self, query, prepared=False):
        """ Process query recursively, if the text is too long,
        it is split and processed bit a bit.

        Args:
            query (sdict): Text to be processed.
            prepared (bool): True when the query is ready to be submitted via
            POST request.
        Returns:
            str: Body ready to be submitted to the API.
        """

        # Exit condition and POST
        if prepared is True:
            files = {'query': str(query)}

            logger.debug('About to submit the following query {}'.format(query))

            res, status = self.post(
                self.disambiguate_service,
                files=files,
                headers={'Accept': 'application/json'},
            )

            if status == 200:
                return self.decode(res), status
            else:
                logger.debug('Disambiguation failed.')
                return None, status

        text = query['text']

        sentence_coordinates = [
            {
                "offsetStart": 0,
                "offsetEnd": len(text)
            }
        ]

        total_nb_sentences = len(sentence_coordinates)  # Sentences from text.
        sentences_groups = []

        if len(text) > self.max_text_length:
            res, status_code = self.segment(text)

            if status_code == 200:
                sentence_coordinates = res['sentences']
                total_nb_sentences = len(sentence_coordinates)
            else:
                logger.error('Error during the segmentation of the text.')

            logger.debug(
                'Text too long, split in {} sentences; building groups of {} '
                'sentences.'.format(
                    total_nb_sentences, self.sentences_per_group
                )
            )
            sentences_groups = self._group_sentences(
                total_nb_sentences,
                self.sentences_per_group
            )
        else:
            query['sentence'] = "true"

        if total_nb_sentences > 1:
            query['sentences'] = sentence_coordinates

        if len(sentences_groups) > 0:
            for group in sentences_groups:
                query['processSentence'] = group

                res, status_code = self._process_query(query, prepared=True)

                if status_code == 200:
                    if 'entities' in res:
                        query['entities'] = res[u'entities']
                    query['language'] = res[u'language']
                else:
                    logger.error(
                        "Error when processing the query {}".format(query)
                    )
                    return None, status_code

        else:
            res, status_code = self._process_query(query, prepared=True)

            if status_code == 200:
                query['language'] = res[u'language']
                if 'entities' in res:
                    query['entities'] = res[u'entities']
            else:
                logger.error("Error when processing the query {}".format(query))
                return None, status_code

        return query, status_code

    @staticmethod
    def _group_sentences(total_nb_sentences, group_length):
        """ Split sentences in groups, given a specific group length.

        Args:
            total_nb_sentences (int): Total available sentences.
            group_length (int): Limit of length for each group.

        Returns:
            list: Contains groups (lists) of sentences.
        """
        sentences_groups = []
        current_sentence_group = []

        for i in range(0, total_nb_sentences):
            if i % group_length == 0:
                if len(current_sentence_group) > 0:
                    sentences_groups.append(current_sentence_group)
                current_sentence_group = [i]
            else:
                current_sentence_group.append(i)

        if len(current_sentence_group) > 0:
            sentences_groups.append(current_sentence_group)

        return sentences_groups

    def disambiguate_pdf(self, file, language=None, entities=None):
        """ Call the disambiguation service in order to process a pdf file .

        Args:
            pdf (file): PDF file to be disambiguated.
            language (str): language of text (if known)

        Returns:
            dict, int: API response and API status.
        """

        body = {
            "customisation": "generic"
        }

        if language:
            body['language'] = {"lang": language}

        if entities:
            body['entities'] = entities

        files = {
            'query': str(body),
            'file': (
                file,
                open(file, 'rb'),
                'application/pdf',
                {'Expires': '0'}
            )
        }

        res, status = self.post(
            self.disambiguate_service,
            files=files,
            headers={'Accept': 'application/json'},
        )

        if status != 200:
            logger.debug('Disambiguation failed with error ' + str(status))

        return self.decode(res), status

    def disambiguate_terms(self, terms, language="en", entities=None):
        """ Call the disambiguation service in order to get meanings.

            Args:
                terms (obj): list of objects of term, weight
                language (str): language of text, english if not specified
                entities (list): list of entities or mentions to be supplied by
                    the user.

            Returns:
                dict, int: API response and API status.
            """

        body = {
            "termVector": terms,
            "entities": [],
            "onlyNER": "false",
            "customisation": "generic"
        }

        body['language'] = {"lang": language}

        if entities:
            body['entities'] = entities

        files = {'query': str(body)}

        logger.debug('About to submit the following query {}'.format(body))

        res, status = self.post(
            self.disambiguate_service,
            files=files,
            headers={'Accept': 'application/json'},
        )

        if status == 200:
            return self.decode(res), status
        else:
            logger.debug('Disambiguation failed.')
            return None, status

    def disambiguate_text(self, text, language=None, entities=None):
        """ Call the disambiguation service in order to get meanings.

        Args:
            text (str): Text to be disambiguated.
            language (str): language of text (if known)
            entities (list): list of entities or mentions to be supplied by
                the user.

        Returns:
            dict, int: API response and API status.
        """

        body = {
            "text": text,
            "entities": [],
            "onlyNER": "false",
            "customisation": "generic"
        }

        if language:
            body['language'] = {"lang": language}

        if entities:
            body['entities'] = entities

        result, status_code = self._process_query(body)

        if status_code != 200:
            logger.debug('Disambiguation failed.')

        return result, status_code

    def disambiguate_query(self, query, language=None, entities=None):
        """ Call the disambiguation service in order to disambiguate a search query.

        Args:
            text (str): Query to be disambiguated.
            language (str): language of text (if known)
            entities (list): list of entities or mentions to be supplied by
                the user.

        Returns:
            dict, int: API response and API status.
        """

        body = {
            "shortText": query,
            "entities": [],
            "onlyNER": "false",
            "customisation": "generic"
        }

        if language:
            body['language'] = {"lang": language}

        if entities:
            body['entities'] = entities

        files = {'query': str(body)}

        logger.debug('About to submit the following query {}'.format(body))

        res, status = self.post(
            self.disambiguate_service,
            files=files,
            headers={'Accept': 'application/json'},
        )

        if status == 200:
            return self.decode(res), status
        else:
            logger.debug('Disambiguation failed.')
            return None, status

    def segment(self, text):
        """ Call the segmenter in order to split text in sentences.

        Args:
            text (str): Text to be segmented.

        Returns:
            dict, int: A dict containing a list of dicts with the offsets of
                each sentence; an integer representing the response code.
        """

        files = {'text': text}
        res, status_code = self.post(self.segmentation_service, files=files)

        if status_code != 200:
            logger.debug('Segmentation failed.')

        return self.decode(res), status_code

    def get_language(self, text):
        """ Recognise the language of the text in input

        Args:
              id (str): The text whose the language needs to be recognised

        Returns:
            dict, int: A dict containing the recognised language and the
                confidence score.
        """
        files = {'text': text}
        res, status_code = self.post(self.language_service, files=files)

        if status_code != 200:
            logger.debug('Language recognition failed.')

        return self.decode(res), status_code

    def get_concept(self, conceptId, lang='en'):
        """ Fetch the concept from the Knowledge base

        Args:
              id (str): The concept id to be fetched, it can be Wikipedia
                page id or Wikiedata id.

        Returns:
            dict, int: A dict containing the concept information; an integer
                representing the response code.
        """
        url = urljoin(self.concept_service + '/', conceptId)

        res, status_code = self.get(url, params={'lang': lang})

        if status_code != 200:
            logger.debug('Fetch concept failed.')

        return self.decode(res), status_code


# -*- coding: utf-8 -*-

"""
scikit-MDR was primarily developed at the University of Pennsylvania by:
    - Randal S. Olson (rso@randalolson.com)
    - Tuan Nguyen (tnguyen4@swarthmore.edu)
    - and many more generous open source contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

from __future__ import print_function
from collections import defaultdict

import numpy as np

from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.ensemble import BaggingClassifier
from sklearn.metrics import accuracy_score

from .mdr import MDR

from ._version import __version__

class MDREnsemble(BaseEstimator, ClassifierMixin):

    """Bagging ensemble of Multifactor Dimensionality Reduction (MDR) models for prediction in machine learning"""

    def __init__(self, n_estimators=100, tie_break=1, default_label=0, random_state=None):
        """Sets up the MDR ensemble

        Parameters
        ----------
        n_estimators: int (default: 100)
            Number of MDR models to include in the ensemble
        tie_break: int (default: 1)
            Default label in case there's a tie in a set of feature pair values
        default_label: int (default: 0)
            Default label in case there's no data for a set of feature pair values
        random_state: int, RandomState instance or None (default: None)
            If int, random_state is the seed used by the random number generator;
            If RandomState instance, random_state is the random number generator;
            If None, the random number generator is the RandomState instance used by np.random.

        Returns
        -------
        None

        """
        self.n_estimators = n_estimators
        self.tie_break = tie_break
        self.default_label = default_label
        self.random_state = random_state
        self.feature_map = defaultdict(lambda: default_label)
        self.ensemble = BaggingClassifier(base_estimator=MDR(tie_break=tie_break, default_label=default_label),
                                          n_estimators=n_estimators, random_state=random_state)

    def fit(self, features, classes):
        """Constructs the MDR ensemble from the provided training data

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix
        classes: array-like {n_samples}
            List of class labels for prediction

        Returns
        -------
        None

        """
        self.ensemble.fit(features, classes)

        # Construct the feature map from the ensemble predictions
        unique_rows = list(set([tuple(row) for row in features]))
        for row in unique_rows:
            self.feature_map[row] = self.ensemble.predict([row])[0]

    def predict(self, features):
        """Uses the MDR ensemble to construct a new feature from the provided features

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix to transform

        Returns
        ----------
        array-like: {n_samples}
            Constructed features from the provided feature matrix

        """
        return self.ensemble.predict(features)

    def fit_predict(self, features, classes):
        """Convenience function that fits the provided data then constructs a new feature from the provided features

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix
        classes: array-like {n_samples}
            List of true class labels

        Returns
        ----------
        array-like: {n_samples}
            Constructed features from the provided feature matrix

        """
        self.ensemble.fit(features, classes)
        return self.ensemble.predict(features)

    def score(self, features, classes, scoring_function=None, **scoring_function_kwargs):
        """Estimates the accuracy of the predictions from the MDR ensemble

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix to predict from
        classes: array-like {n_samples}
            List of true class labels

        Returns
        -------
        accuracy_score: float
            The estimated accuracy based on the constructed feature

        """
        new_feature = self.ensemble.predict(features)

        if scoring_function is None:
            return accuracy_score(classes, new_feature)
        else:
            return scoring_function(classes, new_feature, **scoring_function_kwargs)


# -*- coding: utf-8 -*-

"""
scikit-MDR was primarily developed at the University of Pennsylvania by:
    - Randal S. Olson (rso@randalolson.com)
    - Tuan Nguyen (tnguyen4@swarthmore.edu)
    - and many more generous open source contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

from __future__ import print_function
from collections import defaultdict

import numpy as np

from sklearn.base import BaseEstimator, TransformerMixin, ClassifierMixin
from sklearn.metrics import accuracy_score

class MDRBase(BaseEstimator):

    """Base Multifactor Dimensionality Reduction (MDR) functions.

    MDR can take categorical features and binary endpoints as input, and outputs a binary constructed feature or prediction."""

    def __init__(self, tie_break=1, default_label=0):
        """Sets up the MDR algorithm for feature construction.

        Parameters
        ----------
        tie_break: int (default: 1)
            Default label in case there's a tie in a set of feature pair values
        default_label: int (default: 0)
            Default label in case there's no data for a set of feature pair values

        Returns
        -------
        None

        """
        self.tie_break = tie_break
        self.default_label = default_label
        self.class_count_matrix = None
        self.feature_map = None

    def fit(self, features, class_labels):
        """Constructs the MDR feature map from the provided training data.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix
        class_labels: array-like {n_samples}
            List of true class labels

        Returns
        -------
        self: A copy of the fitted model

        """
        unique_labels = sorted(np.unique(class_labels))
        if len(unique_labels) != 2:
            raise ValueError('MDR only supports binary endpoints.')

        # Count the distribution of classes that fall into each MDR grid cell
        self.class_count_matrix = defaultdict(lambda: defaultdict(int))
        for row_i in range(features.shape[0]):
            feature_instance = tuple(features[row_i])
            self.class_count_matrix[feature_instance][class_labels[row_i]] += 1
        self.class_count_matrix = dict(self.class_count_matrix)

        # Only applies to binary classification
        overall_class_fraction = float(sum(class_labels == unique_labels[0])) / class_labels.size

        # If one class is more abundant in a MDR grid cell than it is overall, then assign the cell to that class
        self.feature_map = {}
        for feature_instance in self.class_count_matrix:
            counts = self.class_count_matrix[feature_instance]
            fraction = float(counts[unique_labels[0]]) / np.sum(list(counts.values()))
            if fraction > overall_class_fraction:
                self.feature_map[feature_instance] = unique_labels[0]
            elif fraction == overall_class_fraction:
                self.feature_map[feature_instance] = self.tie_break
            else:
                self.feature_map[feature_instance] = unique_labels[1]

        return self

class MDR(MDRBase, TransformerMixin):

    """Multifactor Dimensionality Reduction (MDR) for feature construction in binary classification problems.

    MDR can take categorical features and binary endpoints as input, and outputs a binary constructed feature."""

    def transform(self, features):
        """Uses the MDR feature map to construct a new feature from the provided features.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix to transform

        Returns
        ----------
        array-like: {n_samples, 1}
            Constructed features from the provided feature matrix

        """
        if self.feature_map is None:
            raise ValueError('The MDR model must be fit before transform can be called.')

        new_feature = np.zeros(features.shape[0], dtype=np.int)

        for row_i in range(features.shape[0]):
            feature_instance = tuple(features[row_i])
            if feature_instance in self.feature_map:
                new_feature[row_i] = self.feature_map[feature_instance]
            else:
                new_feature[row_i] = self.default_label

        return new_feature.reshape(features.shape[0], 1)

    def fit_transform(self, features, class_labels):
        """Convenience function that fits the provided data then constructs a new feature from the provided features.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix
        class_labels: array-like {n_samples}
            List of true class labels

        Returns
        ----------
        array-like: {n_samples, 1}
            Constructed features from the provided feature matrix

        """
        self.fit(features, class_labels)
        return self.transform(features)

class MDRClassifier(MDRBase, ClassifierMixin):

    """Multifactor Dimensionality Reduction (MDR) for binary classification problems.

    MDR can take categorical features and binary endpoints as input, and outputs a binary prediction."""

    def predict(self, features):
        """Uses the MDR feature map to construct predictions from the provided features.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix to transform

        Returns
        ----------
        array-like: {n_samples}
            Constructed features from the provided feature matrix

        """
        if self.feature_map is None:
            raise ValueError('The MDR model must be fit before predict can be called.')

        new_feature = np.zeros(features.shape[0], dtype=np.int)

        for row_i in range(features.shape[0]):
            feature_instance = tuple(features[row_i])
            if feature_instance in self.feature_map:
                new_feature[row_i] = self.feature_map[feature_instance]
            else:
                new_feature[row_i] = self.default_label

        return new_feature

    def fit_predict(self, features, class_labels):
        """Convenience function that fits the provided data then constructs predictions from the provided features.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix
        class_labels: array-like {n_samples}
            List of true class labels

        Returns
        ----------
        array-like: {n_samples}
            Constructed features from the provided feature matrix

        """
        self.fit(features, class_labels)
        return self.predict(features)

    def score(self, features, class_labels, scoring_function=None, **scoring_function_kwargs):
        """Estimates the accuracy of the predictions from the constructed feature.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix to predict from
        class_labels: array-like {n_samples}
            List of true class labels

        Returns
        -------
        accuracy_score: float
            The estimated accuracy based on the constructed feature

        """
        if self.feature_map is None:
            raise ValueError('The MDR model must be fit before score can be called.')

        new_feature = self.predict(features)

        if scoring_function is None:
            return accuracy_score(class_labels, new_feature)
        else:
            return scoring_function(class_labels, new_feature, **scoring_function_kwargs)


# -*- coding: utf-8 -*-

"""
scikit-MDR was primarily developed at the University of Pennsylvania by:
    - Randal S. Olson (rso@randalolson.com)
    - Tuan Nguyen (tnguyen4@swarthmore.edu)
    - and many more generous open source contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

from __future__ import print_function
from collections import defaultdict
import numpy as np
from sklearn.base import BaseEstimator, TransformerMixin
from scipy.stats import ttest_ind

class ContinuousMDR(BaseEstimator, TransformerMixin):

    """Continuous Multifactor Dimensionality Reduction (CMDR) for feature construction in regression problems.

    CMDR can take categorical features and continuous endpoints as input, and outputs a binary constructed feature."""

    def __init__(self, tie_break=1, default_label=0):
        """Sets up the Continuous MDR algorithm for feature construction.

        Parameters
        ----------
        tie_break: int (default: 1)
            Default label in case there's a tie in a set of feature pair values
        default_label: int (default: 0)
            Default label in case there's no data for a set of feature pair values

        Returns
        -------
        None

        """
        self.tie_break = tie_break
        self.default_label = default_label
        self.overall_mean_trait_value = 0.
        self.feature_map = None

    def fit(self, features, targets):
        """Constructs the Continuous MDR feature map from the provided training data.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix
        targets: array-like {n_samples}
            List of target values for prediction

        Returns
        -------
        self: A copy of the fitted model

        """
        self.feature_map = defaultdict(lambda: self.default_label)
        self.overall_mean_trait_value = np.mean(targets)
        self.mdr_matrix_values = defaultdict(list)

        for row_i in range(features.shape[0]):
            feature_instance = tuple(features[row_i])
            self.mdr_matrix_values[feature_instance].append(targets[row_i])

        for feature_instance in self.mdr_matrix_values:
            grid_mean_trait_value = np.mean(self.mdr_matrix_values[feature_instance])
            if grid_mean_trait_value > self.overall_mean_trait_value:
                self.feature_map[feature_instance] = 1
            elif grid_mean_trait_value == self.overall_mean_trait_value:
                self.feature_map[feature_instance] = self.tie_break
            else:
                self.feature_map[feature_instance] = 0

        # Convert defaultdict to dict so CMDR objects can be easily pickled
        self.feature_map = dict(self.feature_map)
        self.mdr_matrix_values = dict(self.mdr_matrix_values)

        return self

    def transform(self, features):
        """Uses the Continuous MDR feature map to construct a new feature from the provided features.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix to transform

        Returns
        ----------
        array-like: {n_samples}
            Constructed feature from the provided feature matrix
            The constructed feature will be a binary variable, taking the values 0 and 1

        """
        new_feature = np.zeros(features.shape[0], dtype=np.int)

        for row_i in range(features.shape[0]):
            feature_instance = tuple(features[row_i])
            if feature_instance in self.feature_map:
                new_feature[row_i] = self.feature_map[feature_instance]
            else:
                new_feature[row_i] = self.default_label

        return new_feature.reshape(features.shape[0], 1)

    def fit_transform(self, features, targets):
        """Convenience function that fits the provided data then constructs a new feature from the provided features.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix
        targets: array-like {n_samples}
            List of true target values

        Returns
        ----------
        array-like: {n_samples}
            Constructed features from the provided feature matrix

        """
        self.fit(features, targets)
        return self.transform(features)

    def score(self, features, targets):
        """Estimates the quality of the ContinuousMDR model using a t-statistic.

        Parameters
        ----------
        features: array-like {n_samples, n_features}
            Feature matrix to predict from
        targets: array-like {n_samples}
            List of true target values

        Returns
        -------
        quality_score: float
            The estimated quality of the Continuous MDR model

        """
        if self.feature_map is None:
            raise ValueError('The Continuous MDR model must be fit before score() can be called.')

        group_0_trait_values = []
        group_1_trait_values = []

        for feature_instance in self.feature_map:
            if self.feature_map[feature_instance] == 0:
                group_0_trait_values.extend(self.mdr_matrix_values[feature_instance])
            else:
                group_1_trait_values.extend(self.mdr_matrix_values[feature_instance])

        return abs(ttest_ind(group_0_trait_values, group_1_trait_values).statistic)