##################################################################################################
# Copyright (c) 2012 Brett Dixon
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
##################################################################################################

"""
Gallery API

::

    GET     /        Lists the galleries currently visible by the current user
    POST    /        Creates a gallery object
    GET     /id      Gallery object if visible by the current user
    PUT     /id      Adds image or video objects to the gallery
    DELETE  /id      Removes image or video objects from the gallery
    GET     /filter  Returns a filtered list of image and video objects
"""

import time
import functools
import logging

import requests
from django.core.mail import mail_managers
from django.http import JsonResponse
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.db.models import Q, Count
from django.db import connection
from django.db.utils import ProgrammingError
from django.template.loader import render_to_string
from django.views.decorators.http import require_POST
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.decorators import login_required
from django.conf import settings

import six
import json

try:
    from haystack.query import SearchQuerySet

    HAYSTACK = True
except (ImportError, ImproperlyConfigured):
    HAYSTACK = False

from frog.models import (
    Gallery,
    Image,
    Video,
    Group,
    GallerySubscription,
    SiteConfig,
    Piece,
)
from frog.common import Result, getObjectsFromGuids, getClientIP

LOGGER = logging.getLogger("frog")
try:
    QUERY_MODELS = [
        _
        for _ in ContentType.objects.filter(app_label="frog")
        if issubclass(_.model_class(), Piece)
    ]
except ProgrammingError:
    pass
BATCH_LENGTH = 75


def index(request, obj_id=None):
    """Handles a request based on method and calls the appropriate function"""
    if request.method == "GET":
        return get(request, obj_id)
    elif request.method == "POST":
        return post(request)
    elif request.method == "PUT":
        return put(request, obj_id)
    elif request.method == "DELETE":
        return delete(request, obj_id)


def get(request, obj_id=None):
    if obj_id:
        obj = Gallery.objects.get(pk=obj_id)
        if obj.security != Gallery.PUBLIC and request.user.is_anonymous:
            raise PermissionDenied
    else:
        res = Result()

        personal = []
        clearance = Gallery.PUBLIC

        if request.user.is_authenticated:
            personal = Gallery.objects.filter(
                security=Gallery.PERSONAL, owner=request.user
            )
            try:
                clearance = request.user.frog_prefs.first().clearance
            except AttributeError:
                clearance = Gallery.PUBLIC

        # Staff members should see everything
        if request.user.is_staff:
            clearance = Gallery.GUARDED

        objects = Gallery.objects.filter(security__lte=clearance)
        ids = []

        for gallery in objects:
            if gallery.security == Gallery.PERSONAL:
                continue
            if gallery.id in ids:
                continue

            ids.append(gallery.id)
            res.append(gallery.json())

        for gallery in personal:
            res.append(gallery.json())

        return JsonResponse(res.asDict())


@login_required
def post(request):
    """ Create a Gallery """
    defaultname = "New Gallery %i" % Gallery.objects.all().count()
    data = json.loads(request.body)["body"]
    title = data.get("title", defaultname)
    description = data.get("description", "")
    security = int(
        data.get("security", request.user.frog_prefs.first().clearance)
    )

    g, created = Gallery.objects.get_or_create(title=title)
    g.security = security
    g.description = description
    g.owner = request.user
    g.save()

    res = Result()
    res.append(g.json())
    res.message = "Gallery created" if created else ""

    return JsonResponse(res.asDict())


@login_required
def put(request, obj_id=None):
    """ Adds Image and Video objects to Gallery based on GUIDs """
    data = json.loads(request.body)["body"]
    guids = data.get("guids", "").split(",")
    move = data.get("from")
    security = data.get("security")
    gallery = Gallery.objects.get(pk=obj_id)

    # Set the security first so subsequent securityChecks will get the correct security level
    if security is not None:
        gallery.security = json.loads(security)
        gallery.save()
        for child in gallery.gallery_set.all():
            child.security = gallery.security
            child.save()

    if guids:
        items = getObjectsFromGuids(guids)
        gallery.addItems(items)

        if move:
            fromgallery = Gallery.objects.get(pk=move)
            fromgallery.removeItems(items)

    res = Result()
    res.append(gallery.json())

    return JsonResponse(res.asDict())


@login_required
def delete(request, obj_id=None):
    """ Removes ImageVideo objects from Gallery """
    data = json.loads(request.body)
    guids = data.get("guids").split(",")
    items = getObjectsFromGuids(guids)
    gallery = Gallery.objects.get(pk=obj_id)

    LOGGER.info(
        "{} removed {} from {}".format(request.user.email, guids, gallery)
    )

    gallery.removeItems(items)

    res = Result()

    return JsonResponse(res.asDict())


@login_required
def filterObjects(request, obj_id):
    """
    Filters Gallery for the requested ImageVideo objects.  Returns a Result object with
    serialized objects
    """
    if int(obj_id) == 0:
        obj = None
    else:
        obj = Gallery.objects.get(pk=obj_id)

    isanonymous = request.user.is_anonymous

    if isanonymous and obj is None:
        LOGGER.warning(
            "There was an anonymous access attempt from {} to {}".format(
                getClientIP(request), obj
            )
        )
        raise PermissionDenied()

    if isanonymous and obj and obj.security != Gallery.PUBLIC:
        LOGGER.warning(
            "There was an anonymous access attempt from {} to {}".format(
                getClientIP(request), obj
            )
        )
        raise PermissionDenied()

    if obj and obj.security != Gallery.PERSONAL:
        if request.user.frog_prefs.first().clearance < obj.security:
            raise PermissionDenied()

    tags = json.loads(request.GET.get("filters", "[[]]"))
    more = json.loads(request.GET.get("more", "false"))
    orderby = request.GET.get(
        "orderby", request.user.frog_prefs.get().json()["orderby"]
    )

    tags = [t for t in tags if t]

    return _filter(request, obj, tags=tags, more=more, orderby=orderby)


def _filter(request, object_, tags=None, more=False, orderby="created"):
    """Filters Piece objects from self based on filters, search, and range

    :param tags: List of tag IDs to filter
    :type tags: list
    :param more -- bool, Returns more of the same filtered set of images based on session range

    return list, Objects filtered
    """
    res = Result()

    idDict = {}
    objDict = {}
    data = {}
    modelmap = {}

    # Get all IDs for each model
    for m in QUERY_MODELS:
        modelmap[m.model_class()] = m.model

        if object_:
            idDict[m.model] = m.model_class().objects.filter(gallery=object_)
        else:
            idDict[m.model] = m.model_class().objects.all()

        if idDict[m.model] is None:
            continue

        if tags:
            for bucket in tags:
                searchQuery = ""
                o = None
                for item in bucket:
                    if item == 0:
                        # filter by tagless
                        idDict[m.model].annotate(num_tags=Count("tags"))
                        if not o:
                            o = Q()
                        o |= Q(num_tags__lte=1)
                        break
                    elif isinstance(item, six.integer_types):
                        # filter by tag
                        if not o:
                            o = Q()
                        o |= Q(tags__id=item)
                    else:
                        # add to search string
                        searchQuery += item + " "
                        if not HAYSTACK:
                            if not o:
                                o = Q()
                            # use a basic search
                            o |= Q(title__icontains=item)

                if HAYSTACK and searchQuery != "":
                    # once all tags have been filtered, filter by search
                    searchIDs = search(searchQuery, m.model_class())
                    if searchIDs:
                        if not o:
                            o = Q()
                        o |= Q(id__in=searchIDs)

                if o:
                    # apply the filters
                    idDict[m.model] = (
                        idDict[m.model]
                        .annotate(num_tags=Count("tags"))
                        .filter(o)
                    )
                else:
                    idDict[m.model] = idDict[m.model].none()

        # Remove hidden items before slicing so we get an accurate count
        idDict[m.model] = idDict[m.model].exclude(hidden=True)
        
        # Remove deleted items before slicing so we get an accurate count
        idDict[m.model] = idDict[m.model].exclude(deleted=True)

        # Get all ids of filtered objects, this will be a very fast query
        idDict[m.model] = list(
            idDict[m.model]
            .order_by("-{}".format(orderby))
            .values_list("id", flat=True)
        )
        lastid = request.session.get("last_{}".format(m.model), 0)
        if not idDict[m.model]:
            continue

        if not more:
            lastid = idDict[m.model][0]

        try:
            index = idDict[m.model].index(lastid)
        except ValueError:
            index = 0

        if more and lastid != 0:
            index += 1
        idDict[m.model] = idDict[m.model][index : index + BATCH_LENGTH]

        # perform the main query to retrieve the objects we want
        objDict[m.model] = m.model_class().objects.filter(
            id__in=idDict[m.model]
        )
        objDict[m.model] = (
            objDict[m.model]
            .select_related("author")
            .prefetch_related("tags")
            .order_by("-{}".format(orderby))
        )
        objDict[m.model] = list(objDict[m.model])

    # combine and sort all objects by date
    objects = _sortObjects(orderby, **objDict)
    objects = objects[:BATCH_LENGTH]

    # Find out last ids
    lastids = {}
    for obj in objects:
        lastids["last_{}".format(modelmap[obj.__class__])] = obj.id

    for key, value in lastids.items():
        request.session[key] = value

    # serialize objects
    for i in objects:
        res.append(i.json())

    data["count"] = len(objects)
    if settings.DEBUG:
        data["queries"] = connection.queries

    res.value = data

    return JsonResponse(res.asDict())


