#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    Скрипт для управления Lunapark API сервером.
    Позволяет запустить, остановить сервер, получить информацию о текущем статусе.
"""

import os
import sys
import socket
import subprocess
import fcntl
import errno
import signal
import time
import logging

DEFAULT_MODE = 'info'
PID_FILENAME = 'tank_api_server.pid'

logger = logging.getLogger('tankapi')


def check_server_pid(workdir):
    """
        Проверить pid из рабочей директории
        @return: pid в виде числа int, если файл существует, в нём записано число и он залочен;
                 в противном случае возвращается None
    """
    pidfile_name = os.path.join(workdir, PID_FILENAME)
    if not os.path.exists(pidfile_name):
        # если нет pid-файла, считаем, что сервер не запущен
        return None
    pidfile = open(pidfile_name, 'r')
    pid = int(pidfile.read())
    pidfile.close()
    # проверяем лок файла
    pidfile = open(pidfile_name, 'a')
    try:
        fcntl.lockf(pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError as error:
        if error.errno in (errno.EACCES, errno.EAGAIN):
            return pid
        else:
            raise
    fcntl.lockf(pidfile, fcntl.LOCK_UN)
    pidfile.close()
    return None


def is_port_free(port):
    """
        Занят ли порт @port на localhost
        @return: True, если порт занят; в противном случае возвращается False
    """
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect(('localhost', port))
        sock.close()
        return False
    except socket.error:
        pass
    return True


def is_lunapark_api_server_launched(script_options):
    """
        Запущен ли сервер (проверяется pid и указанный порт)
        @return: True, если сервер запущен; в противном случае возвращается False
    """
    # проверяем pid-файл
    pid = check_server_pid(script_options.workdir)
    if not pid:
        return False
    # проверяем, занят ли порт
    if is_port_free(script_options.port):
        return False
    else:
        return True


def get_lunapark_api_server_state(script_options):
    """
        Вывести на экран состояние Lunapark API server
    """
    result = 'Stopped.'
    if is_lunapark_api_server_launched(script_options):
        result = 'Launched.'
    return result


def print_lunapark_api_server_config(script_options):
    """
        Вывести на экран текущую конфигурацию сервера
    """
    logger.info('Tank API server settings:')
    logger.info('  - Tank API server workdir: {}'.format(script_options.workdir))
    logger.info('  - Tank API server port: {}'.format(script_options.port))
    logger.info('  - Tank API server source dir: {}'.format(script_options.sourcedir))


def print_lunapark_api_info(script_options):
    """
        Вывести на экран информацию о сервере
    """
    logger.info("Lunapark API server state: {}".format(get_lunapark_api_server_state(script_options)))
    print_lunapark_api_server_config(script_options)


def start_lunapark_server(script_options):
    """
        Запустить сервер.
        Сервер запускается, если он ещё не запущен (проверка по PID файлу) и свободен указанный порт
    """
    logger.info('Run tank API server.')
    print_lunapark_api_server_config(script_options)
    server_pid = check_server_pid(script_options.workdir)
    if server_pid:
        logger.info("Tank API server is already launched. PID: {}.".format(server_pid))
        return
    if is_port_free(script_options.port):
        server_path = os.path.join(script_options.sourcedir,
                                   'tank_api_server.py')
        server_environ = {
            'LUNAPARK_API_SERVER_PORT': str(script_options.port),
            'LUNAPARK_API_SERVER_WORKDIR': str(script_options.workdir),
            'LUNAPARK_API_SERVER_DAEMONIZE': 'True'
        }
        subprocess.Popen(server_path, env=server_environ)
        return
    else:
        logger.error('Error. Port {} is not free. Cannot start the server.'.format(script_options.port))
        exit(1)


def stop_lunapark_server(script_options):
    """
        Остановить Lunapark API сервер.
        Проверяется лок PID-файла, если он залочен, убивается процесс, PID которого указан в файле
    """
    logger.info('Stop tank API server.')
    server_pid = check_server_pid(script_options.workdir)
    logger.info(server_pid)
    if server_pid:
        logger.info("Kill process {}.".format(server_pid))
        os.kill(server_pid, signal.SIGTERM)
    else:
        logger.info('Server is not launched.')


def check_mode_option(args):
    """
        Проверить указанный режим работы. Если указан неправильный режим работы,
            выводится сообщение с предложением вывести справку и завершается весь скрипт
    """
    incorrect = False

    if len(args) > 1:
        incorrect = True
    elif len(args) == 1:
        mode = args[0]
        if mode not in LUNAPARK_API_MODES.keys():
            incorrect = True
    else:
        mode = DEFAULT_MODE

    if incorrect:
        logger.error("Incorrect arguments. Use --help options for details.")
        exit(1)

    return mode


def restart_lunapark_server(script_options):
    """
        Перезапустить Lunapark API сервер
    """
    if is_lunapark_api_server_launched(script_options):
        stop_lunapark_server(script_options)
        logger.info("Wait 3 seconds")
        time.sleep(3)
    start_lunapark_server(script_options)


LUNAPARK_API_MODES = {
    'info': print_lunapark_api_info,
    'start': start_lunapark_server,
    'restart': restart_lunapark_server,
    'stop': stop_lunapark_server,
}


def get_parameters():
    """
        Получить параметры, заданные при запуске сервера
        @return: словарь с параметрами, ключи словаря:
         port - порт сервера
         log_path - путь до файла логов
    """
    sys.path.append('/usr/share/tankapi')
    import server_defaults

    from optparse import OptionParser
    usage = "usage: %%prog [options] [{options}] (default is {default_value})".format(**{
        'options': '|'.join(LUNAPARK_API_MODES.keys()),
        'default_value': DEFAULT_MODE
    })
    parser = OptionParser(usage=usage)

    parser.add_option('-p',
                      '--port',
                      type='int',
                      action='store',
                      default=server_defaults.port,
                      help='Port for tank API server.')
    parser.add_option('-w',
                      '--workdir',
                      type='string',
                      action='store',
                      default=server_defaults.workdir,
                      help='Path to tank API server workdir.')
    parser.add_option('-s',
                      '--sourcedir',
                      type='string',
                      action='store',
                      default=server_defaults.sourcedir,
                      help='Path to tank API server source dir.')

    script_options, args = parser.parse_args()
    script_options.mode = check_mode_option(args)
    return script_options


def main():
    options = get_parameters()
    # вызваем фукнцию по указанному режиму
    LUNAPARK_API_MODES[options.mode](options)
    logger.info('Done.')
    exit(0)
