#!/usr/bin/python

from subprocess import check_output,check_call, CalledProcessError, Popen, PIPE
import tempfile
import json
import re
import hashlib
import os
import shutil
import sys
import platform
import lib.utils as utils
from string import upper
from charmhelpers.fetch import (
    add_source,
    apt_update,
    apt_install
)
from charmhelpers.core.hookenv import relations_of_type, status_set
from charmhelpers.core.host import mkdir, rsync
from charmhelpers.core.templating import render


status_set('maintenance', 'Configuring MySQL')

# Add archive source if provided
add_source(utils.config_get('source'), utils.config_get('key'))
apt_update()

num_re = re.compile('^[0-9]+$')

# There should be a library for this
def human_to_bytes(human):
    if num_re.match(human):
        return human
    factors = { 'K' : 1024 , 'M' : 1048576, 'G' : 1073741824, 'T' : 1099511627776 }
    modifier=human[-1]
    if modifier in factors:
        return int(human[:-1]) * factors[modifier]
    if modifier == '%':
        total_ram = human_to_bytes(get_memtotal())

        if IS_32BIT_SYSTEM and total_ram > SYS_MEM_LIMIT:
            total_ram = SYS_MEM_LIMIT

        factor = int(human[:-1]) * 0.01
        pctram = total_ram * factor
        return int(pctram - (pctram % PAGE_SIZE))
    raise ValueError("Can only convert K,M,G, or T")


# Going for the biggest page size to avoid wasted bytes. InnoDB page size is
# 16MB
PAGE_SIZE = 16*1024*1024
try:
    IS_32BIT_SYSTEM = sys.maxsize < 2**32
except OverflowError:
    IS_32BIT_SYSTEM = True

if platform.machine() in ['armv7l']:
    SYS_MEM_LIMIT = human_to_bytes('2700M') # experimentally determined
else:
    SYS_MEM_LIMIT = human_to_bytes('4G')

if IS_32BIT_SYSTEM:
    check_call(['juju-log','-l','INFO','32bit system restrictions in play'])

configs=json.loads(check_output(['config-get','--format=json']))

def get_memtotal():
    with open('/proc/meminfo') as meminfo_file:
        meminfo = {}
        for line in meminfo_file:
            (key, mem) = line.split(':', 2)
            if key == 'MemTotal':
                (mtot, modifier) = mem.strip().split(' ')
                return '%s%s' % (mtot, upper(modifier[0]))


if configs['prefer-ipv6']:
    utils.check_ipv6_compatibility()

# There is preliminary code for mariadb, but switching
# from mariadb -> mysql fails badly, so it is disabled for now.
valid_flavors = ['distro','percona']
if configs['flavor'] not in valid_flavors:
    check_call(['juju-log','-l',
            'ERROR',
            'Invalid flavor, must be one of %s' % ','.join(valid_flavors)])
    sys.exit(1)

remove_pkgs=[]
if configs['flavor'] == 'distro':
    apt_sources = []
    package = 'mysql-server'
elif configs['flavor'] == 'percona':
    apt_sources = ['repo.percona.com/apt']
    package = 'percona-server-server'
    remove_pkgs = ['mysql-client-core-5.5','mysql-server-core-5.5']
elif configs['flavor'] == 'mariadb':
    apt_sources = ['ftp.osuosl.org/pub/mariadb/repo/5.5/ubuntu']
    package = 'mariadb-server'

series = check_output(['lsb_release','-cs'])

for source in apt_sources:
    server = source.split('/')[0]
    if os.path.exists('keys/%s' % server):
        check_call(['apt-key','add','keys/%s' % server])
    else:
        check_call(['juju-log','-l','ERROR',
                'No key for %s' % (server)])
        sys.exit(1)
    check_call(['add-apt-repository','-y','deb http://%s %s main' % (source, series)])
    check_call(['apt-get','update'])

ROOT_PASSFILE = os.path.join('/root', 'mysql.passwd')
VAR_PASSFILE = os.path.join('/var', 'lib', 'mysql', 'mysql.passwd')
if os.path.isfile(ROOT_PASSFILE):
    PASSFILE = ROOT_PASSFILE
else:
    PASSFILE = VAR_PASSFILE

with open(PASSFILE,'r') as rpw:
    root_pass = rpw.read()

dconf = Popen(['debconf-set-selections'], stdin=PIPE)
dconf.stdin.write("%s %s/root_password password %s\n" % (package, package, root_pass))
dconf.stdin.write("%s %s/root_password_again password %s\n" % (package, package, root_pass))
dconf.communicate()
dconf.wait()

if len(remove_pkgs):
    check_call(['apt-get','-y','remove'] + remove_pkgs)
apt_install(package)

# mysql.passwd was stored in /root till after pkg install due to Bug #1567778
if os.path.isfile(ROOT_PASSFILE):
    shutil.move(ROOT_PASSFILE, VAR_PASSFILE)

# smart-calc stuff in the configs
dataset_bytes = human_to_bytes(configs['dataset-size'])

check_call(['juju-log','-l','INFO','dataset size in bytes: %d' % dataset_bytes])

query_cache_size = configs.get('query-cache-size', 0)

QUERY_CACHE_SIZE_FACTOR = (dataset_bytes * 0.20)

if query_cache_size <= 0:
    if configs['query-cache-type'] in ('ON','DEMAND', ):
        query_cache_size = int(QUERY_CACHE_SIZE_FACTOR - (QUERY_CACHE_SIZE_FACTOR % PAGE_SIZE))
    else:
        query_cache_size = 0

