"""
Database Classes
Classes that save/load python content to/from the hard drive(or other means of non-volatile memory).
Make sure to provide pickle-able data, no objects, classes or anything that might be hard to convert to a string
Please do not use DatabaseEntry directly, nor DatabaseDictBase. Those are exposed for overloading purposes.
Use DatabaseList, DatabaseDict or DatabaseOrderedDict!
"""


### INCLUDES ###
import os
import copy
import logging

from . import file_system
from . import pickle
from .ordered_dict import OrderedDict


### CONSTANTS ###
## Logger ##
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.WARNING)
# LOGGER.setLevel(logging.DEBUG)


### CLASSES ###
## Base Database Class ##
class DatabaseEntry(object):
    """ DatabaseEntry class """
    def __init__(self, entry_data_type, db_file=None, defaults=None):
        super(DatabaseEntry, self).__init__()
        self._main = entry_data_type()
        self._db_file = db_file
        self._defaults = defaults
        self.load()

    ## Load Methods ##
    def load(self):
        """ External Load """
        db_main = self._load()
        if db_main is not None:
            self._main = db_main

    def _load(self):
        """ Internal Load - loads main from a file if it exists """
        db_main = None
        if self._db_file is not None:
            # LOGGER.debug('Loading db_file: ' + str(self._db_file))
            database_data = pickle.unpickle_file(self._db_file)

            if database_data is not False:
                db_main = database_data

        if self._defaults is not None and db_main is None:
            db_main = self._load_default()

        return db_main

    def _load_default(self):
        """ Loads main with defaults """
        # LOGGER.debug("type(defaults) = " + str(type(self._defaults)))

        if type(self._defaults) is type(self._main):
            # LOGGER.debug("Deep Copy")
            defaults = copy.deepcopy(self._defaults)
        else:
            # LOGGER.error("Defaults type: " + str(type(self._defaults)) + " is not supported!")
            defaults = None

        return defaults

    ## Save Methods ##
    def save(self, db_content=None):
        """ Saves main to a file """
        if db_content is None:
            db_content = self._main

        if self._db_file is not None:
            if os.sep in self._db_file:
                sub_folders_path = os.path.dirname(self._db_file)
                file_system.make_dir(sub_folders_path)

            # LOGGER.debug('Saving db_file: ' + str(self._db_file))
            pickle.pickle_file(self._db_file, db_content)

    ## Delete Methods ##
    def delete(self):
        """ Deletes database file """
        if self._db_file is not None:
            file_system.remove_file(self._db_file)

    ## Generic Macros ##
    def __iter__(self):
        return iter(self._main)

    def __getitem__(self, key):
        """ Allows using self[key] method """
        return self._main[key]

    def __setitem__(self, key, value):
        """ Allows using self[key] = value method """
        self._main[key] = value

    def __delitem__(self, key):
        """ Allows using del self[key] method """
        del self._main[key]

    def __len__(self):
        """ Allows using len(self) method """
        return len(self._main)

    def __repr__(self):
        """ Allows using self method. Returns list of dictionaries """
        return repr(self._main)

    def __str__(self):
        """ Allows using print self method """
        return str(self.__repr__())

    def pop(self, index):
        """ Allows using pop method """
        return self._main.pop(index)


## List, Dict and OrderedDict Database Classes ##
class DatabaseList(DatabaseEntry):
    """ DatabaseList class """
    def __init__(self, **kwargs):
        """ Load database entry """
        super(DatabaseList, self).__init__(list, **kwargs)

    ## Some List Specific Methods ##
    def append(self, value):
        """ Allows using append method """
        self._main.append(value)

    def extend(self, value):
        """ Allows using extend method """
        self._main.extend(value)

    def insert(self, index, value):
        """ Allows using insert method """
        self._main.insert(index, value)


class DatabaseDictBase(DatabaseEntry):
    """ Some Dictionary Specific Methods """
    def __init__(self, *args, **kwargs):
        super(DatabaseDictBase, self).__init__(*args, **kwargs)
        self._default_value = None
        self.auto_key_creation = True

    def update(self, value):
        """ Allows using update method """
        self._main.update(value)

    def items(self):
        """ Allows using items method """
        return self._main.items()
    
    def values(self):
        """ Allows using values method """
        return self._main.values()
    
    def keys(self):
        """ Allows using keys method """
        return self._main.keys()

    def clear(self):
        """ Allows using clear method """
        return self._main.clear()

    ## Overloading Generic Macros ##
    def __getitem__(self, key):
        """ Overloading to allow automatic key creation feature """
        if key not in self._main:
            if self._default_value is not None:
                LOGGER.warning("Warning key '" + str(key) + "' does not exists! Providing default value!")
                return self._default_value

            if self.auto_key_creation:
                LOGGER.warning("Warning key '" + str(key) + "' does not exists! Creating one!")

                default_value = None
                if key in self._defaults:
                    default_value = self._defaults[key]

                self.__setitem__(key, default_value)

        return super(DatabaseDictBase, self).__getitem__(key)