def _sortObjects(orderby="created", **kwargs):
    """Sorts lists of objects and combines them into a single list"""
    o = []

    for m in kwargs.values():
        for l in iter(m):
            o.append(l)
    o = list(set(o))
    sortfunc = _sortByCreated if orderby == "created" else _sortByModified
    if six.PY2:
        o.sort(sortfunc)
    else:
        o.sort(key=functools.cmp_to_key(sortfunc))

    return o


def _sortByCreated(a, b):
    """Sort function for object by created date"""
    if a.created < b.created:
        return 1
    elif a.created > b.created:
        return -1
    else:
        return 0


def _sortByModified(a, b):
    """Sort function for object by modified date"""
    if a.modified < b.modified:
        return 1
    elif a.modified > b.modified:
        return -1
    else:
        return 0


def search(query, model):
    """ Performs a search query and returns the object ids """
    query = query.strip()
    LOGGER.debug(query)
    sqs = SearchQuerySet()
    results = sqs.raw_search("{}*".format(query)).models(model)
    if not results:
        results = sqs.raw_search("*{}".format(query)).models(model)
    if not results:
        results = sqs.raw_search("*{}*".format(query)).models(model)

    return [o.pk for o in results]


@require_POST
@login_required
def subscribe(request, obj_id):
    gallery = Gallery.objects.get(pk=obj_id)
    data = json.loads(request.body)["body"]
    frequency = data.get("frequency", GallerySubscription.WEEKLY)

    sub, created = GallerySubscription.objects.get_or_create(
        gallery=gallery, user=request.user, frequency=frequency
    )

    if not created:
        # it already existed so delete it
        sub.delete()

    return JsonResponse(Result().asDict())

