"""
 *
 * 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 __future__ import absolute_import
from config_manager import ConfigManager
from util.string_util import generate_password
from util.file_util import fix_trailing_slash
from . import toml


class ProfileException(Exception):
    pass


class Profile:
    def __init__(self):
        """
        A profile describes all possible settings needed for installing rasdaman
        and associated components
        """
        # Dictionary holding the parsed profile config
        self.profile = None

        # [general]
        self.user = "rasdaman"
        self.auto = False
        self.profile_path = ""
        self.install = True
        self.update = False
        self.uninstall = False
        self.enterprise = False

        # [pre_install]
        self.install_dependencies = True

        # [install]
        self.install_type = "source"
        self.install_path = "/opt/rasdaman/"
        self.database = "sqlite"
        self.database_url = self.install_path + "data/"
        self.rasmgr_port = 7001

        # [install.source]
        self.repository = "git://rasdaman.org/rasdaman.git"
        self.version = "master"
        self.debug = False
        self.debug_logs = False
        self.strict = False
        self.patch = None
        self.generate_docs = False
        self.simd_extensions = None  # supports SSE42, AVX, AVX2, AVX512
        self.source_path = self.install_path + "source/"

        # [install.webapps]
        self.webapps_enabled = True
        self.webapps_deployment = "embedded"
        self.webapps_path = None
        self.webapps_logs = None

        # [install.webapps.petascope]
        self.petascope_standalone_port = 8080
        self.petascopedb_url = "jdbc:postgresql://localhost:5432/petascopedb"
        self.petascopedb_username = "petauser"
        self.petascopedb_password = generate_password()

        # [post_install]
        self.insert_demo = True
        self.systemtest = False
        self.generate_package = False

        # [post_install.package]
        self.package_profile_path = "profiles/package/install.toml"
        self.package_name = "rasdaman"
        self.package_description = "Rasdaman is the leading Array " \
            "Database for flexible, scalable analytics of massive " \
            "multi-dimensional array (raster) data, such as " \
            "spatio-temporal datacubes."
        self.package_version = "9.8.0"
        self.package_iteration = "{iteration}"
        self.package_vendor = "rasdaman"
        self.package_licence = "GPLv3"
        self.package_category = "devel"
        self.package_maintainer = "Dimitar Misev <misev@rasdaman.com>"
        self.package_url = "https://rasdaman.org"

        # TODO use
        self.package_upload = False
        self.package_upload_hostname = None
        self.package_upload_username = None
        self.package_upload_password = None
        self.package_upload_deploy_path = None

        # [uninstall]
        self.remove_data = True
        self.remove_configs = True

    def parse_profile(self, path_to_profile):
        """
        Parses the profile data from a toml configuration file.
        :param str path_to_profile: the toml profile path
        """
        if path_to_profile.endswith(".json"):
            raise ProfileException("JSON profile configurations are not " +
                "supported anymore by this version of the installer.\nPlease " +
                "specify a profile configuration in the TOML format, e.g. " +
                "./install.sh -j profile.toml\nA default, fully documented " +
                "example can be found at https://rasdaman.org/wiki/InstallerConfig\n\n" +
                "If you were executing update_rasdaman.sh, then please execute " +
                "one of the following commands instead:\n" +
                "  ./install.sh                 - if you used the default profile\n" +
                "  ./install.sh -j profile.toml - if you used a custom profile")
        self.profile_path = path_to_profile
        with open(path_to_profile) as toml_profile_file:
            profile_str = toml_profile_file.read()
            try:
                self.profile = toml.loads(profile_str)
            except Exception as e:
                raise ProfileException("Failed decoding profile configuration '" +
                                       path_to_profile + "': " + str(e))
            self.__parse_general()
            self.__parse_pre_install()
            self.__parse_install()
            self.__parse_post_install()
            self.__parse_uninstall()

    def __parse_general(self):
        section = get_value(self.profile, ["general"])
        self.user = get_value(section, ["user"], self.user)
        validate_nonempty("user", self.user)
        ConfigManager.default_user = self.user
        self.auto = get_value(section, ["auto"], self.auto)
        validate_bool("auto", self.auto)
        self.install = get_value(section, ["install"], self.install)
        validate_bool("install", self.install)
        self.uninstall = get_value(section, ["uninstall"], self.uninstall)
        validate_bool("uninstall", self.uninstall)
        self.enterprise = get_value(section, ["enterprise"], self.enterprise)
        validate_bool("enterprise", self.enterprise)

    def __parse_pre_install(self):
        section = get_value(self.profile, ["pre_install"])
        self.install_dependencies = get_value(section, ["install_dependencies"],
                                              self.install_dependencies)
        validate_bool("install_dependencies", self.install_dependencies)

    def __parse_install(self):
        section = get_value(self.profile, ["install"])
        self.install_type = get_value(section, ["from"], self.install_type)
        validate_alternatives("from", self.install_type, ["source", "package"])
        if not self.install:
            self.install_type = "none"
        self.install_path = get_value(section, ["install_path"], self.install_path)
        validate_nonempty("install_path", self.install_path)
        self.install_path = fix_trailing_slash(self.install_path)
        if self.install_path[0] != "/":
            raise ProfileException("The installation path '" + self.install_path +
                                   "' is not an absolute path")
        self.database = get_value(section, ["database"], self.database)
        validate_alternatives("database", self.database, ["sqlite", "postgresql"])
        if self.database == "sqlite":
            self.database_url = self.install_path + "data/"
        else:
            self.database_url = "RASBASE"
        self.rasmgr_port = get_value(section, ["port"], self.rasmgr_port)
        validate_int("port", self.rasmgr_port)

        self.__parse_install_source()
        self.__parse_install_webapps()
        self.__parse_install_webapps_petascope()

    def __parse_install_source(self):
        section = get_value(self.profile, ["install", "source"])
        self.repository = get_value(section, ["repository"], self.repository)
        validate_nonempty("repository", self.repository)
        self.version = get_value(section, ["version"], self.version)
        validate_nonempty("version", self.version)
        if self.version == "{branch}":
            self.version = "master"
        self.debug = get_value(section, ["debug"], self.debug)
        validate_bool("debug", self.debug)
        self.debug_logs = get_value(section, ["debug_logs"], self.debug_logs)
        validate_bool("debug_logs", self.debug_logs)
        self.strict = get_value(section, ["strict"], self.strict)
        validate_bool("strict", self.strict)
        self.generate_docs = get_value(section, ["generate_docs"], self.generate_docs)
        validate_bool("generate_docs", self.generate_docs)
        self.simd_extensions = get_value(section, ["simd_extensions"], self.simd_extensions)
        supported_simd_extensions = ["SSE42", "AVX", "AVX2", "AVX512"]
        validate_alternatives("simd_extensions", self.simd_extensions, supported_simd_extensions)
        self.patch = get_value(section, ["patch"], self.patch)
        if self.patch is not None and "{patch_id}" in self.patch:
            self.patch = None  # do not apply patch in this case
        self.source_path = self.install_path + "source/"

    def __parse_install_webapps(self):
        section = get_value(self.profile, ["install", "webapps"])
        self.webapps_enabled = get_value(section, ["enable"], self.webapps_enabled)
        validate_bool("enable", self.webapps_enabled)

        self.webapps_deployment = get_value(section, ["deployment"], self.webapps_deployment)
        validate_alternatives("deployment", self.webapps_deployment, ["external", "standalone", "embedded"])
        if self.webapps_deployment == "embedded":
            self.webapps_deployment = "standalone"

        self.webapps_path = get_value(section, ["webapps_path"], self.webapps_path)
        if self.webapps_path is not None:
            self.webapps_path = fix_trailing_slash(self.webapps_path)
        self.webapps_logs = get_value(section, ["webapps_logs"], self.webapps_logs)
        if self.webapps_logs is not None:
            self.webapps_logs = fix_trailing_slash(self.webapps_logs)

    def __parse_install_webapps_petascope(self):
        section = get_value(self.profile, ["install", "webapps", "petascope"])
        self.petascope_standalone_port = get_value(section, ["standalone_port"], self.petascope_standalone_port)
        validate_int("standalone_port", self.petascope_standalone_port)
        self.petascopedb_url = get_value(section, ["petascopedb_url"], self.petascopedb_url)
        self.petascopedb_username = get_value(section, ["petascopedb_username"], self.petascopedb_username)
        self.petascopedb_password = get_value(section, ["petascopedb_password"], self.petascopedb_password)
        if self.petascopedb_password is None or self.petascopedb_password == "":
            self.petascopedb_password = generate_password()

    def __parse_post_install(self):
        section = get_value(self.profile, ["post_install"])
        self.insert_demo = get_value(section, ["insert_demo"], self.insert_demo)
        validate_bool("insert_demo", self.insert_demo)
        self.systemtest = get_value(section, ["systemtest"], self.systemtest)
        validate_bool("systemtest", self.systemtest)
        self.generate_package = get_value(section, ["generate_package"], self.generate_package)
        validate_bool("generate_package", self.generate_package)
        self.__parse_post_install_package()

    def __parse_post_install_package(self):
        section = get_value(self.profile, ["post_install", "package"])
        self.package_profile_path = get_value(section, ["profile_path"], self.package_profile_path)
        self.package_name = get_value(section, ["name"], self.package_name)
        self.package_description = get_value(section, ["description"], self.package_description)
        self.package_version = get_value(section, ["version"], self.package_version)
        self.package_iteration = get_value(section, ["iteration"], self.package_iteration)
        if self.package_iteration == "{iteration}":
            self.package_iteration = "1"
        self.package_vendor = get_value(section, ["vendor"], self.package_vendor)
        self.package_licence = get_value(section, ["licence"], self.package_licence)
        self.package_category = get_value(section, ["category"], self.package_category)
        self.package_maintainer = get_value(section, ["maintainer"], self.package_maintainer)
        self.package_url = get_value(section, ["url"], self.package_url)

        self.package_upload = get_value(section, ["upload"], self.package_upload)
        validate_bool("upload", self.package_upload)
        self.package_upload_hostname = get_value(section, ["upload_hostname"], self.package_upload_hostname)
        self.package_upload_username = get_value(section, ["upload_username"], self.package_upload_username)
        self.package_upload_password = get_value(section, ["upload_password"], self.package_upload_password)
        self.package_upload_deploy_path = get_value(section, ["upload_deploy_path"], self.package_upload_deploy_path)

    def __parse_uninstall(self):
        section = get_value(self.profile, ["uninstall"])
        self.remove_data = get_value(section, ["remove_data"], self.remove_data)
        validate_bool("remove_data", self.remove_data)
        self.remove_configs = get_value(section, ["remove_configs"], self.remove_configs)
        validate_bool("remove_configs", self.remove_configs)

    def __str__(self):
        out = ""
        out += """
