#!/usr/bin/python
import os
import sys
import time
import threading
import logging
import sha
import random
import socket
import traceback

from common import xmlrpc
from common import exception

import hostconfig
import config
import db
import sessions

from db.sedb import SEDB

# port server runs on
port = 5741
clientPort = 5740

# Logger
log = logging.getLogger()

class Server(object):
    
    def __init__(self, cfg):
        log.info("Creating Server Object")

        self.cfg = cfg
        self.db = SEDB()
        

    def incorperate(self):
        """Check for changes in host configuration"""

        # File based configs
        configs = hostconfig.HostDict(self.db)
        configs.load()

    def deployActions(self):
        for id in self.db.getHosts():
            name = self.db.getHost(id)
            log.info("Deploying actions for %s" % name)

            url = "http://%s:%s" % (name, clientPort)
            client = xmlrpc.ServerProxy(url,
                                        self.cfg.getCert(),
                                        self.cfg.getCA(),
                                        self.cfg.getPeer())

            self.hostDeploy(id, client)

    
    def hostDeploy(self, id, rpc):
        act = self.db.queueGet(id)
        while act:
            log.debug("Action: (%s, %s, %s)" % (act['name'], 
                                                act['args'], 
                                                act['kwargs']))
            
            try:
                uuid = rpc.getUID()
                authedUuid = self.db.getUID(id)
                log.debug("Client UUID: %s" % uuid)

                if uuid == "":
                    # Client got reinstalled / doesn't have uuid
                    # What if this means client was compromised?
                    uuid = self.makeUID(id)
                    ret = rpc.register(uuid)
                    if ret[0] == 0:
                        log.info("Registered host: %s" % self.db.getHost(id))
                    else:
                        log.warning("Could not register host: %s" \
                                    % self.db.getHost(id))
                elif uuid != authedUuid:
                    log.info("I found a registered? host %s but its UID doesn't match" % self.db.getHost(id))
                    # Wait until the host I want checks in and updates
                    # its info.  
                    return
                
                ret = rpc.runAction(act['name'], 
                                    act['args'], act['kwargs'])
            except Exception, e:
                log.warning("Exception Occured: %s" % str(e))
                return   # Run other actions when error clears
            
            log.debug("Action returned: %s" % ret)
            # XXX: Did action succed?
            self.db.queueRemove(act['queueID'])

            act = self.db.queueGet(id)

        
    def makeUID(self, id):
        # XXX: I'd really like to take some information from the client
        # into making the uid.  That way it could be validated.  But, what
        # info can I take from the host that would be portable beyond Red
        # Hat products, is unique, and not network related.  (We assume
        # network name/ip can/will change.)

        s = "StateEngine-%s-%s" % (random.random(), self.cfg.getSecret())

        uuid = sha.new(s).hexdigest()
        self.db.setUID(id, uuid)
        return uuid

    
    def getActionTuple(self, id):
        
        if id == None:
            return None
            
        act = self.db.queueGet(id)

        if act == None:
            return None
        
        return act['queueID'], act['name'], act['args'], act['kwargs']
        
    
    def unQueueAction(self, key):
        self.db.queueRemove(key)
        # XXX: move to history
        

class ServerThread(threading.Thread):

    def __init__(self, cfg):
        threading.Thread.__init__(self)
        self._stop = False
        self.server = Server(cfg)

    
    def stop(self):
        self._stop = True


    def run(self):
        log.info("Incorperating...")
        self.server.incorperate()

        while not self._stop:
            log.info("Deploying Actions...")
            self.server.deployActions()
            time.sleep(5)
    
    

class RPCAPI(object):
    
    def __init__(self, cfg):
        self.cfg = cfg
        self.rpcserver = None
        self.server = Server(cfg)
        self.db = self.server.db
        
    
    def _dispatch(self, method, params):
        # Must see what the tracebacks are...
        func = getattr(self, method)

        try:
            return func(*params)
        except Exception:
            traceback.print_exc()
            raise

        
    def login(self, uuid):
        hostid = self.db.getHostByUUID(uuid)
        if hostid == None:
            # Invalid hostid
            log.warning("Invalid UUID tried to login")
            return (1, {})

        fqdn = socket.getfqdn(self.rpcserver.client_address[0])
        if fqdn != self.db.getHost(hostid):
            log.info("Updating hostname for hostid %s" % hostid)
            self.db.setHostName(hostid, fqdn)

        sess = sessions.Session()
        sess.setTimeOut(3600)
        sess['hostid'] = hostid
        sess.save()

        log.info("Authenticated UUID: %s" % uuid)
        log.info("   From host: %s" % fqdn)
        return (0, {"session":sess.sid})


    def register(self):
        "Register a new client."

        # Resolve hostname
        fqdn = socket.getfqdn(self.rpcserver.client_address[0])
        
        hostid = self.server.db.getHostID(fqdn)
        log.info("register(): hostid: %s" % hostid)
        if hostid is not None:
            log.info("Host attempting to re-register: %s" % fqdn)
        else:
            log.warning("Denied Unknown client attempting to regsiter: %s" \
                        % fqdn)
            return (1, {"error":"Unknown client FQDN."})
            
        # XXX: Control flag to mark hosts that are reinstalling
        # XXX: Register anyway, don't spool actions until admin OKs?
        # XXX: Ony let pre configured clients register?

        if hostid is None:
            hostid = self.db.addHost(fqdn)
            
        # Always make a new UUID for the host
        uuid = self.server.makeUID(hostid)

        return (0, {"uid":uuid})
        

    def get(self, sid):
        sess = sessions.Session(sid)
        if sess.isNew() or not sess.isValid():
            return (1, {"error":"Invaild session ID"})
        
        hostid = sess['hostid']
        action = self.server.getActionTuple(hostid)
        if action == None:
            log.debug("No more actions for host: %s" % self.db.getHost(hostid))
            return (0, {})
        else:
            self.server.unQueueAction(action[0])
            log.debug("Spooled off action for host: %s" \
                      % self.db.getHost(hostid))
            return (0, {"action": action})


    def submit(self, sid, tuple):
        sess = sessions.Session(sid)
        if sess.isNew() or not sess.isValid():
            return (1, {"error":"Invaild session ID"})
        
        # XXX: Place holder for action return values.
        # What's the API look like here?

        return (0, {})
            

class RPCServer(object):
    # Make this into a thread latter?
    
    def __init__(self, cfg):
        self.cfg = cfg
        self.api = RPCAPI(cfg)


    def run(self):
        testServer = xmlrpc.Server("", port,
                                   self.cfg.getCert(),
                                   self.cfg.getCA(),
                                   self.cfg.getPeer())
        
        testServer.register_instance(self.api)
        self.api.rpcserver = testServer
        
        testServer.serve_forever()
        


def startServer():

    cfg = config.Server()
    
    # Init DB
    db.selectBackend(cfg)

    # Start up RPC server 
    rpc = RPCServer(cfg)
    
    # Start push thread
    server = ServerThread(cfg)
    server.start()

    try:
        rpc.run()
    except KeyboardInterrupt:
        pass
        
    server.stop()
    
