import datetime
from xml.etree import ElementTree
from collections import OrderedDict
import os
import threading
import time

from parallels.core.logging import get_logger
from parallels.core.runners.base import BaseRunner
from parallels.core.runners.entities import ExecutionOptions
from parallels.core.utils.common import open_no_inherit
from parallels.plesk.hosting_analyser import messages
from parallels.core.utils.migrator_utils import create_rsync_command, normalize_domain_name
from parallels.core.connections.source_server import SourceServer
from parallels.core.connections.target_servers import TargetServer
from parallels.plesk.source.plesk.server import PleskSourceServer
from parallels.plesk.connections.target_connections import PleskTargetConnections
from parallels.plesk.connections.target_server import PleskTargetServer
from parallels.core.utils.yaml_utils import read_yaml, write_yaml
from parallels.core.registry import Registry
from parallels.core.utils.config_utils import ConfigSection
from parallels.core.utils import windows_utils


logger = get_logger(__name__)

# Analysis estimations
UNKNOWN = frozenset([0])
CRITICAL = frozenset([1])
OK = frozenset([2])
ANY = frozenset([0, 1, 2])

analyze_lock = threading.Lock()
result_file_lock = threading.Lock()


# Print helper
def get_bytes_with_suffix(num, prefix='', suffix='B'):
    """
    :type num: int
    :type prefix: str | unicode
    :type suffix: str | unicode
    :rtype: str | unicode
    """
    all_prefixes = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
    prefixes = all_prefixes[all_prefixes.index(prefix):]
    for unit in prefixes:
        if abs(num) < 1024.0:
            return "%3.1f %s%s" % (num, unit, suffix)
        num /= 1024.0
    return "%.1f %s%s" % (num, 'Y', suffix)


# Print helper
def get_time_duration_for_human(seconds):
    """
    :type seconds: int
    :rtype: str | unicode
    """
    return time.strftime('%M:%S', time.gmtime(seconds))


def get_unix_strategies():
    """
    :rtype: OrderedDict
    """
    # TODO: extend this after collecting enough experimental data
    return OrderedDict([
        ##############################################
        # order: (CPU, RAM, HDD, NET)
        ##############################################
        # without ANY
        ##############################################

        ##############################################
        # with 1 ANY
        ##############################################

        ##############################################
        # with 2 ANY
        ##############################################
        ((UNKNOWN, CRITICAL, ANY, ANY), {'compression': True, 'msg': messages.CPU_UNKNOWN_RAM_CRITICAL_USE_COMPRESSION}),
        ((UNKNOWN, OK, ANY, ANY), {'compression': True, 'msg': messages.CPU_UNKNOWN_RAM_OK_USE_COMPRESSION}),
        ##############################################
        # with 3 ANY
        ##############################################
        ((CRITICAL, ANY, ANY, ANY), {'compression': False, 'msg': messages.CPU_CRITICAL_SKIP_COMPRESSION}),
        ((ANY, ANY, CRITICAL, ANY), {'compression': False, 'msg': messages.HDD_CRITICAL_SKIP_COMPRESSION}),
        ((ANY, ANY, ANY, CRITICAL), {'compression': True, 'msg': messages.NET_CRITICAL_USE_COMPRESSION}),
        ((ANY, ANY, ANY, OK), {'compression': True, 'msg': 'NET = OK: use compression'}),
        ##############################################
        # with 4 ANY: Default strategy
        ##############################################
        ((ANY, ANY, ANY, ANY), {'compression': True, 'msg': messages.DEFAULT_STRATEGY_USE_COMPRESSION}),
    ])


def get_windows_strategies():
    """
    :rtype: OrderedDict
    """
    return OrderedDict([
        ##############################################
        # order: (CPU, RAM, HDD, NET)
        ##############################################
        # without ANY
        ##############################################

        ##############################################
        # with 1 ANY
        ##############################################

        ##############################################
        # with 2 ANY
        ##############################################

        ##############################################
        # with 3 ANY
        ##############################################
        ((ANY, ANY, ANY, CRITICAL), {'compression': True, 'msg': messages.NET_CRITICAL_USE_COMPRESSION_MSG}),
        ##############################################
        # with 4 ANY: Default strategy
        ##############################################
        ((ANY, ANY, ANY, ANY), {'compression': False, 'msg': messages.DEFAULT_STRATEGY_SKIP_COMPRESSION}),
    ])


def get_coefficients_for_unix():
    """
    :rtype: list
    """
    # (k/bin/, k/txt/, k/unk/, k/hdd/, k1/compr/, k2/compr/, compression_level)
    return [(0.952927995773256, 0.289034361542407, 0.620981178657831, 0.00998496317436691, 0.05075646656905807711078574914592, 0.41483650561249389946315275744265, 3),
            (0.951531607401822, 0.256572015679910, 0.604051811540866, 0.00998496317436691, 0.05075646656905807711078574914592, 0.41483650561249389946315275744265, None),
            (0.949659735711172, 0.253363634140486, 0.601511684925829, 0.00998496317436691, 0.05075646656905807711078574914592, 0.41483650561249389946315275744265, 9)]


def get_coefficients_for_windows():
    """
    :rtype: list
    """
    return get_coefficients_for_unix()


