#!/usr/bin/env python

import os, re

from fabric.api import hide, run, put, prompt
from fabric.operations import get
from fabric.context_managers import cd
from fabric.contrib.files import append, exists, sed, upload_template


def get_daemon(initfile):
    """ Parse DAEMON variable in the init file """
    re_daemon = re.compile('^DAEMON=(.+)$', re.MULTILINE)

    if not exists(initfile):
        return None

    local_file = get(initfile)

    f = open(local_file[0], 'r')
    data = f.read()
    f.close()

    match = re_daemon.search(data)
    daemon = ''
    if match:
        daemon = match.group(1)
    if not exists(daemon):
        return None
    return daemon


def load_config(conf_file, base_conf=[], spec_conf=[], delimiter=' '):
    if exists(conf_file):
        with hide('output'):
            config_data = run('cat %s' % conf_file)
    else:
        config_data = ''
    confs = base_conf + spec_conf
    for conf in confs:
        param, value = conf.split(delimiter, 1)
        value = re.sub(r'#.*$', "", str(value)) # Delete comments
        match = re.search('^%s[ ]?%s[ ]?(.*)' % (param, delimiter), config_data, re.MULTILINE)
        if match:
            orig_value = match.group(1).strip()
            orig_line = '%s' % match.group(0).strip()
            if orig_value != str(value):
                if config_data and param in spec_conf:
                    continue # Do not override already existing specific configurations
                print('%s %s change to %s' % (param, orig_value, value))
                sed(conf_file, orig_line, '%s%s%s' % (param, delimiter, value))
            else:
                print('Config OK: %s%s%s' % (param, delimiter, value))
        else:
            print('Add config %s%s%s' % (param, delimiter, value))
            append(conf_file, '%s%s%s' % (param, delimiter, value))


def install_mongodb(*args, **kwargs):
    VERSION = '3.2.1'
    home = run('echo $HOME')
    port = kwargs.get('port')
    base_dir = kwargs.get('base_dir') or os.path.join(home, 'mongodb')
    data_dir = os.path.join(base_dir, 'data')
    data_backup_dir = os.path.join(home, 'mongodb_data_bak')
    if not port:
        print('Enter a local port for the mongodb server')
        return 1

    if exists(data_dir):
        if exists(data_backup_dir):
            print('Backup directory %s already exists.' % data_backup_dir)
            return 1
        else:
            run('mv %s %s' % (data_dir, data_backup_dir))
    if exists(base_dir):
        run('rm -rf %s' % (base_dir))

    LONG_BIT = run('getconf LONG_BIT')
    if LONG_BIT == '64':
        package_name = 'mongodb-linux-x86_64-%s.tgz' % VERSION
    else:
        package_name = 'mongodb-linux-i686-%s.tgz' % VERSION

    package_path = os.path.join(home, package_name)
    if not exists(package_path):
        with hide('output'):
            run('wget https://fastdl.mongodb.org/linux/%s -O %s' % (package_name, package_path))
    if not exists(package_path.replace('.tgz', '')):
        run('tar xzf ' + package_path)
    run('mv %s %s' % (package_path.replace('.tgz', ''), base_dir))

    if exists(data_backup_dir):
        run('mv %s %s' % (data_backup_dir, data_dir))

    #run('export LC_ALL=C')
    #run('mongod --dbpath %s' % data_dir )

    base_conf = [
        'port = %s' % port,
    ]
    spec_conf = [
        'bind_ip = 127.0.0.1',
        'logappend = True',
        'journal = true',
        'nohttpinterface = true',
    ]

    load_config(
            os.path.join(base_dir, 'mongodb.conf'),
            base_conf=base_conf, spec_conf=spec_conf, delimiter='= '
        )


