#!/usr/bin/env python3
#
# Copyright (c) Greenplum Inc 2009. All Rights Reserved.
#
#
# NOTE: The file semantics here are subtle.  In this file, we are reading or writing to filename, which is
#   presumed to be a postgresql.conf format file.  When reading a file, we read that file in a single open(file) call
#   When writing a file, we read that file, and write the new file contents to a temporary file created securely.
#   Then, we use os.rename() to overwrite that existing file.  These rules allow us to either get the OLD file or the
#   NEW file in any subsequent operation.  This is because os.rename() POSIX requires does not overwrite the existing
#   file but swaps the inodes of the files, so the os.rename() does NOT affect any current readers who have already
#   opened the OLD file.

try:
    import os
    import shutil
    import sys
    import tempfile
    import base64
    import pickle

    from optparse import Option, OptionParser
    from gppylib.gpparseopts import OptParser, OptChecker
except ImportError as e:
    sys.exit('Cannot import modules.  Please check that you have sourced greenplum_path.sh.  Detail: ' + str(e))

_help = ["""This enables one to add, get and remove postgresql.conf configuration parameters.
The absolute path to the postgresql.conf file is required."""]


def parseargs():
    parser = OptParser(option_class=OptChecker)

    parser.setHelp(_help)

    parser.remove_option('-h')
    parser.add_option('-h', '-?', '--help', action='help', help='show this help message and exit')

    parser.add_option('--file', type='string', help='Required: The absolute path of postgresql.conf')
    parser.add_option('--add-parameter', type='string', help='The configuration parameter to add. --value is required.')
    parser.add_option('--value', type='string', help='The configuration value to add when using --add-parameter.')
    parser.add_option('--get-parameter', type='string', help='The configuration parameter value to return.')
    parser.add_option('--remove-parameter', type='string', help='The configuration parameter value to disable.')

    (options, args) = parser.parse_args()
    return validate_args(options)


def validate_args(options):
    if not options.file:
        sys.stderr.write("--file required")
        sys.exit(1)

    if options.add_parameter and not options.value:
        sys.stderr.write("Missing --value <value> when adding parameter '%s'." % options.add_parameter)
        sys.exit(1)

    if (options.get_parameter or options.remove_parameter) and options.value:
        sys.stderr.write("Cannot specify --value when using --get-parameter or --remove-parameter")
        sys.exit(1)

    if (options.add_parameter and options.get_parameter) or \
            (options.add_parameter and options.remove_parameter) or \
            (options.get_parameter and options.remove_parameter):
        sys.stderr.write("Can only specify one of --add-parameter, --get-parameter, or --remove-parameter")
        sys.exit(1)

    return options


def _read_from_file_and_get_empty_tempfile(filename):
    with open(filename, 'r') as infile:
        lines = infile.readlines()

    # TODO: does this work in the case of temp_conf_file containing spaces?
    (tempFD, temp_conf_path) = tempfile.mkstemp(prefix="postgresql_", suffix=".conf", dir=os.path.abspath(os.path.dirname(filename)))
    os.close(tempFD)
    os.chmod(os.path.abspath(temp_conf_path), os.stat(os.path.abspath(filename)).st_mode)
    return lines, temp_conf_path


def comment_parameter(filename, name):
    lines, temp_conf_path = _read_from_file_and_get_empty_tempfile(filename)

    new_lines = 0
    with open(os.path.abspath(temp_conf_path), 'w') as outfile:
        for line in lines:
            potential_match = line.split("=", 1)[0]
            if potential_match.strip() == name:
                outfile.write('#')
                new_lines = new_lines + 1
            outfile.write(line)

    # If there are matching parameters, replace the conf file with the tempfile,
    # else just remove the tempfile.
    if new_lines > 0:
        os.rename(os.path.abspath(temp_conf_path), filename)
    else:
        os.unlink(os.path.abspath(temp_conf_path))


def add_parameter(filename, name, value):
    lines, temp_conf_path = _read_from_file_and_get_empty_tempfile(filename)

    with open(os.path.abspath(temp_conf_path), 'w') as outfile:
        for line in lines:
            outfile.write(line)
        outfile.write(name + '=' + pickle.loads(base64.urlsafe_b64decode(value)) + os.linesep)

    os.rename(os.path.abspath(temp_conf_path), filename)


# NOTE: though apparently not documented, postgresQL returns the last valid value
def get_parameter(filename, name):
    with open(filename, 'r') as f:
        for line in reversed(f.readlines()):
            parts = line.split("=", 1)
            if len(parts) > 1 and parts[0].lstrip().startswith(name):
                return parts[1].strip()


def main():
    options = parseargs()
    if options.get_parameter:
        try:
            value = get_parameter(options.file, options.get_parameter)
            sys.stdout.write(base64.urlsafe_b64encode(pickle.dumps(value)).decode())
            return
        except Exception as err:
            sys.stderr.write("Failed to get value for parameter '%s' in file %s due to: %s" % (
                options.get_parameter, options.file, str(err)))
            sys.exit(1)

    if options.remove_parameter:
        try:
            comment_parameter(options.file, options.remove_parameter)
            return
        except Exception as err:
            sys.stderr.write("Failed to remove parameter '%s' in file %s due to: %s" %
                             (options.remove_parameter, options.file, str(err)))
            sys.exit(1)

    if options.add_parameter:
        try:
            comment_parameter(options.file, options.add_parameter)
            add_parameter(options.file, options.add_parameter, options.value)
            return
        except Exception as err:
            sys.stderr.write("Failed to add parameter '%s' in file %s due to: %s" %
                             (options.add_parameter, options.file, str(err)))
            sys.exit(1)


if __name__ == "__main__":
    main()
