from ...configurator import Config
from ...common import mask_password, hide_big_content
from .types import Types
from .handlers import Console, File
from .defaults import Defaults

from dataclasses import dataclass
from datetime import datetime
from typing import Sequence, Any



@dataclass(frozen=True)
class _Const:
    buffer_limit: int = 5242880  # 5MB (5 * 1024 * 1024)
    pass



_const = _Const()



class Logger:
    __slots__ = ('__types', '__outputs', '__data', '__indent')

    def __init__(
        self,
        types: Sequence[str]|str = None,
        outputs:Sequence[Console|File] = None
    ):
        """
        Create new logger.

        :param types: logging types
        :param outputs: logging outputs
        """

        if types is None:
            try: types = Config.logging.types
            except: pass

        if outputs is None:
            outputs = (
                Console(),
                File()
            )

        self.__types = Types.types2tuple(types=types)
        self.__outputs = outputs
        self.__data = ''
        self.__indent = 0
        pass


    @property
    def types(self) -> tuple[str,...]:
        return self.__types


    @property
    def data(self) -> str:
        return self.__data


    @property
    def indent(self) -> int:
        return self.__indent


    @indent.setter
    def indent(self, indent: int = None):
        if indent is None:
            indent = 0
        self.__indent = indent
        pass


    @property
    def indentation(self) -> str:
        return ' ' * 4 * self.__indent


    @property
    def is_default(self) -> bool:
        # is default logger
        res = False

        try: res = bool(Defaults.logger is self)
        except: pass

        return res


    def log(
        self,
        obj: Any,
        prefix: str = None,
        type_: str = None,
        date_: bool = None,
        indent: bool = None,
        end_line: int = None
    ):
        """
        Log object.

        :param obj: 'log',
        :param prefix:
            [
                'paramName=',
                'className.',
                'otherReference.'
            ]
        :param type_:
            [
                types.trace,
                types.debug,
                types.info,
                types.warning,
                types.error
            ]
        :param date_: True / False,
        :param indent: True / False,
        :param end_line: 0..X lines,
        :return:
        """

        if prefix is None:
            prefix = ''

        if type_ is None:
            type_ = Types.info

        if date_ is None:
            date_ = True

        if indent is None:
            indent = True

        if end_line is None:
            end_line = 1

        # eval logging type -----
        if type_ in self.__types:
            datetime_ = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f') if date_ else ' ' * 26  # len('yyyy-mm-dd hh:mi:ss.ffffff')
            indentation = self.indentation if indent else ''
            endl = '\n' * end_line

            obj = mask_password(name=prefix,value=obj)

            if Types.trace not in self.__types:
                obj = hide_big_content(obj)

            # add log to queue -----
            self.__data = f'{self.__data}{datetime_}  {indentation}{prefix}{obj}{endl}'

            # fix: memory leak protection for daemon apps execution -> auto-flush
            if len(self.__data) > _const.buffer_limit:
                self.flush()

        pass


    def debug(
        self,
        obj: Any,
        prefix: str = None,
        date_: bool = None,
        indent: bool = None,
        end_line: int = None
    ):
        """
        Log interface for Debug method.

        :param obj: 'log',
        :param prefix:
            [
                'paramName=',
                'className.',
                'otherReference.'
            ]
        :param date_: True / False,
        :param indent: True / False,
        :param end_line: 0..X lines,
        :return:
        """

        self.log(
            obj=obj,
            prefix=prefix,
            type_=Types.debug,
            date_=date_,
            indent=indent,
            end_line=end_line
        )
        pass


    def info(
        self,
        obj: Any,
        prefix: str = None,
        date_: bool = None,
        indent: bool = None,
        end_line: int = None
    ):
        """
        Log interface for Info method.

        :param obj: 'log',
        :param prefix:
            [
                'paramName=',
                'className.',
                'otherReference.'
            ]
        :param date_: True / False,
        :param indent: True / False,
        :param end_line: 0..X lines,
        :return:
        """

        self.log(
            obj=obj,
            prefix=prefix,
            type_=Types.info,
            date_=date_,
            indent=indent,
            end_line=end_line
        )
        pass


    def warning(
        self,
        obj: Any,
        prefix: str = None,
        date_: bool = None,
        indent: bool = None,
        end_line: int = None
    ):
        """
        Log interface for Warning method.

        :param obj: 'log',
        :param prefix:
            [
                'paramName=',
                'className.',
                'otherReference.'
            ]
        :param date_: True / False,
        :param indent: True / False,
        :param end_line: 0..X lines,
        :return:
        """

        self.log(
            obj=obj,
            prefix=prefix,
            type_=Types.warning,
            date_=date_,
            indent=indent,
            end_line=end_line
        )
        pass


    def error(
        self,
        obj: Any,
        prefix: str = None,
        date_: bool = None,
        indent: bool = None,
        end_line: int = None
    ):
        """
        Log interface for Debug method.

        :param obj: 'log',
        :param prefix:
            [
                'paramName=',
                'className.',
                'otherReference.'
            ]
        :param date_: True / False,
        :param indent: True / False,
        :param end_line: 0..X lines,
        :return:
        """

        self.log(
            obj=obj,
            prefix=prefix,
            type_=Types.error,
            date_=date_,
            indent=indent,
            end_line=end_line
        )
        pass


    def flush(self):
        """
        Write log & clear buffer.

        :return:
        """
        if len(self.__data) > 0:
            content = '{defaultLogger_begin}{data}{defaultLogger_end}'.format(
                defaultLogger_begin='\n\nLogger.Default: {\n' if self.is_default else '',
                data=self.__data,
                defaultLogger_end='}' if self.is_default else ''
            )

            for output in self.__outputs:
                if output.enabled:
                    output.flush(content=content)

            self.__data = ''
        pass


    def __del__(self):
        try: self.flush()
        except: pass  # continue if logger-object is already destroyed
        pass

    pass