def install_mongodb(*args, **kwargs):
    version = kwargs.get('version') or '3.2.6'
    home = run('echo $HOME')
    port = kwargs.get('port')
    base_dir = kwargs.get('base_dir') or os.path.join(home, 'mongodb')
    data_dir = os.path.join(base_dir, 'data')
    data_backup_dir = os.path.join(home, 'mongodb_data_bak')
    if not port:
        print('Enter a local port for the mongodb server')
        return 1

    if exists(data_dir):
        if exists(data_backup_dir):
            print('Backup directory %s already exists.' % data_backup_dir)
            return 1
        else:
            run('mv %s %s' % (data_dir, data_backup_dir))
    if exists(base_dir):
        run('rm -rf %s' % (base_dir))

    LONG_BIT = run('getconf LONG_BIT')
    if LONG_BIT == '64':
        package_name = 'mongodb-linux-x86_64-%s.tgz' % version
    else:
        package_name = 'mongodb-linux-i686-%s.tgz' % version

    package_path = os.path.join(home, package_name)
    if not exists(package_path):
        with hide('output'):
            run('wget https://fastdl.mongodb.org/linux/%s -O %s' % (package_name, package_path))
    if not exists(package_path.replace('.tgz', '')):
        run('tar xzf ' + package_path)
    run('mv %s %s' % (package_path.replace('.tgz', ''), base_dir))
    if exists(data_backup_dir):
        run('mv %s %s' % (data_backup_dir, data_dir))
    else:
        run('mkdir -p %s/db' % data_dir)

    base_conf = [
        'port = %s' % port,
    ]

    spec_conf = [
        'bind_ip = 127.0.0.1',
        'logappend = True',
        'journal = true',
        'nohttpinterface = true',
    ]

    load_config(
            os.path.join(base_dir, 'mongodb.conf'),
            base_conf=base_conf, spec_conf=spec_conf, delimiter='= '
        )

    if not exists('~/init'):
        run('mkdir ~/init')
    run('wget https://templates.wservices.ch/mongodb/init.%s -O ~/init/mongodb' % LONG_BIT)
    sed('~/init/mongodb', 'DAEMON=/usr/bin/mongod', 'DAEMON=$HOME/mongodb/bin/mongod')
    run('chmod 750 ~/init/mongodb')
    run('~/init/mongodb start')


def install_mongodb_supervisord():
    setup_supervisord()
    runsd_config_file = os.path.join(home, 'supervisor', 'programs', 'mongodb')
    home = run('echo $HOME')

    if not exists(runsd_config_file):
        append(runsd_config_file, '[program:mongodb]')

    configs = [
        'directory=%s' % base_dir,
        'environment=MALLOC_ARENA_MAX="4",ES_HEAP_SIZE="100M",JAVA_OPTS="-XX:ParallelGCThreads=1 -XX:ReservedCodeCacheSize=4m -XX:MaxPermSize=32m"',
        'command=%s/bin/mongod' % base_dir,
        'stdout_logfile=%s/sd.log' % base_dir,
        'stderr_logfile=%s/sd.err' % base_dir,
        'autostart=true',
        'autorestart=true',
        'stopsignal=INT',
    ]

    load_config(runsd_config_file, base_conf=configs, delimiter='=')

    run('~/init/supervisord reload')


def install_openssl(*args,**kwargs):
    version = kwargs.get('version') or '1.0.2f'
    package_name='openssl-%s' % version

    if not exists(package_name + '.tar.gz'):
        run('wget https://www.openssl.org/source/openssl-%s.tar.gz -O %s.tar.gz' % (version, package_name))
    if not exists(package_name):
        run('tar xzf %s.tar.gz' % package_name)
    with cd(package_name):
        run('./Configure --prefix=$HOME/openssl shared linux-generic32')
        run('make')
        run('make install')