class HostingAnalyser():
    """Hosting_analyser calculates the most effective content transfer strategy according to content & environment
    analysis and is used by 'copy-content' action. It detects if data compression is needed and have 2 algorithms
    for calculation:
    - "Matrix of Strategies". Based on knowledge database with combination of environment conditions only.
    - "Math Analysis" [used by default]. Based on mathematical analysis of environment & content conditions.

    Main public methods of Hosting_analyser:

    - analyse() is used for first analysis.    It fetches first content & environment reports. Average time execution
    ~8 min.

    - get_webspace_actual_transfer_strategy() is used for refreshing environment reports only (every 15 minutes
    by default) and returns the most effective transfer strategy for the current conditions. Average time execution
    ~20 sec. This method requires at least one execution of analyse().
    """
    def __init__(self, migrator_server):
        """
        :type migrator_server: parallels.core.connections.migrator_server.MigratorServer
        """
        # Test file sizes for network speed approximation [MBytes]
        # 1 = 0-10Mbps | 3 = 10Mbps-33Mbps | 6 = 33Mbps-66Mbps | 12 = 66Mpbs-100Mpbs | 24 = 100Mbps-150Mbps
        self.sizes = [1, 3, 6, 12, 24, 48]
        # Amount of estimations for each test file size
        self.network_estimation_attempts_for_steps = 10
        self.remote_xml_report_filename = 'summary.xml'
        self.remote_agent_report_folder_name = 'hosting_analysis_report'  # hosting analyser agent foldername with report
        self.local_dst_plesk_server_report_filename = 'destination-plesk.xml'
        self.agent_filename = 'hosting_analyser_agent.py'
        self.network_analysis_report_filename = '_rsync_network_report'
        self.migration_session_dir = migrator_server.get_session_dir_path()
        self.local_analyser_data_folder_path = os.path.join(self.migration_session_dir, 'hosting_analysis_reports')

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

        self.local_agent_full_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.agent_filename)
        for size in self.sizes:
            test_file = self._get_extras_file_path(self._get_network_test_filename(size))
            if not os.path.exists(test_file):
                with open_no_inherit(test_file, "wb") as fp:
                    for i in xrange(size):
                        fp.write(self._get_random_string())

    def _get_analyser_session_file_path(self, filename):
        """
        :type filename: str | unicode
        :rtype: str | unicode
        """
        return os.path.join(self.local_analyser_data_folder_path, filename)

    def _get_extras_file_path(self, filename):
        """
        :type filename: str | unicode
        :rtype: str | unicode
        """
        return os.path.join(self.migration_session_dir, filename)

    def _get_network_test_filename(self, size):
        """
        :type size: int
        :rtype: str | unicode
        """
        return "_rsync_net_test_file_%d" % size

    def _is_server_source(self, server):
        """
        :type server: object
        :rtype: bool
        """
        return True if isinstance(server, SourceServer) else False

    def _is_server_target(self, server):
        """
        :type server: object
        :rtype: bool
        """
        return True if isinstance(server, TargetServer) else False

    def _is_server_supported(self, server):
        """
        :type server: object
        :rtype: bool
        """
        if self._is_server_source(server):
            return True if isinstance(server, PleskSourceServer) else False
        elif self._is_server_target(server):
            return True if isinstance(server, PleskTargetServer) else False
        else:
            return False

    @staticmethod
    def _is_server_target_connections_supported(target_connections):
        """
        :type target_connections: parallels.core.connections.target_connections.TargetConnections
        :rtype: bool
        """
        return True if isinstance(target_connections, PleskTargetConnections) else False

    @staticmethod
    def _get_random_string(length=1048576):
        """
        :type length: int
        :rtype: str | unicode
        """
        # default = 1MB
        # Random/identical file content does not affect network transfer test
        # Random/identical file content affects HDD read/write estimation test
        # This generator method is used only for network speed estimation, it's faster than "real" random generator
        return ''.join(['1' for n in xrange(length)])

    # local_mgr_server:./migration-session/hosting_analysis_reports/_tmp_file_1
    def _create_temp_file(self, filename, content):
        """
        :type filename: str | unicode
        :type content: str | unicode
        """
        file_full_path = self._get_analyser_session_file_path(filename)
        if os.path.exists(file_full_path) and os.path.isfile(file_full_path):
            os.remove(file_full_path)
        with open_no_inherit(file_full_path, "wb") as fp:
            fp.write(content)

    def _clear_existed_data(self):
        try:
            for root, dirs, files in os.walk(self.local_analyser_data_folder_path):
                for name in files:
                    os.remove(os.path.join(root, name))
        except:
            logger.warning(messages.UNABLE_TO_CLEAN_UP_FOLDER % root)

    def _get_python_bin(self, server):
        """
        :type server: parallels.core.connections.source_server.SourceServer | parallels.core.connections.target_servers.TargetServer
        :rtype: str | unicode
        """
        python_bin = server.python_bin
        if python_bin is not None:
            logger.debug(messages.DETECTED_PYTHON_BIN.format(path=python_bin))
        return python_bin

    @staticmethod
    def _prepare_remote_dst_server_for_network_analysis(destination_runner, remote_dst_net_test_file):
        """
        :type destination_runner: parallels.core.runners.base.BaseRunner
        :type remote_dst_net_test_file: str | unicode
        """
        logger.debug(messages.PREPARING_TARGET_SERVER_FOR_NETWORK_ANALYSIS)
        try:
            destination_runner.remove_file(remote_dst_net_test_file)
            logger.debug(messages.TARGET_SERVER_IS_READY_FOR_NETWORK_ANALYSIS)
        except:
            logger.debug(messages.TARGET_SERVER_IS_READY_FOR_ANALYSIS)

    def _get_network_speed_estimations_via_rsync_linux_nodes(
            self, key_pathname, source_runner, source_server, source_node_test_transfer_file_full_path,
            remote_dst_net_test_file, destination_server, size
    ):
        """
        :type key_pathname: str | unicode
        :type source_runner: parallels.core.runners.base.BaseRunner
        :type source_server: parallels.plesk.source.plesk.server.PleskSourceServer
        :type source_node_test_transfer_file_full_path: str | unicode
        :type remote_dst_net_test_file: str | unicode
        :type destination_server: parallels.plesk.connections.target_server.PleskTargetServer
        :type size: int
        :rtype: list[float]
        """
        net_results = []
        rsync_cmd, args = create_rsync_command(
            key_pathname=key_pathname, source_runner=source_runner, source_user=source_server.user(),
            source_ip=source_server.ip(), source_filepath=source_node_test_transfer_file_full_path,
            target_filepath=remote_dst_net_test_file, rsync_additional_args=['-v'],
            source_port=source_server.settings().ssh_auth.port
        )
        for _ in xrange(self.network_estimation_attempts_for_steps):
            with destination_server.runner() as destination_runner:
                assert isinstance(destination_runner, BaseRunner)
                self._prepare_remote_dst_server_for_network_analysis(destination_runner, remote_dst_net_test_file)
                command_res = destination_runner.execute_command(
                    rsync_cmd, args, ExecutionOptions(ignore_exit_code=True)
                )
            if command_res.exit_code == 0:
                net_results.append(self._get_network_speed_from_rsync_report(command_res.stdout))
            else:
                logger.debug(messages.ERROR_WHILE_TRANSFERRING_TEST_FILE % size)
                # If we could not copy the file across the network 5 times, we believe that the network is unstable and
                # the net speed is equal to 0
                return [0]
        return net_results

    def _get_network_speed_estimations_via_rsync_windows_nodes(
        self, destination_server, remote_dst_net_test_file, source_server,
        source_node_test_transfer_file_full_path, size
    ):
        """
        :type destination_server: parallels.plesk.connections.target_server.PleskTargetServer
        :type remote_dst_net_test_file: str | unicode
        :type source_server: parallels.plesk.source.plesk.server.PleskSourceServer
        :type source_node_test_transfer_file_full_path: str | unicode
        :type size: int
        :rtype: list[float]
        """
        net_results = []
        for _ in xrange(self.network_estimation_attempts_for_steps):
            with destination_server.runner() as destination_runner:
                self._prepare_remote_dst_server_for_network_analysis(destination_runner, remote_dst_net_test_file)
                global_context = Registry.get_instance().get_context()
                rsync = global_context.get_rsync(source_server, destination_server, source_server.ip)
                try:
                    stdout = rsync.sync(
                        source_path='migrator/%s' % source_node_test_transfer_file_full_path,
                        target_path=windows_utils.convert_path_to_cygwin(remote_dst_net_test_file),
                        exclude=None, rsync_additional_args=['-v']
                    )
                    net_results.append(self._get_network_speed_from_rsync_report(stdout))
                except:
                    logger.debug(messages.ERROR_OCCURRED_WHILE_TRANSFERRING_TEST_FILE % size)
                    # If we could not copy the file across the network 5 times, we believe that the network is unstable and
                    # the net speed is equal to 0
                    return [0]
        return net_results

    def _get_network_speed_analysis(self, destination_server, source_server, key_pathname=None):
        """
        :type destination_server: parallels.plesk.connections.target_server.PleskTargetServer
        :type source_server: parallels.plesk.source.plesk.server.PleskSourceServer
        :type key_pathname: str | unicode | None
        """
        logger.info(messages.ANALYSING_NETWORK_SPEED % source_server.node_id)
        max_estimated_speed = 0
        cur_estimated_speed = 0
        with source_server.runner() as source_runner:
            for size in self.sizes:
                logger.debug(messages.DEBUG_CALCULATING_NET_SPEED, size, max_estimated_speed, cur_estimated_speed)
                # If new speed value is more than 15% bigger - re-analyse with bigger test-file
                if cur_estimated_speed >= 1.15 * max_estimated_speed:
                    filename = self._get_network_test_filename(size)
                    # remote_src_server:/tmp/_rsync_net_test_file_12
                    source_node_test_transfer_file_full_path = source_server.get_session_file_path(filename)
                    # remote_dst_server:/tmp/_rsync_net_test_file_12
                    remote_dst_net_test_file = destination_server.get_session_file_path(filename)
                    if not source_runner.file_exists(source_node_test_transfer_file_full_path):
                        source_runner.upload_file(
                            # local_mgr_server:/opt/panel-migrator/thirdparties/python/lib/python2.7/site-packages/parallels/hosting_analyser/extras/_rsync_net_test_file_12
                            self._get_extras_file_path(self._get_network_test_filename(size)),
                            # remote_src_server:/tmp/_rsync_net_test_file_20
                            source_node_test_transfer_file_full_path
                        )

                    if destination_server.is_windows():
                        net_results = self._get_network_speed_estimations_via_rsync_windows_nodes(
                            destination_server, remote_dst_net_test_file, source_server,
                            source_node_test_transfer_file_full_path, size
                        )
                    else:
                        net_results = self._get_network_speed_estimations_via_rsync_linux_nodes(
                            key_pathname, source_runner, source_server, source_node_test_transfer_file_full_path,
                            remote_dst_net_test_file, destination_server, size
                        )

                    average_speed = (max(net_results) + min(net_results)) / 2
                    logger.debug(messages.DEBUG_NET_SPEED_RESULTS, size, str(net_results), average_speed)
                    # Update max speed & save current speed
                    if cur_estimated_speed > 0 and cur_estimated_speed > max_estimated_speed:
                        max_estimated_speed = cur_estimated_speed
                    cur_estimated_speed = average_speed
                # Peak network speed found, last value - max
                elif cur_estimated_speed > max_estimated_speed:
                    max_estimated_speed = cur_estimated_speed
                # Peak network speed found, last value - not max
                else:
                    pass
                logger.debug(messages.DEBUG_NET_SPEED_MAX, size, cur_estimated_speed)
            # save last estimation result if is the biggest
            if cur_estimated_speed > max_estimated_speed:
                max_estimated_speed = cur_estimated_speed
            logger.debug(messages.DEBUG_NET_SPEED_ESTIMATION_FINISHED, max_estimated_speed)
        # fixme: [workaround] parallels.core.run_command.SSHRunner.upload_file_content() - Not Implemented
        # local_mgr_server:./migration-session/hosting_analysis_reports/_rsync_network_report
        self._create_temp_file(self.network_analysis_report_filename, str(max_estimated_speed))
        source_runner.upload_file(
            # local_mgr_server:./migration-session/hosting_analysis_reports/_rsync_network_report
            self._get_analyser_session_file_path(self.network_analysis_report_filename),
            # remote_src_server:/tmp/_rsync_network_report
            source_server.get_session_file_path(self.network_analysis_report_filename)
        )
        logger.info(u"Completed")

    def _analyse_network_speed_via_rsync(self, destination_server, source_servers, ssh_key_pool):
        """
        :type destination_server: parallels.plesk.connections.target_server.PleskTargetServer
        :type source_servers: dict[str | unicode, parallels.plesk.source.plesk.server.PleskSourceServer]
        :type ssh_key_pool: parallels.core.utils.ssh_key_pool.SSHKeyPool
        """
        # Linux -> Linux
        if not destination_server.is_windows():
            for source_server in source_servers.values():
                if source_server.is_windows():
                    logger.debug(messages.NETWORK_SPEED_ANALYSIS_WAS_SKIPPED)
                    continue
                key_pathname = ssh_key_pool.get(source_server, destination_server).key_pathname
                self._get_network_speed_analysis(destination_server, source_server, key_pathname)
        # Windows -> Windows
        else:
            for source_server in source_servers.values():
                if not source_server.is_windows():
                    logger.debug(messages.NETWORK_SPEED_ANALYSIS_WAS_SKIPPED)
                    continue
                self._get_network_speed_analysis(destination_server, source_server)

    @staticmethod
    def _get_network_speed_from_rsync_report(text):
        """
        :type text: str | unicode
        :rtype: int | float
        """
        result = 0
        try:
            text_elements = text.split()
            if len(text_elements) > 12 and text_elements[5] == 'sent' and text_elements[10] == 'bytes':
                result = float(text_elements[11].replace(',', ''))
        except:
            logger.debug(messages.ERROR_WHILE_READING_RSYNC_OUTPUT)
        return result

    def _get_update_source_nodes_analysis(self, connections, ssh_key_pool):
        """
        :type connections: parallels.plesk.source.plesk.connections.source_connections.PleskMigratorConnections
        :type ssh_key_pool: parallels.core.utils.ssh_key_pool.SSHKeyPool
        """
        # Re-estimate network speed
        target_server = connections.target.plesk_server
        source_servers = {}
        for source_server_id in connections.get_source_servers().keys():
            source_servers[source_server_id] = connections.get_source_node(source_server_id)
        self._analyse_network_speed_via_rsync(target_server, source_servers, ssh_key_pool)
        # Re-launch 'hosting_analyser_agent'
        for server in source_servers.values():
            try:
                with server.runner() as runner:
                    assert isinstance(runner, BaseRunner)
                    agent_dst_full_path = server.get_session_file_path(self.agent_filename)
                    if not runner.file_exists(agent_dst_full_path):
                        runner.upload_file(self.local_agent_full_path, agent_dst_full_path)
                    src = os.path.join(
                        server.vhosts_dir, self.remote_agent_report_folder_name, self.remote_xml_report_filename
                    )
                    dst = self._get_analyser_session_file_path('update_%s.xml' % server.node_id)
                    logger.info(messages.LOG_ANALYSING_SOURCE_NODE, server.node_id)
                    python_bin = self._get_python_bin(server)
                    if python_bin is None:
                        logger.warning(
                            messages.HOSTING_ANALYZER_NO_PYTHON_AVAILABLE.format(server=server.description())
                        )
                        return
                    exit_code = runner.execute_command(
                        '{python_bin} {agent_path} -f -d {vhosts_dir} -n {report_file} -c', dict(
                            python_bin=python_bin,
                            agent_path=agent_dst_full_path,
                            vhosts_dir=server.vhosts_dir,
                            report_file=server.get_session_file_path(self.network_analysis_report_filename)
                        ),
                        ExecutionOptions(ignore_exit_code=True)
                    ).exit_code
                    # pick up XML report
                    if exit_code == 0:
                        with result_file_lock:
                            runner.get_file(src, dst)
                        logger.info(u"Completed")
                    else:
                        logger.warning(u"Failed")
            except Exception as e:
                logger.warning(messages.INTERNAL_ERROR_UNABLE_TO_REANALYSE_SOURCE)
                logger.debug(u"Exception: ", exc_info=e)

    def _get_node_analysis(self, server):
        """
        :type server: parallels.plesk.source.plesk.server.PleskSourceServer | parallels.plesk.connections.target_server.PleskTargetServer
        """
        try:
            with server.runner() as runner:
                assert isinstance(runner, BaseRunner)
                # remote_server:/tmp/hosting_analyser_agent.py
                agent_dst_full_path = server.get_session_file_path(self.agent_filename)
                runner.upload_file(self.local_agent_full_path, agent_dst_full_path)
                python_bin = self._get_python_bin(server)

                command = '{python_bin} {agent_path} -f'
                args = dict(
                    python_bin=python_bin, agent_path=agent_dst_full_path
                )
                # source
                if self._is_server_source(server):
                    # remote_src_server:/var/www/vhosts
                    command += ' -d {vhosts_dir} -n {report_filename}'
                    args['vhosts_dir'] = server.vhosts_dir
                    args['report_filename'] = server.get_session_file_path(self.network_analysis_report_filename)
                    # remote_src_server:/var/www/vhosts/hosting_analysis_report/summary.xml
                    src = os.path.join(
                        server.vhosts_dir, self.remote_agent_report_folder_name, self.remote_xml_report_filename
                    )
                    # local_mgr_server:./migration-session/hosting_analysis_reports/pfu5.xml
                    dst = self._get_analyser_session_file_path('%s.xml' % server.node_id)
                    logger.info(u"Analysing source node '%s'", server.node_id)
                # destination
                else:
                    command += ' -c'
                    # remote_dst_server:/tmp/hosting_analysis_report/summary.xml
                    src = os.path.join(
                        server.get_session_dir_path(), self.remote_agent_report_folder_name, self.remote_xml_report_filename
                    )
                    # local_mgr_server:./migration-session/hosting_analysis_reports/destination-plesk.xml
                    dst = self._get_analyser_session_file_path(self.local_dst_plesk_server_report_filename)
                    logger.info(u"Analysing target node")
                # SRV: 'python' '/tmp/hosting_analyser_agent.py' '-d' '/var/www/vhosts' '-n' '/tmp/_rsync_network_report'
                # DST: 'python' '/tmp/hosting_analyser_agent.py' '-c'
                if python_bin is None:
                    logger.warning(messages.HOSTING_ANALYZER_NO_PYTHON_AVAILABLE.format(server=server.description()))
                    return
                exit_code = runner.execute_command(command, args, ExecutionOptions(ignore_exit_code=True)).exit_code
                # pick up XML report
                if exit_code == 0:
                    runner.get_file(src, dst)
                    logger.info(u"Completed")
                else:
                    logger.warning(u"Failed")
        except Exception as e:
            logger.warning(messages.INTERNAL_ERROR_UNABLE_TO_ANALYSE_SN)
            logger.debug(u"Exception: ", exc_info=e)

    # Check nodes (remote_sources, remote_destination) types
    def is_nodes_type_supported(self, connections):
        """
        :type connections: parallels.core.connections.connections.Connections
        """
        target_connections = connections.target
        source_servers = {}
        for source_server_id in connections.get_source_servers().keys():
            source_servers[source_server_id] = connections.get_source_node(source_server_id)
        try:
            if not self._is_server_target_connections_supported(target_connections):
                return False
            for source_server in source_servers.values():
                if not self._is_server_supported(source_server):
                    return False
            return True
        except Exception as e:
            logger.debug(u"Exception: ", exc_info=e)
            return False

    def get_webspace_actual_transfer_strategy(self, subscription):
        """
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: parallels.plesk.hosting_analyser.hosting_analyser.TransferStrategy
        """
        global_context = Registry.get_instance().get_context()
        if self.is_nodes_type_supported(global_context.conn):
            logger.debug(messages.DETERMINING_SUITABLE_CONTENT_TRANSFER_STRATEGY)
            source_server = subscription.web_source_server
            if self.has_input_data(source_server.node_id):
                global_section = ConfigSection(global_context.config, 'GLOBAL')
                use_math_analyse = global_section.get('use-math-analyse', 'true').lower() == 'true'
                transfer_strategy = self.get_updated_webspace_transfer_strategy(source_server.node_id, subscription.name_idn, use_math_analyse)
                if transfer_strategy is not None:
                    logger.debug(transfer_strategy.message)
                    return transfer_strategy
            else:
                logger.info(
                    messages.INFO_FOR_HOSTING_ENVIRONMENT_ANALYSIS_NOT_AVAILABLE)
        return TransferStrategy(subscription.name_idn)

    def analyse(self, connections, ssh_key_pool):
        """
        :type connections: parallels.plesk.source.plesk.connections.source_connections.PleskMigratorConnections
        :type ssh_key_pool: parallels.core.utils.ssh_key_pool.SSHKeyPool
        """
        # clear analyser data folder before new analysis
        self._clear_existed_data()
        # Nodes initialisation
        target_server = connections.target.plesk_server
        source_servers = {}
        for source_server_id in connections.get_source_servers().keys():
            source_servers[source_server_id] = connections.get_source_node(source_server_id)
        # Step 1: Network speed analysis between SOURCES and DESTINATION
        self._analyse_network_speed_via_rsync(target_server, source_servers, ssh_key_pool)
        # Step 2: Source nodes analysing
        for source_server in source_servers.values():  # Handling SOURCE servers
            self._get_node_analysis(source_server)

    def has_input_data(self, source_node_id):
        """
        :type source_node_id: str | unicode
        :rtype: bool
        """
        # local_mgr_server:./migration-session/hosting_analysis_reports/pfu1.xml
        input_xml_file = self._get_analyser_session_file_path("%s.xml" % source_node_id)
        return True if os.path.exists(input_xml_file) and os.path.isfile(input_xml_file) else False

    def get_updated_webspace_transfer_strategy(self, source_node_id, webspace_name, use_math_analyse=True):
        """
        :type source_node_id: str | unicode
        :type webspace_name: str | unicode
        :type use_math_analyse: bool
        :rtype: parallels.plesk.hosting_analyser.hosting_analyser.TransferStrategy
        """
        global_context = Registry.get_instance().get_context()
        global_section = ConfigSection(global_context.config, 'GLOBAL')
        hosting_analysis_ttl = int(global_section.get('hosting-analysis-ttl', 900))
        last_hosting_analysis_time = self._get_last_hosting_analysis_time(global_context)
        # Update environment input data if needed and possible
        if last_hosting_analysis_time is None or (datetime.datetime.now() - last_hosting_analysis_time).seconds > hosting_analysis_ttl:
            result = analyze_lock.acquire(False)
            if result:
                try:
                    logger.debug(messages.UPDATING_RESULTS_OF_SOURCE_NODES_ANALYSIS)
                    self._get_update_source_nodes_analysis(global_context.conn, global_context.ssh_key_pool)
                    self._set_last_hosting_analysis_time(global_context, datetime.datetime.now())
                except Exception:
                    logger.exception()
                    logger.warning(messages.UNABLE_TO_UPDATE_ENVIRONMENT_ESTIMATION_FOR_SOURCE)
                finally:
                    analyze_lock.release()
        transfer_strategy_calculator = TransferStrategyCalculator()
        # local_mgr_server:./migration-session/hosting_analysis_reports/pfu1.xml
        input_xml_file = self._get_analyser_session_file_path("%s.xml" % source_node_id)
        update_input_xml_file = self._get_analyser_session_file_path("update_%s.xml" % source_node_id)
        # Read startup "env" + "content" input data, and update "env" input data if it is available
        try:
            transfer_strategy_calculator.load_data(input_xml_file)
            if os.path.exists(update_input_xml_file) and os.path.isfile(update_input_xml_file):
                transfer_strategy_calculator.load_data(update_input_xml_file)
        except Exception:
            logger.exception()
            logger.warning(messages.UNABLE_TO_LOAD_INPUT_DATA_FOR_STRATEGY_CALCULATION)
            return None
        if use_math_analyse:
            # using math analysis, ~10% of inaccuracy
            return transfer_strategy_calculator.get_math_analyse(webspace_name)
        else:
            # using "matrix of strategies" knowledge base
            return transfer_strategy_calculator.get_known_strategy(webspace_name)

    @staticmethod
    def _get_last_hosting_analysis_time(global_context):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :rtype: datetime.datetime
        """
        return read_yaml(
            global_context.session_files.get_path_to_last_hosting_analysis_time(),
            True
        )

    @staticmethod
    def _set_last_hosting_analysis_time(global_context, last_hosting_analysis_time):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type last_hosting_analysis_time: datetime.datetime
        """
        write_yaml(
            global_context.session_files.get_path_to_last_hosting_analysis_time(),
            last_hosting_analysis_time
        )


