# AUTOGENERATED FROM: babilim/core/config.ipynb

# Cell: 0
"""doc
# babilim.core.config

> The base class for every config.

This code is under the MIT License.
"""

# Cell: 1
# MIT License
#
# Copyright (c) 2019 Michael Fuerst
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


# Cell: 2
import os
import sys
from typing import Dict, Any
import json
import importlib
import inspect


# Cell: 3
class Config(object):
    def __init__(self) -> None:
        """
        A configuration for a deep learning project.
        
        This class should never be instantiated directly, subclass it instead and add your atributes after calling super.
        """
        # Training parameters.
        self.train_batch_size = 1
        self.train_epochs = 1

        # Required for general dataset loading. (Non architecture specific.)
        self.problem_base_dir = None
        self.problem_shuffle = True
        self.problem_num_threads = 0

    def __repr__(self) -> str:
        return "Config(" + self.__str__() + ")"
    
    def __str__(self) -> str:
        out = ""
        for k, v in sorted(self.__dict__.items(), key=lambda x: x[0]):
            out += "{}: {}\n".format(k, v)
        return out


# Cell: 4
"""doc
Every configuration has these filds, which you may overwrite as you need.

### Dataset/Problem Parameters
* `self.problem_base_dir = None`: The path to the root of the dataset folder.
* `self.problem_shuffle = True`: If the dataloader used for training should shuffle the data.
* `self.problem_num_threads = 0`: How many threads the dataloader should use. (0 means no multithreading and is most stable)

### Training Parameters
* `self.train_batch_size = 1`: The batch size used for training the neural network. This is required for the dataloader from the dataset.
* `self.train_epochs = 1`: The number epochs for how many a training should run.
"""

# Cell: 5
"""doc
# Dynamic Config Import

When you write a library and need to dynamically import configs, use the following two functions.
"""

# Cell: 6
def import_config(config_file: str, *args, **kwargs) -> Config:
    """
    Only libraries should use this method. Human users should directly import their configs.
    Automatically imports the most specific config from a given file.

    :param config_file: Path to the configuration file (e.g. configs/my_config.py)
    :return: The configuration object.
    """
    module_name = config_file.replace("\\", ".").replace("/", ".").replace(".py", "")
    module = importlib.import_module(module_name)
    module = importlib.reload(module)
    symbols = list(module.__dict__.keys())
    symbols = [x for x in symbols if not x.startswith("__")]
    n = None
    for x in symbols:
        if not inspect.isclass(module.__dict__[x]):  # in Case we found something that is not a class ignore it.
            continue
        if issubclass(module.__dict__[x], Config):
            # Allow multiple derivatives of config, when they are derivable from each other in any direction.
            if n is not None and not issubclass(module.__dict__[x], module.__dict__[n]) and not issubclass(
                    module.__dict__[n], module.__dict__[x]):
                raise RuntimeError(
                    "You must only have one class derived from Config in {}. It cannot be decided which to use.".format(
                        config_file))
            # Pick the most specific one if they can be derived.
            if n is None or issubclass(module.__dict__[x], module.__dict__[n]):
                n = x
    if n is None:
        raise RuntimeError("There must be at least one class in {} derived from Config.".format(config_file))
    config = module.__dict__[n](*args, **kwargs)
    return config


# Cell: 7
def import_checkpoint_config(config_file: str, *args, **kwargs) -> Any:
    """
    Adds the folder in which the config_file is to the pythonpath, imports it and removes the folder from the python path again.

    :param config_file: The configuration file which should be loaded.
    :return: The configuration object.
    """
    config_file = config_file.replace("\\", "/")
    config_folder = "/".join(config_file.split("/")[:-2])
    config_file_name="/".join(config_file.split("/")[-2:])

    sys.path.append(config_folder)
    config = import_config(config_file_name, *args, **kwargs)
    sys.path.remove(config_folder)
    return config
