from random import randint

from django import forms
from django.conf import settings
from django.db.models import OneToOneField, ForeignKey, CharField, ManyToManyField, Q

from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _

from django.utils import translation

from crispy_forms.helper import FormHelper
import crispy_forms.bootstrap as bootstrap
import crispy_forms.layout as layout

from connections.models import Entry, POS, Status, SchemaHook
from syntax.models import (
    Schema, SchemaOpinion, InherentSie, Negativity, Predicativity, Aspect,
    Position, SyntacticFunction, Control, PredicativeControl,
)
from syntax.models_phrase import PhraseTypeModel, PhraseType, EmptyAttributes, XPAttributes, AdvPAttributes, ComparAttributes, FixedAttributes, LemmaOperator, LemmaCooccur, Lemma

from semantics.models import (
    Frame, FrameOpinion,
    Argument, SemanticRole, RoleAttribute,
    PredefinedSelectionalPreference, RelationalSelectionalPreference, SelectionalPreferenceRelation,
)

from meanings.models import Synset
from unifier.models import UnifiedFrame

from .form_fields.generic_fields import (
    RangeFilter,
    RegexFilter,
    ChoiceFilter,
    ModelChoiceFilter,
    MultipleChoiceFilter,
    ModelMultipleChoiceFilter,
    OperatorField,
    SwitchField,
)

from .form_fields.specialised_fields import (
    PhraseoFilter,
    PhraseTypeFilter,
    RoleNameFilter,
    RoleAttributeFilter,
)

from .form_fields.query_managers import SingleValueQueryManager

# TODO better to import this from the importer, the web app should not be aware of the importer! :)
from importer.PhraseAttributes import attrs_helpers

from . import polish_strings


class QueryForm(forms.Form):
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-sm-2'
        self.helper.field_class = 'col-sm-10'
        self.helper.attrs = { 'data_formtype' : 'unknown' }
    
    @staticmethod
    def get_child_form_prefix(child_form):
        print(type(child_form))
        raise NotImplementedError
    
    def get_queries(self, negated_attrs):
        queries = []
        for key, value in self.cleaned_data.items():
            #print('========', key, value)
            if value and not key.startswith('filter') and not key.startswith('negate'):
                form_field = self.fields[key]
                if hasattr(form_field, 'query_manager'):
                    conjunction = form_field.query_manager.default_conjunction
                    negate = key in negated_attrs
                    qs = form_field.query_manager.make_queries(value, conjunction)
                    assert(len(qs) < 2)
                    queries += [~q if negate else q for q in qs]
        return queries
    
    def is_negated(self):
        for key, value in self.cleaned_data.items():
            if key.startswith('negate') and value:
                return True
        return False
    
    @classmethod
    def make_field(cls, field_name):
        field_object = cls.base_fields[field_name]
        return field_object.layout(field_name)

def or_button(button_label, add_button_id):
    return bootstrap.StrictButton('+ {} ({})'.format(_('Lub'), button_label), css_class='or-button btn-xs btn-info', data_add_id=add_button_id)

def other_button(button_label, add_button_id):
    return bootstrap.StrictButton('+ {} ({})'.format(_('Inny'), button_label), css_class='other-button btn-xs btn-warning', data_add_id=add_button_id)

def and_or_form_creator(button_label, button_id, field=None, data_add=None, add_other=False, help=None, tooltip=None, *args, **kwargs):
    add_button = None
    data_add = 'switch' if data_add is None else data_add
    if field is not None:
        add_button = bootstrap.FieldWithButtons(field, bootstrap.StrictButton('+ {}'.format(_('Dodaj')), css_id=button_id, css_class='add-button btn-xs btn-success', data_add=data_add, *args, **kwargs))
    else:
        add_button = bootstrap.StrictButton('+ {}'.format(button_label), css_id=button_id, css_class='add-button btn-xs btn-success', data_add=data_add, *args, **kwargs)
    ret = [
        layout.HTML('<div class="and-or-forms py-1" data-formtype="{}">'.format(data_add)),
        add_button,
        or_button(button_label, button_id),
        layout.HTML('</div>'),
    ]
    if add_other:
        ret.insert(-1, other_button(button_label, button_id))
    if help:
        help_tooltip = ''
        if tooltip:
            help_tooltip = f' <span data-toggle="tooltip" data-placement="bottom" title="{tooltip}"><img src="{settings.STATIC_URL}common/img/info.svg" alt="info" width="12" height="12"/></span>'
        ret.insert(-1, layout.HTML('<small class="form-text text-muted">{}{}</small>'.format(help, help_tooltip)))
    return ret

