#
# pubsub_simpledoc.py
#
# This source file is part of the FoundationDB open source project
#
# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import os
import sys

import fdb
import simpledoc

feed_messages = simpledoc.OrderedIndex("messages.?", "fromfeed")
feed_watching_inboxes = simpledoc.OrderedIndex("messages.?", "fromfeed")

feeds = simpledoc.root.feeds
inboxes = simpledoc.root.inboxes
messages = simpledoc.root.messages

@simpledoc.transactional
def _create_feed_internal(metadata):
    feed = feeds[metadata]
    feed.set_value(metadata)
    return feed

@simpledoc.transactional
def _create_inbox_internal(metadata):
    inbox = inboxes[metadata]
    inbox.set_value(metadata)
    return inbox

@simpledoc.transactional
def _create_feed_and_inbox_internal(metadata):
    _create_feed_internal(metadata)
    _create_inbox_internal(metadata)
    return True

@simpledoc.transactional
def _create_subscription_internal(feed, inbox):
    inbox.subs[feed.get_name()] = ""
    inbox.dirtyfeeds[feed.get_name()] = "1"
    return True

@simpledoc.transactional
def _post_message_internal(feed, message_id, contents):
    message = messages.prepend()
    message.set_value(contents)
    message.fromfeed = feed.get_name()

    # mark all the "watching inboxes" as dirty, so they know to re-copy
    for inbox in feed.watchinginboxes.get_children():
        inboxes[inbox.get_name()].dirtyfeeds[feed.get_name()] = "1"
    feed.watchinginboxes.clear_all()

@simpledoc.transactional
def _list_messages_internal(inbox):
    print "messages in %s's inbox:" % inbox.get_value()
    for feed in inbox.subs.get_children():
        print " from %s:" %  feeds[feed.get_name()].get_value()
        for message in feed_messages.find_all(feed.get_name()):
            print "   ", message.get_value()

@simpledoc.transactional
def _get_feed_messages_internal(feed, limit):
    message_list = []
    counter = 0
    for message in feed_messages.find_all(feed.get_name()):
        if counter == limit:
            break
        message_list.append(message.get_value())
        counter += 1
    return message_list

@simpledoc.transactional
def _copy_dirty_feeds(inbox):
    changed = False
    latest_id = inbox.latest_message.get_value()
    #print "latest message is", latest_id
    for feed in inbox.dirtyfeeds.get_children():
        #print "working on dirty feed", feed.get_name()
        for message in feed_messages.find_all(feed.get_name()):
            #print "found message", message.get_name()
            if latest_id != None and message.get_name() >= latest_id:
                break
            changed = True
            inbox.messages[message.get_name()] = feed.get_name()
            #print "copied message", message.get_name()

        # now that we have copied, mark this inbox as watching the feed
        feeds[feed.get_name()].watchinginboxes[inbox.get_name()] = "1"

    inbox.dirtyfeeds.clear_all()
    return changed

@simpledoc.transactional
def _get_inbox_subscriptions_internal(inbox, limit):
    subscriptions = []

    for message in inbox.subs.get_children():
        subscriptions.append(message.get_name())

    return subscriptions

@simpledoc.transactional
def _get_inbox_messages_internal(inbox, limit):
    inbox_changed = _copy_dirty_feeds(inbox)

    message_ids = []
    for message in inbox.messages.get_children():
        if len(message_ids) >= limit:
            break
        message_ids.append(message.get_name())

    # As simpledoc returns children in order, I do not think this is needed (the above depends on it NOT being needed)
    # message_ids.sort()
    if inbox_changed and len(message_ids) > 0:
        inbox.latest_message = message_ids[0]
    message_ids = message_ids[:max(limit, len(message_ids))]

    return [messages[mid].get_value() for mid in message_ids]

@simpledoc.transactional
def _clear_all_messages():
    simpledoc.root.clear_all()

@simpledoc.transactional
def _print_internals(feed_or_inbox = None):
    if feed_or_inbox is None:
        print simpledoc.root.get_json(False)
    else:
        print feed_or_inbox.get_json(False)

class PubSub(object):
    def __init__(self, db):
        self.db = db

    def create_feed(self, metadata):
        return _create_feed_internal(self.db, metadata)

    def create_inbox(self, metadata):
        return _create_inbox_internal(self.db, metadata)

    def create_inbox_and_feed(self, metadata):
        _create_feed_and_inbox_internal(self.db, metadata)

    def get_feed_by_name(self, metadata):
        return feeds[metadata]

    def get_inbox_by_name(self, metadata):
        return inboxes[metadata]

    def create_subscription(self, feed, inbox):
        return _create_subscription_internal(self.db, feed, inbox)

    def post_message(self, feed, contents):
        message_id = os.urandom(8)
        return _post_message_internal(self.db, feed, message_id, contents)

    def list_inbox_messages(self, inbox):
        return _list_messages_internal(self.db, inbox)

    def get_feed_messages(self, feed, limit=10):
        return _get_feed_messages_internal(self.db, feed, limit)

    def get_inbox_subscriptions(self, inbox, limit=10):
        return _get_inbox_subscriptions_internal(self.db, inbox, limit)

    def get_inbox_messages(self, inbox, limit=10):
        return _get_inbox_messages_internal(self.db, inbox, limit)

    def print_feed_stats(self, feed):
        _print_internals(self.db, feed_or_inbox=feed)

    def print_internals(self):
        _print_internals(self.db)