class TransferStrategy():
    def __init__(self, webspace_name):
        """
        :type webspace_name: str | unicode
        """
        self.webspace_name = webspace_name
        self.need_compression = True
        # Compression level for rsync, None(default) = 6 level of 9 in rsync
        self.compression_level = None
        # Message that shortly describes the strategy
        self.message = messages.DEFAULT_TRANSFER_STRATEGY


class TransferStrategyCalculator():
    def __init__(self):
        logger.debug(messages.TRANSFER_STRATEGY_CALCULATOR_WAS_CREATED)
        self.webspaces = {}
        self.environment = None

    def _clear_environment_input_data(self):
        logger.debug(messages.INFO_ABOUT_ENVIRONMENT_WAS_CLEARED)
        self.environment = None

    def load_data(self, input_xml_file):
        """
        :type input_xml_file: str | unicode
        """
        logger.debug(messages.TRYING_TO_LOAD_INPUT_XML_FILE % input_xml_file)
        if os.path.exists(input_xml_file) and os.path.isfile(input_xml_file):
            xml = ElementTree.parse(input_xml_file)
        else:
            raise Exception(messages.UNABLE_TO_LOAD_INPUT_XML_DATA % input_xml_file)

        root = xml.getroot()
        if self._is_xml_structure_correct(root):
            logger.debug(messages.DEBUG_PARSING_XML_FILE, input_xml_file)
            for section in root:
                if section.tag == 'files':
                    logger.debug(messages.PARSING_FILES_SECTION_OF_FILE % input_xml_file)
                    for webspace in section:
                        webspace_name = normalize_domain_name(webspace.find('name').text)
                        self.webspaces[webspace_name] = Webspace(
                            name=webspace_name,
                            directory=webspace.find('dir').text,
                            bin_c=webspace.find('bin-files-counter').text,
                            txt_c=webspace.find('txt-files-counter').text,
                            unk_c=webspace.find('unk-files-counter').text,
                            all_c=webspace.find('all-files-counter').text,
                            bin_s=webspace.find('bin-files-size').text,
                            txt_s=webspace.find('txt-files-size').text,
                            unk_s=webspace.find('unk-files-size').text,
                            all_s=webspace.find('all-files-size').text,
                            symlinks_c=webspace.find('symlinks').text,
                            unproc_c=webspace.find('unprocessed').text
                        )
                    logger.debug(messages.LOG_COMPLETED)
                elif (section.tag == 'os'):
                    self._clear_environment_input_data()
                    logger.debug(messages.PARSING_ENVIRONMENT_SECTION_OF_FILE % input_xml_file)
                    self.environment = Environment(
                        cpu_usage=section.find('cpu-usage').text,
                        ram_usage=section.find('ram-usage').text,
                        hdd_write=section.find('hdd-write').text,
                        hdd_read=section.find('hdd-read').text,
                        network=section.find('network').text
                    )
                    logger.debug(messages.LOG_COMPLETED)
            logger.debug(messages.XML_FILE_WAS_SUCCESSFULLY_PARSED % input_xml_file)
        else:
            logger.warning(messages.INPUT_XML_FILE_IS_CORRUPTED % input_xml_file)

    def _is_xml_structure_correct(self, root):
        """
        :type root: xml.etree.ElementTree.Element
        :rtype: bool
        """
        logger.debug(messages.CHECKING_INPUT_XML_STRUCTURE)
        for section in root:
            if section.tag == 'files':
                webspaces = section
                for webspace in webspaces:
                    if webspace.find('name') is None or \
                        webspace.find('dir') is None or \
                        webspace.find('bin-files-counter') is None or \
                        webspace.find('txt-files-counter') is None or \
                        webspace.find('unk-files-counter') is None or \
                        webspace.find('all-files-counter') is None or \
                        webspace.find('bin-files-size') is None or \
                        webspace.find('txt-files-size') is None or \
                        webspace.find('unk-files-size') is None or \
                        webspace.find('all-files-size') is None or \
                        webspace.find('symlinks') is None or \
                        webspace.find('unprocessed') is None:
                            logger.debug(messages.INPUT_XML_IS_CORRUPTED_IN_FILES_SECTION)
                            return False
            elif section.tag == 'os':
                environment = section
                if environment.find('cpu-usage') is None or \
                    environment.find('cpu-usage') is None or \
                    environment.find('ram-usage') is None or \
                    environment.find('hdd-write') is None or \
                    environment.find('hdd-read') is None or \
                    environment.find('network') is None:
                        logger.debug(messages.INPUT_XML_IS_CORRUPTED_IN_OS_SECTION)
                        return False
        logger.debug(messages.DEBUG_INPUT_XML_OK)
        return True

    def get_known_strategy(self, webspace_name):
        """
        :type webspace_name: str | unicode
        :rtype: parallels.plesk.hosting_analyser.hosting_analyser.TransferStrategy
        """
        strategy = TransferStrategy(webspace_name)
        if webspace_name not in self.webspaces.keys():
            logger.info(messages.UNABLE_TO_FIND_INFO_ABOUT_WEBSPACE % webspace_name)
            return strategy
        # (CPU, RAM, HDD, NET). example: (CRITICAL, ANY, ANY, ANY)
        env_estimation = self.environment.get_estimation()
        most_effective_strategy = self._select_strategy(env_estimation)
        logger.debug(messages.FOLLOWING_CONTENT_TRANSFER_STRATEGY_WAS_SELECTED % str(most_effective_strategy))
        if most_effective_strategy['compression'] is True:
            strategy.need_compression = True
        elif most_effective_strategy['compression'] is False:
            strategy.need_compression = False
        else:
            logger.debug(messages.UNABLE_TO_DETERMINE_PROPER_STRATEGY)
        strategy.message = most_effective_strategy['msg']
        return strategy

    def get_math_analyse(self, webspace_name):
        """
        :type webspace_name: str | unicode
        :rtype: parallels.plesk.hosting_analyser.hosting_analyser.TransferStrategy
        """
        strategy = TransferStrategy(webspace_name)
        # check webspace & environment analysis values before calculation
        if normalize_domain_name(webspace_name) not in self.webspaces.keys():
            logger.warning(messages.UNABLE_TO_FIND_INFO_ABOUT_WEBSPACE % webspace_name)
            return strategy
        if not self.environment.is_cpu_analysed():
            logger.warning(messages.UNABLE_TO_DETECT_CPU_USAGE)
            return strategy
        if not self.environment.is_network_analysed() or self.environment.network <= 0:
            logger.warning(messages.UNABLE_TO_DETECT_NETWORK_SPEED)
            return strategy

        try:
            (rsync_acceleration, compression_level) = self.estimate_rsync_acceleration(
                self.webspaces[normalize_domain_name(webspace_name)],
                self.environment
            )
            if rsync_acceleration is None:
                logger.info(messages.NOT_ALL_ENVIRONMENT_PARAMETERS_WERE_DETECTED)
                return strategy
            strategy.message = messages.FASTER_CALCULATION % (compression_level, str(round(rsync_acceleration, 2)))
            if rsync_acceleration > 1:
                logger.debug(messages.DATA_COMPRESSION_IS_RECOMMENDED)
                strategy.need_compression = True
                strategy.compression_level = compression_level
            else:
                logger.debug(messages.DATA_COMPRESSION_IS_NOT_RECOMMENDED)
                strategy.need_compression = False
        except Exception as e:
            logger.warning(messages.UNABLE_TO_ESTIMATE_RSYNC_ACCELERATION)
            logger.debug(u"Exception: ", exc_info=e)
        return strategy

    def estimate_rsync_acceleration(self, webspace, environment):
        """
        :type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
        :type environment: parallels.plesk.hosting_analyser.hosting_analyser.Environment
        :rtype: tuple(float , int) | tuple(float, None) | tuple(None, None)
        """
        rsync_acceleration = None
        compression_level = None
        context = Registry.get_instance().get_context()
        coefficients = get_coefficients_for_windows() if context.conn.target.is_windows else get_coefficients_for_unix()
        # (k/bin/, k/txt/, k/unk/, k/hdd/, k1/compr/, k2/compr/, compression_level)
        for (k_bin, k_txt, k_unk, k_hdd, k_compr_1, k_compr_2, _compression_level) in coefficients:
            logger.debug(messages.CALCULATING_RSYNC_ACCELERATION_FOR_COMPRESSION_LEVEL % _compression_level)
            var1 = k_bin*webspace.bin_size + k_txt*webspace.txt_size + k_unk*webspace.unk_size
            var2 = (
                environment.get_webspace_hdd_reading_rought_time(webspace, k_hdd) +
                environment.get_webspace_file_compress_rough_time(webspace, k_compr_1, k_compr_2)
            ) * environment.network
            var3 = webspace.all_size + environment.get_webspace_hdd_reading_rought_time(webspace, k_hdd) * environment.network
            if var1 + var2 <= 0:
                continue
            _rsync_acceleration = var3 / (var1 + var2)
            try:
                # debug more information, not necessary information which could return ZeroDivision exceptions sometimes
                debug_var_hdd_read = environment.get_webspace_hdd_reading_rought_time(webspace, k_hdd)
                debug_var_file_compr = environment.get_webspace_file_compress_rough_time(webspace, k_compr_1, k_compr_2)
                debug_var_send_w_compr = environment.get_webspace_transfer_time_with_compression(webspace, k_bin, k_txt, k_unk)
                debug_var_send_wo_compr = environment.get_webspace_transfer_time_without_compression(webspace)
                logger.debug(
                    messages.DEBUG_NET_SPEED,
                    str(get_bytes_with_suffix(environment.network))
                )
                logger.debug(
                    messages.ESTIMATED_TIME_FOR_READING_FILES_FROM_HDD,
                    get_time_duration_for_human(debug_var_hdd_read)
                )
                logger.debug(
                    messages.ESTIMATED_TIME_FOR_COMPRESSING_FILES,
                    get_time_duration_for_human(debug_var_file_compr)
                )
                logger.debug(
                    messages.ESTIMATED_TIME_FOR_SENDING_FILES_WITH_COMPRESSION,
                    get_time_duration_for_human(debug_var_send_w_compr)
                )
                logger.debug(
                    messages.ESTIMATED_TIME_FOR_SENDING_FILES_NO_COMPRESSION,
                    get_time_duration_for_human(debug_var_send_wo_compr)
                )
                logger.debug(messages.DEBUG_RSYNC_ACC_COEF, _rsync_acceleration)
                logger.debug(
                    messages.DEBUG_EST_TIME_COMPRESSION_NO_CACHE,
                    get_time_duration_for_human(debug_var_hdd_read + debug_var_send_w_compr + debug_var_file_compr)
                )
                logger.debug(
                    messages.DEBUG_EST_TIME_NO_COMPRESSION_NO_CACHE,
                    get_time_duration_for_human(debug_var_send_wo_compr + debug_var_hdd_read)
                )
                logger.debug(
                    messages.DEBUG_EST_TIME_COMPRESSION_CACHE %
                    get_time_duration_for_human(debug_var_send_w_compr + debug_var_file_compr)
                )
                logger.debug(
                    messages.DEBUG_EST_TIME_NO_COMPRESSION_CACHE %
                    get_time_duration_for_human(debug_var_send_wo_compr)
                )
            except:
                logger.debug(messages.UNABLE_TO_LOG_DEBUG_INFO_ABOUT_RSYNC_CALCULATION)
            if rsync_acceleration is None or rsync_acceleration < _rsync_acceleration:
                rsync_acceleration = _rsync_acceleration
                compression_level = _compression_level

        return rsync_acceleration, compression_level

    def _select_strategy(self, env_estimation):
        """
        :type env_estimation: tuple(frozenset, frozenset, frozenset, frozenset)
        :rtype: dict
        """
        context = Registry.get_instance().get_context()
        strategies = get_windows_strategies() if context.conn.target.is_windows else get_unix_strategies()
        logger.debug(messages.DEBUG_SELECTING_STRATEGY)
        for estimation_pattern, known_strategy in strategies.items():
            if self._is_suitable_estimation(env_estimation, estimation_pattern):
                logger.debug(messages.DEBUG_ENV_EST_FITS, str(env_estimation), str(estimation_pattern))
                return known_strategy
            else:
                logger.debug(messages.DEBUG_ENV_EST_DOES_NOT_FIT, str(env_estimation), str(estimation_pattern))

    # example:
    # env_estimation (source) = (CRITICAL, ANY, ANY, ANY)
    # pattern_estimation (destination) = (ANY, ANY, ANY, ANY)
    @staticmethod
    def _is_suitable_estimation(env_estimation, pattern_estimation):
        """
        :type env_estimation: tuple(frozenset, frozenset, frozenset, frozenset)
        :type pattern_estimation: tuple(frozenset, frozenset, frozenset, frozenset)
        :rtype: bool
        """
        for index in range(0, len(env_estimation)):
            if not env_estimation[index].issubset(pattern_estimation[index]):
                return False
        return True


