#!/usr/bin/env python

import os
import sys
import re
import time
import traceback
import types
import readline
from StringIO import StringIO
from optparse import OptionParser

from sfa.util.version import version_tag
from sfa.util.config import Config

def validator(validated_variables):
    pass
#    maint_user = validated_variables["PLC_API_MAINTENANCE_USER"]
#    root_user = validated_variables["PLC_ROOT_USER"]
#    if maint_user == root_user:
#        errStr="PLC_API_MAINTENANCE_USER=%s cannot be the same as PLC_ROOT_USER=%s"%(maint_user,root_user)
#        raise plc_config.ConfigurationException(errStr)

usual_variables = [
    "SFA_GENERIC_FLAVOUR",
    "SFA_INTERFACE_HRN",
    "SFA_REGISTRY_ROOT_AUTH",
    "SFA_REGISTRY_HOST", 
    "SFA_AGGREGATE_HOST",
    "SFA_SM_HOST",
    "SFA_DB_HOST",
    ]

flavour_xml_section_hash = { \
    'pl':'sfa_plc',
    'openstack':'sfa_nova',
    'fd':'sfa_federica',
    'nitos':'sfa_nitos',
    'dummy':'sfa_dummy',
    }
configuration={ \
    'name':'sfa',
    'service':"sfa",
    'usual_variables':usual_variables,
    'config_dir':"/etc/sfa",
    'validate_variables':{},
    'validator':validator,
    }


# GLOBAL VARIABLES
#
g_configuration=None
usual_variables=None
config_dir=None
service=None

def noop_validator(validated_variables):
    pass

# historically we could also configure the devel pkg....
def init_configuration ():
    global g_configuration
    global usual_variables, config_dir, service

    usual_variables=g_configuration["usual_variables"]
    config_dir=g_configuration["config_dir"]
    service=g_configuration["service"]

    global def_default_config, def_site_config, def_consolidated_config
    def_default_config= "%s/default_config.xml" % config_dir
    def_site_config = "%s/configs/site_config" % config_dir
    def_consolidated_config = "%s/%s_config" % (config_dir, service)

    global mainloop_usage
    mainloop_usage= """Available commands:
 Uppercase versions give variables comments, when available
 u/U\t\t\tEdit usual variables
 w\t\t\tWrite
 r\t\t\tRestart %(service)s service
 R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
 q\t\t\tQuit (without saving)
 h/?\t\t\tThis help
---
 l/L [<cat>|<var>]\tShow Locally modified variables/values
 s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
 e/E [<cat>|<var>]\tEdit variables (all, in category, single)
---
 c\t\t\tList categories
 v/V [<cat>|<var>]\tList Variables (all, in category, single)
---
Typical usage involves: u, [l,] w, r, q
""" % globals()

def usage ():
    command_usage="%prog [options] [default-xml [site-xml [consolidated-xml]]]"
    init_configuration ()
    command_usage +="""
\t default-xml defaults to %s
\t site-xml defaults to %s
\t consolidated-xml defaults to %s""" % (def_default_config,def_site_config, def_consolidated_config)
    return command_usage

####################
variable_usage= """Edit Commands :
#\tShow variable comments
.\tStops prompting, return to mainloop
/\tCleans any site-defined value, reverts to default
=\tShows default value
>\tSkips to next category
?\tThis help
"""

####################
def get_value (config,  category_id, variable_id):
    value = config.get (category_id, variable_id)
    return value

def get_type (config, category_id, variable_id):
    value = config.get (category_id, variable_id)
    #return variable['type']
    return str

def get_current_value (cread, cwrite, category_id, variable_id):
    # the value stored in cwrite, if present, is the one we want
    try:
        result=get_value (cwrite,category_id,variable_id)
    except:
        result=get_value (cread,category_id,variable_id)
    return result

# refrain from using plc_config's _sanitize
def get_varname (config,  category_id, variable_id):
    varname = category_id +"_"+ variable_id
    config.locate_varname(varname)
    return varname

# could not avoid using _sanitize here..
def get_name_comments (config, cid, vid):
    try:
        (category, variable) = config.get (cid, vid)
        (id, name, value, comments) = config._sanitize_variable (cid,variable)
        return (name,comments)
    except:
        return (None,[])

