#!/usr/bin/env python
# -*- coding: utf-8 -*-
##
#
# Vaisala software source code file
#
# Copyright (c) Vaisala Oyj 2016. All rights reserved.
#
##
"""Command line tool for setting up values in INI and Java .properties files
with logic for restoring configuration values from .rpmsave-files created by
RPM.

"""
from __future__ import print_function

import ConfigParser
import argparse
import codecs
import datetime
import filecmp
import grp
import logging
import os
import pwd
import re
import shutil
import string
import sys

from os import environ as env
from os import path


logger = logging.getLogger('rsw-ini-property-tool')


def extract_option(file_path, section, option):
    """Extracts the option value from a given ini file and does no error
    handling. User is expected to handle ConfigParser thrown errors.

    See https://docs.python.org/2.7/library/configparser.html#ConfigParser.Error
    for details.
    """
    config = ConfigParser.SafeConfigParser()
    config.read(file_path)
    return config.get(section, option)


def extract_option_from_properties(file_path, option):
    """Extracts the option value from a given properties file and does no error
    handling.
    """
    with open(file_path) as file:
        for line in file:
            if line.startswith(option):
                return line.split('=')[1].strip()


def configure(file_path, section, option, value):
    conf = ConfigParser.RawConfigParser()
    logger.debug(u"Read configuration from {}".format(file_path))
    conf.read(file_path)

    if not conf.has_section(section):
        conf.add_section(section)

    conf.set(section, option, value)
    return conf


def is_properties_file(file_name):
    return args.file.endswith(".properties")


def main(args):
    # File ownership and permissions setup
    file_permissions = int(args.file_permissions, 8)

    file_owner = args.file_owner
    if file_owner is None:
        try:
            file_owner = pwd.getpwuid(os.stat(args.file).st_uid).pw_name
        except:
            sys.exit(u"Cannot determine file owner for '{}' and ".format(args.file) +
                     "it's not passed in via command line.")
    file_owner_uid = pwd.getpwnam(file_owner).pw_uid

    file_group = args.file_group
    if file_group is None:
        try:
            file_group = grp.getgrgid(os.stat(args.file).st_gid).gr_name
        except:
            sys.exit(u"Cannot determine file group for '{}' and ".format(args.file) +
                     "it's not passed in via command line.")
    file_group_gid = grp.getgrnam(file_group).gr_gid

    # File names
    rpmsave_file = args.file + ".rpmsave"
    temp_file = args.file + ".tmp"
    properties_file = is_properties_file(args.file)

    # Configuration logic proper
    current_value = None
    try:
        if properties_file:
            current_value = extract_option_from_properties(args.file, args.option)
        else:
            current_value = extract_option(args.file, args.section, args.option)
    except ConfigParser.Error:
        pass

    if current_value is not None and args.placeholder_value_regex is not None:
        result = re.match(args.placeholder_value_regex, current_value)
        if result is not None:
            current_value = None

    if current_value is not None and not args.force:
        logger.info(u"Option '{}' already set.".format(args.option))
        sys.exit(0)

    backup_value = None
    if args.use_rpmsave:
        try:
            if properties_file:
                backup_value = extract_option_from_properties(rpmsave_file, args.option)
            else:
                backup_value = extract_option(rpmsave_file, args.section, args.option)
        except:
            pass

        if isinstance(backup_value, basestring):
            logger.info(u"Option '{}' found from .rpmsave, using that.".format(args.option))

    if args.new_value is None and backup_value is None:
        sys.exit("Cannot resolve old value from .rpmsave and no new value passed in.")

    new_value = filter(None, [args.new_value, backup_value])[0] # take first non-False value

    if properties_file:
        with open(temp_file, "wb") as tmp_f:
            with open(args.file) as f:
                replaced = False
                for line in f:
                    if line.startswith(args.option):
                        newline = args.option + " = " + new_value
                        tmp_f.write(newline)
                        replaced = True
                    else:
                        tmp_f.write(line)
                if not replaced:
                    newline = args.option + " = " + new_value
                    tmp_f.write(newline)
    else:
        config = configure(args.file, args.section, args.option, new_value)
        with open(temp_file, "wb") as f:
            config.write(f)

    changed = not filecmp.cmp(temp_file, args.file)
    if changed:
        backup_suffix = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
        backup_file = args.file + "." + backup_suffix
        shutil.copyfile(args.file, backup_file)

        try:
            if not args.no_chown:
                os.chown(backup_file, file_owner_uid, file_group_gid)
            os.chmod(backup_file, file_permissions)
        except:
            pass

        if not args.no_chown:
            os.chown(temp_file, file_owner_uid, file_group_gid)
        os.chmod(temp_file, file_permissions)
        os.rename(temp_file, args.file)

    if os.path.isfile(temp_file):
        logger.debug(u"Remove temporary file {}".format(temp_file))
        os.remove(temp_file)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("--file", dest="file", required=True,
                        help="INI file to operate on.")
    parser.add_argument("-s", "--section", dest="section", required=True,
                        help="INI section the password property is in.")
    parser.add_argument("-o", "--option", dest="option", required=True,
                        help="INI property to set.")

    parser.add_argument("--placeholder-value-regex", dest="placeholder_value_regex",
                        required=False, default=None,
                        help="Regular expression which matches a placeholder value that should be ignored.")

    parser.add_argument("--resolve-from-rpmsave", dest="use_rpmsave",
                        action="store_true", default=False,
                        help="If specified, old option value " +
                        "is restored from file with '.rpmsave' suffix.")

    parser.add_argument("--new-value", dest="new_value",
                        action="store_true", default=None)

    parser.add_argument("--force", dest="force", action="store_true",
                        default=False, help="Override current value even if it's set.")

    parser.add_argument("--file-permissions", dest="file_permissions",
                        default="0o600", required=False,
                        help="Permissions for the file.")
    parser.add_argument("--file-owner", dest="file_owner",
                        help="Username for the file, if not set, use file's current one.",
                        default=None, required=False)
    parser.add_argument("--file-group", dest="file_group",
                        help="Group for the file, if not set, use file's current one.",
                        default=None, required=False)

    # Testing argument
    parser.add_argument("--no-chown", dest="no_chown",
                        help=argparse.SUPPRESS, default=False, action="store_true")

    parser.add_argument("-d", "--debug", dest="debug", action="store_true",
                        help="Enable debug messages.")
    args = parser.parse_args()

    def setup_logging(debug_enabled):
        root = logging.getLogger()
        if debug_enabled:
            root.setLevel(logging.DEBUG)
        else:
            root.setLevel(logging.INFO)
        ch = logging.StreamHandler(codecs.getwriter('utf-8')(sys.stdout))
        ch.setLevel(logging.DEBUG)
        formatter = logging.Formatter(u'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        ch.setFormatter(formatter)
        root.addHandler(ch)
    setup_logging(args.debug)

    main(args)
