"""
 *
 * 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>.
 *
"""
import os
from builder.builder import BuilderBasis, WarFileInstallBuilder, RasdamanCmakeBuilder, \
    WarFileUninstallBuilder
from builder.configurer import InstallPrefixConfigurer, WebappStandaloneConfigurer, \
    PostgresConfigurer, SqliteConfigurer, DebugConfigurer, BasisConfigurer, \
    NetcdfConfigurer, HdfConfigurer, GribConfigurer, EnableStrictConfigurer, ReleaseConfigurer, \
    InternalConverterConfigurer, GenerateDocsConfigurer, SetSimdExtensionsConfigurer, \
    UnityBuildConfigurer, DebugLogsConfigurer
from finalizers.finalizers import FinalizerBasis, RasdamanDemoDataFinalizer, \
    PetascopeDemoDataFinalizer
from packager.packager import DoNothingPackager, DebPackager, RPMPackager, \
    LinuxPackageInfo
from preparer.preparer import PreparerBasis, SqlitePreparer, PostgresPreparer, \
    TomcatServerPreparer, PetascopePreparer, StartServicesPreparer, \
    UpdateWebappsLogPathsPreparer, CopyInstallerPreparer, EnableDebugLoggingPreparer, \
    MigratePreviousVersionConfigsPreparer, AddTomcatToRasdamanGroup, \
    UpdateRasdamanScriptPreparer, ServiceInitDPreparer, PermissionFixPreparer
from services import rasdaman_service, linux_distro
from util.distrowrapper import DistroTypes, DistroWrapper
from util.file_util import find_line_starting_with, get_values_for_property_keys
from util.log import log
from retriever.retriever import DoNothingRetriever, PatchManagerRetriever, \
    RepositoryRetriever
from strategies.base_strategy import Strategy
from uninstaller.uninstaller import UninstallerBasis, RasdamanServiceUninstaller, \
    RasdamanPathUninstaller, PostgresUninstaller, SqliteUninstaller, \
    WebappUninstaller
from tester.tester import MockTester, RasdamanSystemTester
from tpinstaller.osinstaller import DependenciesInstallerFactory
from validator.validator import ValidatorBasis, RasdamanIsRunningValidator
from config_manager import ConfigManager