def install_apache2(*args,**kwargs):
    PORT = kwargs.get('PORT')
    version = kwargs.get('version') or '2.4.18'
    home = run('echo $HOME')
    base_dir = os.path.join(home, 'apache2')

    if not PORT:
        print('Enter a local port which has been created in the control panel')
        sys.exit(1)

    if exists(os.path.join(base_dir, 'conf')):
        run('mv %s %s' % (os.path.join(base_dir, 'conf'), '~/apache2_conf_bak'))
    if not exists('httpd-%s.tar.bz2' % version):
        run('wget http://www.us.apache.org/dist//httpd/httpd-%s.tar.bz2 -O httpd-%s.tar.bz2' % (version, version))
    if not exists('httpd-%s' % version):
        run('tar xjf httpd-%s.tar.bz2' % version)
    with cd('httpd-%s' % version):
        if not exists('srclib/apr'):
            run('svn export http://svn.apache.org/repos/asf/apr/apr/trunk srclib/apr')
        run('./buildconf')
        run('./configure --with-ssl=$HOME/openssl/ssl --enable-cgi --enable-so --enable-rewrite --enable-mime-magic --enable-static-rotatelogs --enable-speling --enable-dav-fs --enable-modules=all --enable-modules-shared=all --with-included-apr --prefix=$HOME/apache2 --enable-mpms-shared=all')
        run('make')
        run('make install')

    cmds = [
        'export PATH=$HOME/apache2/bin:$PATH',
        'export LD_LIBRARY_PATH=$HOME/openssl/lib:$HOME/apache2/modules',
        'export LD_RUN_PATH=$HOME/apache2/modules',
    ]
    for cmd in cmds:
        run(cmd)
        append('~/.bashrc', cmd)

    with cd('~/apache2'):
        if exists('conf'):
            run('rm -rf conf')
        if not exists('conf.tar.gz'):
            run('wget https://templates.wservices.ch/apache2/conf.tar.gz -O conf.tar.gz')
        if exists('~/apache2_conf_bak'):
            run('mv %s %s' % ('~/apache2_conf_bak', os.path.join(base_dir, 'conf')))
        else:
            run('tar xzf conf.tar.gz')
            config_files = [
                '~/apache2/bin/apachectl',
                '~/apache2/conf/ports.conf',
                '~/apache2/conf/sites-enabled/000-default.conf',
            ]
            for config_file in config_files:
                sed(config_file, 'HTTP_PORT', PORT)
    if not exists('~/init/apachectl'):
        run('ln -s ~/apache2/bin/apachectl ~/init/apachectl')
    run('~/init/apachectl start')


def install_elasticsearch(*args, **kwargs):
    VERSION = '2.1.1'
    port = kwargs.get('port')
    if not port:
        print('Enter a local port for the elasticsearch server')
        return 1
    if not exists('elasticsearch-%s.tar.gz' % VERSION):
        with hide('output'):
            run('wget https://download.elasticsearch.org/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.1.1/elasticsearch-%s.tar.gz' % VERSION)
    if exists('elasticsearch'):
        if exists('supervisor'):
            with cd('supervisor'):
                try:
                    run('supervisorctl stop elasticsearch')
                except SystemExit:
                    pass
        if exists('elasticsearch/data'):
            run('mv elasticsearch/data elasticsearch_data_bak')
        if exists('elasticsearch/config'):
            run('mv elasticsearch/config elasticsearch_conf_bak')
        run('rm -rf elasticsearch')

    run('tar xzf elasticsearch-%s.tar.gz' % VERSION)
    run('mv elasticsearch-%s elasticsearch' % VERSION)

    for bak,dst in {
            'elasticsearch_data_bak': 'elasticsearch/data',
            'elasticsearch_conf_bak': 'elasticsearch/config',
        }.items():
        if exists(bak):
            if exists(dst):
                run('rm -rf %s' % dst)
            run('mv %s %s' % (bak, dst))

    home = run('echo $HOME')
    base_dir = os.path.join(home, 'elasticsearch')
    base_conf = [
        'http.port: %s' % port,
    ]
    spec_conf = [
        'network.host: 127.0.0.1',
        'node.local: True',
        'index.number_of_shards: 1',
        'index.number_of_replicas: 0',
    ]

    load_config(
            os.path.join(base_dir, 'config', 'elasticsearch.yml'),
            base_conf=base_conf, spec_conf=spec_conf, delimiter=': '
        )

    setup_supervisord()
    runsd_config_file = os.path.join(home, 'supervisor', 'programs', 'elasticsearch')

    if not exists(runsd_config_file):
        append(runsd_config_file, '[program:elasticsearch]')

    configs = [
        'directory=%s' % base_dir,
        'environment=MALLOC_ARENA_MAX="4",ES_HEAP_SIZE="100M",JAVA_OPTS="-XX:ParallelGCThreads=1 -XX:ReservedCodeCacheSize=4m -XX:MaxPermSize=32m"',
        'command=%s/bin/elasticsearch' % base_dir,
        'stdout_logfile=%s/sd.log' % base_dir,
        'stderr_logfile=%s/sd.err' % base_dir,
        'autostart=true',
        'autorestart=true',
        'stopsignal=INT',
    ]

    load_config(runsd_config_file, base_conf=configs, delimiter='=')

    run('~/init/supervisord reload')


