import json
import requests
from django.contrib.auth.decorators import login_required
from django.db import transaction
from django.http import JsonResponse, HttpResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.csrf import csrf_exempt

from common.decorators import ajax_required, ajax
from entries.polish_strings import EXAMPLE_SOURCE, EXAMPLE_OPINION
from entries.views import get_scroller_params, get_alternations, get_prefs_list, schema2dict, frame2dict, collect_forms, \
    get_filtered_objects
from importer.unification.UnificationPreprocessXML import UnificationPreprocessHandler
from meanings.models import LexicalUnit
from semantics.choices import FrameStatus
from semantics.models import Frame, ArgumentRole, SemanticRole, RoleAttribute, RoleType
from syntax.models import Schema
from unifier.models import UnifiedFrameArgument, UnifiedRelationalSelectionalPreference, UnifiedFrame, \
    UnifiedFrame2SlowalFrameMapping, UnifiedFrameArgumentSlowalFrameMapping
from users.models import Assignment
from . import choices
from xml.etree.ElementTree import Element, SubElement, tostring
import io
from xml.sax import handler, make_parser

@ajax_required
@transaction.atomic
def save_synset_preference(request):
    if request.method == 'POST':
        frame_id = request.POST['frame_id']
        complement_id = request.POST['complement_id']
        synset_preference_id = request.POST['synset_preference_id']

        unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=int(frame_id), id=int(complement_id))
        unifiedFrameArgument.synsets.add(int(synset_preference_id))
        unifiedFrameArgument.save()
    return JsonResponse({})

@ajax_required
@transaction.atomic
def save_predefined_preference(request):
    if request.method == 'POST':
        frame_id = request.POST['frame_id']
        complement_id = request.POST['complement_id']
        predefined_preference_id = request.POST['predefined_preference_id']

        unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=int(frame_id), id=int(complement_id))
        unifiedFrameArgument.predefined.add(int(predefined_preference_id))
        unifiedFrameArgument.save()
    return JsonResponse({})

@ajax_required
@transaction.atomic
def save_relational_selectional_preference(request):
    if request.method == 'POST':
        frame_id = request.POST['frame_id']
        complement_id_from = request.POST['complement_id_from']
        complement_id_to = request.POST['complement_id_to']
        relation_id = request.POST['relation_id']

        unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=int(frame_id), id=int(complement_id_from))
        relationalSelectionalPreference = UnifiedRelationalSelectionalPreference(to_id=complement_id_to, relation_id=relation_id)
        relationalSelectionalPreference.save()
        unifiedFrameArgument.relations.add(relationalSelectionalPreference)
        unifiedFrameArgument.save()
    return JsonResponse({})


@ajax_required
def get_unified_frames(request):
    if request.method == 'POST':
        scroller_params = get_scroller_params(request.POST)
        exclude_status = request.GET.get('exclude_status')
        restrict_to_user = request.GET.get('restrict_to_user')

        errors_dict = dict()
        forms = collect_forms(request.session['unified_frame_form'], errors_dict)

        res = {}
        # unifiedFrames = UnifiedFrame.objects.all();

        unifiedFrames = get_filtered_objects(forms).filter()

        for unifiedFrame in unifiedFrames:
            res[unifiedFrame.id] = {
                'id': str(unifiedFrame.id),
                'title': unifiedFrame.title,
                'status': unifiedFrame.status,
                'assignee_username': assignment.user.username if (assignment := unifiedFrame.assignments.first()) else None,
            }

        resProcessed = []
        for key, value in res.items():
            if (exclude_status == None or value['status'] != exclude_status) and \
                    (restrict_to_user == None or value['assignee_username'] == restrict_to_user):
                resProcessed.append(value)

        ret = {
                'draw' : scroller_params['draw'],
                'recordsTotal': len(resProcessed),
                'recordsFiltered': len(resProcessed),
                'data': resProcessed
        }

        return JsonResponse(ret)
    return JsonResponse({})



