import json
import uuid
from datetime import datetime

from django import core, db
from django.conf import settings
from django.db import models
from django.db.models import signals
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext_lazy as _

import geonode
from mapstory import search
from mapstory.mapstories.utils import parse_date_time


class MapStory(geonode.base.models.ResourceBase):

    @property
    def chapters(self):
        return self.chapter_list.order_by('chapter_index')

    @property
    def get_chapter_info(self):
        chapter_list = []
        for chapter in self.chapters:

            storypins = chapter.storypin_set.all()
            storypin_list = []
            for storypin in storypins:
                storypin_dict = {
                    'id': storypin.id,
                    'title': storypin.title,
                    'content': storypin.content,
                    'media': storypin.media,
                    'the_geom': storypin.the_geom,
                    'start_time': storypin.start_time,
                    'end_time': storypin.end_time,
                    'in_timeline': storypin.in_timeline,
                    'in_map': storypin.in_map,
                    'appearance': storypin.appearance,
                    'auto_show': storypin.auto_show,
                    'pause_playback': storypin.pause_playback,
                    'auto_play': storypin.auto_play,
                    'offset': storypin.offset,
                    'play_length': storypin.play_length
                }
                storypin_list.append(storypin_dict)

            storyframes = chapter.storyframe_set.all()
            storyframe_list = []
            for storyframe in storyframes:
                storyframe_dict = {
                    'id': storyframe.id,
                    'title': storyframe.title,
                    'description': storyframe.description,
                    'the_geom': storyframe.the_geom,
                    'start_time': storyframe.start_time,
                    'end_time': storyframe.end_time,
                    'data': storyframe.data,
                    'center': storyframe.center,
                    'interval': storyframe.interval,
                    'intervalRate': storyframe.intervalRate,
                    'playback': storyframe.playback,
                    'playbackRate': storyframe.playbackRate,
                    'speed': storyframe.speed,
                    'zoom': storyframe.zoom,
                    'layers': storyframe.layers,
                    'resolution': storyframe.resolution
                }
                storyframe_list.append(storyframe_dict)

            chapter_dict = {
                'story_id': self.id,
                'map_id': chapter.id,
                'chapter_index': chapter.chapter_index,
                'title': chapter.title,
                'abstract': chapter.abstract,
                'layers': chapter.layers,
                'layers_config': chapter.layers_config,
                'storyframes': storyframe_list,
                'storypins': storypin_list
            }
            chapter_list.append(chapter_dict)
        return chapter_list

    def update_from_viewer(self, conf):

        if isinstance(conf, basestring):
            conf = json.loads(conf)

        self.title = conf['about']['title']
        self.abstract = conf['about']['abstract']
        self.is_published = conf['isPublished']
        # Ensure that the slug is unique.
        try:
            instance = MapStory.objects.get(slug=slugify(self.title))
            if instance.id == self.id:
                self.slug = slugify(self.title)
            else:
                self.slug = slugify(self.title) + '-' + str(self.id)
        except MapStory.DoesNotExist:
            self.slug = slugify(self.title)

        if conf['about']['category'] is not None:
            if isinstance(conf['about']['category'], dict):
                categoryID = conf['about']['category']['id']
            else:
                categoryID = conf['about']['category']

            self.category = geonode.base.models.TopicCategory(categoryID)

        if self.uuid is None or self.uuid == '':
            self.uuid = str(uuid.uuid1())

        removed_chapter_ids = conf['removedChapters']
        if removed_chapter_ids is not None and len(removed_chapter_ids) > 0:
            for chapter_id in removed_chapter_ids:
                map_obj = Map.objects.get(id=chapter_id)
                self.chapter_list.remove(map_obj)

        #self.keywords.add(*conf['map'].get('keywords', []))
        self.save()

    def get_absolute_url(self):
        return '/story/' + str(self.slug) + '/'

    def viewer_json(self, user, access_token=None):
        about = {
            'title': self.title,
            'abstract': self.abstract,
            'owner': self.owner.name_long,
            'username': self.owner.username,
            'categoryID': self.category.id if self.category is not None else None
        }

        config = {
            'id': self.id,
            'about': about,
            'chapters': [chapter.viewer_json(user) for chapter in self.chapters],
            'thumbnail_url': settings.MISSING_THUMBNAIL
        }

        return config

    def update_thumbnail(self, first_chapter_obj):
        if first_chapter_obj.chapter_index != 0:
            return

        chapter_base = geonode.base.models.ResourceBase.objects.get(
            id=first_chapter_obj.id)
        geonode.base.models.ResourceBase.objects.filter(id=self.id).update(
            thumbnail_url=chapter_base.thumbnail_url
        )

    @property
    def class_name(self):
        return self.__class__.__name__

    distribution_url_help_text = _(
        'information about on-line sources from which the dataset, specification, or '
        'community profile name and extended metadata elements can be obtained')
    distribution_description_help_text = _(
        'detailed text description of what the online resource is/does')
    distribution_url = db.models.TextField(
        _('distribution URL'),
        blank=True,
        null=True,
        help_text=distribution_url_help_text)
    distribution_description = db.models.TextField(
        _('distribution description'),
        blank=True,
        null=True,
        help_text=distribution_description_help_text)
    slug = db.models.SlugField(max_length=160, null=True, unique=True)

    class Meta(geonode.base.models.ResourceBase.Meta):
        verbose_name_plural = 'MapStories'
        db_table = 'maps_mapstory'