class DatabaseDict(DatabaseDictBase):
    """ DatabaseDict class """
    def __init__(self, **kwargs):
        """ Load database entry """
        super(DatabaseDict, self).__init__(dict, **kwargs)


class DatabaseOrderedDict(DatabaseDictBase):
    """ DatabaseOrderedDict class """
    def __init__(self, **kwargs):
        """ Load database entry """
        super(DatabaseOrderedDict, self).__init__(OrderedDict, **kwargs)

    def insert_after(self, existing_key, key_value):
        """ Allows using insert_after method """
        self._main.insert_after(existing_key, key_value)

    def insert_before(self, existing_key, key_value):
        """ Allows using insert_before method """
        self._main.insert_before(existing_key, key_value)

"""
Database Classes
Classes that save/load python content to/from the hard drive(or other means of non-volatile memory).
Make sure to provide pickle-able data, no objects, classes or anything that might be hard to convert to a string
Please do not use DatabaseEntry directly, nor DatabaseDictBase. Those are exposed for overloading purposes.
Use DatabaseList, DatabaseDict or DatabaseOrderedDict!
"""


### INCLUDES ###
import os
import copy
import logging

from . import file_system
from . import pickle
from .ordered_dict import OrderedDict


### CONSTANTS ###
## Logger ##
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.WARNING)
# LOGGER.setLevel(logging.DEBUG)


### CLASSES ###
## Base Database Class ##
class DatabaseEntry(object):
    """ DatabaseEntry class """
    def __init__(self, entry_data_type, db_file=None, defaults=None):
        super(DatabaseEntry, self).__init__()
        self._main = entry_data_type()
        self._db_file = db_file
        self._defaults = defaults
        self.load()

    ## Load Methods ##
    def load(self):
        """ External Load """
        db_main = self._load()
        if db_main is not None:
            self._main = db_main

    def _load(self):
        """ Internal Load - loads main from a file if it exists """
        db_main = None
        if self._db_file is not None:
            # LOGGER.debug('Loading db_file: ' + str(self._db_file))
            database_data = pickle.unpickle_file(self._db_file)

            if database_data is not False:
                db_main = database_data

        if self._defaults is not None and db_main is None:
            db_main = self._load_default()

        return db_main

    def _load_default(self):
        """ Loads main with defaults """
        # LOGGER.debug("type(defaults) = " + str(type(self._defaults)))

        if type(self._defaults) is type(self._main):
            # LOGGER.debug("Deep Copy")
            defaults = copy.deepcopy(self._defaults)
        else:
            # LOGGER.error("Defaults type: " + str(type(self._defaults)) + " is not supported!")
            defaults = None

        return defaults

    ## Save Methods ##
    def save(self, db_content=None):
        """ Saves main to a file """
        if db_content is None:
            db_content = self._main

        if self._db_file is not None:
            if os.sep in self._db_file:
                sub_folders_path = os.path.dirname(self._db_file)
                file_system.make_dir(sub_folders_path)

            # LOGGER.debug('Saving db_file: ' + str(self._db_file))
            pickle.pickle_file(self._db_file, db_content)

    ## Delete Methods ##
    def delete(self):
        """ Deletes database file """
        if self._db_file is not None:
            file_system.remove_file(self._db_file)

    ## Generic Macros ##
    def __iter__(self):
        return iter(self._main)

    def __getitem__(self, key):
        """ Allows using self[key] method """
        return self._main[key]

    def __setitem__(self, key, value):
        """ Allows using self[key] = value method """
        self._main[key] = value

    def __delitem__(self, key):
        """ Allows using del self[key] method """
        del self._main[key]

    def __len__(self):
        """ Allows using len(self) method """
        return len(self._main)

    def __repr__(self):
        """ Allows using self method. Returns list of dictionaries """
        return repr(self._main)

    def __str__(self):
        """ Allows using print self method """
        return str(self.__repr__())

    def pop(self, index):
        """ Allows using pop method """
        return self._main.pop(index)