def unifiedFrame2dict(frame):
    return {
        'id'            : frame.id,
        'title'            : frame.title,
        'status'            : str(frame.status),
        'assignee_username' : assignment.user.username if (assignment := frame.assignments.first()) else None,
        'arguments'     : [
            {
                'str'         : str(a),
                'id'          : a.id,
                'role'        : {
                    'str': '{}{}'.format(a.role.role.role.lower(), ' ' + a.role.attribute.attribute.lower() if a.role.attribute else '') if a.role is not None else None,
                    'id': str(a.role.id)
                } if a.role is not None else None,
                'role_type'   : a.role_type.type.lower() if a.role_type is not None else '',
                'preferences' : get_prefs_list(a),
                'proposed_roles': [{
                    'str': '{}{}'.format(r.role.role.lower(), ' ' + r.attribute.attribute.lower() if r.attribute else ''),
                    'id': str(r.id)
                } for r in a.proposed_roles.all()],
            } for a in sorted(frame.unified_arguments.all(), key=lambda x: x.id)
        ],

        'slowal_frame_mapping': [
            {
                'slowal_frame_id': slowal_frame_mapping.slowal_frame.id,
                'slowal_frame_argument_mapping': [
                    {
                        'unified_frame_agrument_id': slowal_frame_arg_mapping.unified_agrument.id,
                        'slowal_frame_agrument_id': slowal_frame_arg_mapping.slowal_agrument.id,
                    } for slowal_frame_arg_mapping in slowal_frame_mapping.unified_frame_argument_mapping.all()
                ]
            } for slowal_frame_mapping in frame.unified_frame_2_slowal_frame.all()
        ]

    }

def get_examples(frames):
    examples = []
    for frame in frames:
        for argument in frame.arguments.all():
            for connection in argument.example_connections.all():
                example = connection.example
                frame_ids, argument_ids, lu_ids, schema_ids, phrases, phrases_syntax, positions = set(), set(), set(), set(), set(), set(), set()
                for connection in example.example_connections.all():
                    for argument in connection.arguments.all():
                        frame_ids.add(argument.frame.id)
                        argument_ids.add('{}-{}'.format(argument.frame.id, argument.id))
                    if connection.lexical_unit:
                        lu_ids.add(connection.lexical_unit.id)
                    for hook in connection.schema_connections.all():
                        schema_ids.add(hook.schema.id);
                        phrases.add('{}-{}-{}-{}'.format(hook.schema.id, hook.position.id, hook.phrase_type.id, hook.alternation - 1))
                        phrases_syntax.add('{}-{}-{}'.format(hook.schema.id, hook.position.id, hook.phrase_type.id))
                        positions.add('{}-{}'.format(hook.schema.id, hook.position.id))
                elem = {
                    'id'             : str(example.id),
                    'sentence'       : example.sentence,
                    'source'         : EXAMPLE_SOURCE()[example.source.key],
                    'opinion'        : EXAMPLE_OPINION()[example.opinion.key],
                    'note'           : example.note,
                    'frame_ids'      : sorted(frame_ids),
                    'argument_ids'   : sorted(argument_ids),
                    'lu_ids'         : sorted(lu_ids),
                    'schema_ids'     : sorted(schema_ids),
                    'phrases'        : sorted(phrases),
                    'phrases_syntax' : sorted(phrases_syntax),
                    'positions'      : sorted(positions),
                }
                if elem not in examples:
                    examples.append(elem)
    return sorted(examples, key=lambda x: x['sentence'])

def get_unified_frame_json(unifiedFrame, request):

    slowal_frames = [connection.slowal_frame for connection in unifiedFrame.unified_frame_2_slowal_frame.all()]

    all_schema_objects = Schema.objects.filter(schema_hooks__argument_connections__argument__frame__in=slowal_frames).distinct()

    slowal_frames_dict = []
    for slowal_frame in slowal_frames:
        dict = frame2dict(slowal_frame, slowal_frame.lexical_units.all())
        slowal_frames_dict.append(dict)

    alternations, realisation_phrases, realisation_descriptions = get_alternations(all_schema_objects, slowal_frames)
    examples = get_examples(slowal_frames)

    all_schema_objects_dict = [schema2dict(schema, schema.subentries.all()[0].negativity, request.LANGUAGE_CODE) for schema in all_schema_objects]

    subentries = [{
        'str': None,
        'schemata': all_schema_objects_dict
    }]

    unifiedFrame_dict = unifiedFrame2dict(unifiedFrame)

    return { 'unified_frame_id': unifiedFrame.id, 'unified_frame': unifiedFrame_dict, 'subentries': subentries, 'frames' : slowal_frames_dict, 'alternations' : alternations, 'realisation_phrases' : realisation_phrases, 'realisation_descriptions' : realisation_descriptions, 'examples' : examples, 'last_visited' : request.session['last_visited'] }

