# -----------------------------------------------------------------------
#
# ff - a tool for finding files in the filesystem
# Copyright (C) 2020 Lars Gustäbel <lars@gustaebel.de>
#
# This program 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.
#
# This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
#
# -----------------------------------------------------------------------

import enum
import binascii

from . import NoData, MissingImport
from .type import *

__all__ = ["NoData", "MissingImport", "Type", "String", "Path", "Number", "FileType", "Mode",
        "Size", "Time", "Duration", "Boolean", "ListOfStrings", "Plugin", "Speed"]


class Speed(enum.Enum):
    """The speed at which a plugin extracts information from an entry. This is used as a hint to
       group faster tests before slower tests in test sequences.
    """
    FAST = 0
    NORMAL = 1
    SLOW = 2


class Plugin:
    """Base class for all plugins.
    """

    author = "Lars Gustäbel"
    email = "lars@gustaebel.de"
    url = "https://github.com/gustaebel/ff/"

    use_cache = False
    cache_tag = None
    speed = Speed.NORMAL
    attributes = {}

    def __init_subclass__(cls):
        super().__init_subclass__()

        cls.name = cls.__name__.lower()

    @classmethod
    def initialize(cls, logger, source, path):
        """Initialize the Plugin class with basic information.
        """
        cls.logger = logger
        cls.source = source
        cls.path = path
        if cls.cache_tag is None:
            cls.cache_tag = cls.get_plugin_cache_tag(path)
        assert isinstance(cls.cache_tag, int) and cls.cache_tag >= 0
        cls.sql_table_name = f"plugin_{cls.name}_{cls.cache_tag}"

    def __init__(self):
        self.setup()

    @classmethod
    def get_plugin_cache_tag(cls, path):
        """Create a checksum of the plugin module file as a tag for the cache database. This way
           the plugin cache is automatically invalidated everytime the module source code changes.
           Return value must be a positive integer.
        """
        with open(path, "rb") as fobj:
            return binascii.crc32(fobj.read())

    def setup(self):
        """Setup the Plugin class. This is called once for every plugin, but only if it will be
           actually used. This is mainly supposed to be used for importing third-party modules that
           may or may not be installed on the system.
        """

    def can_handle(self, entry):
        """Return True if this plugin can handle this particular Entry object. This is supposed to
           reduce calls to process() and minimize the number of needlessly cached entries.
        """
        raise NotImplementedError

    def get_entry_cache_tag(self, entry):
        """Return a value for the Entry object that if it changes causes the cached value to be
           invalidated. The default uses the modification time of the file. The returned value may
           have any type.
        """
        return entry.time

    def cache(self, entry):
        """Extract and prepare data from the Entry object and cache it. It is then passed on to the
           process() method. cache() is only called if use_cache is True. The data returned may
           have any type. Raise a NoData exception if the required data cannot be extracted.
        """
        # pylint:disable=unused-argument
        return None

    def process(self, entry, cached):
        """Generate key-value pairs or return a dict with the values that were "extracted" from the
           entry. The set of keys must match the ones from the attributes dict, but it is not
           necessary that all the keys actually have values. Raise a NoData exception or return an
           empty sequence if the plugin cannot extract the required attributes properly. The cached
           argument is only present when use_cache is True and contains the cached data that was
           prepared in the cache() method. If the cache() method raised a NoData exception
           process() will not be called.
        """
        raise NotImplementedError
