import json
import random

from math import sin, cos

from django.conf import settings
from django.core.exceptions import ValidationError
from django.shortcuts import get_object_or_404

from tastypie import fields
from tastypie import http
from tastypie.authentication import MultiAuthentication, Authentication, BasicAuthentication, SessionAuthentication
from tastypie.resources import ModelResource, convert_post_to_patch
from tastypie.exceptions import BadRequest

from aspects.models import Element, Facet
from geography.models import Country
from spirithunter import logger

from .authorization import SpiritAuthorization

from ..forms import PatchForm
from ..models.spirit import ElementalStrength, Spirit

class ImageMixin(object):

    def dehydrate(self, bundle):

        bundle.data.update({
            "images": {}
        })

        for size in self.AVAILABLE_IMAGE_SIZES:
            bundle.data["images"][str(size)] = getattr(
                bundle.obj,
                'image{size}'.format(size=size)
            )

        return bundle



class ElementResource(ImageMixin, ModelResource):

    AVAILABLE_IMAGE_SIZES = (16, 32)

    class Meta:
        queryset = Element.objects.all()
        include_resource_uri = False
        resource_name = "elements"



class ElementalStrengthResource(ModelResource):

    AVAILABLE_IMAGE_SIZES = (16, 32)

    element = fields.ToOneField(ElementResource, "element", full=True)

    class Meta:
        queryset = ElementalStrength.objects.all()
        include_resource_uri = False
        resource_name = "elements"



class FacetResource(ImageMixin, ModelResource):

    AVAILABLE_IMAGE_SIZES = (16, 32)

    class Meta:
        queryset = Facet.objects.all()
        include_resource_uri = False
        resource_name = "facets"



class NationalityResource(ModelResource):

    class Meta:
        queryset = Country.objects.all()
        include_resource_uri = False
        resource_name = "nationalities"

    def dehydrate(self, bundle):
        return {
            "code": bundle.obj.country.code,
            "name": bundle.obj.country.name,
        }



class SpiritResource(ImageMixin, ModelResource):

    AVAILABLE_IMAGE_SIZES = (16, 32, 64, 128, 256)
    SPIRITS_TO_GENERATE = 5
    SPAWN_RADIUS = 50

    owner = fields.ToOneField("users.api.UserResource", "owner", null=True)
    elementals = fields.ManyToManyField(
        ElementalStrengthResource,
        "elemental_strengths",
        full=True
    )
    facets = fields.ManyToManyField(
        FacetResource,
        "facets",
        full=True
    )
    nationalities = fields.ManyToManyField(
        NationalityResource,
        "nationalities",
        full=True
    )

    class Meta:
        allowed_methods = ("get", "patch",)
        authentication = MultiAuthentication(
            SessionAuthentication(),
            BasicAuthentication(),
            Authentication()
        )
        authorization = SpiritAuthorization()
        object_class = Spirit
        queryset = Spirit.objects.all()
        resource_name = "spirits"
        filtering = {
            "id": ("exact",),
            "owner": ("exact",),
            "activity": ("exact",),
        }


    def dehydrate(self, bundle):

        bundle = ModelResource.dehydrate(self, bundle)
        bundle = ImageMixin.dehydrate(self, bundle)

        if bundle.obj.activity == Spirit.ACTIVITY_WANDER:
            if bundle.obj.health_current == 0:
                bundle.data["experience_given"] = bundle.obj.get_ladder().xp_given

        return bundle


    @staticmethod
    def dehydrate_origin(bundle):
        if bundle.obj.origin:
            r = json.loads(bundle.obj.origin.geojson)
            r["coordinates"][0] = round(r["coordinates"][0], settings.COORDINATES_ROUNDING)
            r["coordinates"][1] = round(r["coordinates"][1], settings.COORDINATES_ROUNDING)
            return r
        return None


    @staticmethod
    def dehydrate_location(bundle):
        if bundle.obj.location:
            r = json.loads(bundle.obj.location.geojson)
            r["coordinates"][0] = round(r["coordinates"][0], settings.COORDINATES_ROUNDING)
            r["coordinates"][1] = round(r["coordinates"][1], settings.COORDINATES_ROUNDING)
            return r
        return None


    @staticmethod
    def dehydrate_activity(bundle):
        return {
            "id": bundle.obj.activity,
            "name": bundle.obj.get_activity_display()
        }


    def obj_get_list(self, bundle, **kwargs):

        if bundle.request.GET.get("finder"):

            if not bundle.request.location:
                raise BadRequest(
                    "Finder cannot be invoked without a location header"
                )

            if not bundle.request.user.is_authenticated():
                raise BadRequest(
                    "Finder is only available to authenticated users"
                )

            try:
                return self._finder(bundle.request)
            except ValidationError as e:
                raise BadRequest(e.messages[0])

        else:

            return ModelResource.obj_get_list(self, bundle, **kwargs)


    def patch_list(self, request, **kwargs):
        return http.HttpNotImplemented()


    def patch_detail(self, request, **kwargs):

        pk = kwargs.get("pk")
        request = convert_post_to_patch(request)

        self.authorized_update_detail(
            Spirit.objects.filter(pk=pk),
            self.build_bundle(request=request)
        )

        form = PatchForm(
            request,
            get_object_or_404(Spirit, pk=pk),
            self.deserialize(
                request,
                request.body,
                format=request.META.get("CONTENT_TYPE", "application/json")
            )
        )

        if form.is_valid():
            form.save()
            return self.create_response(request, "", status=202)

        raise BadRequest(form.errors.as_text())


    def _finder(self, request):
        """
        Open the app and show me what's here.  If there's nothing here (common)
        make some spirits relevant to the environment to play with.
        """

        lat, lng = (request.location.y, request.location.x)

        if lat > 80 or lat < -80:
            raise ValidationError("Invalid lat value: %s" % lat)

        if lng > 180 or lng < -180:
            raise ValidationError("Invalid lng value: %s" % lng)

        level_low, level_high = 1, 1
        if request.user.is_authenticated():
            spirit_levels = sorted(
                request.user.spirits.filter(
                    activity=Spirit.ACTIVITY_JARRED
                ).values_list(
                    "level",
                    flat=True
                )
            )
            if spirit_levels:
                level_low, level_high = spirit_levels[0], spirit_levels[-1]

        spirits = list(Spirit.objects.filter(
            activity=Spirit.ACTIVITY_WANDER,
            health_current__gt=0,
            location__distance_lte=(request.location, self.SPAWN_RADIUS)
        ))

        while len(spirits) < self.SPIRITS_TO_GENERATE:

            # Magic

            centre_x = float(lat)
            centre_y = float(lng)
            r  = random.uniform(0, self.SPAWN_RADIUS)
            a  = random.uniform(0, 360)

            target_x = centre_x + ((r * cos(a)) / settings.M_LNG)
            target_y = centre_y + ((r * sin(a)) / settings.M_LAT)

            # /Magic

            logger.debug("Creating a spirit at {lat},{lng}".format(
                lat=target_x,
                lng=target_y
            ))

            spirit = Spirit.objects.create_for_environment(
                centre=(centre_x, centre_y),
                target=(target_x, target_y),
                level_low=level_low,
                level_high=level_high
            )
            spirits.append(spirit)

        # Feel lucky?
        if random.randint(1, 10) == 5:
            # Start encounter immediately
            pass

        return SpiritResource.get_object_list(self, request).filter(
            activity=Spirit.ACTIVITY_WANDER,
            health_current__gt=0,
            location__distance_lte=(request.location, 5000)
        )