class EntryForm(QueryForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        #self.helper.form_method = 'get'
        # TODO remove if this form is to be used with different views
        self.helper.form_action = 'entries:get_entries'
        self.helper.form_id = 'main-form'
        self.helper.attrs = { 'data_formtype' : 'entry' }
        self.model_class = Entry
        
        entry_components = [
            self.make_field('lemma'),
            self.make_field('pos'),
            self.make_field('phraseology'),
            self.make_field('status'),
        ]
        
        schema_components = [
            self.make_field('num_schemata'),
        ] + \
        and_or_form_creator(_('Schemat'), 'add-schema-0', data_add='schema', add_other=True, help=_('Schemat(y) występujące w haśle')) + \
        and_or_form_creator(_('Pozycja'), 'add-position-0', data_add='position', add_other=True,
            help=_('Pozycja/e występujące w haśle'),
            tooltip=_('Pozycje mogą występować w różnych schematach. Aby ograniczyć filtrowanie do występowania wymaganych pozycji w obrębie jednego schematu, proszę użyć filtru POZYCJA wewnątrz filtra SCHEMAT powyżej.')
            ) + \
        and_or_form_creator(_('Fraza'), 'add-phrase-0', field=self.make_field('phrase_type'), data_prefix='phrase_',
            help=_('Fraza/y występujące w haśle'),
            tooltip=_('Frazy mogą występować w różnych schematach i na różnych pozycjach. Aby ograniczyć filtrowanie do występowania wymaganych fraz w obrębie jednej pozycji, proszę użyć filtru POZYCJA powyżej lub wewnątrz filtra SCHEMAT.')
        ) + \
        [
            self.make_field('filter_schemata'),
        ]
        
        
        
        frame_components = [
            self.make_field('num_frames'),
        ] + \
        and_or_form_creator(_('Rama'), 'add-frame-0', data_add='frame', add_other=True, help=_('Rama/y występujące w haśle')) + \
        and_or_form_creator(_('Argument'), 'add-argument-0', data_add='argument', help=_('Argument(y) występujące w haśle')) + \
        [
            self.make_field('filter_frames'),
        ]
        
        components = [
            layout.Fieldset(_('Własności hasła'), *entry_components),
            layout.Fieldset(_('Własności składniowe'), *schema_components),
            layout.Fieldset(_('Własności semantyczne'), *frame_components),
            layout.Submit('main-submit', _('Filtruj'), css_class='btn-sm'),
            layout.Reset('main-reset', _('Wyczyść'), css_class='btn-sm'),
        ]
        
        self.helper.layout = layout.Layout(*components)
    
    lemma = RegexFilter(
        label=_('Lemat'),
        lookup='name',
        max_length=200,
        autocomplete='lemma',
    )
    pos = ModelMultipleChoiceFilter(
        label=_('Część mowy'),
        queryset=POS.objects.exclude(tag='unk'),
        key='tag',
        human_values=polish_strings.POS(),
        lookup='pos',
    )
    phraseology = PhraseoFilter()
    status = ModelMultipleChoiceFilter(
        label ='Status',
        queryset=Status.objects.all().order_by('-priority'),
        key = 'key',
        human_values=polish_strings.STATUS(),
        lookup='status',
    )
    num_schemata = RangeFilter(
        label=_('Liczba schematów'),
        lookup='schemata_count',
    )
    num_frames = RangeFilter(
        label=_('Liczba ram'),
        lookup='frames_count',
    )
    phrase_type = PhraseTypeFilter(
        queryset=PhraseTypeModel.objects.filter(main_phrase_types__positions__schemata__isnull=False).distinct(),
        #help_text=_('Typ frazy występujący w haśle.'),
    )
    filter_schemata = SwitchField(_('Ukryj niepasujące schematy'))
    filter_frames = SwitchField(_('Ukryj niepasujące ramy'))
    
    @staticmethod
    def get_child_form_prefix(child_form):
        if child_form.model_class == Schema:
            return 'subentries__schemata__in'
        if child_form.model_class == Position:
            return 'subentries__schemata__positions__in'
        if child_form.model_class == PhraseType:
            return 'subentries__schemata__positions__phrase_types__in'
        if child_form.model_class == Frame:
            return 'subentries__schema_hooks__argument_connections__argument__frame__in'
        if child_form.model_class == Argument:
            return 'subentries__schema_hooks__argument_connections__argument__in'
        raise KeyError(type(child_form))


# for forms that can appear multiple times in the query constructor field
# (e.g. a phrase form), create a separate form class for each form ‘instance’,
# with globally unique field names to ensure proper javascript behaviour
# and retrieving the field values in views
class FormFactory(object):
    
    form_class_name = None
    form_model = None
    form_formtype = None
    form_header = None
    
    @staticmethod
    def unique_number():
        return randint(1000, 12345678)
    
    @staticmethod
    def unique_name(field_name, unique_number):
        return '{}_{}'.format(field_name, unique_number)
    
    @staticmethod
    def make_helper_attrs(form_type, unique_number):
        return { 'data_formtype' : form_type , 'data_formnumber' : unique_number }
    
    @staticmethod
    def make_layout(title, components, as_subform=True):
        elements = []
        if as_subform:
            elements.append('{} <span class="collapsed-info text-info"></span> <button class="btn collapse-button btn-xs btn-secondary" data-collapsed="false" type="button">{}</button><button class="btn remove-button btn-xs btn-danger" type="button">{}</button>'.format(title, _('Zwiń'), _('Usuń')))
        elements += components
        if not as_subform:
            elements += [
                layout.Submit('main-submit', _('Filtruj'), css_class='btn-sm'),
                layout.Reset('main-reset', _('Wyczyść'), css_class='btn-sm'),
            ]
        return layout.Layout(layout.Fieldset(
            *elements
        ))
    
    @staticmethod
    def additional_queries():
        return []
    
    @classmethod    
    def get_form(cls, unique_number=None, as_subform=True, *args, **kwargs):
        unique_number = cls.unique_number() if unique_number is None else unique_number
        
        assert(cls.form_class_name is not None)
        assert(cls.form_model is not None)
        assert(cls.form_formtype is not None)
        assert(cls.form_header is not None)
        
        form_class = type(cls.form_class_name, (QueryForm,), {
            #'__init__' : form_init,
            'get_child_form_prefix' : lambda self, child_form: cls.get_child_form_prefix(child_form),
        })
        
        def form_init(self, *args, **kwargs):
            super(type(self), self).__init__(*args, **kwargs)
            self.helper.attrs = cls.make_helper_attrs(cls.form_formtype, unique_number)
            self.model_class = cls.form_model
            
            components = [self.make_field(cls.unique_name('negated', unique_number))]
            for field_name, field_maker, component_maker in cls.field_makers:
                if component_maker:
                    component = component_maker(unique_number, form_class)
                else:
                    component = self.make_field(cls.unique_name(field_name, unique_number))
                if type(component) == list:
                    components += component
                else:
                    components.append(component)
            self.helper.layout = cls.make_layout(cls.form_header, components, as_subform=as_subform)
        
        form_class.__init__ = form_init
        
        def get_queries(self, negated_attrs):
            return super(type(self), self).get_queries(negated_attrs) + cls.additional_queries()
        
        form_class.get_queries = get_queries
        
        form_class.base_fields[cls.unique_name('negated', unique_number)] = SwitchField(_('Zaneguj'), css_class='negate-switch')
        for field_name, field_maker, layout_maker in cls.field_makers:
            if field_name is not None:
                form_class.base_fields[FormFactory.unique_name(field_name, unique_number)] = field_maker()
        return form_class(*args, **kwargs)
    
    @staticmethod
    def get_child_form_prefix(child_form):
        raise NotImplementedError

class SchemaFormFactory(FormFactory):
    
    form_class_name = 'SchemaForm'
    form_model = Schema
    form_formtype = 'schema'
    form_header = _('Schemat składniowy')
    
    field_makers = (
        (
            'opinion',
            lambda: ModelMultipleChoiceFilter(
                label=_('Opinia o schemacie'),
                queryset=SchemaOpinion.objects.filter(schemata__isnull=False).distinct(),
                key='key',
                human_values=polish_strings.SCHEMA_OPINION(),
                lookup='opinion',
            ),
            None,
        ), 
        (
            'type',
            lambda: MultipleChoiceFilter(
                label=_('Typ'),
                choices=(
                    (False, (_('zwykły'), None)),
                    (True,  (_('frazeologiczny'), None)),
                ),
                lookup='phraseologic',
            ),
            None,
        ),
        (
            'sie',
            lambda: ModelMultipleChoiceFilter(
                label=_('Zwrotność'),
                queryset=InherentSie.objects.all(),
                key='name',
                human_values=polish_strings.TRUE_FALSE_YES_NO,
                lookup='subentries__inherent_sie',
            ),
            None,
        ),
        (
            'neg',
            lambda: ModelMultipleChoiceFilter(
                label=_('Negatywność'),
                queryset=Negativity.objects.exclude(name=''),
                key='name',
                human_values=polish_strings.NEGATION(),
                lookup='subentries__negativity',
            ),
            None,
        ),
        (
            'pred',
            lambda: ModelMultipleChoiceFilter(
                label=_('Predykatywność'),
                queryset=Predicativity.objects.all(),
                key='name',
                human_values=polish_strings.TRUE_FALSE_YES_NO,
                lookup='subentries__predicativity',
            ),
            None,
        ),
        (
            'aspect',
            lambda: ModelMultipleChoiceFilter(
                label=_('Aspekt'),
                queryset=Aspect.objects.exclude(name='').exclude(name='_'),
                key='name',
                human_values=polish_strings.ASPECT(),
                lookup='subentries__aspect',
            ),
            None,
        ),
        #phrase = RegexFilter(
        #label='Fraza',
        #max_length=200,
        ##initial='np.* !& !np.* | xp.*',
        #lookup='positions__phrase_types__text_rep',
        #additional_operators=True,
        #autocomplete='phrasetype',
        #)
        (
            'num_positions',
            lambda: RangeFilter(
                label=_('Liczba pozycyj'),
                lookup='positions_count',
            ),
            None,
        ),
        #(
        #    'num_phrase_types',
        #    lambda: RangeFilter(
        #        label='Liczba typów fraz w pozycji',
        #        entry_lookup='subentries__schemata__positions__phrases_count',
        #        object_lookup='positions__phrases_count',
        #    )
        #),
        (
            None, None,
            lambda n, cls: and_or_form_creator(_('Pozycja'), 'add-position-{}'.format(n), data_add='position', add_other=True, help=_('Pozycja/e występujące w schemacie')),
        ),
    )
    
    @staticmethod
    def get_child_form_prefix(child_form):
        if child_form.model_class == Position:
            return 'positions__in'
        raise KeyError(type(child_form))


class PositionFormFactory(FormFactory):
    
    form_class_name = 'PositionForm'
    form_model = Position
    form_formtype = 'position'
    form_header = _('Pozycja składniowa')
    
    field_makers = (
        (
            'gram_function',
            lambda: ModelMultipleChoiceFilter(
                label=_('Funkcja gramatyczna'),
                queryset=SyntacticFunction.objects.all(),
                key='name',
                human_values=polish_strings.GRAM_FUNCTION(),
                lookup='function',
            ), None,
        ),
        (
            'control',
            lambda: ModelMultipleChoiceFilter(
                label=_('Kontrola'),
                queryset=Control.objects.filter(positions__isnull=False).distinct(),
                key='name',
                human_values=polish_strings.CONTROL(),
                lookup='control',
            ), None,
        ),
        (
            'pred_control',
            lambda: ModelMultipleChoiceFilter(
                label=_('Kontrola predykatywna'),
                queryset=PredicativeControl.objects.all(),
                key='name',
                human_values=polish_strings.CONTROL(),
                lookup='pred_control',
            ), None,
        ),
        (
            'num_phrases',
            lambda: RangeFilter(
                label=_('Liczba fraz'),
                lookup='phrases_count',
            ),
            None,
        ),
        (
            'phrase_type',
            #lambda: PhraseTypeFilter(help_text=_('Typ frazy występujący na pozycji.')),
            lambda: PhraseTypeFilter(),
            lambda n, cls: and_or_form_creator(_('Fraza'), 'add-phrase-{}'.format(n), field=cls.make_field(FormFactory.unique_name('phrase_type', n)), data_prefix='phrase_', help=_('Fraza/y występujące na pozycji')),
        ),
    )
     
    @staticmethod
    def get_child_form_prefix(child_form):
        if child_form.model_class == PhraseType:
            return 'phrase_types__in'
        raise KeyError(type(child_form))


class LemmaFormFactory(FormFactory):
    
    form_class_name = 'LemmaForm'
    form_model = Lemma
    form_formtype = 'lemma'
    form_header = _('Lemat')
    
    field_makers = (
        (
            'lemma',
            lambda: RegexFilter(
                label=_('Lemat'),
                max_length=32,
                lookup='name',
                autocomplete='lex_lemma'
            ), None,
        ),
    )


class LexFormFactory(FormFactory):
    
    form_class_name = 'LexForm'
    form_model = PhraseType
    form_formtype = 'phrase_lex'
    form_header = _('Fraza zleksykalizowana')
    
    field_makers = (
        (
            'lemma_operator',
            lambda: ModelMultipleChoiceFilter(
                label=_('Wybór lematów'),
                queryset=LemmaOperator.objects.all(),
                key='name',
                human_values=polish_strings.LEMMA_OPERATOR(),
                lookup='lemma_operator',
            ), None,
        ),
        (
            'lemma_cooccur',
            lambda: ModelMultipleChoiceFilter(
                label=_('Łączenie lematów'),
                queryset=LemmaCooccur.objects.all(),
                key='name',
                human_values=polish_strings.LEMMA_COOCCUR(),
                lookup='lemma_cooccur',
            ), None,
        ),
        (
            None, None,
            lambda n, cls: and_or_form_creator(_('Lemat'), 'add-lemma-{}'.format(n), data_add='lemma'),
        ),
        (
            'lex_type',
            lambda: PhraseTypeFilter(lex=True, help_text=_('Typ składniowy frazy zleksykalizowanej.')),
            lambda n, cls: and_or_form_creator(_('Typ frazy'), 'add-lex-{}'.format(n), field=cls.make_field(FormFactory.unique_name('lex_type', n)), data_prefix='phrase_lex')
        ),
    )
    
    @staticmethod
    def additional_queries():
        return [Q(('main_type__name', 'lex'))]
    
    @staticmethod
    def get_child_form_prefix(child_form):
        if child_form.model_class == PhraseType:
            return 'id__in'
        if child_form.model_class == Lemma:
            return 'lemmata__in'
        raise KeyError(type(child_form))


class FrameFormFactory(FormFactory):
    
    form_class_name = 'FrameForm'
    form_model = Frame
    form_formtype = 'frame'
    form_header = _('Rama semantyczna')
    
    field_makers = (
        (
            'opinion',
            lambda: ModelMultipleChoiceFilter(
                label=_('Opinia'),
                queryset=FrameOpinion.objects.exclude(key='unk').filter(frame__isnull=False).distinct(),
                key='key',
                human_values=polish_strings.FRAME_OPINION(),
                lookup='opinion',
            ), None,
        ),
        (
            'num_arguments',
            lambda: RangeFilter(
                label=_('Liczba argumentów'),
                lookup='arguments_count',
            ), None
        ),
        (
            None, None,
            lambda n, cls: and_or_form_creator(_('Argument'), 'add-argument-{}'.format(n), data_add='argument'),
        ),
    )
    
    @staticmethod
    def get_child_form_prefix(child_form):
        if child_form.model_class == Argument:
            return 'arguments__in'
        raise KeyError(type(child_form))



class UnifiedFrameFormFactory(FormFactory):

    form_class_name = 'UnifiedFrameForm'
    form_model = UnifiedFrame
    form_formtype = 'unifiedframe'
    form_header = _('Zunifikowana rama semantyczna')

    field_makers = (
        (
            'opinion',
            lambda: ModelMultipleChoiceFilter(
                label=_('Opinia'),
                queryset=FrameOpinion.objects.exclude(key='unk').filter(frame__isnull=False).distinct(),
                key='key',
                human_values=polish_strings.FRAME_OPINION(),
                lookup='opinion',
            ), None,
        ),
        (
            'num_arguments',
            lambda: RangeFilter(
                label=_('Liczba argumentów'),
                lookup='arguments_count',
            ), None
        ),
        (
            'num_frames',
            lambda: RangeFilter(
                label=_('Liczba ram semantycznych'),
                lookup='slowal_frames_count',
            ), None
        ),
        (
            'name',
            lambda: RegexFilter(
                label=_('Nazwa'),
                max_length=200,
                lookup='title',
            ), None,
        ),
        (
            'lemmas',
            lambda: RegexFilter(
                label=_('Lematy jednostek leksykalnych'),
                max_length=200,
                lookup='unified_frame_2_slowal_frame__slowal_frame__lexical_units__base',
            ), None,
        ),
        (
            None, None,
            lambda n, cls: and_or_form_creator(_('UnifiedFrameArgument'), 'add-argument-{}'.format(n), data_add='argument'),
        ),
    )

    @staticmethod
    def get_child_form_prefix(child_form):
        if child_form.model_class == Argument:
            return 'arguments__in'
        raise KeyError(type(child_form))


class ArgumentFormFactory(FormFactory):
    
    form_class_name = 'ArgumentForm'
    form_model = Argument
    form_formtype = 'argument'
    form_header = _('Argument semantyczny')
    
    field_makers = (
        (
            'role',
            lambda: RoleNameFilter(
                label=_('Rola'),
                lookup='role__role',
            ), None,
        ),
        (
            'role_attribute',
            lambda: RoleAttributeFilter(
                label=_('Atrybut roli'),
                lookup='role__attribute',
            ), None,
        ),
        (
            'num_preferences',
            lambda: RangeFilter(
                label=_('Liczba preferencyj selekcyjnych argumentu'),
                lookup='preferences_count',
            ), None
        ),
        (
            'preference_type',
            lambda: ChoiceFilter(
                label=_('Preferencja selekcyjna'),
                choices=(('', _('wybierz')), ('predefined', _('Predefiniowana grupa znaczeń')), ('relational', _('Wyrażona przez relację')), ('synset', _('Wyrażona przez jednostkę leksykalną Słowosieci'))),
            ),
            lambda n, cls: and_or_form_creator(_('Preferencja selekcyjna'), 'add-preference-{}'.format(n), field=cls.make_field(FormFactory.unique_name('preference_type', n)))
        ),
        # removing until we discuss whether this field is needed & how it should work
        #(
        #    None, None,
        #    lambda n, cls: and_or_form_creator(_('Pozycja'), 'add-position-{}'.format(n), data_add='position'),
        #),
        (
            'phrase_type',
            lambda: PhraseTypeFilter(help_text=_('Typ frazy, przez którą może być realizowany argument.')),
            lambda n, cls: and_or_form_creator(_('Fraza'), 'add-phrase-{}'.format(n), field=cls.make_field(FormFactory.unique_name('phrase_type', n)), data_prefix='phrase_'),
        ),
    )
    
    @staticmethod
    def get_child_form_prefix(child_form):
        if child_form.model_class == PredefinedSelectionalPreference:
            return 'predefined__in'
        if child_form.model_class == RelationalSelectionalPreference:
            return 'relations__in'
        if child_form.model_class == Synset:
            return 'synsets__in'
        if child_form.model_class == PhraseType:
            return 'argument_connections__schema_connections__phrase_type__in'
        raise KeyError(type(child_form))


class PredefinedPreferenceFormFactory(FormFactory):
    
    form_class_name = 'PredefinedPreferenceForm'
    form_model = PredefinedSelectionalPreference
    form_formtype = 'predefined'
    form_header = _('Preferencja predefiniowana')
    
    field_makers = (
        (
            'predefined',
            lambda: ModelMultipleChoiceFilter(
                label=_('Predefiniowane'),
                queryset=PredefinedSelectionalPreference.objects.all(),
                key='key',
                lookup='key',
            ), None,
        ),
    )


class RelationalPreferenceFormFactory(FormFactory):
    
    form_class_name = 'RelationalPreferenceForm'
    form_model = RelationalSelectionalPreference
    form_formtype = 'relational'
    form_header = _('Preferencja – relacja')
    
    field_makers = (
        (
            'relation',
            lambda: ModelMultipleChoiceFilter(
                label=_('Relacja'),
                queryset=SelectionalPreferenceRelation.objects.all(),
                key='key',
                human_values=polish_strings.RELATION(),
                lookup='relation',
                checkboxes=False,
            ), None,
        ),
        (
            'to_role',
            lambda: RoleNameFilter(
                label=_('Do: rola'),
                lookup='to__role__role',
            ), None,
        ),
        (
            'to_attribute',
            lambda: RoleAttributeFilter(
                label=_('Do: atrybut'),
                lookup='to__role__attribute',
            ), None,
        )
    )
       

class SynsetPreferenceFormFactory(FormFactory):
    
    form_class_name = 'SynsetPreferenceForm'
    form_model = Synset
    form_formtype = 'synset'
    form_header = _('Preferencja – Słowosieć')
    
    field_makers = (
        (
            'synset',
            lambda: RegexFilter(
                label=_('Jednostka leksykalna'),
                max_length=200,
                lookup='lexical_units__text_rep',
                autocomplete='lu'
            ), None,
        ),
    )


class PhraseAttributesFormFactory(FormFactory):
    
    # field types are dynamic here
    field_makers = None
    
    @classmethod
    def make_form_class(cls, n, model, lex_model, phrase_type):
        #print('*****************', n, model, lex_model, phrase_type)
        lex_phrase = lex_model is not None
        fields = list(filter(lambda x: type(x) in (OneToOneField, ForeignKey, CharField, ManyToManyField) and not x.name.endswith('_ptr'), model._meta.get_fields()))
        if lex_phrase:
            lex_fields = list(filter(lambda x: type(x) in (OneToOneField, ForeignKey, CharField, ManyToManyField) and not x.name.endswith('_ptr'), lex_model._meta.get_fields()))
        else:
            lex_fields = []
        all_fields = [(f, '{}'.format(f.name)) for f in fields] + [(f, 'lex_{}'.format(f.name)) for f in lex_fields]
        
        class AttributesForm(QueryForm):

            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.helper.attrs = cls.make_helper_attrs('phrase_{}{}'.format('lex' if lex_model else '', phrase_type), n)
                self.helper.form_id = 'phrase-form'
                self.model_class = PhraseType
                
                components = [self.make_field(cls.unique_name('negated', n))]
                for field, name in all_fields:
                    uniq_name = cls.unique_name(name, n)
                    if name == 'reals':
                        flds = and_or_form_creator(_('Fraza'), 'add-phrase-1-{}'.format(n), field=self.make_field(uniq_name), data_prefix='phrase_')
                    elif name.endswith('_lexes'):
                        flds = and_or_form_creator(_('Fraza'), 'add-phrase-2-{}'.format(n), field=self.make_field(uniq_name), data_prefix='phrase_lex')
                    elif name.endswith('_lex'):
                        flds = and_or_form_creator(_('Fraza'), 'add-phrase-3-{}'.format(n), field=self.make_field(uniq_name), data_prefix='phrase_lex')
                    elif name == 'phrase':
                        flds = and_or_form_creator(_('Fraza'), 'add-phrase-4-{}'.format(n), field=self.make_field(uniq_name), data_prefix='phrase_')
                    else:
                        flds = [self.make_field(uniq_name)]
                    components += flds
                pt = polish_strings.PHRASE_TYPE()[phrase_type]
                header = pt.capitalize() if phrase_type in polish_strings.NO_PHRASE_NAME else format_lazy(_('Fraza {}'), pt)
                if lex_model:
                    header += ' ({})'.format(_('zleksykalizowana'))
                self.helper.layout = cls.make_layout(
                    header.capitalize(),
                    components
                )
            
            # add a phrase type query
            def get_queries(self, negated_attrs):
                pt_q = Q(('{}main_type__name'.format('lexicalized_phrase__' if lex_phrase else ''), phrase_type))
                return super().get_queries(negated_attrs) + [pt_q]
            
            @staticmethod
            def get_child_form_prefix(child_form):
                if model == XPAttributes and child_form.model_class == PhraseType:
                    return 'attributes__xpattributes__reals__in'
                if model == AdvPAttributes and child_form.model_class == PhraseType:
                    return 'attributes__advpattributes__reals__in'
                if model == ComparAttributes and child_form.model_class == PhraseType:
                    return 'attributes__lexphrasetypeattributes__lexcomparattributes__lexes__in'
                if model == FixedAttributes and child_form.model_class == PhraseType:
                    return 'attributes__fixedattributes__phrase__in'
                print(model, child_form.model_class)
                raise KeyError(type(child_form))
        
        
        AttributesForm.base_fields[cls.unique_name('negated', n)] = SwitchField(_('Zaneguj'), css_class='negate-switch')
        for field, name in all_fields:
            #print('***', name, field.name, type(field))
            if field.name == 'reals':
                queryset = None
                if model == XPAttributes:
                    # choice only from phrase types that are realisations of some XP
                    queryset = PhraseTypeModel.objects.filter(main_phrase_types__xpattributes__isnull=False).distinct()
                if model == AdvPAttributes:
                    # choice only from phrase types that are realisations of some AdvP
                    queryset = PhraseTypeModel.objects.filter(main_phrase_types__advpattributes__isnull=False).distinct()
                form_field = PhraseTypeFilter(queryset=queryset, help_text=_('Realizacja składniowa frazy.'))
            elif field.name == 'lexes':
                form_field = PhraseTypeFilter(
                    queryset=PhraseTypeModel.objects.filter(main_phrase_types__lex_phrases__lexcomparattributes__isnull=False).distinct(),
                    help_text=_('Fraza składowa zleksykalizowanej konstrukcji porównawczej.'), lex=True)
            elif field.name == 'lex':
                form_field = PhraseTypeFilter(help_text=_('Fraza zleksykalizowana.'))
            elif field.name == 'phrase':
                form_field = PhraseTypeFilter(
                    queryset=PhraseTypeModel.objects.filter(main_phrase_types__fixedattributes__isnull=False).distinct(),
                    help_text=_('Typ składniowy frazy zleksykalizowanej.'))
            elif type(field) == CharField:
                # TODO other autocompletes?
                autocomplete = 'fixed' if field.name == 'text' else None
                form_field = RegexFilter(
                    label=polish_strings.PHRASE_ATTRIBUTE().get(field.name, field.name),
                    lookup='attributes__{}attributes__{}'.format(phrase_type, field.name),
                    max_length=200,
                    autocomplete=autocomplete,
                )
            else:
                lex_attr = name.startswith('lex')
                queryset_q = Q(('{}{}attributes__isnull'.format('lex' if lex_attr else '', phrase_type), False))
                queryset = type(field.related_model()).objects.filter(queryset_q).distinct()
                form_field = ModelMultipleChoiceFilter(
                    label=polish_strings.PHRASE_ATTRIBUTE().get(field.name, field.name),
                    queryset=queryset,
                    key='name',
                    human_values=polish_strings.PHRASE_ATTRIBUTE_VALUE(),
                    checkboxes=(len(queryset) <= 8),
                    lookup='{}attributes__{}{}attributes__{}'.format(
                        'lexicalized_phrase__' if lex_phrase and not lex_attr else '',
                        'lexphrasetypeattributes__lex' if lex_attr else '',
                        phrase_type,
                        field.name,
                    ),
                )
            AttributesForm.base_fields[cls.unique_name(name, n)] = form_field
        
        return AttributesForm
    
    @classmethod    
    def get_form(cls, phrase_type, unique_number=None, *args, **kwargs):
        unique_number = cls.unique_number() if unique_number is None else unique_number
        lex = phrase_type.startswith('lex')
        phrase_type = phrase_type[3:] if lex else phrase_type
        form_class = None
        if phrase_type in attrs_helpers:
            helper = attrs_helpers[phrase_type]
            attrs_cls, lex_attrs_cls = helper.attrs_cls, helper.lex_attrs_cls
            if lex:
                assert(lex_attrs_cls is not None)
                form_class = cls.make_form_class(unique_number, attrs_cls, lex_attrs_cls, phrase_type)
            else:
                form_class = cls.make_form_class(unique_number, attrs_cls, None, phrase_type)
        if form_class is None:
            form_class = cls.make_form_class(unique_number, EmptyAttributes, None, phrase_type)
        return form_class(*args, **kwargs)