"""
 *
 * This file is part of rasdaman community.
 *
 * Rasdaman community is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Rasdaman community is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU  General Public License for more details.
 *
 * You should have received a copy of the GNU  General Public License
 * along with rasdaman community.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2003 - 2016 Peter Baumann / rasdaman GmbH.
 *
 * For more information please see <http://www.rasdaman.org>
 * or contact Peter Baumann via <baumann@rasdaman.com>.
 *
"""
from abc import abstractmethod

from config_manager import ConfigManager
from util.file_util import write_to_file
from util.commands import make_directory, sudo_remove, sudo_chown, sudo_chmod, remove_all_in_directory
from util.log import log
from util.file_util import fix_trailing_slash
from services import executor
from util.distrowrapper import DistroWrapper
import os


class Packager:
    def __init__(self):
        """
        A packager packages the rasdaman installation in a specific format,
        like a tar.gz, deb or rpm
        """
        pass

    def package(self):
        """
        Packages the installation
        """
        pass


class DoNothingPackager(Packager):
    def __init__(self):
        """
        A packager that does not do anything
        """
        Packager.__init__(self)

    def package(self):
        Packager.package(self)
        log.info("No packaging requested.")


class LinuxPackageInfo:
    def __init__(self, name, description, version, iteration, vendor,
                 licence, category, maintainer, url):
        self.url = url
        self.maintainer = maintainer
        self.category = category
        self.licence = licence
        self.vendor = vendor
        self.name = name
        self.description = description
        self.version = version
        self.iteration = iteration