def redis_status(*args, **kwargs):
    status = run('echo "ping" | redis-cli -s $HOME/redis/redis.sock')
    if status == 'PONG':
        print('Status: OK')
        return 0
    else:
        print('Status: %s' % status)
        return 1


def install_redis(*args, **kwargs):
    port = kwargs.get('port') or 0 # disable TCP port listening by default, use unix sockets (faster).
    home = run('echo $HOME')
    redis_base_dir = os.path.join(home, 'redis')
    redis_conf_file = os.path.join(redis_base_dir, 'redis.conf')
    redis_conf_order = [
        'dir', 'daemonize', 'bind', 'port', 'logfile', 'pidfile', 'unixsocket', 'unixsocketperm',
        'dbfilename', 'timeout', 'tcp-backlog', 'tcp-keepalive', 'loglevel', 'databases', 'save',
    ]
    redis_base_conf = {
        'dir': redis_base_dir,
        'daemonize': 'yes',
        'logfile': os.path.join(redis_base_dir, 'redis.log'),
        'pidfile': os.path.join(redis_base_dir, 'redis.pid'),
        'unixsocket': os.path.join(redis_base_dir, 'redis.sock'),
        'unixsocketperm': 700,
    }
    redis_spec_conf = {
        'bind': '127.0.0.1',
        'dbfilename': 'dump.rdb',
        'timeout': 0,
        'tcp-backlog': 511,
        'tcp-keepalive': 60,
        'loglevel': 'notice',
        'databases': 16,
        'save': '900 1 300 10 60 10000',
    }

    if port:
        redis_base_conf['port'] = port
    else:
        redis_spec_conf['port'] = port

    redis_conf = redis_base_conf
    redis_conf.update(redis_spec_conf)

    restart = False

    if not exists(redis_base_dir):
        run('mkdir ' + redis_base_dir)
    if exists(redis_conf_file):
        restart = True
        with hide('output'):
            redis_config_data = run('cat %s' % redis_conf_file)
    else:
        redis_config_data = ''

    for param in redis_conf_order:
        value = redis_conf.get(param)
        value = re.sub(r'#.*$', "", str(value)) # Delete comments
        match = re.search('^%s +(.*)' % param, redis_config_data, re.MULTILINE)
        if match:
            orig_value = match.group(1).strip()
            orig_line = '%s' % match.group(0).strip()
            if orig_value != str(value):
                if redis_config_data and param in redis_spec_conf.keys():
                    continue # Do not override already existing specific configurations
                print('%s %s change to %s' % (param, orig_value, value))
                print('group 0: %s' % orig_line)
                sed(redis_conf_file, orig_line, '%s %s' % (param, value))
            else:
                print('Config OK: %s %s' % (param, value))
        else:
            print('Add config %s %s' % (param, value))
            append(redis_conf_file, '%s %s' % (param, value))

    with hide('output'):
        run('wget https://templates.wservices.ch/redis/redis-server -O ~/init/redis-server')
        run('chmod 755 ~/init/redis-server')
    if restart:
        run('~/init/redis-server restart')
    else:
        run('~/init/redis-server start')
    return redis_status()


