#!/opt/thinlinc/libexec/python3
# -*-Python-*-
import os
import sys
import getopt
from thinlinc import hiveconf
import locale

VERSION="0.0"
DEFAULT_TYPE="string"
DEFAULT_ROOT_HIVE="/etc/root.hconf"

def safe_string(s):
    """Makes a string safe to print on sys.stdout"""
    enc = sys.stdout.encoding
    if enc is None:
        enc = locale.getpreferredencoding(False)
    return s.encode(enc, errors="replace").decode(enc)

def get_type(s):
    """Parse parameter string. Returns (type, parameter)"""
    words = s.split(":", 1)
    if not words[1:]:
        # No colon in string
        return (DEFAULT_TYPE, s)
    
    (beforecolon, aftercolon) = words
    if beforecolon in ("string", "bool", "integer", "float", "binary",
                       "string_list", "bool_list", "integer_list",
                       "float_list", "binary_list"):
        return (beforecolon, aftercolon)
    else:
        return (DEFAULT_TYPE, s)


def get_value(s):
    """Parse parameter string. Returns (parameterpath, value)"""
    words = s.split("=", 1)
    if not words[1:]:
        # No equal sign in string
        return (s, None)
    else:
        return tuple(words)


def handle_param(hive, param):
    """Print parameter. Returns zero on success"""
    (paramtype, param) = get_type(param)
    (parampath, input_value) = get_value(param)

    current_value = None
    new_param = False
    method = getattr(hive, "get_%s" % paramtype)
    try:
        current_value = method(parampath)
    except hiveconf.NotAParameterError:
        display_error =  "%s: Not a parameter" % parampath
        new_param = True

    if current_value == None:
        display_error = "%s: No such parameter" % parampath
        new_param = True

    if not input_value:
        # Display value
        if new_param:
            # Can't display the value of a parameter that doesn't exist
            print(display_error, file=sys.stderr)
            return 1
        else:
            if isinstance(current_value, str):
                current_value = safe_string(current_value)
            print(current_value)
    else:
        # Set value
        method = getattr(hive, "set_%s" % paramtype)
        if not method(parampath, input_value):
            print("Failed to set parameter", parampath, file=sys.stderr)
            return 1
        if new_param:
            print("Created new parameter: ", parampath)
    return 0


def eval_print(hive, varname, param, export):
    (paramtype, parampath) = get_type(param)
    method = getattr(hive, "get_%s" % paramtype)
    value = method(parampath)
    if export:
        print("export", end=' ')
    if value:
        value = str(value)
        # ' is the only character that needs special handling
        value = value.replace("'", "'\\''")
        print("%s='%s'" % (varname, value))
    else:
        print("%s=''" % varname)
    if value == None:
        return 1
    else:
        return 0

def _walk(hive, folderpath="/"):
    params = hive.get_parameters(folderpath)
    folders = hive.get_folders(folderpath)

    yield (folderpath, params, folders)

    for subname in folders:
        if subname == "/":
            continue
        if folderpath != "/":
            subname = "/" + subname
        subfolder = folderpath + subname
        yield from _walk(hive, subfolder)

def imp_walk(hive, ih):
    for (folderpath, params, folders) in _walk(ih):
        for paramname in params:
            parampath = folderpath + "/" + paramname
            value = ih.get_string(parampath)
            #print "setting", parampath, "to", value
            hive.set_string(parampath, value)
            if parampath in purge_params:
                purge_params.remove(parampath)

# A list of parameters to delete
purge_params = []

def purge_walk(hive, ph):
    for (folderpath, params, folders) in _walk(ph):
        for paramname in params:
            parampath = folderpath + "/" + paramname
            if hive.lookup(parampath) != None:
                # Add to delete-list
                purge_params.append(parampath)


def print_walk(hive, root, recursive=True):
    folder = hive.lookup(root)
    rootdepth = len(folder.sectionname.split("/"))

    # Iterate through the levels of the hive,
    # one sub-folder deeper for each iteration
    for (folderpath, params, folders) in _walk(hive, root):
        folder = hive.lookup(folderpath)
        section = folder.sectionname
        depth = len(folder.sectionname.split("/"))
        indent = (depth - rootdepth) * 4
        if len(folder.sources) > 0:
            source = folder.sources[0]
        else:
            source = None

        # Unless it's the first iteration - print sub-folder name.
        # It should be printed above it's contents in recursive mode.
        if folderpath != root:
            foldername = os.path.basename(folderpath)
            foldername = safe_string(foldername)
            sys.stdout.write(" " * (indent-4))
            sys.stdout.write(foldername + "/\n")

        # Print Parameters and values
        for param in params:
            parampath = folderpath + "/" + param
            value = hive.get_string(parampath)
            param = safe_string(param)
            value = safe_string(value)
            sys.stdout.write(" " * indent)
            sys.stdout.write("%s = %s\n" % (param, value))

        if not recursive:
            # Print sub-folder names without the folder contents
            for folder in folders:
                foldername = safe_string(folder)
                if foldername == "/":
                    continue

                sys.stdout.write(" " * indent)
                sys.stdout.write(foldername + "/\n")

        if not recursive:
            break


