# -*- coding: utf-8 -*-
# Code for Life
#
# Copyright (C) 2019, Ocado Innovation Limited
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ADDITIONAL TERMS – Section 7 GNU General Public Licence
#
# This licence does not grant any right, title or interest in any “Ocado” logos,
# trade names or the trademark “Ocado” or any other trademarks or domain names
# owned by Ocado Innovation Limited or the Ocado group of companies or any other
# distinctive brand features of “Ocado” as may be secured from time to time. You
# must not distribute any modification of this program using the trademark
# “Ocado” or claim any affiliation or association with Ocado or its employees.
#
# You are not authorised to use the name Ocado (or any of its trade names) or
# the names of any author or contributor in advertising or for publicity purposes
# pertaining to the distribution of this program, without the prior written
# authorisation of Ocado.
#
# Any propagation, distribution or conveyance of this program must include this
# copyright notice and these terms. You must not misrepresent the origins of this
# program; modified versions of the program must be marked as such and not
# identified as the original program.
from __future__ import division

import json
import re

from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db import transaction
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.utils.safestring import mark_safe
from portal.models import Student, Class, Teacher
from portal.templatetags import app_tags

import game.level_management as level_management
import game.messages as messages
import game.permissions as permissions
from game import app_settings
from game import random_road
from game.cache import cached_level_decor, cached_level_blocks
from game.character import get_all_character
from game.decor import get_all_decor, get_decor_element
from game.models import Level, Block
from game.theme import get_all_themes
from game.views.level import LevelSerializer


def level_editor(request):
    """ Renders the level editor page.

    **Context**

    ``RequestContext``
    ``blocks``
        Blocks that can be chosen to be played with later on.
        List of :model:`game.Block`.

    **Template:**

    :template:`game/level_editor.html`
    """

    return render(
        request,
        "game/level_editor.html",
        context={
            "blocks": available_blocks(),
            "decor": get_all_decor(),
            "characters": get_all_character(),
            "themes": get_all_themes(),
            "cow_level_enabled": app_settings.COW_FEATURE_ENABLED,
            "night_mode_feature_enabled": str(
                app_settings.NIGHT_MODE_FEATURE_ENABLED
            ).lower(),
        },
    )


def available_blocks():
    if app_settings.COW_FEATURE_ENABLED:
        return Block.objects.all()
    else:
        return Block.objects.all().exclude(
            type__in=["declare_event", "puff_up", "sound_horn"]
        )


def play_anonymous_level(request, levelId, from_level_editor=True, random_level=False):
    night_mode = (
        False if not app_settings.NIGHT_MODE_FEATURE_ENABLED else "night" in request.GET
    )
    level = Level.objects.filter(id=levelId)

    if not level.exists():
        return redirect(reverse("level_editor"), permanent=True)

    level = level[:1].get()

    if not level.anonymous:
        return redirect(reverse("level_editor"), permanent=True)

    lesson = mark_safe(messages.description_level_default())
    hint = mark_safe(messages.hint_level_default())

    attempt = None
    house = get_decor_element("house", level.theme).url
    cfc = get_decor_element("cfc", level.theme).url
    background = get_decor_element("tile1", level.theme).url
    character = level.character

    character_url = character.top_down
    character_width = character.width
    character_height = character.height
    wreckage_url = "van_wreckage.svg"

    decor_data = cached_level_decor(level)

    if night_mode:
        block_data = level_management.get_night_blocks(level)
        night_mode = "true"
        lesson = messages.title_night_mode()
        model_solution = "[]"
    else:
        block_data = cached_level_blocks(level)
        night_mode = "false"
        model_solution = level.model_solution

    return_view_name = "level_editor" if from_level_editor else "levels"

    level.delete()

    return render(
        request,
        "game/game.html",
        context={
            "level": level,
            "decor": decor_data,
            "blocks": block_data,
            "lesson": lesson,
            "character": character,
            "background": background,
            "house": house,
            "cfc": cfc,
            "hint": hint,
            "attempt": attempt,
            "random_level": random_level,
            "return_url": reverse(return_view_name),
            "character_url": character_url,
            "character_width": character_width,
            "character_height": character_height,
            "wreckage_url": wreckage_url,
            "night_mode": night_mode,
            "night_mode_feature_enabled": str(
                app_settings.NIGHT_MODE_FEATURE_ENABLED
            ).lower(),
            "model_solution": model_solution,
        },
    )