# Redefining Map Model and adding additional fields
class Map(geonode.maps.models.Map):
    story = db.models.ForeignKey(
        MapStory, related_name='chapter_list', blank=True, null=True)

    chapter_index = db.models.IntegerField(
        _('chapter index'), null=True, blank=True)
    viewer_playbackmode = db.models.CharField(
        _('Viewer Playback'), max_length=32, blank=True, null=True)
    layers_config = db.models.TextField(null=True, blank=True)

    def update_from_viewer(self, conf):

        if isinstance(conf, basestring):
            conf = json.loads(conf)

        # super allows us to call base class function implementation from geonode
        super(Map, self).update_from_viewer(conf, context={})

        self.viewer_playbackmode = conf['viewerPlaybackMode'] or 'Instant'

        self.chapter_index = conf.get('index')
        story_id = conf.get('storyID', 0)
        story_obj = MapStory.objects.get(id=story_id)
        self.layers_config = json.dumps(conf["layers"])
        self.story = story_obj
        self.save()

    def viewer_json(self, user, access_token=None, *added_layers):
        base_config = super(Map, self).viewer_json(
            user, *added_layers)
        base_config['viewer_playbackmode'] = self.viewer_playbackmode
        base_config['tools'] = [{'outputConfig': {
            'playbackMode': self.viewer_playbackmode}, 'ptype': 'gxp_playback'}]

        return base_config


def mapstory_map_post_save(instance, sender, **kwargs):
    # Call basic post save map functionality from geonode
    geonode.geoserver.signals.geoserver_post_save_map(
        instance, sender, **kwargs)

    # Assuming map thumbnail was created successfully, updating Story object here
    if instance.chapter_index == 0:
        instance.story.update_thumbnail(instance)

    try:
        search.utils.update_es_index(
            MapStory, MapStory.objects.get(id=instance.story.id))
    except:
        pass


def default_is_published(sender, **kwargs):
    """
    GeoNode's resourcebase model defaults is_published to True; override this
    so that new MapStory/Map objects are not published by default.
    """
    instance = kwargs['instance']
    if instance.id is None:  # only reset default when object is first created
        instance.is_published = False


class StoryFrameManager(models.Manager):

    def copy_map_story_frames(self, source_id, target):
        source = Map.objects.get(id=source_id)
        copies = []

        for storyframe in source.storyframe_set.all():
            storyframe.map = target
            storyframe.pk = None
            copies.append(storyframe)

        StoryFrame.objects.bulk_create(copies)