Install path: {install_path}
User: {user}
Database: {database}, {database_url}"""
        if self.rasmgr_port != 7001:
            out += """
Rasmgr port: {rasmgr_port}"""
        if self.install_type == "source":
            out += """
Repository: {repository}
Version: {version}
Debug: {debug}"""
        if self.simd_extensions is not None:
            out += """
SIMD extensions: {simd_extensions}"""
        if self.patch is not None:
            out += """
Apply patch: {patch}"""
        out += """
Install webapps: {webapps_enabled}"""
        if self.webapps_enabled:
            out += """
  Petascopedb url: {petascopedb_url}
  Petascopedb user: {petascopedb_username}
  Deployment: {webapps_deployment}"""
            if self.webapps_deployment == "external":
                out += """
  Webapps path: {webapps_path}
  Webapps logs: {webapps_logs}"""
            else:
                out += """
  Petascope standalone port: {petascope_standalone_port}"""
        out += """
Insert demos: {insert_demo}
Run systemtest: {systemtest}
Generate package: {generate_package}"""
        if self.generate_package:
            out += """
  Profile path: {package_profile_path}
  Name: {package_name}
  Description: {package_description}
  Version: {package_version}
  Iteration: {package_iteration}
  Vendor: {package_vendor}
  Licence: {package_licence}
  Category: {package_category}
  Maintainer: {package_maintainer}
  URL: {package_url}"""
        if self.uninstall:
            out += """