class Webspace():
    def __init__(self, name, directory, bin_c, txt_c, unk_c, all_c, bin_s, txt_s, unk_s, all_s, symlinks_c, unproc_c):
        """
        :type name: str | unicode
        :type directory: str | unicode
        :type bin_c: int
        :type txt_c: int
        :type unk_c: int
        :type all_c: int
        :type bin_s: int
        :type txt_s: int
        :type unk_s: int
        :type all_s: int
        :type symlinks_c: int
        :type unproc_c: int
        """
        self.name = str(name)
        self.directory = str(directory)
        self.bin_counter = float(bin_c)
        self.txt_counter = float(txt_c)
        self.unk_counter = float(unk_c)
        self.all_counter = float(all_c)
        self.bin_size = float(bin_s)
        self.txt_size = float(txt_s)
        self.unk_size = float(unk_s)
        self.all_size = float(all_s)
        self.symlinks_counter = int(symlinks_c)
        self.unprocessed_counter = int(unproc_c)

    def is_bin_by_size(self):
        """
        :rtype: bool
        """
        return True if self.get_bin_files_percent_by_size() >= self.get_txt_files_percent_by_size() else False

    def is_txt_by_size(self):
        """
        :rtype: bool
        """
        return True if self.get_txt_files_percent_by_size() >= self.get_bin_files_percent_by_size() else False

    # 0%, 5%, 20%, 33%, 50%, 51%, 75%, 80%, 100%
    def get_bin_files_percent_by_size(self):
        """
        :rtype: int
        """
        return self.bin_size * 100 / self.all_size if self.all_size != 0 else 100

    # 0%, 5%, 20%, 33%, 50%, 51%, 75%, 80%, 100%
    def get_txt_files_percent_by_size(self):
        """
        :rtype: int
        """
        return self.txt_size * 100 / self.all_size if self.all_size != 0 else 100

    # 0%, 5%, 20%, 33%, 50%, 51%, 75%, 80%, 100%
    def get_unk_files_percent_by_size(self):
        """
        :rtype: int
        """
        return self.unk_size * 100 / self.all_size if self.all_size != 0 else 100