class StoryFrame(models.Model):
    objects = StoryFrameManager()

    PLAYBACK_RATE = (('seconds', 'Seconds'), ('minutes', 'Minutes'),)
    INTERVAL_RATE = (('minutes', 'Minutes'), ('hours', 'Hours'),
                     ('weeks', 'Weeks'), ('months', 'Months'), ('years', 'Years'),)

    map = models.ForeignKey(Map)
    title = models.CharField(max_length=100)
    description = models.TextField(blank=True, null=True)
    the_geom = models.TextField(blank=True, null=True)
    start_time = models.BigIntegerField(blank=True, null=True)
    end_time = models.BigIntegerField(blank=True, null=True)
    data = models.TextField(blank=True, null=True)
    center = models.TextField(blank=True, null=True)
    interval = models.IntegerField(blank=True, null=True)
    intervalRate = models.CharField(
        max_length=10, choices=INTERVAL_RATE, blank=True, null=True)
    playback = models.IntegerField(blank=True, null=True)
    playbackRate = models.CharField(
        max_length=10, choices=PLAYBACK_RATE, blank=True, null=True)
    speed = models.TextField(blank=True, null=True)
    zoom = models.IntegerField(blank=True, null=True)
    layers = models.TextField(blank=True, null=True)
    resolution = models.TextField(blank=True, null=True)

    def _timefmt(self, val):
        return datetime.isoformat(datetime.utcfromtimestamp(val))

    def set_start(self, val):
        self.start_time = parse_date_time(val)

    def set_end(self, val):
        self.end_time = parse_date_time(val)

    @property
    def start_time_str(self):
        return self._timefmt(self.start_time) if self.start_time else ''

    @property
    def end_time_str(self):
        return self._timefmt(self.end_time) if self.end_time else ''

    class Meta:
        verbose_name_plural = "StoryFrames"


class StoryPinManager(models.Manager):

    def copy_map_storypins(self, source_id, target):
        source = Map.objects.get(id=source_id)
        copies = []
        for ann in source.storypin_set.all():
            ann.map = target
            ann.pk = None
            copies.append(ann)
        StoryPin.objects.bulk_create(copies)


class StoryPin(models.Model):
    objects = StoryPinManager()

    map = models.ForeignKey(Map)
    title = models.TextField()
    content = models.TextField(blank=True, null=True)
    media = models.TextField(blank=True, null=True)
    the_geom = models.TextField(blank=True, null=True)
    start_time = models.BigIntegerField(blank=True, null=True)
    end_time = models.BigIntegerField(blank=True, null=True)
    in_timeline = models.BooleanField(default=False)
    in_map = models.BooleanField(default=False)
    appearance = models.TextField(blank=True, null=True)
    auto_show = models.BooleanField(default=False)
    pause_playback = models.BooleanField(default=False)
    auto_play = models.BooleanField(default=False)
    offset = models.BigIntegerField(null=True, default=0)
    play_length = models.BigIntegerField(null=True, default=0)

    def _timefmt(self, val):
        return datetime.isoformat(datetime.utcfromtimestamp(val))

    def set_start(self, val):
        self.start_time = parse_date_time(val)

    def set_end(self, val):
        self.end_time = parse_date_time(val)

    @property
    def start_time_str(self):
        return self._timefmt(self.start_time) if self.start_time else ''

    @property
    def end_time_str(self):
        return self._timefmt(self.end_time) if self.end_time else ''

    class Meta:
        verbose_name_plural = "StoryPins"


signals.post_init.connect(default_is_published, sender=MapStory)
signals.post_init.connect(default_is_published, sender=Map)
db.models.signals.post_save.connect(mapstory_map_post_save, sender=Map)


class LayerStyle(models.Model):
    class Meta:
        unique_together = (("style_id", "map_story"),)

    style_id = models.TextField()
    map_story = models.ForeignKey(MapStory, on_delete=models.CASCADE)
    style = models.TextField()