## List, Dict and OrderedDict Database Classes ##
class DatabaseList(DatabaseEntry):
    """ DatabaseList class """
    def __init__(self, **kwargs):
        """ Load database entry """
        super(DatabaseList, self).__init__(list, **kwargs)

    ## Some List Specific Methods ##
    def append(self, value):
        """ Allows using append method """
        self._main.append(value)

    def extend(self, value):
        """ Allows using extend method """
        self._main.extend(value)

    def insert(self, index, value):
        """ Allows using insert method """
        self._main.insert(index, value)


class DatabaseDictBase(DatabaseEntry):
    """ Some Dictionary Specific Methods """
    def __init__(self, *args, **kwargs):
        super(DatabaseDictBase, self).__init__(*args, **kwargs)
        self._default_value = None
        self.auto_key_creation = True

    def update(self, value):
        """ Allows using update method """
        self._main.update(value)

    def items(self):
        """ Allows using items method """
        return self._main.items()
    
    def values(self):
        """ Allows using values method """
        return self._main.values()
    
    def keys(self):
        """ Allows using keys method """
        return self._main.keys()

    def clear(self):
        """ Allows using clear method """
        return self._main.clear()

    ## Overloading Generic Macros ##
    def __getitem__(self, key):
        """ Overloading to allow automatic key creation feature """
        if key not in self._main:
            if self._default_value is not None:
                LOGGER.warning("Warning key '" + str(key) + "' does not exists! Providing default value!")
                return self._default_value

            if self.auto_key_creation:
                LOGGER.warning("Warning key '" + str(key) + "' does not exists! Creating one!")

                default_value = None
                if key in self._defaults:
                    default_value = self._defaults[key]

                self.__setitem__(key, default_value)

        return super(DatabaseDictBase, self).__getitem__(key)


class DatabaseDict(DatabaseDictBase):
    """ DatabaseDict class """
    def __init__(self, **kwargs):
        """ Load database entry """
        super(DatabaseDict, self).__init__(dict, **kwargs)


class DatabaseOrderedDict(DatabaseDictBase):
    """ DatabaseOrderedDict class """
    def __init__(self, **kwargs):
        """ Load database entry """
        super(DatabaseOrderedDict, self).__init__(OrderedDict, **kwargs)

    def insert_after(self, existing_key, key_value):
        """ Allows using insert_after method """
        self._main.insert_after(existing_key, key_value)

    def insert_before(self, existing_key, key_value):
        """ Allows using insert_before method """
        self._main.insert_before(existing_key, key_value)

Database Classes
 Classes that save/load python content to/from the hard drive(or other means of non-volatile memory).
 Make sure to provide pickle-able data, no objects, classes or anything that might be hard to convert to a string
 Please do not use DatabaseEntry directly, nor DatabaseDictBase. Those are exposed for overloading purposes.
 Use DatabaseList, DatabaseDict or DatabaseOrderedDict! ### INCLUDES ### ### CONSTANTS ### ## Logger ## # LOGGER.setLevel(logging.DEBUG) ### CLASSES ### ## Base Database Class ## DatabaseEntry class ## Load Methods ## External Load Internal Load - loads main from a file if it exists # LOGGER.debug('Loading db_file: ' + str(self._db_file)) Loads main with defaults # LOGGER.debug("type(defaults) = " + str(type(self._defaults))) # LOGGER.debug("Deep Copy") # LOGGER.error("Defaults type: " + str(type(self._defaults)) + " is not supported!") ## Save Methods ## Saves main to a file # LOGGER.debug('Saving db_file: ' + str(self._db_file)) ## Delete Methods ## Deletes database file ## Generic Macros ## Allows using self[key] method Allows using self[key] = value method Allows using del self[key] method Allows using len(self) method Allows using self method. Returns list of dictionaries Allows using print self method Allows using pop method ## List, Dict and OrderedDict Database Classes ## DatabaseList class Load database entry ## Some List Specific Methods ## Allows using append method Allows using extend method Allows using insert method Some Dictionary Specific Methods Allows using update method Allows using items method Allows using values method Allows using keys method Allows using clear method ## Overloading Generic Macros ## Overloading to allow automatic key creation feature DatabaseDict class Load database entry DatabaseOrderedDict class Load database entry Allows using insert_after method Allows using insert_before method