#
# Copyright 2014 the original author or authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
from contextlib import closing
from glob import glob
import os
import shutil
import tarfile
import subprocess
import signal

from cabalgata.silla.configuration import Definition
from cabalgata.silla.util import pid

from cabalgata.zookeeper import catalog, util


class ZookeeperFactory(object):
    definitions = (
        Definition('server_id', int, False),
        Definition('client_port', int, False),
        Definition('election_port', int, False),
        Definition('leader_port', int, False),
        Definition('peers', list, False, [])
    )

    def __init__(self, directory, configuration=None):
        self.directory = directory
        self.download_root = os.path.join(directory, 'download')
        self.unpack_root = os.path.join(directory, 'unpack')
        self.install_root = os.path.join(directory, 'install')
        self.configuration = configuration or {}

        if not os.path.exists(self.download_root):
            os.mkdir(self.download_root)

        if not os.path.exists(self.unpack_root):
            os.mkdir(self.unpack_root)

        if not os.path.exists(self.install_root):
            os.mkdir(self.install_root)

    @staticmethod
    def versions():
        return util.collect_zookeeper_versions()

    def install(self, name, version, configuration=None):
        with catalog.load_catalog(self.directory) as c:
            if version not in c.downloaded:
                downloaded_file = c.downloaded[version] = util.download_zookeeper(self.download_root, version)

                with closing(tarfile.open(downloaded_file)) as tar:
                    tar.extractall(self.unpack_root)

            number = c.next_number()

            c.installed[name] = catalog.Installation(number, version, configuration, False, 0)

            install_path = compute_install_path(self.install_root, number)
            data_path = os.path.join(install_path, 'data')
            log_path = os.path.join(install_path, 'log')

            os.mkdir(install_path)
            os.mkdir(data_path)
            os.mkdir(log_path)

    def uninstall(self, name):
        with catalog.load_catalog(self.directory) as c:
            installation = c.installed.pop(name)
            shutil.rmtree(compute_install_path(self.install_root, installation.number))

    def load(self, name):
        with catalog.load_catalog(self.directory, rw=False) as c:
            installation = c.installed[name]

            return Zookeeper(name,
                             self.directory,
                             compute_unpacked_path(self.unpack_root, installation.version),
                             compute_install_path(self.install_root, installation.number))


class Zookeeper(object):
    definitions = ()

    def __init__(self, name, directory, unpacked_directory, install_directory):
        self.name = name
        self.directory = directory
        self.unpacked_directory = unpacked_directory
        self.install_directory = install_directory

    def configure(self, configuration):
        pass

    def start(self):
        with catalog.load_catalog(self.directory) as c:
            installation = c.installed[self.name]
            installation.running = True
            configuration = installation.configuration

            data_path = os.path.join(self.install_directory, 'data')
            log_path = os.path.join(self.install_directory, 'log')

            config_path = os.path.join(self.install_directory, 'zoo.cfg')
            log4j_path = os.path.join(self.install_directory, 'log4j.properties')

            with open(config_path, 'w') as config:
                config.write('tickTime=2000\n')
                config.write('dataDir=%s\n' % data_path)
                config.write('maxClientCnxns=0\n')
                config.write('clientPort=%s\n' % configuration['client_port'])

                # setup a replicated setup if peers are specified
                if 'peers' in configuration and configuration['peers']:
                    config.write('initLimit=4\n')
                    config.write('syncLimit=2\n')
                    config.write('server.%s=localhost:%s:%s\n' % (
                        configuration['server_id'], configuration['leader_port'], configuration['election_port']))
                    for server_id, leader_port, election_port in configuration['peers']:
                        config.write('server.%s=localhost:%s:%s\n' % (server_id, leader_port, election_port))

            with open(log4j_path, 'w') as log4j:
                log4j.write("""
# DEFAULT: console appender only
log4j.rootLogger=INFO, ROLLINGFILE
log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLINGFILE.Threshold=DEBUG
log4j.appender.ROLLINGFILE.File=""" + (log_path + os.sep + 'zookeeper.log\n'))

            process = subprocess.Popen(args=['java',
                                             '-cp', self.classpath,
                                             '-Dreadonlymode.enabled=true',
                                             '-Dzookeeper.log.dir=%s' % log_path,
                                             '-Dzookeeper.root.logger=INFO,CONSOLE',
                                             '-Dlog4j.configuration=file:%s' % log4j_path,
                                             # '-Dlog4j.debug',
                                             'org.apache.zookeeper.server.quorum.QuorumPeerMain',
                                             config_path])
            installation.pid = process.pid

    def stop(self, timeout=None):
        with catalog.load_catalog(self.directory) as c:
            installation = c.installed[self.name]

            installation.running = False

            os.kill(installation.pid, signal.SIGTERM)

            pid.wait_pid(installation.pid, timeout=timeout)

    def kill(self):
        with catalog.load_catalog(self.directory) as c:
            installation = c.installed[self.name]

            installation.running = False

            os.kill(installation.pid, signal.SIGKILL)

            pid.wait_pid(installation.pid)

    @property
    def running(self):
        with catalog.load_catalog(self.directory) as c:
            installation = c.installed[self.name]
            return installation.running

    @property
    def classpath(self):
        """Get the classpath necessary to run ZooKeeper."""
        jars = glob((os.path.join(self.unpacked_directory, 'zookeeper-*.jar')))
        jars.extend(glob(os.path.join(self.unpacked_directory, 'lib/*.jar')))

        return ':'.join(jars)


def compute_unpacked_path(directory, version):
    return os.path.join(directory, 'zookeeper-%s' % version)


def compute_install_path(directory, number):
    return os.path.join(directory, str(number))
