#!/usr/bin/env python
##
#
# Vaisala software source code file
#
# Copyright (c) Vaisala Oyj 2015. All rights reserved.
#
##
from __future__ import print_function

import ConfigParser
import StringIO
import argparse
import datetime
import errno
import httplib
import logging
import os
import platform
import pwd
import socket
import stat
import subprocess
import sys
import time
import traceback
import urlparse

from os import environ as env
from os import path


_logger = logging.getLogger('rsw-basemap-installer')


CONFIG_DIRECTORY = os.environ.get("VAISALA_RADARSW_CONFIG_DIR",
                                  '/etc/vaisala/radarsw/configuration')


def dbg(*args, **kwargs):
    buf = StringIO.StringIO()
    kwargs['file'] = buf
    print(*args, **kwargs)
    _logger.debug(buf.getvalue().rstrip('\n'))
    buf.close()


def log(*args, **kwargs):
    buf = StringIO.StringIO()
    kwargs['file'] = buf
    print(*args, **kwargs)
    _logger.info(buf.getvalue().rstrip('\n'))
    buf.close()


class StepFailed(Exception):
    pass


class Step(object):
    def describe(self, what):
        log(u"# {0}".format(self.description))
        if what is not None:
            log(u"#  {0}".format(what))
        log(u"##")


class FunctionStep(Step):
    def __init__(self, func, description):
        self.func = func
        self.description = description

    def __call__(self):
        self.describe(None)
        return self.func()


class ShellCommandStep(Step):
    def __init__(self, command, description, shell=False):
        self.command = command
        self.description = description
        self.shell = shell

    def dry_run(self):
        if isinstance(self.command, basestring):
            self.describe('`' + self.command + '`')
        else:
            self.describe('`' + " ".join(self.command) + '`')

    def __call__(self):
        self.dry_run()
        proc = subprocess.Popen(self.command,
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                                shell=self.shell)

        def log_stream_lines(stream):
            for line in iter(stream.readline, b''):
                log(line)

        while proc.poll() is None:
            log_stream_lines(proc.stdout)
            log_stream_lines(proc.stderr)
            time.sleep(0.01)

        # Handle lines possibly left in the buffers
        log_stream_lines(proc.stdout)
        log_stream_lines(proc.stderr)

        status = proc.returncode
        if status != 0:
            raise StepFailed(
                u"Command `{0}` failed with status code {1}".format(" ".join(self.command), status))


def skip_retry_fail():
    allowed_inputs = ['skip', 'retry', 'fail']

    while True:
        log('Skip, retry or fail step (skip/retry/fail)?')
        reply = raw_input().replace('\n','').strip()
        if reply not in allowed_inputs:
            log(u"Allowed inputs are {0}.".format(", ".join(allowed_inputs)))
        else:
            break

    return reply