Rasdaman will be uninstalled"""
            if self.install:
                out += " before installing it"
            out += """
  Remove data: {remove_data}
  Remove configs: {remove_configs}"""
        if not self.install:
            out += """
Rasdaman will not be installed"""
        elif self.update:
            out += """
Update: found an existing rasdaman installation"""
        return out.format(**self.__dict__)


def get_value(profile_dict, key, default_value=None):
    """
    Return the value of the (potentially nested) key in config_dict.
    @param profile_dict a dictionary containing the profile options; can be None
    @param key a list of nested keys
    @return the value if the keys exist in profile_dict, or None otherwise
    """
    ret = profile_dict
    if ret is not None:
        for k in key:
            if k in ret:
                ret = ret[k]
            else:
                ret = None
                break
    if ret is None:
        ret = default_value
    return ret


def validate_alternatives(key, value, valid_alternatives):
    if value is not None and value not in valid_alternatives:
        raise ProfileException("Invalid value '" + value + "' for option '" +
                               key + "'; expected one of (" +
                               "|".join(valid_alternatives) + ")")


def validate_bool(key, value):
    if value is not None and not isinstance(value, bool):
        raise ProfileException("Invalid value '" + value + "' for option '" +
                               key + "'; expected 'true' or 'false'.")


def validate_int(key, value):
    if value is not None and not isinstance(value, int):
        raise ProfileException("Invalid value '" + value + "' for option '" +
                               key + "'; expected an integer number.")


def validate_nonempty(key, value):
    if value is None or value == "":
        raise ProfileException("The value for option '" + key + "' must not be empty.")