class StrategyBuilder:
    def __init__(self, profile):
        """
        The general strategy builder installs rasdaman from source, runs
        systemtests, generates packages, etc.
        :param Profile profile: the profile to configure the strategy
        """
        self.profile = profile
        self.package_install = self.profile.install_type == "package"
        self.source_install = self.profile.install_type == "source"
        self.install = self.profile.install
        self.uninstall = self.profile.uninstall

    def update_profile(self):
        """
        Check if this is an update of an existing installation; update the
        Tomcat paths in the profile.
        """
        if self.profile.webapps_path is None or self.profile.webapps_path == "":
            self.profile.webapps_path = linux_distro.get_tomcat_path() + "/webapps"
        if self.profile.webapps_logs is None or self.profile.webapps_logs == "":
            self.profile.webapps_logs = linux_distro.get_tomcat_logs()
        petascope_props_path = self.profile.install_path + "/etc/petascope.properties"
        test_files = [petascope_props_path]
        if self.package_install:
            test_files = ["/etc/init.d/rasdaman"]
        for test_file in test_files:
            if os.path.exists(test_file):
                self.profile.update = True
                break

        # profile sets "standalone" server deployment, however, on package
        # update we need to check if the existing petascope.properties sets
        # external deployment
        if self.profile.webapps_deployment != "external" and \
           not self.profile.generate_package:
            if os.path.exists(petascope_props_path):
                # update webapps_deployment
                external_deployment = find_line_starting_with(
                    petascope_props_path, "java_server=external")
                if external_deployment is not None:
                    log.info("External deployment configured in " +
                             "petascope.properties, updating profile.")
                    self.profile.webapps_deployment = "external"
            else:
                path = self.profile.webapps_path + "/rasdaman.war"
                if os.path.exists(path):
                    log.info("{} not found, but {} was found, so updating "
                             "profile to external deployment.".format(
                        petascope_props_path, path))
                    self.profile.webapps_deployment = "external"

        # update petascopedb username/password if it's an update installation
        props_exist = os.path.exists(petascope_props_path)
        if self.package_install and self.profile.update and props_exist:
            values = get_values_for_property_keys(
                petascope_props_path, ['spring.datasource.url',
                                       'spring.datasource.username',
                                       'spring.datasource.password',
                                       'server.port'])
            if len(values) == 4:
                log.debug("Updating existing installation, will read petascopedb "
                          "configuration from petascope.properties")
                if values[0] is not None:
                    self.profile.petascopedb_url = values[0]
                if values[1] is not None:
                    self.profile.petascopedb_username = values[1]
                if values[2] is not None:
                    self.profile.petascopedb_password = values[2]
                if values[3] is not None:
                    self.profile.petascope_standalone_port = int(values[3])

    def generate_validator(self):
        """
        Generates the validators
        :rtype: Validator
        """
        ret = RasdamanIsRunningValidator(ValidatorBasis(), self.profile.rasmgr_port)
        return ret

    def generate_uninstaller(self):
        """
        Generates the uninstallers
        :rtype: Uninstaller
        """
        ret = UninstallerBasis()
        if not self.uninstall:
            return ret
        ret = RasdamanServiceUninstaller(ret)
        ret = RasdamanPathUninstaller(ret, self.profile.install_path,
            self.profile.remove_data, self.profile.remove_configs)
        if self.profile.remove_data:
            if self.profile.database == "postgresql":
                ret = PostgresUninstaller(ret, self.profile.user, "RASBASE")
            elif self.profile.database == "sqlite":
                ret = SqliteUninstaller(ret, self.profile.database_url)
            if self.profile.webapps_enabled:
                # get only the database name not the full url
                db = self.profile.petascopedb_url.split("/")[-1]
                ret = PostgresUninstaller(ret, self.profile.petascopedb_username, db)
        if self.profile.webapps_enabled:
            ret = WebappUninstaller(ret, self.profile.webapps_path)
        return ret

    def generate_os_installer(self):
        """
        Generates the OS installer
        :rtype: OSInstaller
        """
        ret = None
        if self.install:
            ret = DependenciesInstallerFactory.get_installer(self.profile)
        return ret

    def generate_retriever(self):
        """
        Returns a retriever based on the installation profile
        :rtype: Retriever
        """
        ret = DoNothingRetriever()
        if self.source_install:
            shallow = self.profile.systemtest or self.profile.generate_package
            ret = RepositoryRetriever(self.profile.repository,
                                      self.profile.source_path,
                                      self.profile.version, shallow)
            if self.profile.patch is not None:
                ret = PatchManagerRetriever(ret, self.profile.source_path,
                                            self.profile.patch)
        return ret

    def generate_configurer(self):
        """
        Returns a configurer based on the installation profile
        :rtype: Configurer
        """
        distro = DistroWrapper()
        ret = BasisConfigurer()

        if self.package_install or not self.install:
            return ret
        ret = InstallPrefixConfigurer(ret, self.profile.install_path)
        ret = GribConfigurer(HdfConfigurer(NetcdfConfigurer(InternalConverterConfigurer(ret))))

        if self.profile.debug:
            ret = DebugConfigurer(ret)
        elif self.profile.debug_logs:
            ret = DebugLogsConfigurer(ret)
        else:
            ret = ReleaseConfigurer(ret)
        if self.profile.database == "sqlite":
            ret = SqliteConfigurer(ret, self.profile.database_url)
        elif self.profile.database == "postgresql":
            ret = PostgresConfigurer(ret)
        if self.profile.webapps_deployment == "standalone":
            ret = WebappStandaloneConfigurer(ret)
        if self.profile.strict:
            ret = EnableStrictConfigurer(ret)
        if self.profile.simd_extensions is not None:
            ret = SetSimdExtensionsConfigurer(ret, self.profile.simd_extensions)
        ret = UnityBuildConfigurer(ret, False if self.profile.enterprise else True)
        ret = GenerateDocsConfigurer(ret, self.profile.generate_docs)
        return ret

    def generate_builder(self, configurer):
        """
        Returns a builder based on the installation profile
        :param Configurer configurer: the configurer object
        :rtype: Builder
        """
        ret = BuilderBasis()
        if not self.install:
            return ret
        webapps_path = self.profile.webapps_path
        if self.source_install:
            istest = self.profile.systemtest and not self.package_install
            ret = RasdamanCmakeBuilder(ret, configurer, self.profile.source_path,
                self.profile.install_path, self.profile.update, istest)

        if not self.profile.generate_package and \
           self.profile.webapps_enabled and \
           self.profile.webapps_deployment == "external":
            wardir = self.profile.install_path + "share/rasdaman/war/"
            ret = WarFileInstallBuilder(ret, webapps_path, wardir + "rasdaman.war")
            ret = WarFileUninstallBuilder(ret, webapps_path, "def.war")
        return ret

    def generate_preparer(self):
        """
        Returns a preparer based on the installation profile
        :rtype: Preparer
        """
        ret = PreparerBasis()
        if self.profile.generate_package or not self.install:
            return ret
        install_path = self.profile.install_path

        # Migrate petascope.properties, rasfed.properties, etc.
        # This needs to be done *before* the SqlitePreparer below which does
        # update_db.sh, as update_db.sh could also do migration of properties
        # files which depend on them having the latest keys set by
        # update_properties.sh
        if self.package_install:
            ret = MigratePreviousVersionConfigsPreparer(ret, install_path)

        # Create RASBASE; update_db.sh is also called if this is an
        # installation update
        if self.profile.database == "sqlite":
            ret = SqlitePreparer(ret, install_path,
                                 self.profile.database_url)
        elif self.profile.database == "postgresql":
            ret = PostgresPreparer(ret, install_path,
                                   self.profile.user, self.profile.update)

        # Update petascope and SECORE properties based on the installer profile
        external_deployment = self.profile.webapps_deployment == "external"
        tomcat_port = 8080 if external_deployment else self.profile.petascope_standalone_port

        if self.profile.webapps_enabled:
            ret = PetascopePreparer(ret, self.profile)
            ret = TomcatServerPreparer(ret, install_path,
                                       not external_deployment, tomcat_port)
            if external_deployment:
                ret = UpdateWebappsLogPathsPreparer(
                    ret, install_path, self.profile.webapps_logs)
                ret = AddTomcatToRasdamanGroup(
                    ret, linux_distro.get_tomcat_user(), self.profile.user)

        if not self.profile.generate_package:
            ret = ServiceInitDPreparer(ret, install_path, self.profile, True)
            if not self.package_install:
                ret = CopyInstallerPreparer(ret, install_path)
            if self.profile.systemtest and self.profile.debug_logs:
                ret = EnableDebugLoggingPreparer(ret, install_path, "log-rasmgr.conf")
                ret = EnableDebugLoggingPreparer(ret, install_path, "log-client.conf")

        if self.source_install:
            ret = UpdateRasdamanScriptPreparer(ret, self.profile.source_path,
                                       self.profile.profile_path, install_path)
        if self.package_install:
            ret = PermissionFixPreparer(ret, install_path, self.profile.update)

        # start rasdaman and Tomcat if external deployment
        ret = StartServicesPreparer(ret, install_path,
                                    self.profile.webapps_enabled,
                                    external_deployment, tomcat_port)

        return ret

    def generate_finalizer(self):
        """
        Returns a finalizer based on the installation profile
        :rtype: Finalizer
        """
        ret = FinalizerBasis()
        if not self.install:
            return ret

        install_path = self.profile.install_path
        if not self.profile.generate_package and self.profile.insert_demo and \
           not self.profile.update:
            ret = RasdamanDemoDataFinalizer(ret, install_path,
                                            self.profile.rasmgr_port)
            if self.profile.webapps_enabled and ConfigManager.petascope_available:
                ret = PetascopeDemoDataFinalizer(ret, install_path)
        rasdaman_service.bin_path = install_path + "bin/"

        return ret

    def generate_tester(self):
        """
        Returns a tester based on the installation profile
        :rtype: Tester
        """
        if not self.profile.systemtest or self.package_install or not self.install:
            return MockTester()
        return RasdamanSystemTester(self.profile)

    def generate_packager(self):
        """
        Returns a package generator.
        :rtype: Packager
        """
        ret = DoNothingPackager()
        if self.install and self.profile.generate_package:
            installer = DependenciesInstallerFactory.get_installer(self.profile)
            deps = installer.get_run_packages()
            info = LinuxPackageInfo(self.profile.package_name,
                                    self.profile.package_description,
                                    self.profile.package_version,
                                    self.profile.package_iteration,
                                    self.profile.package_vendor,
                                    self.profile.package_licence,
                                    self.profile.package_category,
                                    self.profile.package_maintainer,
                                    self.profile.package_url)
            if linux_distro.is_deb():
                ret = DebPackager(self.profile.install_path,
                                  self.profile.package_profile_path, deps, info)
            elif linux_distro.is_rpm():
                ret = RPMPackager(self.profile.install_path,
                                  self.profile.package_profile_path, deps, info)
        return ret

    def generate(self):
        """
        Generates a strategy based on a given profile
        :rtype: Strategy
        """
        self.update_profile()
        return Strategy(self.generate_validator(),
                        self.generate_uninstaller(),
                        self.generate_os_installer(),
                        self.generate_retriever(),
                        self.generate_builder(self.generate_configurer()),
                        self.generate_preparer(),
                        self.generate_finalizer(),
                        self.generate_tester(),
                        self.generate_packager(),
                        self.install,
                        self.uninstall)