class LinuxPackager(Packager):

    PKG_ROOT = "/tmp/rasdaman-packager"
    BUILT_PKGS_DIR = "/tmp/rasdaman-built-packages"

    def __init__(self, install_path, profile_path, package_info):
        """
        A packager that packages the rasdaman installation as deb/rpm package
        :param str install_path: the installation path of rasdaman
        :param str profile_path: relative path starting from the installer root
        :param LinuxPackageInfo package_info: information about the package
        """
        Packager.__init__(self)
        self.install_path = install_path
        self.profile_path = profile_path
        self.package_info = package_info

    @abstractmethod
    def get_dependencies(self):
        """
        Returns the packages that we depend on to run rasdaman
        :rtype: list[str]
        """
        pass

    @abstractmethod
    def get_type(self):
        """
        Returns the type of the linux package we want to build
        :rtype: str
        """
        pass

    @abstractmethod
    def get_fpm_cmd(self):
        """
        Returns the command needed to run fpm bare. E.g. fpm or /usr/local/bin/fpm
        :rtype: str
        """
        pass

    def get_fpm_formatted_deps(self):
        """
        Returns the dependecies in fpm format
        :return:
        """
        dep_list = []
        deps = self.get_dependencies()
        for dep in deps:
            dep_list.append("--depends")
            dep_list.append(dep)
        return dep_list

    def __prepare_pkg_root(self):
        sudo_remove(self.PKG_ROOT)
        dst = self.PKG_ROOT + self.install_path
        # Some directories contain things that are not needed or specific to the
        # current installation, e.g. logs and data
        excludes = []
        for dir in ["bin/directql", "data", "source", ".gradle", "log", ".m2", "build", "third_party", "etc/rmankey"]:
            excludes.append('--exclude=/' + dir)
        for dir in ["doc-guides", "doc-petascope"]:
            excludes.append('--exclude=/share/rasdaman/doc/' + dir)
        for dir in ["doc-all", "doc-basedbms"]:
            excludes.append('--exclude=/share/rasdaman/doc/manuals/' + dir)
        make_directory(dst)
        executor.execute(["rsync", "-av"] + excludes + [self.install_path, dst])
        return dst

    def __strip_binaries(self, pkg_root):
        pkg_root = fix_trailing_slash(pkg_root)
        bin_dir = pkg_root + "bin"
        executables = ["rascontrol", "rasmgr", "rasql", "rasserver"]
        for executable in executables:
            full_path = bin_dir + "/" + executable
            if os.path.exists(full_path):
                executor.execute(["strip", "--strip-unneeded", full_path])

    def __prepare_package_installer(self, pkg_root):
        """
        Copy the installer to the package, it will run as a post installation
        script
        :param str pkg_root: the root path of the package
        :rtype str
        """
        installer_path = pkg_root + "share/rasdaman/installer/"
        executor.executeSudo(["rsync", "-av", "--exclude", "*.pyc",
            ConfigManager.installer_directory + "/", installer_path])
        sudo_chown(installer_path, ConfigManager.default_user)
        return installer_path

    def __generate_preinst_script(self, installer_path):
        """
        Generates the pre installation script for the package
        :param str installer_path: where to create the preinst script
        :rtype: str
        """
        preinst_path = installer_path + "preinst.sh"
        backup_path = "/tmp/rasdaman-backup"
        etc_path = self.install_path + "etc/"
        write_to_file(preinst_path, """#!/usr/bin/env bash
# backup configuration files before installing package
mkdir -p {0}/etc
if [ -d {1} ]; then
  cp -r {1} {0}
  echo "Existing config files saved to /tmp/rasdaman-backup/etc/"
fi
""".format(backup_path, etc_path))
        sudo_chmod(preinst_path, "755")
        log.debug("  Generated preinst script at '{}'.".format(preinst_path))
        return preinst_path

    def __generate_postinst_script(self, installer_path):
        """
        Generates the post installation script for the package.
        If the user want to use a custom installation profile, the path
        to it can be indicated in the RAS_INSTALL_PATH env variable before
        installing the package.
        :param str installer_path: where to create the postinst script
        :rtype: str
        """
        postinst_path = installer_path + "postinst.sh"
        installer_dest_in_package = installer_path.replace(self.PKG_ROOT, "")
        write_to_file(postinst_path, """#!/usr/bin/env bash
profile_path=${{RAS_INSTALL_PROFILE:-{0}{1}}}

# sets PYTHONBIN to python/python2/python3, whichever is available in that order
PYTHONBIN=
for b in python python2 python3; do
  if $b --version > /dev/null 2>&1; then
    PYTHONBIN=$b; break;
  fi
done
if [ -z "$PYTHONBIN" ]; then
  echo "Please install python."
  exit 1
fi

#ignore_trace_dirs=$($PYTHONBIN -c 'import os, sys; print(os.pathsep.join(sys.path[1:]))')
#sudo $PYTHONBIN -m trace --ignore-dir "$ignore_trace_dirs" -t "{0}/main.py" "$profile_path"

sudo $PYTHONBIN "{0}/main.py" "$profile_path"
""".format(installer_dest_in_package, self.profile_path))
        sudo_chmod(postinst_path, "755")
        log.debug("  Generated postinst script at '{}'.".format(postinst_path))
        return postinst_path

    def __generate_postuninst_script(self, installer_path):
        """
        Generates the post installation script for the package.
        :param str installer_path: where to create the postuninst script
        :rtype: str
        """
        postuninst_path = installer_path + "postuninst.sh"
        write_to_file(postuninst_path, """#!/usr/bin/env bash
rm -f /etc/systemd/system/rasdaman.service
rm -f /etc/profile.d/rasdaman.sh
exit 0
""")
        sudo_chmod(postuninst_path, "755")
        log.debug("  Generated postinst script at '{}'.".format(postuninst_path))
        return postuninst_path

    def __generate_preuninst_script(self, installer_path):
        """
        Generates the pre uninstallation script for the package
        :param str installer_path: where to create the preuninstall script
        :rtype: str
        """
        preuninst_path = installer_path + "preuninst.sh"
        write_to_file(preuninst_path, """#!/usr/bin/env bash
# Stop the rasdaman service
service rasdaman stop
exit 0
""")
        sudo_chmod(preuninst_path, "755")
        log.debug("  Generated pre-uninstall script at '{}'.".format(preuninst_path))
        return preuninst_path

    def __generate_fpm_cmd(self, preinst_path, postinst_path, preuninst_path, postuninst_path, conf_dir):
        """
        Generates the command for the fpm tool
        :param str postinst_path: the path to the preinst script
        :param str postinst_path: the path to the postinst script
        :param str preuninst_path: the path to the preinst script
        :param str postuninst_path: the path to the postinst script
        :param str conf_dir: the directory where configuration files reside
        :rtype: list[str]
        """
        fpm_base_command = [self.get_fpm_cmd(),
                            "-s", "dir",
                            "-t", self.get_type(),
                            "-C", self.PKG_ROOT,
                            "--name", self.package_info.name,
                            "--config-files", conf_dir,
                            "--version", self.package_info.version,
                            "--iteration", self.package_info.iteration,
                            "--description", self.package_info.description,
                            "--url", self.package_info.url,
                            "--maintainer", self.package_info.maintainer,
                            "--vendor", self.package_info.vendor,
                            "--license", self.package_info.licence,
                            "--before-install", preinst_path,
                            "--after-install", postinst_path,
                            "--before-remove", preuninst_path,
                            "--after-remove", postuninst_path]

        fpm = fpm_base_command + self.get_fpm_formatted_deps()
        return fpm

    def package(self):
        Packager.package(self)
        log.info("Packaging rasdaman as " + self.get_type() + " package...")
        pkg_root = self.__prepare_pkg_root()

        self.__strip_binaries(pkg_root)

        installer_dest = self.__prepare_package_installer(pkg_root)
        preinst_path = self.__generate_preinst_script(installer_dest)
        postinst_path = self.__generate_postinst_script(installer_dest)
        preuninst_path = self.__generate_preuninst_script(installer_dest)
        postuninst_path = self.__generate_postuninst_script(installer_dest)

        # Create the package
        sudo_chown(pkg_root, ConfigManager.default_user)
        conf_dir = self.install_path + "etc/"
        if conf_dir[0] == "/":
            conf_dir = conf_dir[1:]
        fpm_cmd = self.__generate_fpm_cmd(preinst_path, postinst_path, preuninst_path, postuninst_path, conf_dir)
        package_location = self.BUILT_PKGS_DIR
        make_directory(package_location)
        executor.executeSudo(fpm_cmd, package_location)
        log.info("Rasdaman packaged successfully at: " + package_location)


