# Copyright 2020 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""Hook module."""

import os
import threading
from importlib import import_module

from mindinsight.conf import settings
from mindinsight.utils.exceptions import FileSystemPermissionError


class BaseHook:
    """Base hook class."""

    def register_secure_domains(self):
        """Hook function to register secure domains."""
        return []

    def register_startup_arguments(self, parser):
        """
        Hook function to register startup arguments.

        Args:
            parser (ArgumentParser): specify parser to which arguments are added.
        """

    def on_startup(self, logger):
        """
        Hook function to on startup.

        Args:
            logger (Logger): script logger of start command.
        """

    def on_shutdown(self, logger):
        """
        Hook function to on shutdown.

        Args:
            logger (Logger): script logger of stop command.
        """

    def on_init(self):
        """Hook function to on init."""


class HookUtils:
    """
    Lock utilities.

    Examples:
        >>> from mindinsight.utils.hook import HookUtils
        >>> for hook in HookUtils.instance().hooks():
        >>>     domains = hook.register_secure_domains()
        >>>     hook.register_startup_arguments(parser)
        >>>     hook.on_startup(logger)
        >>>     hook.on_shutdown(logger)
        >>>     hook.on_init()
    """

    _lock = threading.Lock()
    _instance = None

    def __new__(cls, *args, **kwargs):
        """Built-in __new__ function."""
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls, *args, **kwargs)
                    cls._instance.discover()
        return cls._instance

    def discover(self):
        """Discover hook instances."""
        self.__hooks = []
        mindinsight_path = os.path.join(__file__, os.pardir, os.pardir)
        hook_path = os.path.realpath(os.path.join(mindinsight_path, 'common/hook'))
        files = os.listdir(hook_path)
        files.sort()
        for file in files:
            if file.startswith('_') or not file.endswith('.py'):
                continue
            hook_name = file[:-len('.py')]
            hook_module = import_module('mindinsight.common.hook.{}'.format(hook_name))
            hook_cls = getattr(hook_module, 'Hook', None)
            if hook_cls is not None and issubclass(hook_cls, BaseHook):
                self.__hooks.append(hook_cls())

    def hooks(self):
        """
        Return list of hook instances.

        Returns:
            list, list of hook instances.
        """
        return self.__hooks

    @classmethod
    def instance(cls):
        """
        Produce singleton instance of HookUtils.

        Returns:
            HookUtils, singleton instance of HookUtils.
        """
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance


def init(workspace='', config='', **kwargs):
    """
    Init MindInsight context.

    Args:
        workspace (str): specify mindinsight workspace, default is ''.
        config (str): specify mindinsight config file, default is ''.

    Raises:
        FileSystemPermissionError, if workspace is not allowed to access or available.
    """
    permissions = os.R_OK | os.W_OK | os.X_OK

    # set umask to 0o077
    os.umask(permissions << 3 | permissions)

    # assign argument values into environment
    if workspace:
        kwargs['workspace'] = workspace

    if config:
        kwargs['config'] = config

    for key, value in kwargs.items():
        variable = 'MINDINSIGHT_{}'.format(key.upper())
        os.environ[variable] = str(value)

    settings.refresh()

    if os.path.exists(settings.WORKSPACE):
        if not os.access(settings.WORKSPACE, permissions):
            raise FileSystemPermissionError('Workspace {} not allowed to access'.format(workspace))
    else:
        try:
            mode = permissions << 6
            os.makedirs(settings.WORKSPACE, mode=mode, exist_ok=True)
        except OSError:
            # race condition or priority problem
            raise FileSystemPermissionError('Workspace {} not available'.format(workspace))

    for hook in HookUtils.instance().hooks():
        hook.on_init()