class Environment():
    def __init__(self, cpu_usage, ram_usage, hdd_write, hdd_read, network):
        """
        :type cpu_usage: float
        :type ram_usage: float
        :type hdd_write: float
        :type hdd_read: float
        :type network: float
        """
        self.unknown = -1  # Unknown result special value
        self.cpu_usage = float(cpu_usage)  # Percent of CPU usage (%)
        self.ram_usage = float(ram_usage)  # Percent of RAM usage (%)
        self.hdd_write = float(hdd_write)  # HDD write speed (bytes/sec)
        self.hdd_read = float(hdd_read)  # HDD read speed (bytes/sec)
        self.network = float(network)  # Network upload speed (bytes/sec)
        logger.debug(messages.DEBUG_ENV_ANALYSIS)
        logger.debug(messages.DEBUG_CPU_USAGE, self.cpu_usage)
        logger.debug(messages.DEBUG_RAM_USAGE, self.ram_usage)
        logger.debug(messages.DEBUG_HDD_WRITE, self.hdd_write)
        logger.debug(messages.DEBUG_HDD_READ, self.hdd_read)
        logger.debug(messages.DEBUG_NET_UPLOAD_SPEED, self.network)

    def is_cpu_analysed(self):
        """
        :rtype: bool
        """
        return True if self.cpu_usage != self.unknown else False

    def is_ram_analysed(self):
        """
        :rtype: bool
        """
        return True if self.ram_usage != self.unknown else False

    def is_hdd_analysed(self):
        """
        :rtype: bool
        """
        return True if self.hdd_write != self.unknown and self.hdd_read != self.unknown else False

    def is_network_analysed(self):
        """
        :rtype: bool
        """
        return True if self.network != self.unknown else False

    # TODO implement more complicated logic here
    def get_estimation(self):
        """
        :rtype: tuple(frozenset, frozenset, frozenset, frozenset)
        """
        # Order: (CPU, RAM, HDD, NET)
        cpu = UNKNOWN
        if self.is_cpu_analysed():
            if self.cpu_usage > 80:
                cpu = CRITICAL
            else:
                cpu = OK

        ram = UNKNOWN
        if self.is_ram_analysed():
            if self.ram_usage > 90:
                ram = CRITICAL
            else:
                ram = OK

        hdd = UNKNOWN
        if self.is_hdd_analysed():
            # < 2 MB/s
            if self.hdd_write < 2097152 or self.hdd_read < 2097152:
                hdd = CRITICAL
            else:
                hdd = OK

        net = UNKNOWN
        if self.is_network_analysed():
            # < 100 KBps ( set 200-300 KBps here? )
            if self.network < 102400:
                net = CRITICAL
            else:
                net = OK

        env_estimation = (cpu, ram, hdd, net)
        logger.debug(messages.DEBUG_ENV_ESTIMATION, str(env_estimation))
        return env_estimation

    def get_webspace_transfer_time_with_compression(self, webspace, k_bin, k_txt, k_unk):
        """
        :type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
        :type k_bin: float
        :type k_txt: float
        :type k_unk: float
        :rtype: float
        """
        if not self.is_network_analysed() or self.network <= 0:
            return self.unknown
        return (k_bin*webspace.bin_size + k_txt*webspace.txt_size + k_unk*webspace.unk_size) / self.network

    def get_webspace_transfer_time_without_compression(self, webspace):
        """
        :type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
        :rtype: float | int
        """
        if not self.is_network_analysed() or self.network <= 0:
            return self.unknown
        return webspace.all_size / self.network

    # all_files_counter is much more influencing for the time, than all_file_size
    # so we just ignore all_file_size expecting that we are handling average webspaces:
    # ~4000 files, 150-200Mb, 50%/50% - bin/txt
    # Also, we do not take into account HDD utilisation yet,
    # it's not mandatory because of fixed maximal HDD speed (~150 Mbps for SATA)
    # Coefficient was got for SATA HDD in mostly idle mode
    # we calculate ratio between transfer time with compression and without compression,
    # so innacuracity of time of HDD reading will be reduced in final result (numerator & denominator of the formula has it)
    @staticmethod
    def get_webspace_hdd_reading_rought_time(webspace, k_hdd):
        """
        :type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
        :type k_hdd: float
        :rtype: float
        """
        return k_hdd * webspace.all_counter

    # Depends on: all_files_size [bytes], cpu_usage [percent]
    # This relation formula was got for the following hardware configuration:
    # - OS: Windows 7 SP1 (x64)
    # - CPU AMD 6 Cores 3.50GHz each
    # - RAM 16 Gb
    # - HDD Seagate ST500DM002-1BD14 SCSI
    # CPU utilisation tests were produced for cases when 1-6 CPU cores were busy
    def get_webspace_file_compress_rough_time(self, webspace, k_compr_1, k_compr_2, compression = 6):
        """
        :type webspace: parallels.plesk.hosting_analyser.hosting_analyser.Webspace
        :type k_compr_1: float
        :type k_compr_2: float
        :type compression: int
        :rtype: float | int
        """
        # fixme: this formula was got for default zlib compression level - 6 and could affect the result for another compression level
        if not self.is_cpu_analysed():
            return self.unknown
        cpu_usage = self.cpu_usage
        if cpu_usage >= 100:
            cpu_usage = 99
        if compression == 6:
            size_in_megabytes = webspace.all_size / 1048576
            return size_in_megabytes*(k_compr_1+(k_compr_2/(100-cpu_usage)))
        return self.unknown