def print_name_comments (config, cid, vid):
    (name,comments)=get_name_comments(config,cid,vid)
    if name:
        print "### %s" % name
    if comments:
        for line in comments:
            print "# %s" % line
    else:
        print "!!! No comment associated to %s_%s" % (cid,vid)

####################
def list_categories (config):
    result=[]
    for section in config.sections():
        result += [section]
    return result

def print_categories (config):
    print "Known categories"
    for cid in list_categories(config):
        print "%s" % (cid.upper())

####################
def list_category (config, cid):
    result=[]
    for section in config.sections():
        if section == cid.lower():
            for (name,value) in config.items(section):
                result += ["%s_%s" %(cid,name)]    
    return result

def print_category (config, cid, show_comments=True):
    cid=cid.lower()
    CID=cid.upper()
    vids=list_category(config,cid)
    if (len(vids) == 0):
        print "%s : no such category"%CID
    else:
        print "Category %s contains" %(CID)
        for vid in vids:
            print vid.upper()

####################
def consolidate (default_config, site_config, consolidated_config):
    global service
    try:
        conso = Config(default_config)
        conso.load (site_config)
        conso.save (consolidated_config)
    except Exception, inst:
        print "Could not consolidate, %s" % (str(inst))
        return
    print ("Merged\n\t%s\nand\t%s\ninto\t%s"%(default_config,site_config,
                                              consolidated_config))

def reload_service ():
    global service
    os.system("set -x ; service %s reload" % service)

####################
def restart_service ():
    global service
    print ("==================== Stopping %s" % service)
    os.system("service %s stop" % service)
    print ("==================== Starting %s" % service)
    os.system("service %s start" % service)

####################
def prompt_variable (cdef, cread, cwrite, category, variable,
                     show_comments, support_next=False):


    category_id = category
    variable_id = variable

    while True:
        default_value = get_value(cdef,category_id,variable_id)
        variable_type = get_type(cdef,category_id,variable_id)
        current_value = get_current_value(cread,cwrite,category_id, variable_id)
        varname = get_varname (cread,category_id, variable_id)

        if show_comments :
            print_name_comments (cdef, category_id, variable_id)
        prompt = "== %s : [%s] " % (varname,current_value)
        try:
            answer = raw_input(prompt).strip()
        except EOFError :
            raise Exception ('BailOut')
        except KeyboardInterrupt:
            print "\n"
            raise Exception ('BailOut')

        # no change
        if (answer == "") or (answer == current_value):
            return None
        elif (answer == "."):
            raise Exception ('BailOut')
        elif (answer == "#"):
            print_name_comments(cread,category_id,variable_id)
        elif (answer == "?"):
            print variable_usage.strip()
        elif (answer == "="):
            print ("%s defaults to %s" %(varname,default_value))
        # revert to default : remove from cwrite (i.e. site-config)
        elif (answer == "/"):
            cwrite.delete(category_id,variable_id)
            print ("%s reverted to %s" %(varname,default_value))
            return
        elif (answer == ">"):
            if support_next:
                raise Exception ('NextCategory')
            else:
                print "No support for next category"
        else:
            if cdef.validate_type(variable_type, answer):
                cwrite.set(category_id, variable_id, answer)
                return
            else:
                print "Not a valid value"

def prompt_variables_all (cdef, cread, cwrite, show_comments):
    try:
        for (category_id, (category, variables)) in cread.variables().iteritems():
            print ("========== Category = %s" % category_id.upper())
            for variable in variables.values():
                try:
                    newvar = prompt_variable (cdef, cread, cwrite, category, variable,
                                              show_comments, True)
                except Exception, inst:
                    if (str(inst) == 'NextCategory'): break
                    else: raise

    except Exception, inst:
        if (str(inst) == 'BailOut'): return
        else: raise

def prompt_variables_category (cdef, cread, cwrite, cid, show_comments):
    cid=cid.lower()
    CID=cid.upper()
    try:
        print ("========== Category = %s" % CID)
        for vid in list_category(cdef,cid):
            (category,variable) = cdef.locate_varname(vid.upper())
            newvar = prompt_variable (cdef, cread, cwrite, category, variable,
                                      show_comments, False)
    except Exception, inst:
        if (str(inst) == 'BailOut'): return
        else: raise