def main():
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("--gis-db-dump", dest="gis_db_dump", metavar="PATH",
                        help="GIS database dump.", required=True)
    parser.add_argument("--terrain-dir", dest="terrain_dir", metavar="DIRECTORY",
                        default=None, help="Terrain directory.")
    parser.add_argument("--skip-terrain", dest="skip_terrain", action="store_true",
                        help="Skip terrain layer.", default=False)
    parser.add_argument("--max-zoom", dest="max_zoom", type=int,
                        help=argparse.SUPPRESS)
    parser.add_argument("-d", "--debug", dest="debug", action="store_true",
                        help="Enable debug messages.")
    args = parser.parse_args()

    if args.skip_terrain is False and args.terrain_dir is None:
        parser.error("--terrain-dir required if --skip-terrain is not specified")

    if not os.path.exists(args.gis_db_dump):
        parser.error(u"GIS database dump '{}' does not exist.".format(args.gis_db_dump))

    if not args.skip_terrain and not os.path.exists(args.terrain_dir):
        parser.error(u"Terrain directory '{}' does not exist.".format(args.terrain_dir))

    if pwd.getpwuid(os.getuid()).pw_name != 'root':
        sys.exit("This script must be run as root.")

    # Set up logging
    log_file_path = os.path.join(os.environ['HOME'],
                                 u"rsw-basemap-installer-{0}.log".format(
                                     time.strftime("%Y%m%d-%H%M%S")))
    file_handler = logging.FileHandler(log_file_path)
    file_handler.setLevel(logging.DEBUG)
    _logger.addHandler(file_handler)

    log("###")
    log("## Begin system information section")
    log("###")
    def no_check_output(*args, **kwargs):
        try:
            return subprocess.check_output(*args, **kwargs)
        except Exception, e:
            return str(e)

    log("Installed packages with postgres|postgis|vaisala in their name:")
    log(no_check_output(['yum list installed | grep postgres'], shell=True))
    log(no_check_output(['yum list installed | grep postgis'], shell=True))
    log(no_check_output(['yum list installed | grep vaisala'], shell=True))

    stdout_handler = logging.StreamHandler(sys.stdout)
    if args.debug:
        stdout_handler.setLevel(logging.DEBUG)
        _logger.setLevel(logging.DEBUG)
    else:
        stdout_handler.setLevel(logging.INFO)
        _logger.setLevel(logging.INFO)
    _logger.addHandler(stdout_handler)
    log(u"Logging to stdout and to '{0}'.".format(log_file_path))
    # End of logging setup

    log("Current directory: {}".format(os.getcwd()))
    log("Started with command line {}".format(" ".join(sys.argv)))

    steps = [
        ShellCommandStep(['service', 'vaisala-radarsw-geoserver', 'start'],
                         u"Start GeoServer (or make sure it's running)"),
        ShellCommandStep(['rsw-http-wait-until-healthy', '-u', 'http://localhost:34180/geoserver/',
                          '-c', '302', '-n', 'GeoServer'], u"Waiting for GeoServer to start up"),

        ShellCommandStep(['rsw-geoserver-password-tool', 'reset-master-password'], u"Change GeoServer master password"),
        ShellCommandStep(['service', 'vaisala-radarsw-geoserver', 'stop'],
                         u"Stop GeoServer to take new admin password and plugins into use"),

        ShellCommandStep(['rsw-geoserver-password-tool', 'reset-admin-password'], u"Change GeoServer admin password"),
        ShellCommandStep(['rsw-basemap-install-plugins'], u"Install GeoServer plugins"),
        ShellCommandStep(['rsw-basemap-install-configuration'], u"Install GeoServer configuration"),
        ShellCommandStep(['rsw-basemap-install-resources'], u"Install GeoServer resources"),
        ShellCommandStep(['rsw-geoserver-configure-logging'], u"Configure GeoServer production logging"),
    ]

    if not args.skip_terrain:
        steps.append(
            ShellCommandStep(['rsw-basemap-install-terrain-files', args.terrain_dir],
                             u"Install terrain files"))

    finalization_steps = [
        ShellCommandStep(['service', 'vaisala-radarsw-geoserver', 'restart'],
                         u"Restart GeoServer to take changes into effect"),
        ShellCommandStep(['rsw-http-wait-until-healthy', '-u', 'http://localhost:34180/geoserver/',
                          '-c', '302', '-n', 'GeoServer'], u"Waiting for GeoServer to start up"),
        ShellCommandStep(['rsw-gis-db-tool', 'create'], u"Create GIS database"),
        ShellCommandStep(['rsw-gis-db-tool', 'restore-dump', '--gis-db-dump', args.gis_db_dump],
                         u"Populate GIS database"),
        ShellCommandStep(['rsw-gis-db-tool', 'vacuum'], u"Run maintenance on GIS database"),
    ]

    if args.max_zoom is not None:
        cmd = ['rsw-basemap-layer-setup', '--max-zoom', str(args.max_zoom)]
        if args.skip_terrain:
            cmd.append('--skip-terrain')

        finalization_steps.append(
            ShellCommandStep(cmd, u"Create layers for GeoServer (with specified max zoom)"))
    else:
        cmd = ['rsw-basemap-layer-setup']
        if args.skip_terrain:
            cmd.append('--skip-terrain')

        finalization_steps.append(ShellCommandStep(cmd, u"Create layers for GeoServer"))

    runnable_steps = steps + finalization_steps
    for idx, step in enumerate(runnable_steps):
        if idx > 0:
            log()
        log(u"## Basemap Installer Step {0}/{1}".format(idx + 1, len(runnable_steps)))

        while True:
            try:
                step()
                break
            except StepFailed, f:
                try:
                    log(u"Step failed: {0}".format(str(f)))
                    skipretryfail = skip_retry_fail()
                    if skipretryfail == 'skip':
                        break
                    elif skipretryfail == 'retry':
                        pass
                    elif skipretryfail == 'fail':
                        raise f
                except KeyboardInterrupt, kbi:
                    _logger.error(traceback.format_exc())
                    raise kbi

if __name__ == '__main__':
   main()
