#!/usr/bin/env python

import argparse
import copy
import os
import json
import sys
import warnings

from redis import StrictRedis

from autocnet.io.db.redis_queue import pop_computetime_push, finalize
from autocnet.graph.network import NetworkCandidateGraph
from autocnet.graph.node import NetworkNode
from autocnet.graph.edge import NetworkEdge
from autocnet.io.db.model import Points, Measures, Overlay
from autocnet.utils.utils import import_func
from autocnet.utils.serializers import JsonEncoder, object_hook


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('-r', '--host', help='The host URL for the redis queue to to pull messages from.')
    parser.add_argument('-p', '--port', help='The port for used by redis.')
    parser.add_argument('processing_queue', help='The name of the processing queue to draw messages from.')
    
    return parser.parse_args()

def _instantiate_obj(msg, ncg):
    along = msg['along']
    id = msg['id']
    image_path = msg['image_path']
    if along == 'node':
        obj = NetworkNode(node_id=id, image_path=image_path)
    elif along == 'edge':
        obj = NetworkEdge()
        obj.source = NetworkNode(node_id=id[0], image_path=image_path[0])
        obj.destination = NetworkNode(node_id=id[1], image_path=image_path[1])
    obj.parent = ncg
    return obj

def _instantiate_row(msg, ncg):
    # Get the dict mapping iterable keyword types to the objects
    objdict = ncg.apply_iterable_options
    rowid = msg['id']
    obj = objdict[msg['along']]
    with ncg.session_scope() as session:
        res = session.query(obj).filter(getattr(obj, 'id')==msg['id']).one()
        session.expunge(res) # Disconnect the object from the session
    return res

def main(msg):
    ncg = NetworkCandidateGraph()
    ncg.config_from_dict(msg['config'])
    if msg['along'] in ['node', 'edge']:
        obj = _instantiate_obj(msg, ncg)
    elif msg['along'] in ['points', 'measures', 'overlaps', 'images']:
        obj = _instantiate_row(msg, ncg)
    else:
        obj = msg['along']

    # Grab the function and apply. This assumes that the func is going to
    # have a True/False return value. Basically, all processing needs to
    # occur inside of the func, nothing belongs in here.
    #
    # All args/kwargs are passed through the RedisQueue, and then right on to the func.
    func = msg['func']
    if callable(func):  # The function is a de-serialzied function
        msg['args'] = (obj, *msg['args'])
        msg['kwargs']['ncg'] = ncg
    elif hasattr(obj, msg['func']):  # The function is a method on the object
        func = getattr(obj, msg['func'])
    else:  # The func is a function from a library to be imported
        func = import_func(msg['func'])
        # Get the object begin processed prepended into the args.
        msg['args'] = (obj, *msg['args'])
        # For now, pass all the potential config items through
        # most funcs will simply discard the unnecessary ones.
        msg['kwargs']['ncg'] = ncg
        msg['kwargs']['Session'] = ncg.Session

    # Now run the function.
    res = func(*msg['args'], **msg['kwargs'])

    # Update the message with the True/False
    msg['results'] = res
    # Update the message with the correct callback function

    return msg

if __name__ == '__main__':
    args = parse_args()

    # Get the message
    queue = StrictRedis(host=args.host, port=args.port, db=0)
    msg = json.loads(queue.rpop(args.processing_queue), object_hook=object_hook)

    if msg is None:
        warnings.warn('Expected to process a cluster job, but the message queue is empty.')
        sys.exit()

    # Apply the algorithm
    response = main(msg)
    # Should go to a logger someday!
    print(response)

    # Alert the caller on failure to relaunch with next parameter set
    #finalize(response, remove_key, queue,
    #         config['redis']['completed_queue'],
    #         config['redis']['working_queue'])