def level_data_for(level):
    return {
        "name": level.name,
        "owner": app_tags.make_into_username(level.owner.user),
        "id": level.id,
    }


def levels_shared_with(user):
    shared_levels = level_management.levels_shared_with(user)
    shared_data = map(level_data_for, shared_levels)
    return shared_data


def levels_owned_by(user):
    levels_owned_by_user = level_management.levels_owned_by(user)
    owned_data = map(level_data_for, levels_owned_by_user)
    return owned_data


def get_list_of_loadable_levels(user):
    owned_data = levels_owned_by(user)
    shared_data = levels_shared_with(user)

    return {"ownedLevels": owned_data, "sharedLevels": shared_data}


def owned_levels(request):
    level_data = levels_owned_by(request.user)
    return HttpResponse(json.dumps(level_data), content_type="application/javascript")


def shared_levels(request):
    level_data = levels_shared_with(request.user)
    return HttpResponse(json.dumps(level_data), content_type="application/javascript")


def get_loadable_levels_for_editor(request):
    response = get_list_of_loadable_levels(request.user)
    return HttpResponse(json.dumps(response), content_type="application/javascript")


def load_level_for_editor(request, levelID):
    level = get_object_or_404(Level, id=levelID)

    if not permissions.can_load_level(request.user, level):
        return HttpResponseUnauthorized()

    level_dict = LevelSerializer(level).data
    level_dict["theme"] = level.theme.id
    level_dict["decor"] = cached_level_decor(level)
    level_dict["blocks"] = cached_level_blocks(level)

    response = {"owned": level.owner == request.user.userprofile, "level": level_dict}

    return HttpResponse(json.dumps(response), content_type="application/javascript")


@transaction.atomic
def save_level_for_editor(request, levelId=None):
    """ Processes a request on creation of the map in the level editor """
    data = json.loads(request.POST["data"])
    if levelId is not None:
        level = get_object_or_404(Level, id=levelId)
    else:
        level = Level(default=False, anonymous=data["anonymous"])
        if permissions.can_create_level(request.user):
            level.owner = request.user.userprofile
    if not permissions.can_save_level(request.user, level):
        return HttpResponseUnauthorized()

    pattern = re.compile("^(\w?[ ]?)*$")
    if pattern.match(data["name"]):
        level_management.save_level(level, data)
        # Add the teacher automatically if it is a new level and the student is not
        # independent
        if (
            (levelId is None)
            and hasattr(level.owner, "student")
            and not level.owner.student.is_independent()
        ):
            level.shared_with.add(level.owner.student.class_field.teacher.user.user)
            level.save()
            if not data["anonymous"]:
                level_management.email_new_custom_level(
                    level.owner.student.class_field.teacher.new_user.email,
                    request.build_absolute_uri(reverse("level_moderation")),
                    request.build_absolute_uri(
                        reverse("play_custom_level", kwargs={"levelId": level.id})
                    ),
                    request.build_absolute_uri(reverse("home")),
                    str(level.owner.student),
                    level.owner.student.class_field.name,
                )
        response = {"id": level.id}
        return HttpResponse(json.dumps(response), content_type="application/javascript")
    else:
        return HttpResponseUnauthorized()


@transaction.atomic
def delete_level_for_editor(request, levelId):
    level = get_object_or_404(Level, id=levelId)

    if not permissions.can_delete_level(request.user, level):
        return HttpResponseUnauthorized()

    level_management.delete_level(level)

    return HttpResponse(json.dumps({}), content_type="application/javascript")


def generate_random_map_for_editor(request):
    """
    Generates a new random path suitable for a random level with the parameters provided
    """
    data = dict(request.POST)

    size = int(data["numberOfTiles"][0])
    branchiness = float(data["branchiness"][0])
    loopiness = float(data["loopiness"][0])
    curviness = float(data["curviness"][0])
    traffic_lights = data["trafficLights"][0] == "true"
    scenery = data["scenery"][0] == "true"

    data = random_road.generate_random_map_data(
        size, branchiness, loopiness, curviness, traffic_lights, scenery, False
    )

    return HttpResponse(json.dumps(data), content_type="application/javascript")


def get_sharing_information_for_editor(request, levelID):
    """ Returns a information about who the level can be and is shared with """
    level = get_object_or_404(Level, id=levelID)
    valid_recipients = {}

    if permissions.can_share_level(request.user, level):
        userprofile = request.user.userprofile
        valid_recipients = {}

        # Note: independent users can't share levels so no need to check
        if hasattr(userprofile, "student"):
            student = userprofile.student

            # First get all the student's classmates
            class_ = student.class_field
            classmates = Student.objects.filter(class_field=class_).exclude(
                id=student.id
            )
            valid_recipients["classmates"] = [
                {
                    "id": classmate.user.user.id,
                    "name": app_tags.make_into_username(classmate.user.user),
                    "shared": level.shared_with.filter(
                        id=classmate.user.user.id
                    ).exists(),
                }
                for classmate in classmates
            ]

            # Then add their teacher as well
            teacher = class_.teacher
            valid_recipients["teacher"] = {
                "id": teacher.user.user.id,
                "name": app_tags.make_into_username(teacher.user.user),
                "shared": level.shared_with.filter(id=teacher.user.user.id).exists(),
            }

        elif hasattr(userprofile, "teacher"):
            teacher = userprofile.teacher

            # First get all the students they teach
            valid_recipients["classes"] = []
            classes_taught = Class.objects.filter(teacher=teacher)
            for class_ in classes_taught:
                students = Student.objects.filter(class_field=class_)
                valid_recipients["classes"].append(
                    {
                        "name": class_.name,
                        "id": class_.id,
                        "students": [
                            {
                                "id": student.user.user.id,
                                "name": app_tags.make_into_username(student.user.user),
                                "shared": level.shared_with.filter(
                                    id=student.user.user.id
                                ).exists(),
                            }
                            for student in students
                        ],
                    }
                )

            if not teacher.school:
                valid_recipients["teachers"] = []
            else:
                fellow_teachers = Teacher.objects.filter(school=teacher.school)
                valid_recipients["teachers"] = [
                    {
                        "id": fellow_teacher.user.user.id,
                        "name": app_tags.make_into_username(fellow_teacher.user.user),
                        "shared": level.shared_with.filter(
                            id=fellow_teacher.user.user.id
                        ).exists(),
                    }
                    for fellow_teacher in fellow_teachers
                    if teacher != fellow_teacher
                ]

    return HttpResponse(
        json.dumps(valid_recipients), content_type="application/javascript"
    )


def share_level_for_editor(request, levelID):
    """ Shares a level with the provided list of recipients """
    recipientIDs = request.POST.getlist("recipientIDs[]")
    action = request.POST.get("action")

    level = get_object_or_404(Level, id=levelID)
    recipients = User.objects.filter(id__in=recipientIDs)

    def can_share_level_with(r):
        return permissions.can_share_level_with(r, level.owner.user)

    users = [
        recipient.userprofile.user
        for recipient in recipients
        if can_share_level_with(recipient)
    ]

    if action == "share":
        level_management.share_level(level, *users)
    elif action == "unshare":
        level_management.unshare_level(level, *users)

    return get_sharing_information_for_editor(request, levelID)


class HttpResponseUnauthorized(HttpResponse):
    def __init__(self):
        super(HttpResponseUnauthorized, self).__init__(
            content="Unauthorized", status=401
        )
