import os
import os.path
import sha
import logging
import config
from pyparsing import *
from db import SEDB

from common import modules
from common import prototypes

# The logger interface
log = logging.getLogger()

class HostDict(dict):

    def __init__(self, db):
        self.conf = config.Server()
        self.db = db
        
        dict.__init__(self)

    def _getFilesAndDirs(self, path):
        # Helper function for filesystem recurstion

        dirs = []
        files = []

        if not os.path.isabs(path):
            path = os.path.abspath(path)

        dir = os.listdir(path)
        for node in dir:
            apath = os.path.join(path, node)
            if os.path.isdir(apath):
                dirs.append(apath)
            elif os.path.isfile(apath):
                files.append(apath)

        return files, dirs

    def refresh(self):
        self.load(ignoreDups=False)

    def load(self, dir=None, ignoreDups=True):
        if dir == None:
            dir = self.conf.ConfigDir()

        files, dirs = self._getFilesAndDirs(dir)
        
        for file in files:
            key = os.path.basename(file)
            if key.startswith('.'):
                continue
            
            if self.has_key(key):
                if not ignoreDups:
                    print "Multiple Config files for %s" % file
                    # Leave existing value in place
            else:
                h = FileHostConfig(self.db)
                h.create(key, file)
                dict.__setitem__(self, key, h)

        for dir in dirs:
            if not os.path.basename(dir).startswith('.'):
                self.load(dir)

    def __getitem__(self, key):
        return dict.__getitem__(self, key)

    def __setitem__(self, key, value):
        pass

    def __delitem__(self, key):
        pass


# Parsing routines for the state
class Parser(object):

    # gah...make pyparsing handle quotes right
    quotedString.setParseAction(removeQuotes)

    # Python variable namespace
    stateKey = Word(alphanums) 
    # data...like root hashes and crap
    arg      = quotedString ^ Word(printables)  
    # key word arguments
    kwarg    = Group(Word(alphanums) + Suppress("=") + Word(printables))

    stateConfig = stateKey + ZeroOrMore(kwarg ^ arg)
    stateConfig.validate()
    stateConfig.ignore("#" + restOfLine)

    def __init__(self):
        pass
        
    def parseLine(self, line):
        try:
            parse = self.stateConfig.parseString(line)
        except ParseException, e:
            # A comment, empty line, something that wasn't what we want
            # Pyparsing doesn't seem to have a good way to handle comments.
            return None
            
        args = []
        kw = {}

        for i in parse[1:]:
            if type(i) == type([]) or type(i) == type(()):
                kw[i[0]] = i[1]
            else:
                args.append(i)

        return (parse[0], args, kw)
    

    def parseBlob(self, blob):
        # Handle text blob or multiple lines in a list
        if type(blob) == type([]):
            lines = blob
        else:
            lines = blob.split('\n')

        configs = []
        for line in lines:
            ret = self.parseLine(line)
            if ret != None:
                configs.append(ret)

        return configs

    
class FileParser(Parser):

    def __init__(self, path):
        Parser.__init__(self)
        self.path = path
        self.version = None
        
    def readConfig(self):
        fd = self._getConfigFile()

        configs = self.parseBlob(fd.readlines())

        return configs

    def getOnDiskVersion(self):
        fd = open(self.path)
        v = sha.new(fd.read()).hexdigest()
        fd.close()

        return v

    def _getConfigFile(self):
        # return an open file of the config

        self.version = self.getOnDiskVersion()

        fd = open(self.path)
        
        return fd
    
    
class HostConfig(object):
   
    def __init__(self, db, hostid=None):
        self.db = db
        self.hostid = hostid

    def create(self, hostname):
        if self.hostid == None:
            self.hostid = self.db.addHost(hostname)

        return self.hostid
        
    def addState(self, stateKey, args, kwargs):
        # stateKey references the state object
        # args is a list of string arguments
        # kwargs is a dict to represent var = value

        stateid = self.db.getStateID(stateKey, args, kwargs)

        # If DB already knows about this state (when we re-read in configs)
        if not self.db.hasState(self.hostid, stateid):
            self.db.setStateSet(self.hostid, stateid)
            self.incorperate(stateKey, args, kwargs)

    def incorperate(self, stateKey, args, kwargs):
        # Transmute this State into Actions and dumb them in the DB
        log.info("Incorperating host: %s" % self.hostid)
        
        try:
            stateClass = modules.getClass("state/", 
                                          prototypes.State, stateKey)
            if not stateClass:
                log.warning("State %s not in system." % stateKey)
                return
            else:
                stateInstance = stateClass(*args, **kwargs)
        except Exception, e:
            log.error("Exception occured incorperating state: %s" % 
                       stateKey)
            log.error(str(e))
            return

        stateInstance.setup()
        q = stateInstance.getQueue()

        if not q.empty():
            log.info("Loading actions for host: %s" % self.hostid)
            
        while not q.empty():
            act, a, k = q.get()
            log.debug("Action: %s\nArgs: %s\nKwargs: %s" % (act, 
                       a, k))
            self.db.queuePut(self.hostid, act, a, k)

    def getStateSet(self):
        return self.db.getStateSet(self.hostid)

    def getState(self, id):
        # Returns (name, args, kwargs)
        return self.db.getState(id)


class FileHostConfig(HostConfig):

    def __init__(self, db, hostid=None):
        HostConfig.__init__(self, db, hostid)
        self.timestamp = None

    def create(self, hostname, path):
        if self.hostid != None:
            log.error("Re-create host ID: %s" % hostid)
            return
        
        HostConfig.create(self, hostname)
        stat = os.stat(path)
        self.timestamp = stat.st_mtime
        
        parser = FileParser(path)

        configs = parser.readConfig()
        for state in configs:
            self.addState(state[0], state[1], state[2])
        

def main():
    import sys

    if len(sys.argv) != 2:
        print "Usage: %s hostname"
        sys.exit()

    hc = HostConfig("../configs/%s" % sys.argv[1])

    keys = hc.getStateKeys()

    for k in keys:
        print "%s: Args = %s kwargs = %s" % (k, hc.getArguments(k),
                                             hc.getKWArguments(k))
        
        
if __name__ == "__main__":
    main()

