from pathlib import Path
import hashlib
import inspect
import librec_auto
from librec_auto.librec_auto.util import SubPaths
from librec_auto.librec_auto.util.utils import force_path
from collections import OrderedDict

class Files:
    """
    This class encapsulates the file system for librec-auto.

    The files for librec-auto can be stored in a number of possible locations:
    - global location: determined by the install location of librec-auto
    - rules-specific location: determined by the location of a rules directory [Not implemented]
    - user-specific location: associated with the user's home directory [Not implemented]
    - experiment-specific location: in a directory associated with a specific experiment. See sub_paths.py for a class
      that handles the sub-experiment-specific files.

    These are the types of files that are managed:
    - configuration files (XML). Stores information about how experiments are configured.
    - properties files (key=value format). Generated from configuration files for input to librec
    - split files (UIR or UIRT format). Generated by separating input rules files into different test/training splits
    - experiment logs (csv files). Content is algorithm-specific. Generated by a run of an experiment

    NOTE: User and rules directories are currently unused.
    """

    _global_dir_path = None
    _sub_path_dict = None
    _config_dir_path = None
    _rules_dir_path = None
    #_res_dir_path = None
    _split_dir_path = None
    _jar_dir_path = None
    _post_dir_path = None

    _prop_file_name = None

    _DEFAULT_GLOBAL_DIR_STR = inspect.getfile(librec_auto) # This needs to be tested.
#    _DEFAULT_GLOBAL_DIR_STR = "."

    _DEFAULT_CONFIG_DIR_NAME = "conf"
    _DEFAULT_RULES_DIR_NAME = "rules"
    _DEFAULT_RES_DIR_NAME = "result"
    _DEFAULT_SPLIT_DIR_NAME = "data/split"
    _DEFAULT_JAR_DIR_NAME = "librec_auto/jar"
    _DEFAULT_POST_DIR_NAME = "post"
    _EXP_DIR_PATTERN = "exp{:05d}"

    _DEFAULT_PROP_FILE_NAME = "librec.properties"
    _DEFAULT_LA_JAR = "auto.jar"
    _DEFAULT_LR_JAR = "librec.jar"
    _DEFAULT_RULES_FILE = "librec_auto/rules/element-rules.xml"
    LOG_PATH = "../log/librec.log"
    DEFAULT_CONFIG_FILENAME = "config.xml"

    # 2019-11-22 RB Do we use this anywhere?
    # _DEFAULT_CACHE_FILENAME = ".cache"

    def __init__(self):
        self._config_dir_path = Path(self._DEFAULT_CONFIG_DIR_NAME)
        self._rules_dir_path = Path(self._DEFAULT_RULES_DIR_NAME)
        #self._res_dir_path = Path(self._DEFAULT_RES_DIR_NAME)
        self._split_dir_path = Path(self._DEFAULT_SPLIT_DIR_NAME)
        self._jar_dir_path = Path(self._DEFAULT_JAR_DIR_NAME)
        self._post_dir_path = Path(self._DEFAULT_POST_DIR_NAME)
        self._sub_path_dict = OrderedDict()

        module_init_path = Path(Files._DEFAULT_GLOBAL_DIR_STR).parent
        self._global_path = module_init_path.parent
        # maybe_user_path = Path(Files._DEFAULT_USER_PATH_STR)
        # if maybe_user_path.is_dir():
        #     self._user_path = maybe_user_path

    def get_global_path(self): return self._global_path

    def get_jar_path(self): return self._jar_dir_path

    def get_exp_path(self): return self._exp_path

    def get_split_path(self): return self._exp_path / self._split_dir_path

    def get_config_path(self): return self._exp_path / self._config_dir_path / self._config_file_name

    def get_post_path(self): return self._exp_path / self._post_dir_path

    def set_global_path(self, path): self._global_path = Path(path)

    def set_config_dir_path(self, path): self._config_dir_path = Path(path)

    def set_exp_path(self, path): self._exp_path = Path(path)

    def set_config_file(self, filename): self._config_file_name = Path(filename)

    def get_rules_path (self):
        return self.get_global_path() / self._DEFAULT_RULES_FILE

    # 2019-11-23 RB TODO: Separate librec.jar and auto.jar files. Then restore the two jar classpaths here.
    def get_classpath (self):
        # return (self.get_global_path() / self.get_jar_path() / self._DEFAULT_LA_JAR).absolute().as_posix() + ";" + \
        #        (self.get_global_path() / self.get_jar_path() / self._DEFAULT_LR_JAR).absolute().as_posix()
        return (self.get_global_path() / self.get_jar_path() / self._DEFAULT_LA_JAR).absolute().as_posix()

    def get_subexp_name (self, count):
        return self._EXP_DIR_PATTERN.format(count)

    def detect_sub_path(self, exp_no):
        return (self.get_exp_path() / self.get_subexp_name(exp_no)).exists()

    def detect_sub_paths (self, count=0):
        sub_count = 0
        while self.detect_sub_path(sub_count):
            self._sub_path_dict[sub_count] = SubPaths(self.get_exp_path(), self.get_subexp_name(sub_count), create=False)
            sub_count += 1
        if sub_count != count:
            print(f'librec-auto: Expecting {count} existing experiment directories in {self.get_exp_path()}. Found {sub_count}.')

    def create_sub_paths (self, tuple_count):
        if tuple_count == 0:
            sub_exp_count = 1
        else:
            sub_exp_count = tuple_count

        for i in range(sub_exp_count):
            self._sub_path_dict[i] = SubPaths(self.get_exp_path(), self.get_subexp_name(i), create=True)

    def ensure_sub_paths(self, exp_count):
        if self.detect_sub_path(0):
            self.detect_sub_paths(count=exp_count)
        else:
            self.create_sub_paths(exp_count)

    def get_sub_count (self):
        if len(self._sub_path_dict) > 0:
            return max(self._sub_path_dict.keys())+1
        else:
            return 0

    def get_sub_paths(self, count):
        if count in self._sub_path_dict:
            return self._sub_path_dict[count]
        else:
            return None

    def get_sub_paths_iterator(self):
        return self._sub_path_dict.values()

    @staticmethod
    def dir_hash(maybe_path):
        """
        Starting from a directory, gets all subdirectories, extracts the individual files and creates a hash value
        using the file name, size, and last modification date.

        Probably just modification date is enough.
        :param maybe_path:
        :return:
        """
        hasher = hashlib.sha1()
        path = force_path(maybe_path)
        full_listing = path.glob('**/*')
        files = [fl for fl in full_listing if fl.is_file()]
        for fl in files:
            fl_stat = fl.stat()
            fl_size = fl_stat.st_size
            fl_date = fl_stat.st_mtime
            fl_info = "{}-{}-{}".format(fl.name, fl_size, fl_date)
            fl_bytes = fl_info.encode('utf-8')
            hasher.update(fl_bytes)
        return hasher.hexdigest()
