#! /usr/bin/env python

"""
DC-API deployement script
"""

import os, re, shutil, tempfile, uuid
from optparse import OptionParser
from ConfigParser import RawConfigParser

class Replacement:
    def __init__(self, name, value):
        self.name = name
        self.value = value
        self.re = re.compile("\s*" + name + "\s*=")
        self.found = False
    
    def subst(self, string):
        if self.re.match(string):
            self.found = True
            pos = string.find('=')
            return string[:pos] + "= " + self.value + "\n"
        else:
            return string

def modify_config(values, section = "Master"):
    """Modify the DC-API configuration. We can't use the write() method of
    RawConfigParser() since that does not preserve comments."""

    subst = []
    for k, v in values.iteritems():
        r = Replacement(k, v)
        subst.append(r)

    input = open(options.config, "r")
    input_lines = input.readlines()
    input.close()

    output_lines = []
    section_found = False
    section_match = re.compile("[[]" + section + "[]]")
    other_section = re.compile("[[]")

    for line in input_lines:
        if not section_found:
            if section_match.match(line):
                section_found = True
            output_lines.append(line)
        else:
            if other_section.match(line):
                section_found = False
                for s in subst:
                    if s.found:
                        continue
                    output_lines.append(s.name + " = " + s.value + "\n")
            for s in subst:
                line = s.subst(line)
            output_lines.append(line)

    handle, name = tempfile.mkstemp(prefix = ".dcapi.", dir =
                                    os.path.dirname(options.config))
    try:
        output = os.fdopen(handle, "w")
        output.writelines(output_lines)
        output.close()
        shutil.copymode(options.config, name)
        os.rename(name, options.config)
    except:
        os.unlink(name)
        raise

def deploy():
    if not backend:
        parser.error("The backend must be specified")

    defaults = backend.master_defaults()
    defaults["InstanceUUID"] = str(uuid.uuid4())
    defaults["WorkingDirectory"] = options.workdir

    modify_config(defaults)

def check():
    if not backend:
        parser.error("The backend must be specified")

    config = RawConfigParser()
    config.readfp(open(options.config))

    if not config.has_section('Master'):
        raise SystemExit("The [Master] section is missing")

    # The [Master] section may contain either master keys or defaults for client
    # keys
    keys = config.options('Master')
    seen = dict([(x, False) for x in keys])
    for name, required in backend.get_master_keys():
        name = name.lower()
        if name in seen:
            seen[name] = True
        elif required:
            raise SystemExit("Required option '" + name + "' is missing in the [Master] section")
    for name, required in backend.get_client_keys():
        name = name.lower()
        if name in seen:
            seen[name] = True

    for name, found in seen.iteritems():
        if not found:
            print "Option '" + name + "' in the [Master] section seems unused"

    # Now check the client sections
    for section in config.sections():
        if section == "Master":
            continue
        if not section.startswith("Client-"):
            print "Unexpected section name: " + section
            continue

        keys = config.options(section)
        seen = dict([(x, False) for x in keys])
        for name, required in backend.get_client_keys():
            name = name.lower()
            if name in seen:
                seen[name] = True
            elif required:
                if not config.has_option("Master", name):
                    raise SystemExit("Required option '" + name + "' is missing "
                                     "in the [" + section + "] section")
        for name, found in seen.iteritems():
            if not found:
                print "Option '" + name + "' in the [" + section + "] section seems unused"

command_table = {'deploy': deploy,
                 'check' : check}

parser = OptionParser(usage = "%prog [options] <command>")
parser.add_option("--config", "-c",
                  metavar = "FILE",
                  help = "specify the location of the config file. The default "
                         "is dc-api.conf in the current directory")
parser.add_option("--backend", "-b",
                  metavar = "NAME",
                  help = "the backend implementation to use")
parser.add_option("--workdir", "-w",
                  metavar = "NAME",
                  help = "the work directory of DC-API. Defaults to the "
                         "directory where the config file lives")
options, args = parser.parse_args()
if len(args) != 1:
    parser.error("Wrong number of arguments")
if not args[0] in command_table:
    parser.error("Unknown command " + args[0])
if options.backend is None:
    parser.error("The backend name must be specified")

if options.config is None:
    options.config = "dc-api.conf"
if options.workdir is None:
    options.workdir = os.path.dirname(os.path.abspath(options.config))

# Load the backend module
module = __import__("DCAPI." + options.backend)
cl = getattr(module, options.backend + "_Backend")
backend = getattr(cl, options.backend + "_Backend")()

command_table[args[0]]()
