import os
import shutil

import pickle




def save(object, filename, binary=True):
    """
    Convenience method for saving object to file without risking wrong mode overwrites
    :param binary: whether to save as a binary file
    :param object: the object to pickle
    :param filename: the filename
    """
    mode = 'wb' if binary else 'w'
    with open(filename, mode) as file:
        if binary:
            pickle.dump(object, file)
        else:
            file.write(str(object))
    return filename


def load(filename, binary=True):
    """
    Convenience method for loading object from file without risking wrong mode overwrites
    :param binary: whether to load as a binary file
    :param filename: the filename
    """
    mode = 'rb' if binary else 'r'
    with open(filename, mode) as file:
        return pickle.load(file)


def get_dir_name(file):
    """
    Get the directory of the given file
    :param file: the file
    :return: the file's directory
    """
    return os.path.dirname(os.path.realpath(file))


def get_sibling_file(file,
                     sibling_name):
    """
    Get the full path to a file within the same directory as another file
    :param file: the file whose directory we care about
    :param sibling_name: the name of the file we're looking for
    :return: a full path to the sibling file
    """
    return make_path(get_dir_name(file), sibling_name)


def make_path(root,
              *args):
    """
    Creates a path from the given parameters
    :param root: the root of the path
    :param args: the elements of the path
    :return: a string, each element separated by a forward slash.
    """
    path = root
    if path.endswith('/'):
        path = path[0:-1]
    for element in args:
        if not isinstance(element, str):
            element = str(element)
        if element[0] != '/':
            path += '/'
        path += element
    return path


def exists(path):
    return os.path.exists(path)


def make_dir(path,
             clean=True):
    """
    Create a new directory (create the entire tree if necessary)
    :param path: the directory's path
    :param clean: whether the directory should made empty (if it already exists)
    """

    exists = os.path.exists(path)
    if not exists:
        # print("Directory missing, creating: " + path)
        try:
            os.makedirs(path)
        except FileExistsError:
            pass
    elif clean:
            # Delete directory
            has_files = False
            for dirpath, dirnames, files in os.walk(path):
                if files:
                    has_files = True
                break
            # if has_files:
            #     print("NOT DELETING!!!! TOO DANGEROUS!")
            #     exit(1)
            shutil.rmtree(path)
            os.makedirs(path)


def copy_directory(source, dest):
    """
    Copy a directory tree from source to a destination. The directory must not exist in the destination
    :param source:
    :param dest:
    """
    if os.path.exists(dest):
        raise ValueError(dest + " already exists!")
    shutil.copytree(source, dest)


def merge(input_files, output_file):
    """
    Merge a list of files into one single file
    :param input_files: the list of input files
    :param output_file: the output file
    """
    with open(output_file, 'w') as wfd:
        for f in input_files:
            with open(f, 'r') as fd:
                shutil.copyfileobj(fd, wfd)


def files_in_dir(directory):
    """
    Generate the files inside a directory tree
    :param directory: the directory tree to traverse
    :return: the path and filename of files within the tree
    """

    for dirpath, dirnames, filenames in os.walk(directory):
        for f in filenames:
            file = make_path(dirpath, f)
            if os.path.isfile(file):
                yield dirpath, f