def setup_supervisord(*args, **kwargs):
    run('mkdir -p ~/supervisor/programs')
    run('wget https://templates.wservices.ch/supervisor/supervisord.conf -O ~/supervisor/supervisord.conf')
    run('wget https://templates.wservices.ch/supervisor/init -O ~/init/supervisord')
    run('chmod 700 ~/init/supervisord')
    try:
        run('~/init/supervisord start')
    except SystemExit:
        run('~/init/supervisord restart')


def install_swampdragon(*args, **kwargs):
    """ Install swampdragon with an example app (todo) """
    home = run('echo $HOME')
    projectname = kwargs.get('project')
    sd_host = kwargs.get('sd_host')
    sd_port = kwargs.get('sd_port')
    redis_port = kwargs.get('redis_port')
    appname = 'todo'
    errors = []

    if not projectname:
        error = 'Enter the project name'
        projectname = prompt(error)
        if not projectname:
            errors.append(error)
    if not sd_host:
        error = 'Enter the domain name of the website'
        sd_host = prompt(error)
        if not sd_host:
            errors.append(error)
            errors.append('Enter the host name which has been used in the django installer name.')
    if not sd_port:
        error = 'Enter a remote port for the swampdragon websocket server'
        sd_port = prompt(error)
        if not sd_port:
            errors.append(error)
    if not redis_port:
        error = 'Enter a local port for the redis server'
        redis_port = prompt(error)
        if not redis_port:
            errors.append(error)

    projectdir = os.path.join(home, projectname)
    if not exists(projectdir):
        errors.append('The Django project ~/%s must already exits.' % projectname)
        errors.append('Swampdragon will be installed into the directory ~/%s.' % projectname)

    init_file = os.path.join(home, 'init', projectname)
    if not exists(init_file):
        errors.append('The gunicorn init file ~/init/%s must exits.' % projectname)

    if errors:
        errors.append('Usage: fab -H localhost install_swampdragon:project=$PROJECT_NAME,sd_host=$SD_HOST,sd_port=$SD_PORT,redis_port=$REDIS_PORT')
        for error in errors:
            print(error)
        return 1

    # Install redis.
    # The redis port is necessary for swampdragon, there is no possibility to connect via unix socket.
    install_redis(port=redis_port)

    # Find already existing virtualenv
    daemon = get_daemon(init_file)
    if not daemon:
        venv_path = prompt('Enter the path to virtualenv (Default: ~/%s/venv/)' % projectname)
        if not venv_path:
            venv_path = os.path.join(projectdir, 'venv')
        if not venv_path.endswith('/'):
            venv_path += '/'
        print('Replace DAEMON variable in init script')
        sed(init_file, 'DAEMON=.*', 'DAEMON=%sbin/gunicorn' % venv_path)
    elif daemon and not daemon.startswith('/usr/bin/'):
        venv_path = os.path.dirname(os.path.dirname(daemon))
        print('Found an existing virtualenv for the project %s in the directory %s' % (projectname, venv_path))
    else:
        print('Replace DAEMON variable in init script')
        sed(init_file, 'DAEMON=/usr/bin/', 'DAEMON=$HOME/%s/venv/bin/' % projectname)
        venv_path = os.path.join(projectdir, 'venv')

    # Replace $HOME and ~, because it doesn't work with supervisord
    venv_path = venv_path.replace('$HOME', home).replace('~', home)
    vpython = os.path.join(venv_path, 'bin', 'python')
    vpip = os.path.join(venv_path, 'bin', 'pip')

    with cd(os.path.join(projectdir)):
        if not exists(os.path.join(venv_path, 'bin', 'python')):
            run('virtualenv %s' % venv_path)
        for packet in ['mysqlclient', 'swampdragon', 'gunicorn==18.0', 'gevent==1.1rc3']:
            if not run(vpip + ' show ' + packet.split('==')[0]):
                with hide('output'):
                    run(vpip + ' install ' + packet)
        if not run(vpip + ' show swampdragon'):
            with hide('output'):
                run(vpip + ' install mysqlclient swampdragon gunicorn==18.0 gevent==1.1rc3')
        if not exists(os.path.join(projectdir, 'todo.tar.gz')):
            run('wget https://templates.wservices.ch/swampdragon/todo.tar.gz -O ~/%s/todo.tar.gz' % projectname)
        if not exists(os.path.join(projectdir, 'todo')):
            run('tar xzf %s/todo.tar.gz' % projectdir)
        sd_settings_file = os.path.join(projectdir, appname, 'swampdragon_settings.py')
        sed(sd_settings_file, 'X_REDIS_PORT', redis_port)
        sed(sd_settings_file, 'X_SD_HOST', sd_host)
        sed(sd_settings_file, 'X_SD_PORT', sd_port)

        settings_file = os.path.join(projectdir, projectname, 'settings.py')
        append(settings_file, 'INSTALLED_APPS = list(INSTALLED_APPS) + [\'swampdragon\', \'todo\']')
        append(settings_file, 'from todo.swampdragon_settings import *')

        urls_file = os.path.join(projectdir, projectname, 'urls.py')
        append(urls_file, 'from django.views.generic import TemplateView')
        append(urls_file, 'urlpatterns += [url(r\'^todo/$\', TemplateView.as_view(template_name=\'index.html\'), name=\'home\')]')

        run(vpython + ' manage.py migrate')
        run(vpython + ' manage.py collectstatic --noinput')

        try:
            run(init_file + ' restart')
        except SystemExit:
            run(init_file + ' start')


        # Setup supervisord
        setup_supervisord()
        runsd_config_file = os.path.join(home, 'supervisor', 'programs', projectname)
        if not exists(runsd_config_file):
            configs = [
                '[program:%s-sd]' % projectname,
                'directory=%s' % projectdir,
                'environment=DJANGO_SETTINGS_MODULE=%s.settings' % projectname,
                'command=%s manage.py runsd' % vpython,
                'stdout_logfile=%s/sd.log' % projectdir,
                'stderr_logfile=%s/sd.err' % projectdir,
                'autostart=true',
                'autorestart=true',
                'stopsignal=INT',
            ]

            for config in configs:
                append(runsd_config_file, config)

            run('~/init/supervisord reload')


