#!/usr/bin/python2
# -*- mode: python -*-
#
# This file is part of Plinth.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

"""
Configuration helper for Plinth PageKite interface

Unfortunately there is no python3 package for augeas yet
"""

import argparse
import augeas
import os
import subprocess

import util
from pagekite_util import SERVICE_PARAMS, convert_to_service, \
    convert_service_to_string, get_augeas_servicefile_path, CONF_PATH

aug = augeas.Augeas()

PATHS = {
    'service_on': os.path.join(CONF_PATH, '*', 'service_on', '*'),
    'kitename': os.path.join(CONF_PATH, '10_account.rc', 'kitename'),
    'kitesecret': os.path.join(CONF_PATH, '10_account.rc', 'kitesecret'),
    'abort_not_configured': os.path.join(CONF_PATH, '10_account.rc',
                                         'abort_not_configured'),
    'defaults': os.path.join(CONF_PATH, '20_frontends.rc', 'defaults'),
    'frontend': os.path.join(CONF_PATH, '20_frontends.rc', 'frontend'),
}


def parse_arguments():
    """Return parsed command line arguments as dictionary"""
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')

    # Enable/disable the pagekite service
    subparsers.add_parser('is-running', help='Get whether PakeKite is running')
    subparsers.add_parser('start-and-enable', help='Enable PageKite service')
    subparsers.add_parser('stop-and-disable', help='Disable PageKite service')
    subparsers.add_parser('restart', help='Restart PageKite service')

    # Frontend
    subparsers.add_parser('get-frontend', help='Get pagekite frontend')
    set_frontend = subparsers.add_parser('set-frontend',
                                         help='Set pagekite frontend')
    set_frontend.add_argument('url', help='frontend url')

    # Kite details (name + secret)
    subparsers.add_parser('get-kite',
                          help='Get configured kite name and secret')
    set_kite = subparsers.add_parser('set-kite',
                                     help='Configure kite name and its secret')
    set_kite.add_argument('--kite-name',
                          help='Name of the kite (eg: mybox.pagekite.me)')
    set_kite.add_argument('--kite-secret', help='Secret for the kite')

    # Add/remove pagekite services (service_on entries)
    subparsers.add_parser('get-services', help='Get list of enabled services')
    add_service = subparsers.add_parser('add-service',
                                        help='Add a pagekite service')
    add_service.add_argument('--service', help='":"-separated service string')
    remove_service = subparsers.add_parser('remove-service',
                                           help='Remove a pagekite service')
    remove_service.add_argument('--service', help='":"-separated service \
                                string')

    return parser.parse_args()


def _service(action):
    """Start/stop/restart the pagekite service"""
    error = subprocess.call(['service', 'pagekite', action])
    if error:
        raise Exception('Unable to %s PageKite server' % action)


def subcommand_is_running(_):
    """Print whether pagekite is enabled (yes or no)"""
    print 'yes' if util.service_is_running('pagekite') else 'no'


def subcommand_restart(_):
    """Restart the pagekite service"""
    _service('restart')
    print 'restarted'


def subcommand_start_and_enable(_):
    aug.remove(PATHS['abort_not_configured'])
    aug.save()
    # 'start' alone sometimes fails, even if the service is not running
    _service('restart')
    print 'enabled'


def subcommand_stop_and_disable(_):
    _service('stop')
    aug.set(PATHS['abort_not_configured'], '')
    aug.save()
    print 'disabled'


def subcommand_get_frontend(_):
    """Get pagekite frontend url"""
    if aug.match(PATHS['defaults']):
        print "pagekite.net"
    else:
        url = aug.get(PATHS['frontend'])
        print url if url else ""


def subcommand_set_frontend(arguments):
    """Set pagekite frontend url, taking care of defaults and pagekite.net"""
    if arguments.url in ('pagekite.net', 'defaults', 'default'):
        enable_pagekitenet_frontend()
    else:
        aug.remove(PATHS['defaults'])
        aug.set(PATHS['frontend'], arguments.url)
        aug.save()


def enable_pagekitenet_frontend():
    """Enable using default pageket.net frontend

    This disables any other frontends.
    """
    aug.set(PATHS['defaults'], '')
    aug.remove(PATHS['frontend'])
    aug.save()
    print "enabled"


def subcommand_get_services(arguments):
    """ lists all available (enabled) services """
    for match in aug.match(PATHS['service_on']):
        print ":".join([aug.get(os.path.join(match, param)) for param in
                        SERVICE_PARAMS])


def subcommand_remove_service(arguments):
    """Searches and removes the service(s) that match all given parameters"""
    service = convert_to_service(arguments.service)
    paths = get_existing_service_paths(service)
    # TODO: theoretically, everything to do here is:
    # [aug.remove(path) for path in paths]
    # but augeas won't let you save the changed files, and won't tell you why
    for path in paths:
        filepath = convert_augeas_path_to_filepath(path)
        service_found = False
        with open(filepath, 'r') as file:
            lines = file.readlines()
            for i, line in enumerate(lines):
                if line.startswith('service_on') and \
                        all(param in line for param in service.values()):
                    lines[i] = ""
                    service_found = True
                    break
        if service_found:
            with open(filepath, 'w') as file:
                file.writelines(lines)
                # abort to only allow deleting one service
                break
    _service('restart')


def get_existing_service_paths(service):
    """Return paths of existing services that match the given service params"""
    # construct an augeas query path with patterns like:
    #     */service_on/*[protocol='http']
    path = PATHS['service_on']
    for key, value in service.items():
        path += "[%s='%s']" % (key, value)
    return aug.match(path)


def subcommand_add_service(arguments):
    """Add one service"""
    service = convert_to_service(arguments.service)
    if get_existing_service_paths(service):
        msg = "Service with the parameters %s already exists"
        raise RuntimeError(msg % service)

    root = get_new_service_path(service['protocol'])
    # TODO: after adding a service, augeas fails writing the config;
    # so add the service_on entry manually instead
    path = convert_augeas_path_to_filepath(root)
    with open(path, 'a') as servicefile:
        line = "\nservice_on = %s\n" % convert_service_to_string(service)
        servicefile.write(line)
    _service('restart')


def convert_augeas_path_to_filepath(augpath, prefix='/files',
                                    suffix='service_on'):
    """Convert an augeas service_on path to the actual file path"""
    if augpath.startswith(prefix):
        augpath = augpath.replace(prefix, "", 1)

    index = augpath.rfind(suffix)
    if index:
        augpath = augpath[:index]
    return augpath.rstrip('/')


def get_new_service_path(protocol):
    """Get the augeas path of a new service for a protocol

    This takes care of existing services using a /service_on/*/ query"""
    root = get_augeas_servicefile_path(protocol)
    new_index = len(aug.match(root + '/*')) + 1
    return os.path.join(root, str(new_index))


def subcommand_get_kite(_):
    """Print details of the currently configured kite"""
    kitename = aug.get(PATHS['kitename'])
    kitesecret = aug.get(PATHS['kitesecret'])
    print kitename if kitename else ''
    print kitesecret if kitesecret else ''


def subcommand_set_kite(arguments):
    """Set details of the kite"""
    aug.set(PATHS['kitename'], arguments.kite_name)
    aug.set(PATHS['kitesecret'], arguments.kite_secret)
    aug.save()


def main():
    """Parse arguments and perform all duties"""
    arguments = parse_arguments()

    subcommand = arguments.subcommand.replace('-', '_')
    subcommand_method = globals()['subcommand_' + subcommand]
    subcommand_method(arguments)


if __name__ == "__main__":
    main()