####################
def show_variable (cdef, cread, cwrite,
                   category, variable,show_value,show_comments):
    assert category.has_key('id')
    assert variable.has_key('id')

    category_id = category ['id']
    variable_id = variable['id']

    default_value = get_value(cdef,category_id,variable_id)
    current_value = get_current_value(cread,cwrite,category_id,variable_id)
    varname = get_varname (cread,category_id, variable_id)
    if show_comments :
        print_name_comments (cdef, category_id, variable_id)
    if show_value:
        print "%s = %s" % (varname,current_value)
    else:
        print "%s" % (varname)

def show_variables_all (cdef, cread, cwrite, show_value, show_comments):
    for (category_id, (category, variables)) in cread.variables().iteritems():
        print ("========== Category = %s" % category_id.upper())
        for variable in variables.values():
            show_variable (cdef, cread, cwrite,
                           category, variable,show_value,show_comments)

def show_variables_category (cdef, cread, cwrite, cid, show_value,show_comments):
    cid=cid.lower()
    CID=cid.upper()
    print ("========== Category = %s" % CID)
    for vid in list_category(cdef,cid):
        (category,variable) = cdef.locate_varname(vid.upper())
        show_variable (cdef, cread, cwrite, category, variable,
                       show_value,show_comments)

####################
re_mainloop_0arg="^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
re_mainloop_1arg="^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
matcher_mainloop_0arg=re.compile(re_mainloop_0arg)
matcher_mainloop_1arg=re.compile(re_mainloop_1arg)

def mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config):
    global service
    while True:
        try:
            answer = raw_input("Enter command (u for usual changes, w to save, ? for help) ").strip()
        except EOFError:
            answer =""
        except KeyboardInterrupt:
            print "\nBye"
            sys.exit()

        if (answer == "") or (answer in "?hH"):
            print mainloop_usage
            continue
        groups_parse = matcher_mainloop_0arg.match(answer)
        command=None
        if (groups_parse):
            command = groups_parse.group('command')
            arg=None
        else:
            groups_parse = matcher_mainloop_1arg.match(answer)
            if (groups_parse):
                command = groups_parse.group('command')
                arg=groups_parse.group('arg')
        if not command:
            print ("Unknown command >%s< -- use h for help" % answer)
            continue

        show_comments=command.isupper()

        mode='ALL'
        if arg:
            mode=None
            arg=arg.lower()
            variables=list_category (cdef,arg)
            if len(variables):
                # category_id as the category name
                # variables as the list of variable names
                mode='CATEGORY'
                category_id=arg
            arg=arg.upper()
            (category,variable)=cdef.locate_varname(arg)
            if variable:
                # category/variable as output by locate_varname
                mode='VARIABLE'
            if not mode:
                print "%s: no such category or variable" % arg
                continue

        if command in "qQ":
            # todo check confirmation
            return
        elif command == "w":
            try:
                # Confirm that various constraints are met before saving file.
                validate_variables = g_configuration.get('validate_variables',{})
                validated_variables = cwrite.verify(cdef, cread, validate_variables)
                validator = g_configuration.get('validator',noop_validator)
                validator(validated_variables)
                cwrite.save(site_config)
            except:
                print "Save failed due to a configuration exception:"
                print traceback.print_exc()
                print ("Could not save -- fix write access on %s" % site_config)
                break
            print ("Wrote %s" % site_config)
            consolidate(default_config, site_config, consolidated_config)
            print ("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % \
                   (service,service))
        elif command in "uU":
            global usual_variables
            global flavour_xml_section_hash
            try:
                for varname in usual_variables:
                    (category,variable) = cdef.locate_varname(varname)
                    if not (category is None and variable is None):
                        prompt_variable(cdef, cread, cwrite, category, variable, False)
                
                # set the driver variable according to the already set flavour 
                generic_flavour = cwrite.items('sfa')[0][1]
                for section in cdef.sections():
                     if generic_flavour in flavour_xml_section_hash and flavour_xml_section_hash[generic_flavour] == section:
                         for item in cdef.items(section):
                              category = section
                              variable = item[0]
                              prompt_variable(cdef, cread, cwrite, category, variable, False)
                         break

            except Exception, inst:
                if (str(inst) != 'BailOut'):
                    raise
        elif command == "r":
            restart_service()
        elif command == "R":
            reload_service()
        elif command == "c":
            print_categories(cread)
        elif command in "eE":
            if mode == 'ALL':
                prompt_variables_all(cdef, cread, cwrite,show_comments)
            elif mode == 'CATEGORY':
                prompt_variables_category(cdef,cread,cwrite,category_id,show_comments)
            elif mode == 'VARIABLE':
                try:
                    prompt_variable (cdef,cread,cwrite,category,variable,
                                     show_comments,False)
                except Exception, inst:
                    if str(inst) != 'BailOut':
                        raise
        elif command in "vVsSlL":
            show_value=(command in "sSlL")
            (c1,c2,c3) = (cdef, cread, cwrite)
            if command in "lL":
                (c1,c2,c3) = (cwrite,cwrite,cwrite)
            if mode == 'ALL':
                show_variables_all(c1,c2,c3,show_value,show_comments)
            elif mode == 'CATEGORY':
                show_variables_category(c1,c2,c3,category_id,show_value,show_comments)
            elif mode == 'VARIABLE':
                show_variable (c1,c2,c3,category,variable,show_value,show_comments)
        else:
            print ("Unknown command >%s< -- use h for help" % answer)


####################
# creates directory for file if not yet existing
def check_dir (config_file):
    dirname = os.path.dirname (config_file)
    if (not os.path.exists (dirname)):
        try:
            os.makedirs(dirname,0755)
        except OSError, e:
            print "Cannot create dir %s due to %s - exiting" % (dirname,e)
            sys.exit(1)

        if (not os.path.exists (dirname)):
            print "Cannot create dir %s - exiting" % dirname
            sys.exit(1)
        else:
            print "Created directory %s" % dirname

####################
def optParserSetup(configuration):
    parser = OptionParser(usage=usage(), version="%prog " + version_tag )
    parser.set_defaults(config_dir=configuration['config_dir'],
                        service=configuration['service'],
                        usual_variables=configuration['usual_variables'])
    parser.add_option("","--configdir",dest="config_dir",help="specify configuration directory")
    parser.add_option("","--service",dest="service",help="specify /etc/init.d style service name")
    parser.add_option("","--usual_variable",dest="usual_variables",action="append", help="add a usual variable")
    return parser

def main(command,argv,configuration):
    global g_configuration
    g_configuration=configuration

    parser = optParserSetup(configuration)
    (config,args) = parser.parse_args()
    if len(args)>3:
        parser.error("too many arguments")

    configuration['service']=config.service
    configuration['usual_variables']=config.usual_variables
    configuration['config_dir']=config.config_dir
    # add in new usual_variables defined on the command line
    for usual_variable in config.usual_variables:
        if usual_variable not in configuration['usual_variables']:
            configuration['usual_variables'].append(usual_variable)

    # intialize configuration
    init_configuration()

    (default_config,site_config,consolidated_config) = (def_default_config, def_site_config, def_consolidated_config)
    if len(args) >= 1:
        default_config=args[0]
    if len(args) >= 2:
        site_config=args[1]
    if len(args) == 3:
        consolidated_config=args[2]

    for c in (default_config,site_config,consolidated_config):
        check_dir (c)

    try:
        # the default settings only - read only
        cdef = Config(default_config)

        # in effect : default settings + local settings - read only
        cread = Config(default_config)
    except:
        print traceback.print_exc()
        print ("default config files %s not found, is myplc installed ?" % default_config)
        return 1

    # local settings only, will be modified & saved
    config_filename = "%s/sfa_config" % config.config_dir
    cwrite=Config(config_filename)
    try:
        cread.load(site_config)
        cwrite.load(default_config)
        cwrite.load(site_config)
    except:
        cwrite = Config()

    mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config)
    return 0   

if __name__ == '__main__':
    command=sys.argv[0]
    argv = sys.argv[1:]
    main(command,argv,configuration)
