import sys
import csv
import os
import hashlib
import glob
import string

csv.field_size_limit(sys.maxsize)

# Additional extensions that we look for after spitext
EXTENSIONS = ('.txt', '.tsv', '.tab', 'csv')


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def get_output_directory(indir, outdir, levels=0):
    """
    Build an output directory based on the options, if outdir is `NoneType`
    then indir is the outdir. If outdir is supplied then it is used as the
    output directory if `levels==0`. However, if `levels > 0`, then `levels`
    number of directories are taken from the indir and added to the outdir.
    For example, is indir is `/this/is/my/dir` and output direcory is
    `/my/output/dir`, if `levels == 1` then output dir would be
    `/my/output/dir/dir`. if `levels == 2` then output dir would be
    `/my/output/dir/my/dir`. If levels is set > than the number of levels
    that exist, then it will just go down to the bottom level but will not
    error out.

    Parameters
    ----------
    indir : str
        The input directory
    outdir : str or NoneType
        A specific output directory. If `NoneType`, then the output
        directory will be gathered from the location of the `indir`
    levels : int, optional
        add #levels down from the input file to the outdir (default=0)

    Returns
    -------
    outdir : str
        The output directory
    """
    if outdir is None:
        return indir

    if levels > 0:
        outpath = ""
        for level in range(levels):
            subdir = os.path.basename(indir)
            indir = os.path.dirname(indir)
            outpath = os.path.join(subdir, outpath)
        outdir = os.path.join(outdir, outpath)

    return outdir


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def outfile_from_regexp(infile, regexp, delimiter="_"):
    """
    Generate an output file name from a regexp and group capture of the input
    file name.

    Parameters
    ----------
    infile : str
        The input file name, this will be converted to a full path and the
        regexp will be applied to it
    regexp : str
        The regexp should be a precompiled regexp that will be applied using
        re.search. All groups will be captured, although the groups required
        in the output file name should be labeled "A", "B", "C"" using
        the named capure groups `(?P<A>)` etc... . Remember to also use
        non-capturing groups `(?:)` where necessary.
    delimiter : str, optional
        The character to use to join all the regexp extracted groups
        together with (default="_")

    Returns
    -------
    outfile : str
        The output file as defined by the regexp

    Raises
    ------
    ValueError
        If no groups were captured from the regular expression
    """
    # Ensure that the outout file is a full path
    infile = os.path.realpath(infile)

    outreg = regexp.search(infile)

    outfile_components = []

    for i in string.ascii_uppercase:
        try:
            outfile_components.append(outreg.group(i))
        except (IndexError, AttributeError):
            # Can't match groups either because the regexp did not match
            # at all or we have no more groups to match
            break

    if len(outfile_components) == 0:
        raise ValueError("could not match regexp to filename: '{0}'".format(
            infile))

    return delimiter.join(outfile_components), len(outfile_components)


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def gather_input_files(infiles=[], indir=None, inglob=None):
    """
    Using the arguments get all the input files requested by the user

    Parameters
    ----------
    infiles : list of str, optional
        A list of input files, this will be merged with indir contents
    indir : str or NoneType, optional
        An input directory
    inglob : str or NoneType, optional
        Used in conjunction with `indir`. If `NoneType`, then all files in
        the root of `indir` are gathered, otherwise a glob of `indir` and
        `inglob` is used to gather files

    Returns
    -------
    files : list of str
        A list of full file names

    Raises
    ------
    ValueError
        If the arguments produce an empty list of files
    """
    # Make sure all the input files have the absolute path
    all_files = [os.path.abspath(os.path.expanduser(i)) for i in infiles]

    # if an input directory has been supplied
    if indir is not None:
        # Make sure that the input directory is set to the absolute path
        indir = os.path.abspath(os.path.expanduser(indir))

        # Now if we are not globing anything, then list all the files in the
        # input directory
        if inglob is None:
            for f in os.listdir(indir):
                f = os.path.join(indir, f)
                if os.path.isfile(f):
                    all_files.append(f)
        else:
            # Get any globed file names
            for f in glob.glob(os.path.join(indir, inglob)):
                if os.path.isfile(f):
                    all_files.append(f)

    if len(all_files) == 0:
        raise ValueError("no input files found")

    return all_files


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def get_file_components(infile):
    """
    This takes a file name, makes it a realpath and then splits it into
    dirname, filename, extension . The extension goes a bit beyond splitext as
    it will look for things like .txt.gz instead of just gz

    Parameters
    ----------
    infile :str
        The file to process

    Returns
    -------
    directory :str
        The directory location of the file
    file_name :str
        The base name for the file with the extension removed
    extension :str
        The extension of the file
    """
    # First make sure the input file is represented as a full path
    infile = os.path.realpath(infile)

    # Now get the dirname
    directory = os.path.dirname(infile)
    file_name = os.path.basename(infile)

    file_name, extension = os.path.splitext(file_name)

    # there are a lot of .txt.gz files out there so just check and if so
    # do it again
    for i in EXTENSIONS:
        if file_name.endswith(i):
            file_name, other_extension = os.path.splitext(file_name)
            extension = other_extension + extension
            break

    return directory, file_name, extension


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def md5_file(file_name, chunksize=4096):
    """Get the MD5 of a file.

    Parameters
    ----------
    file_name : `str`
        A file name to check the MD5 sum.
    chunk : `int`, optional, default: `4096`
        The size of the chunks to read from the file.

    Returns
    -------
    md5sum : `str`
        The md5 hash of the file (hex).

    Notes
    -----
    This reads the file in chunks and accumilates the MD5 sum to prevent
    loading the whole lot into memory. Taken from
    `here. <https://stackoverflow.com/questions/3431825>`_

    """
    hash_md5 = hashlib.md5()
    with open(file_name, "rb") as f:
        while True:
            chunk = f.read(chunksize)
            if not chunk:
                break
            hash_md5.update(chunk)
    return hash_md5.hexdigest()