@ajax_required
@login_required
def get_unified_frame(request):
    if request.method == 'POST':
        #TODO (*)
        #form = EntryForm(request.POST)
        unified_frame_id = request.POST['unified_frame_id']
        #TODO (*)
        if unified_frame_id.isdigit():# and form.is_valid():
            unified_frame_id = int(unified_frame_id)
            request.session.modified = True
            unifiedFrame = UnifiedFrame.objects.get(id=unified_frame_id)
            return JsonResponse(get_unified_frame_json(unifiedFrame, request))

    return JsonResponse({})

@ajax_required
@transaction.atomic
def extract_frames_to_new_frame(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        target_unified_frame_id = request.POST['target_unified_frame_id']
        slowal_frame_ids = json.loads(request.POST['slowal_frame_ids'])

        unified_frame = UnifiedFrame.objects.get(id=unified_frame_id)
        slowal_frames = Frame.objects.filter(id__in=slowal_frame_ids)
        new_unified_frame = None
        if target_unified_frame_id != '':
            new_unified_frame = UnifiedFrame.objects.get(id=target_unified_frame_id)
        new_frame_fullfiled_and_saved = unified_frame.extract_frames_to(slowal_frames=slowal_frames, new_frame=new_unified_frame)
        return JsonResponse(get_unified_frame_json(new_frame_fullfiled_and_saved, request))
    return JsonResponse({})

@ajax_required
@transaction.atomic
def remove_selectional_preference(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        argument_id = request.POST['argument_id']
        preference_ids = json.loads(request.POST['preference_ids'])

        unified_frame_argument = UnifiedFrameArgument.objects.get(id=argument_id, unified_frame=unified_frame_id)
        unified_frame_argument.predefined.set(unified_frame_argument.predefined.exclude(id__in=preference_ids))
        unified_frame_argument.synsets.set(unified_frame_argument.synsets.exclude(id__in=preference_ids))
        unified_frame_argument.relations.set(unified_frame_argument.relations.exclude(id__in=preference_ids))
        unified_frame_argument.save()

        return JsonResponse({})
    return JsonResponse({})

@ajax_required
@transaction.atomic
def duplicate_unified_frame(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        target_unified_frame_title = request.POST['target_unified_frame_title']

        unified_frame = UnifiedFrame.objects.get(id=unified_frame_id)
        new_frame = unified_frame.duplicate(new_frame_title=target_unified_frame_title)
        return JsonResponse(get_unified_frame_json(new_frame, request))
    return JsonResponse({})

@ajax_required
@transaction.atomic
def change_slowal2unified_fram_argument_mapping(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        slowal_frame_id = request.POST['slowal_frame_id']
        slowal_frame_selected_arguments = json.loads(request.POST['slowal_frame_selected_arguments'])


        unifiedFrame2SlowalFrameMapping = UnifiedFrame2SlowalFrameMapping.objects.get(unified_frame_id=unified_frame_id, slowal_frame=slowal_frame_id)

        arg1_id = int(slowal_frame_selected_arguments[0])
        arg2_id = int(slowal_frame_selected_arguments[1])

        unifiedFrameArgumentMapping1 = UnifiedFrameArgumentSlowalFrameMapping.objects.get(unified_frame_mapping=unifiedFrame2SlowalFrameMapping,
                                                                                             slowal_agrument_id=arg1_id) if arg1_id >= 0 else None
        unifiedFrameArgumentMapping2 = UnifiedFrameArgumentSlowalFrameMapping.objects.get(unified_frame_mapping=unifiedFrame2SlowalFrameMapping,
                                                                                          slowal_agrument_id=arg2_id) if arg2_id >= 0 else None

        if unifiedFrameArgumentMapping1 is not None and unifiedFrameArgumentMapping2 is not None:
            #mamy oba argumenty, zamieniamy miescami
            unifiedFrameArgumentMapping1.slowal_agrument_id = arg2_id
            unifiedFrameArgumentMapping2.slowal_agrument_id = arg1_id
            unifiedFrameArgumentMapping1.save()
            unifiedFrameArgumentMapping2.save()
        elif unifiedFrameArgumentMapping1 is not None and unifiedFrameArgumentMapping2 is None:
            #mamy lewy argument, prawy jest Empty
            unifiedFrameArgumentMapping1.unified_agrument_id = -arg2_id
            unifiedFrameArgumentMapping1.save()
        elif unifiedFrameArgumentMapping1 is None and unifiedFrameArgumentMapping2 is not None:
            #mamy prawy argument, lewy jest Empty
            unifiedFrameArgumentMapping2.unified_agrument_id = -arg1_id
            unifiedFrameArgumentMapping2.save()

        return JsonResponse({})
    return JsonResponse({})

@ajax_required
@transaction.atomic
def change_slowal_frame_status(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        slowal_frame_id = request.POST['slowal_frame_id']
        status = request.POST['status']

        frame = Frame.objects.get(pk=slowal_frame_id)
        frame.status = status
        frame.save()
        return JsonResponse({})
    return JsonResponse({})

@ajax_required
@transaction.atomic
def save_unified_frame_title(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        unified_frame_title = request.POST['unified_frame_title']

        unifiedFrame = UnifiedFrame.objects.get(id=unified_frame_id)

        if unifiedFrame:
            unifiedFrame.title = unified_frame_title
            unifiedFrame.save()
    return JsonResponse({})

@ajax_required
@transaction.atomic
def save_selected_role(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        complement_id = request.POST['complement_id']
        role_id = request.POST['role_id']

        unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=int(unified_frame_id), id=int(complement_id))
        unifiedFrameArgument.role_id = role_id
        unifiedFrameArgument.save()
    return JsonResponse({})

@ajax_required
@transaction.atomic
def save_new_role(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        complement_id = request.POST['complement_id']
        role_id = request.POST['role_id']
        role_type = request.POST['role_type']
        attribute_id = request.POST.get('attribute_id', None)

        argumentRole = ArgumentRole.objects.filter(role_id=role_id, attribute_id=attribute_id).first()
        if argumentRole is None:
            argumentRole = ArgumentRole(role=SemanticRole.objects.get(pk=role_id), attribute=RoleAttribute.objects.get(pk=attribute_id))
            argumentRole.save()

        unifiedFrameArgument = UnifiedFrameArgument.objects.get(unified_frame_id=unified_frame_id, id=complement_id)
        unifiedFrameArgument.role = argumentRole
        unifiedFrameArgument.role_type = RoleType.objects.get(type=role_type)
        unifiedFrameArgument.save()
    return JsonResponse({})

@ajax_required
@transaction.atomic
def add_argument(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']

        unifiedFrame = UnifiedFrame.objects.get(pk=unified_frame_id)
        newUnifiedFrameArgument = UnifiedFrameArgument.objects.create(unified_frame=unifiedFrame)
        newUnifiedFrameArgument.save()
        unifiedFrame.arguments_count = unifiedFrame.arguments_count + 1
        unifiedFrame.save()
    return JsonResponse({})

@ajax_required
@transaction.atomic
def remove_argument(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        complement_id = request.POST['complement_id']

        newUnifiedFrameArgument = UnifiedFrameArgument.objects.get(id=complement_id)
        newUnifiedFrameArgument.delete()
        unifiedFrame = UnifiedFrame.objects.get(pk=unified_frame_id)
        unifiedFrame.arguments_count = unifiedFrame.arguments_count - 1
        unifiedFrame.save()
    return JsonResponse({})

@ajax_required
@transaction.atomic
def change_unified_frame_status_to_ready(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        unifiedFrame = UnifiedFrame.objects.get(pk=unified_frame_id)
        unifiedFrame.status = choices.UnifiedFrameStatus.READY

        for mapping in unifiedFrame.unified_frame_2_slowal_frame.all():
            mapping.verified = True
            mapping.save()

        unifiedFrame.save()
    return JsonResponse({})

def create_unified_frame(lu_id):
    response = requests.post('http://127.0.0.1:8000/en/unifier/build_unified_frame_xml/?lu_id='+str(lu_id))

    parser = make_parser()
    parser.setFeature(handler.feature_external_ges, False)

    unifiedFrameXMLHandler = UnificationPreprocessHandler()
    parser.setContentHandler(unifiedFrameXMLHandler)

    f = io.StringIO(response.content.decode("utf-8"))

    parser.parse(f)

    unified_frame_id = unifiedFrameXMLHandler.unified_frame_ids[0]

    return unified_frame_id

@csrf_exempt
def build_unified_frame_xml(request):
    if request.method == 'POST':
        lu_id = request.GET.get('lu_id')

        lu = LexicalUnit.objects.get(pk=lu_id)
        frames = list(lu.frames.all())
        if len(frames) > 0:
            matchingElem = Element('matching')
            unifier_frameElem = SubElement(matchingElem, 'unifier_frame')

            frame = frames[0]

            arguments = list(frame.arguments.all())
            argCnt = len(arguments)
            for id in range(argCnt):
                argumentElem = SubElement(unifier_frameElem, 'argument', attrib={'id': str(id)})
                semantic_role_typeElem = SubElement(argumentElem, 'semantic_role', attrib={'type': 'role'})
                rolesElem = SubElement(argumentElem, 'roles')
                roleElem1 = SubElement(rolesElem, 'role', attrib={'name': 'Initiator'})
                roleElem2 = SubElement(rolesElem, 'role', attrib={'name': 'Manner'})

            connectionsElem = SubElement(unifier_frameElem, 'connections')

            slowal_frameElem = SubElement(connectionsElem, 'slowal_frame', attrib={'id': str(frame.id)})

            arguments_connectionsElem = SubElement(slowal_frameElem, 'arguments_connections')

            for id, argument in enumerate(arguments):
                arguments_connectionElem = SubElement(arguments_connectionsElem, 'arguments_connection',
                                                      attrib={'unifier_argument_id': str(id), 'slowal_id': str(argument.id)})
            xml = tostring(matchingElem)
            return HttpResponse(xml)

    return HttpResponse()

@ajax_required
@transaction.atomic
def save_unified_frame_title(request):
    if request.method == 'POST':
        unified_frame_id = request.POST['unified_frame_id']
        unified_frame_title = request.POST['unified_frame_title']

        unifiedFrame = UnifiedFrame.objects.get(id=unified_frame_id)

        if unifiedFrame:
            unifiedFrame.title = unified_frame_title
            unifiedFrame.save()
    return JsonResponse({})

@ajax_required
@transaction.atomic
def frame_assign(request):
    if request.method == 'POST':

        lu_id = request.POST['lu_id']
        unified_frame_title = request.POST['unified_frame_title']

        unified_frame_pk = create_unified_frame(lu_id)

        unifiedFrame = get_object_or_404(
            UnifiedFrame.objects,
            pk=unified_frame_pk,
        )
        unifiedFrame.title = unified_frame_title
        unifiedFrame.save()

        Assignment.assign(user=request.user, subject=unifiedFrame)

        slowal_frames = [connection.slowal_frame for connection in unifiedFrame.unified_frame_2_slowal_frame.all()]

        for slowal_frame in slowal_frames:
            slowal_frame.status = FrameStatus.PROCESSING
            Assignment.assign(user=request.user, subject=slowal_frame)
            slowal_frame.save()

        ret = {
            'unified_frame_id': unified_frame_pk
        }

        return JsonResponse(ret)
    return JsonResponse({})

def removeUnifiedFrameMappingsAndAssigments(unified_frame_id):
    #odpianie z ramy zunifikowanej
    unifiedFrame2SlowalFrameMappings = UnifiedFrame2SlowalFrameMapping.objects.filter(unified_frame_id=unified_frame_id)
    for unifiedFrame2SlowalFrameMapping in unifiedFrame2SlowalFrameMappings:
        unifiedFrame2SlowalFrameMapping.slowal_frame.status = 'N'
        unifiedFrame2SlowalFrameMapping.slowal_frame.save()
        Assignment.delete(subject_id=unifiedFrame2SlowalFrameMapping.slowal_frame.id)
        unifiedFrameArgumentSlowalFrameMappings = UnifiedFrameArgumentSlowalFrameMapping.objects.filter(unified_frame_mapping=unifiedFrame2SlowalFrameMapping)
        unifiedFrameArgumentSlowalFrameMappings.delete()
        unifiedFrame2SlowalFrameMapping.delete()
    Assignment.delete(subject_id=unified_frame_id)

@ajax(login_required=True, method='post')
@transaction.atomic
def delete_unified_frame(request, unified_frame_id):

    removeUnifiedFrameMappingsAndAssigments(unified_frame_id)
    UnifiedFrameArgument.objects.filter(unified_frame_id=unified_frame_id).delete()
    UnifiedFrame.objects.get(id=unified_frame_id).delete()

    return {}