configs['query-cache-size'] = query_cache_size

# 5.5 allows the words, but not 5.1
if configs['query-cache-type'] == 'ON':
    configs['query-cache-type']=1
elif configs['query-cache-type'] == 'DEMAND':
    configs['query-cache-type']=2
else:
    configs['query-cache-type']=0

preferred_engines=configs['preferred-storage-engine'].split(',')

chunk_size = int((dataset_bytes - query_cache_size)/len(preferred_engines))
configs['innodb-flush-log-at-trx-commit']=1
configs['sync-binlog']=1
if 'InnoDB' in preferred_engines:
    configs['innodb-buffer-pool-size'] = chunk_size
    if configs['tuning-level'] == 'fast':
        configs['innodb-flush-log-at-trx-commit']=2
else:
    configs['innodb-buffer-pool-size'] = 0

configs['default-storage-engine'] = preferred_engines[0]

if 'MyISAM' in preferred_engines:
    configs['key-buffer'] = chunk_size
else:
    # Need a bit for auto lookups always
    configs['key-buffer'] = human_to_bytes('8M')

if configs['tuning-level'] == 'fast':
    configs['sync-binlog']=0

if configs['max-connections'] == -1:
    configs['max-connections'] = '# max_connections = ?'
else:
    configs['max-connections'] = 'max_connections = %s' % configs['max-connections']

if configs['wait-timeout'] == -1:
    configs['wait-timeout'] = '# wait_timeout = ?'
else:
    configs['wait-timeout'] = 'wait_timeout = %s' % configs['wait-timeout']

if configs['bind-address'] == '0.0.0.0':
    if configs['prefer-ipv6']:
        configs['bind-address'] = '::'

template="""
##############################################
#
# This file generated by the juju MySQL charm!
#
# Local changes will not be preserved!
#
##############################################

[mysqld]
bind-address		= %(bind-address)s
key_buffer_size		= %(key-buffer)s
%(max-connections)s
%(wait-timeout)s
query_cache_size = %(query-cache-size)s
query_cache_type = %(query-cache-type)s

innodb_buffer_pool_size = %(innodb-buffer-pool-size)s
innodb_flush_log_at_trx_commit = %(innodb-flush-log-at-trx-commit)s
sync_binlog = %(sync-binlog)s
default_storage_engine = %(default-storage-engine)s
"""

i_am_a_slave = os.path.isfile('/var/lib/juju/i.am.a.slave')
unit_id = os.environ['JUJU_UNIT_NAME'].split('/')[1]

if not i_am_a_slave and configs['tuning-level'] == 'fast':
    binlog_cnf = ''
else:
    # On slaves, this gets overwritten
    binlog_template = """
[mysqld]
server_id = %s
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = %s
"""

    binlog_cnf = binlog_template % (unit_id,
        configs.get('binlog-format','MIXED'))

mycnf=template % configs

target_prefix='/etc/mysql/conf.d'
if os.path.exists('/etc/mysql/mysql.conf.d'):
    # configuration files in this directory have precedence
    target_prefix='/etc/mysql/mysql.conf.d'

# Naming of config files is crucial. The values read from the last file loaded
# will take precedence. On Xenial and newer there is a complete MySQL
# configuration file in /etc/mysql/mysql.conf.d/mysqld.cnf. Naming our file
# 'mysqld_settings_charm.cnf' will make it load after that.
targets = {os.path.join(target_prefix, 'binlog.cnf'): binlog_cnf,
           os.path.join(target_prefix, 'mysqld_settings_charm.cnf'): mycnf,
           }

need_restart = False
for target,content in targets.iteritems():
    tdir = os.path.dirname(target)
    if len(content) == 0 and os.path.exists(target):
        os.unlink(target)
        need_restart = True
        continue
    with tempfile.NamedTemporaryFile(mode='w',dir=tdir,delete=False) as t:
        t.write(content)
        t.flush()
        tmd5 = hashlib.md5()
        tmd5.update(content)
        if os.path.exists(target):
            with open(target,'r') as old:
                md5=hashlib.md5()
                md5.update(old.read())
                oldhash = md5.digest()
                if oldhash != tmd5.digest():
                    os.rename(target,'%s.%s' % (target, md5.hexdigest()))
                    need_restart = True
        else:
            need_restart = True
        os.chmod(t.name, 0o644)
        os.rename(t.name, target)

if need_restart:
    try:
        check_call(['service', 'mysql', 'restart'])
    except CalledProcessError:
        check_call(['juju-log', '-l', 'INFO', 'Restart failed, trying again'])
        check_call(['service', 'mysql', 'restart'])
if relations_of_type('nrpe-external-master'):
    import nrpe_relations
    nrpe_relations.nrpe_relation()

if (configs.has_key("backup_schedule") and
    configs.has_key("backup_dir") and
    configs["backup_schedule"]):
    rsync("scripts/mysql_backup.sh", "/usr/local/bin/",
          options=["--executability"])
    if not os.path.exists(configs["backup_dir"]):
        mkdir(configs["backup_dir"], perms=0700)
    render("mysql_backup.j2", "/etc/cron.d/mysql_backup", configs)
else:
    for path in ["/etc/cron.d/mysql_backup", "/usr/local/bin/mysql_backup.sh"]:
        if os.path.exists(path):
            os.unlink(path)

status_set('active', 'Ready')