def install_jenkins(*args, **kwargs):
    home = run('echo $HOME')
    init = os.path.join(home,'init')
    jenkins_base_dir = os.path.join(home, 'jenkins')
    jenkins_init = os.path.join(init, 'jenkins')
    port = kwargs.get('port')
    if not exists(jenkins_base_dir):
        run('mkdir ' + jenkins_base_dir)
    if not exists(os.path.join(jenkins_base_dir, 'jenkins.war')):
        with hide('output'):
            run('wget http://mirrors.jenkins-ci.org/war/latest/jenkins.war -O ~/jenkins/jenkins.war')
    if not exists(os.path.join(jenkins_base_dir, 'org.jenkinsci.main.modules.sshd.SSHD.xml')):
        with hide('output'):
            run('wget https://templates.wservices.ch/jenkins/org.jenkinsci.main.modules.sshd.SSHD.xml -O ~/jenkins/org.jenkinsci.main.modules.sshd.SSHD.xml')
    if not exists(init):
        run('mkdir ~/init')
    if not exists(jenkins_init):
        with hide('output'):
            run('wget https://templates.wservices.ch/jenkins/jenkins.init -O ~/init/jenkins')
        run('chmod 750 ~/init/jenkins')
        sed(jenkins_init, 'PORT=HTTP_PORT', 'PORT=%s' % port)
        run('~/init/jenkins start')
    else:
        run('~/init/jenkins restart')


def install_php_fpm():
    run('wget https://templates.wservices.ch/php/init/php-fpm -O ~/init/php-fpm')
    try:
        run('mkdir ~/php')
    except SystemExit:
        pass
    run('wget https://templates.wservices.ch/php/php-fpm.conf -O ~/php/php-fpm.conf')
    run('chmod 755 ~/init/php-fpm')
    run('~/init/php-fpm start')

