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

from django.db.models import Max

from dictionary.models import Lemma, reflex_phrase_types
from semantics.models import LexicalUnitExamples
from semantics.utils import get_structural_matching_frame

def validate_frames(lemma_id):
    lemma = Lemma.objects.get(id=lemma_id)
    actual_frames = lemma.entry_obj.actual_frames()
    error_msg = u''
    for frame in actual_frames.all():
        error_msg = frame_valid(lemma, frame, actual_frames)
        if error_msg:
            break
    return error_msg
        
def frame_valid(lemma, frame, actual_frames):
    error_msg = ''
    complements = frame.complements.all()
    if not arguments_exists(complements):
        error_msg = u'Semantyka: Rama semantyczna %d jest pusta.' % frame.id
    elif not frame.opinion_selected():
        error_msg = u'Semantyka: Rama semantyczna %d nie ma wybranej opinii.' % frame.id
    elif not roles_unique(complements):
        error_msg = u'Semantyka: Rama semantyczna %d nie zawiera unikalnych ról.' % frame.id
    elif not arguments_pinned(complements):
        error_msg = u'Semantyka: Rama semantyczna %d zawiera argumenty, które nie są powiązane z żadnym schematem.' % frame.id
    elif not preferences_selected(complements):
        error_msg = u'Semantyka: Rama semantyczna %d zawiera argumenty bez zdefiniowanych preferencji selekcyjnych.' % frame.id
    elif not examples_added(frame):
        error_msg = u'Semantyka: Rama semantyczna %d nie ma dopiętych przykładów.' % frame.id
    elif duplicates_exists(frame, actual_frames):
        error_msg = u'Semantyka: Rama semantyczna %d posiada duplikaty.' % frame.id
    elif not schemas_reflex_agreed(lemma, frame):
        error_msg = u'Semantyka: Rama semantyczna %d ma dopięte elementy o niezgodnej zwrotności.' % frame.id
    elif nonch_pinned(frame):
        error_msg = u'Semantyka: Rama semantyczna %d jest dopięta do typu frazy nonch.' % frame.id
    elif multiplied_same_arg_in_schema(frame):
        error_msg = u'Semantyka: Rama semantyczna %d posiada argument wielokrotnie powiązany z tym samym schematem.' % frame.id
    return error_msg

def arguments_exists(complements):
    return complements.exists()

def roles_unique(complements):
    roles = set()
    for complement in complements:
        role_ids = [role.id for role in complement.roles.all()]
        role_ids.sort()
        role = tuple(role_ids)
        if role in roles:
            return False
        else:
            roles.add(role)
    return True

def arguments_pinned(complements):
    for compl in complements:
        if not compl.realizations.exists():
            return False
    return True

def preferences_selected(complements):
    for complement in complements:
        if complement.realizations.exists() and complement.has_only_phraseologic_realizations():
            pass
        elif not preference_valid(complement):
            return False
    return True
    
def preference_valid(complement):
    preference = complement.selective_preference
    if preference is None:
        return False
    generals = preference.generals
    synsets = preference.synsets
    relations = preference.relations
    synset_relations = preference.synset_relations
    if generals.count() + synsets.count() + relations.count() + synset_relations.count() > 0:
        return True
    return False
    
def examples_added(frame):
    for lexical_unit in frame.lexical_units.all():
        if LexicalUnitExamples.objects.filter(lexical_unit=lexical_unit).exists():
            return True
    return False

def duplicates_exists(frame, actual_frames):
    frames_to_check = actual_frames.exclude(id=frame.id)
    if get_structural_matching_frame(frames_to_check, frame):
        return True
    return False

def schemas_reflex_agreed(lemma, frame):
    agreed = True
    complements = frame.complements.all()
    lexical_units = frame.lexical_units.all()
    for schema in lemma.frames.all():
        schema_agreed = False
        for lex_unit in lexical_units:
            if schema_lex_unit_reflex_agree(lex_unit, schema, complements):
                schema_agreed = True
                break
        if not schema_agreed:
            agreed = False
            break
    return agreed