[((2661, 2686), 'logging.getLogger', 'logging.getLogger', (['"""frog"""'], {}), "('frog')\n", (2678, 2686), False, 'import logging\n'), ((4852, 4894), 'frog.models.Gallery.objects.get_or_create', 'Gallery.objects.get_or_create', ([], {'title': 'title'}), '(title=title)\n', (4881, 4894), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((5004, 5012), 'frog.common.Result', 'Result', ([], {}), '()\n', (5010, 5012), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((5415, 5445), 'frog.models.Gallery.objects.get', 'Gallery.objects.get', ([], {'pk': 'obj_id'}), '(pk=obj_id)\n', (5434, 5445), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((5977, 5985), 'frog.common.Result', 'Result', ([], {}), '()\n', (5983, 5985), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((6171, 6195), 'json.loads', 'json.loads', (['request.body'], {}), '(request.body)\n', (6181, 6195), False, 'import json\n'), ((6249, 6275), 'frog.common.getObjectsFromGuids', 'getObjectsFromGuids', (['guids'], {}), '(guids)\n', (6268, 6275), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((6290, 6320), 'frog.models.Gallery.objects.get', 'Gallery.objects.get', ([], {'pk': 'obj_id'}), '(pk=obj_id)\n', (6309, 6320), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((6463, 6471), 'frog.common.Result', 'Result', ([], {}), '()\n', (6469, 6471), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((8178, 8186), 'frog.common.Result', 'Result', ([], {}), '()\n', (8184, 8186), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((13200, 13216), 'haystack.query.SearchQuerySet', 'SearchQuerySet', ([], {}), '()\n', (13214, 13216), False, 'from haystack.query import SearchQuerySet\n'), ((13572, 13602), 'frog.models.Gallery.objects.get', 'Gallery.objects.get', ([], {'pk': 'obj_id'}), '(pk=obj_id)\n', (13591, 13602), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((13733, 13836), 'frog.models.GallerySubscription.objects.get_or_create', 'GallerySubscription.objects.get_or_create', ([], {'gallery': 'gallery', 'user': 'request.user', 'frequency': 'frequency'}), '(gallery=gallery, user=request.\n    user, frequency=frequency)\n', (13774, 13836), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((3343, 3373), 'frog.models.Gallery.objects.get', 'Gallery.objects.get', ([], {'pk': 'obj_id'}), '(pk=obj_id)\n', (3362, 3373), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((3506, 3514), 'frog.common.Result', 'Result', ([], {}), '()\n', (3512, 3514), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((4044, 4091), 'frog.models.Gallery.objects.filter', 'Gallery.objects.filter', ([], {'security__lte': 'clearance'}), '(security__lte=clearance)\n', (4066, 4091), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((4614, 4638), 'json.loads', 'json.loads', (['request.body'], {}), '(request.body)\n', (4624, 4638), False, 'import json\n'), ((5259, 5283), 'json.loads', 'json.loads', (['request.body'], {}), '(request.body)\n', (5269, 5283), False, 'import json\n'), ((5597, 5617), 'json.loads', 'json.loads', (['security'], {}), '(security)\n', (5607, 5617), False, 'import json\n'), ((5791, 5817), 'frog.common.getObjectsFromGuids', 'getObjectsFromGuids', (['guids'], {}), '(guids)\n', (5810, 5817), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((6760, 6790), 'frog.models.Gallery.objects.get', 'Gallery.objects.get', ([], {'pk': 'obj_id'}), '(pk=obj_id)\n', (6779, 6790), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((7051, 7069), 'django.core.exceptions.PermissionDenied', 'PermissionDenied', ([], {}), '()\n', (7067, 7069), False, 'from django.core.exceptions import ImproperlyConfigured, PermissionDenied\n'), ((7312, 7330), 'django.core.exceptions.PermissionDenied', 'PermissionDenied', ([], {}), '()\n', (7328, 7330), False, 'from django.core.exceptions import ImproperlyConfigured, PermissionDenied\n'), ((13614, 13638), 'json.loads', 'json.loads', (['request.body'], {}), '(request.body)\n', (13624, 13638), False, 'import json\n'), ((2740, 2784), 'django.contrib.contenttypes.models.ContentType.objects.filter', 'ContentType.objects.filter', ([], {'app_label': '"""frog"""'}), "(app_label='frog')\n", (2766, 2784), False, 'from django.contrib.contenttypes.models import ContentType\n'), ((3639, 3708), 'frog.models.Gallery.objects.filter', 'Gallery.objects.filter', ([], {'security': 'Gallery.PERSONAL', 'owner': 'request.user'}), '(security=Gallery.PERSONAL, owner=request.user)\n', (3661, 3708), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((5894, 5922), 'frog.models.Gallery.objects.get', 'Gallery.objects.get', ([], {'pk': 'move'}), '(pk=move)\n', (5913, 5922), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((7468, 7486), 'django.core.exceptions.PermissionDenied', 'PermissionDenied', ([], {}), '()\n', (7484, 7486), False, 'from django.core.exceptions import ImproperlyConfigured, PermissionDenied\n'), ((4573, 4594), 'frog.models.Gallery.objects.all', 'Gallery.objects.all', ([], {}), '()\n', (4592, 4594), False, 'from frog.models import Gallery, Image, Video, Group, GallerySubscription, SiteConfig, Piece\n'), ((6987, 7007), 'frog.common.getClientIP', 'getClientIP', (['request'], {}), '(request)\n', (6998, 7007), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((7248, 7268), 'frog.common.getClientIP', 'getClientIP', (['request'], {}), '(request)\n', (7259, 7268), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((12591, 12621), 'functools.cmp_to_key', 'functools.cmp_to_key', (['sortfunc'], {}), '(sortfunc)\n', (12611, 12621), False, 'import functools\n'), ((13955, 13963), 'frog.common.Result', 'Result', ([], {}), '()\n', (13961, 13963), False, 'from frog.common import Result, getObjectsFromGuids, getClientIP\n'), ((8983, 9001), 'django.db.models.Q', 'Q', ([], {'num_tags__lte': '(1)'}), '(num_tags__lte=1)\n', (8984, 9001), False, 'from django.db.models import Q, Count\n'), ((9926, 9945), 'django.db.models.Q', 'Q', ([], {'id__in': 'searchIDs'}), '(id__in=searchIDs)\n', (9927, 9945), False, 'from django.db.models import Q, Count\n'), ((8950, 8953), 'django.db.models.Q', 'Q', ([], {}), '()\n', (8951, 8953), False, 'from django.db.models import Q, Count\n'), ((9233, 9249), 'django.db.models.Q', 'Q', ([], {'tags__id': 'item'}), '(tags__id=item)\n', (9234, 9249), False, 'from django.db.models import Q, Count\n'), ((9893, 9896), 'django.db.models.Q', 'Q', ([], {}), '()\n', (9894, 9896), False, 'from django.db.models import Q, Count\n'), ((8869, 8882), 'django.db.models.Count', 'Count', (['"""tags"""'], {}), "('tags')\n", (8874, 8882), False, 'from django.db.models import Q, Count\n'), ((9200, 9203), 'django.db.models.Q', 'Q', ([], {}), '()\n', (9201, 9203), False, 'from django.db.models import Q, Count\n'), ((9574, 9598), 'django.db.models.Q', 'Q', ([], {'title__icontains': 'item'}), '(title__icontains=item)\n', (9575, 9598), False, 'from django.db.models import Q, Count\n'), ((9488, 9491), 'django.db.models.Q', 'Q', ([], {}), '()\n', (9489, 9491), False, 'from django.db.models import Q, Count\n'), ((10132, 10145), 'django.db.models.Count', 'Count', (['"""tags"""'], {}), "('tags')\n", (10137, 10145), False, 'from django.db.models import Q, Count\n')]