def usage():
    print("""
hivetool [options] [type:]parameter[=value] ...

  -a,--all-entries        Print all parameters and values in a folder
  -i,--import <file>      Import all parameters in specified file
  -p,--purge <file>       Remove parameters in specified file which exists elsewhere
  -R,--recursive	  When using -a, ascend folders recursively
  -r,--root <file>	  Specify root hive file. Default is /etc/root.hconf
  -e,--eval VAR=parameter Print parameter value in format suitable for
                          assignment to shell variable, via evaluation
  -E folder               As -e, but print all parameters in specified folder
  -v,--version		  Print version
  -x,--export             When using -e,-E, export variables
  -?,--help		  Show this help message

  type is one of: string, bool, integer, float, binary, string_list,
  bool_list, integer_list, float_list, binary_list. Default is string

  Example:

  hivetool -Ra /

  hivetool -r /etc/samba/smb.conf /global/workgroup=MYWORKGROUP

  hivetool -p /etc/samba/smb.conf -i /etc/samba/smb.conf.rpmsave
""", file=sys.stderr)



def main():
    try:
        locale.setlocale(locale.LC_ALL, "")
    except locale.Error as e:
        print("\nWarning: Can't set the locale (%s); make sure $LC_* and $LANG are correct\n" %
              e, file=sys.stderr)

    try:
        opts, args = getopt.getopt(sys.argv[1:], "a:i:p:Rr:ve:E:x?",
                                   ["all-entries=", "import=", "purge=", "recursive", "root=", "version", "help",
                                    "eval=", "export"])
    except getopt.GetoptError:
        usage()
        sys.exit(2)

    roothive = DEFAULT_ROOT_HIVE
    walk_folders = []
    imp_files = []
    purge_files = []
    recursive = 0
    e_params =[]
    E_params = []
    eval_export = 0 
    for o, a in opts:
        if o in ("-a", "--all-entries"):
            walk_folders.append(a)
        if o in ("-i", "--import"):
            imp_files.append(a)
        if o in ("-p", "--purge"):
            purge_files.append(a)
        if o in ("-R", "--recursive"):
            recursive = 1
        if o in ("-r", "--root"):
            roothive = a
        if o in ("-v", "--version"):
            print("hivetool version", VERSION, file=sys.stderr)
            sys.exit(0)
        if o in ("-e", "--eval"):
            words = a.split("=", 1)
            if not words[1:]:
                usage()
                sys.exit(2)
            e_params.append(words)
        if o in ("-E",):
            E_params.append(a)
        if o in ("-x", "--export"):
            eval_export = 1
        if o in ("-?", "--help"):
            usage()
            sys.exit(0)

    errors = 0

    # Try to open root hive
    hive = hiveconf.open_hive(roothive)

    # Retrieve parameters to purge from specified files
    for purge_file in purge_files:
        ph = hiveconf.open_hive(purge_file)
        reduced_hive = hiveconf.open_hive(roothive, blacklist=[purge_file])
        purge_walk(reduced_hive, ph)

    # Import specified files
    for imp_file in imp_files:
        ih = hiveconf.open_hive(imp_file)
        imp_walk(hive, ih)

    # Actually purge. Note that if you specify all files where the
    # parameter is defined as purge files, then all instances of the
    # parameter will be deleted.
    for purge_file in purge_files:
        ph = hiveconf.open_hive(purge_file)
        for pp in purge_params:
            ph.delete(pp)

    # Handle -e parameters
    for (varname, param) in e_params:
        errors += eval_print(hive, varname, param, eval_export)

    # Handle -E
    for foldername in E_params:
        folder = hive.lookup(foldername)
        params = hive.get_parameters(foldername)
        for param in params:
            errors += eval_print(folder, param, param, eval_export)

    # Get/set listed parameters
    for param in args:
        errors += handle_param(hive, param)

    # Walk
    for foldername in walk_folders:
        folder = hive.lookup(foldername)
        if not folder:
            print("%s: Folder not found" % foldername, file=sys.stderr)
            continue

        if not isinstance(folder, hiveconf.Folder):
            print("%s: not a folder" % foldername, file=sys.stderr)
            continue

        print_walk(hive, foldername, recursive)

    sys.exit(errors)

if __name__ == "__main__":
    main()