def schema_lex_unit_reflex_agree(lexical_unit, schema, complements):
    if complements.filter(realizations__frame=schema).exists():
        if (not reflex_with_self_mark_agreed(lexical_unit, schema) and 
            not (lexical_unit.is_reflexive() and not lexical_unit.is_new() and 
             reflex_with_phrase_types_agreed(lexical_unit, schema, complements))):
            return False
    return True

def reflex_with_self_mark_agreed(lexical_unit, schema):
    schema_self_mark = schema.get_char_value('ZWROTNOŚĆ').value
    if not lexical_unit.is_reflexive() == bool(schema_self_mark):
        return False
    return True

def reflex_with_phrase_types_agreed(lexical_unit, schema, complements):
    max_alternations = complements.all().aggregate(Max('realizations__alternation'))['realizations__alternation__max']
    for alternation in range(1, max_alternations+1):
        if not reflex_with_alternation_phrase_types_agreed(complements, schema, alternation):
            return False
    return True

def reflex_with_alternation_phrase_types_agreed(complements, schema, alternation):
    for compl in complements:
        if compl.realizations.filter(argument__type__in=reflex_phrase_types(),
                                     alternation=alternation,
                                     frame=schema).exists():
            return True
    return False

def nonch_pinned(frame):
    if frame.complements.filter(realizations__argument__text_rep='nonch'):
        return True
    return False

def multiplied_same_arg_in_schema(frame):
    for compl in frame.complements.all():
        for real in compl.realizations.all():
            same_frame_realizations = compl.realizations.filter(frame=real.frame,
                                                                alternation=real.alternation)
            if same_frame_realizations.exclude(position=real.position).exists():
                return True
    return False

def validate_schemas(lemma_id):
    error_msg = ''
    lemma = Lemma.objects.get(id=lemma_id, old=False)
    if not all_schemas_used(lemma):
        error_msg = u'Semantyka nie wykorzystuje wszystkich poprawnych schematów walencyjnych.'
    return error_msg

def all_schemas_used(lemma):
    frames = lemma.entry_obj.actual_frames()
    schemas = lemma.frames
    for schema in schemas.all():
        if not schema_is_bad(lemma, schema) and not schema_used(schema, frames):
            return False
    return True

def schema_is_bad(lemma, schema):
    schema_opinion = lemma.frame_opinions.get(frame=schema)
    if schema_opinion.value.short == 'bad':
        return True
    return False
        
def schema_used(schema, frames):
    for frame in frames:
        if frame.complements.filter(realizations__frame=schema).exists():
            return True
    return False

def validate_lexical_units(lemma_id):
    error_msg = ''
    lemma = Lemma.objects.get(id=lemma_id, old=False)
    lexical_units = lemma.entry_obj.meanings
    for lex_unit in lexical_units.all():
        if not examples_reflex_agreed(lex_unit):
            error_msg = u'Semantyka: Znaczenie %s ma podpięte przykłady o niezgodnej zwrotności.' % unicode(lex_unit)
        elif hanging_meaning(lex_unit):
            error_msg = u'Semantyka: Znaczenie %s nie jest reprezentowane przez żadną ramę semantyczną.' % unicode(lex_unit)
        if error_msg:
            break
    return error_msg

def examples_reflex_agreed(lexical_unit):
    lex_examples = LexicalUnitExamples.objects.filter(lexical_unit=lexical_unit)
    for lex_example in lex_examples:
        schema_reflex = lex_example.example.frame.get_char_value('ZWROTNOŚĆ').value
        if (not (lexical_unit.is_reflexive() == bool(schema_reflex)) and 
            not (lexical_unit.is_reflexive() and not lexical_unit.is_new() and
                 lex_example.example.arguments.filter(arguments__type__in=reflex_phrase_types()).exists())):
            return False
    return True

def hanging_meaning(lexical_unit):
    if lexical_unit.luid < 0 and not lexical_unit.actual_frames().exists():
        return True
    return False
    