class DebPackager(LinuxPackager):
    def __init__(self, install_path, profile_path, deps, package_info):
        """
        A packager that packages the rasdaman installation as deb package
        :param str install_path: the installation path of rasdaman
        :param str profile_path: relative path starting from the installer root
        :param list[str] deps: a list of dependencies for a deb package
        :param LinuxPackageInfo package_info: information about the package
        """
        LinuxPackager.__init__(self, install_path, profile_path, package_info)
        self.deps = deps
        self.package_info.version = self.package_info.version + \
            DistroWrapper().get_distro_codename()

    def get_type(self):
        return "deb"

    def get_fpm_cmd(self):
        return "fpm"

    def get_dependencies(self):
        return self.deps


class RPMPackager(LinuxPackager):
    def __init__(self, install_path, profile_path, deps, package_info):
        """
        A packager that packages the rasdaman installation as rpm package
        :param str install_path: the installation path of rasdaman
        :param str profile_path: relative path starting from the installer root
        :param list[str] deps: a list of dependencies for a deb package
        :param LinuxPackageInfo package_info: information about the package
        """
        LinuxPackager.__init__(self, install_path, profile_path, package_info)
        self.deps = deps

    def get_type(self):
        return "rpm"

    def get_fpm_cmd(self):
        return "/usr/local/bin/fpm"

    def get_dependencies(self):
        return self.deps
