#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| Created by niphlod@gmail.com
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)

Scheduler with redis backend
---------------------------------
"""

import os
import time
import socket
import datetime
import logging
from gluon.utils import web2py_uuid
from gluon.storage import Storage
from gluon.scheduler import *
from gluon.scheduler import _decode_dict
from gluon.contrib.redis_utils import RWatchError

USAGE = """
## Example

For any existing app

Create File: app/models/scheduler.py ======
from gluon.contrib.redis_utils import RConn
from gluon.contrib.redis_scheduler import RScheduler

def demo1(*args,**vars):
    print 'you passed args=%s and vars=%s' % (args, vars)
    return 'done!'

def demo2():
    1/0

rconn = RConn()
mysched = RScheduler(db, dict(demo1=demo1,demo2=demo2), ...., redis_conn=rconn)

## run worker nodes with:

   cd web2py
   python web2py.py -K app

"""


path = os.getcwd()

if 'WEB2PY_PATH' not in os.environ:
    os.environ['WEB2PY_PATH'] = path

try:
    # try external module
    from simplejson import loads, dumps
except ImportError:
    try:
        # try stdlib (Python >= 2.6)
        from json import loads, dumps
    except:
        # fallback to pure-Python module
        from gluon.contrib.simplejson import loads, dumps

IDENTIFIER = "%s#%s" % (socket.gethostname(), os.getpid())

logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)

POLLING = 'POLLING'


class RScheduler(Scheduler):

    def __init__(self, db, tasks=None, migrate=True,
                 worker_name=None, group_names=None, heartbeat=HEARTBEAT,
                 max_empty_runs=0, discard_results=False, utc_time=False,
                 redis_conn=None, mode=1):

        """
        Highly-experimental coordination with redis
        Takes all args from Scheduler except redis_conn which
        must be something closer to a StrictRedis instance.

        My only regret - and the reason why I kept this under the hood for a
        while - is that it's hard to hook up in web2py to something happening
        right after the commit to a table, which will enable this version of the
        scheduler to process "immediate" tasks right away instead of waiting a
        few seconds (see FIXME in queue_task())

        mode is reserved for future usage patterns.
        Right now it moves the coordination (which is the most intensive
        routine in the scheduler in matters of IPC) of workers to redis.
        I'd like to have incrementally redis-backed modes of operations,
        such as e.g.:
            - 1: IPC through redis (which is the current implementation)
            - 2: Store task results in redis (which will relieve further pressure
                 from the db leaving the scheduler_run table empty and possibly
                 keep things smooth as tasks results can be set to expire
                 after a bit of time)
            - 3: Move all the logic for storing and queueing tasks to redis
                 itself - which means no scheduler_task usage too - and use
                 the database only as an historical record-bookkeeping
                 (e.g. for reporting)

        As usual, I'm eager to see your comments.
        """

        Scheduler.__init__(self, db, tasks=tasks, migrate=migrate,
                           worker_name=worker_name, group_names=group_names,
                           heartbeat=heartbeat, max_empty_runs=max_empty_runs,
                           discard_results=discard_results, utc_time=utc_time)

        self.r_server = redis_conn
        from gluon import current
        self._application = current.request.application or 'appname'

    def _nkey(self, key):
        """Helper to restrict all keys to a namespace and track them."""
        prefix = 'w2p:rsched:%s' % self._application
        allkeys = '%s:allkeys' % prefix
        newkey = "%s:%s" % (prefix, key)
        self.r_server.sadd(allkeys, newkey)
        return newkey

    def prune_all(self):
        """Global housekeeping."""
        all_keys = self._nkey('allkeys')
        with self.r_server.pipeline() as pipe:
            while True:
                try:
                    pipe.watch('PRUNE_ALL')
                    while True: