import tempfile, shutil
import os
import re
import subprocess
import time
import datetime
import csv
import json, yaml
import string

from bson.objectid import ObjectId
from bson import json_util
from dateutil.parser import parse
from django.conf import settings
from hashlib import md5

from crits.core.class_mapper import class_from_value
from crits.core.exceptions import ZipFileError
from crits.core.mongo_tools import get_file

def get_file_fs(sample_md5):
    """
    Read a file from the filesystem. The path to the file is:

    /data/files/<md5[:2]>/<md5[2:4]>/<md5>

    :param sample_md5: The MD5 of the file to read off of disk.
    :type sample_md5: str
    :returns: str
    """

    try:
        fin = open('/data/files/%s/%s/%s' % (sample_md5[:2],
                                             sample_md5[2:4],
                                             sample_md5),
                   'rb')
        data = fin.read()
        fin.close()
    except Exception as e:
        raise("error: %s" % e)
    return data

def put_file_fs(data):
    """
    Write a file to the filesystem. The path to write the file to is:

    /data/files/<md5[:2]>/<md5[2:4]>/<md5>

    :param data: The data of the file to write.
    :type data: str
    :returns: str (the md5 of the file written)
    """

    a = md5()
    a.update(data)
    sample_md5 = a.hexdigest()
    try:
        fout = open('/data/files/%s/%s/%s' % (sample_md5[:2],
                                              sample_md5[2:4],
                                              sample_md5),
                    'wb')
        fout.write(data)
        fout.close()
    except Exception as e:
        raise("error: %s" % e)
    return sample_md5

def create_zip(files, pw_protect=True):
    """
    Create a zip file. Creates a temporary directory to write files to on disk
    using :class:`tempfile`. Uses /usr/bin/zip as the zipping mechanism
    currently. Will password protect the zip file as a default. The password for
    the zip file defaults to "infected", but it can be changed in the config
    under zip7_password.

    :param files: The files to add to the zip file.
    :type files: list of files which are in the format of a list or tuple of
                 (<filename>, <data>).
    :param pw_protect: To password protect the zip file or not.
    :type pw_protect: boolean
    :returns: :class:`crits.core.exceptions.ZipFileError`, str
    """

    dumpdir = ""
    try:
        # Zip can take data from stdin to compress, but
        # you can't define the filenames within the archive,
        # they show up as "-".  Therefore, we need to write
        # out the file, compress it and return the zip.
        # Save the sample as a file in a temp directory
        # NOTE: the following line was causing a "permission denied" exception.
        # Removed dir arg.
        from crits.config.config import CRITsConfig
        crits_config = CRITsConfig.objects().first()
        if crits_config:
            zip7_password = crits_config.zip7_password or 'infected'
        else:
            zip7_password = settings.ZIP7_PASSWORD or 'infected'
        dumpdir = tempfile.mkdtemp() #dir=temproot
        #write out binary files
        for f in files:
            filename = f[0]
            file_data = f[1]

            # make sure our desired path doesn't already exist (some files may
            # have the same name but different data)
            path = dumpdir + "/" + filename.encode("utf-8")
            i = 1
            tmp = path
            while os.path.exists(tmp):
                tmp = path+"("+str(i)+")"
                i += 1

            with open(tmp, "wb") as fh:
                fh.write(file_data)

        # Build the command line for zip
        # NOTE: forking subprocess instead of using Python's ZipFile library
        # because ZipFile does not allow us to create password-protected zip
        # archives, only read them.
        # -j don't include original filepath
        zipname = "zip.zip" #The name we give it doesn't really matter
        args = ["/usr/bin/zip", "-r", "-j", dumpdir+"/"+zipname, dumpdir]
        if pw_protect:
            args += ["-P", zip7_password]
        args += [dumpdir+"/"+zipname, dumpdir]

        proc = subprocess.Popen(args,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT)
        # Give the process 30 seconds to complete, otherwise kill it
        waitSeconds = 30
        while (proc.poll() is None and waitSeconds):
            time.sleep(1)
            waitSeconds -= 1

        zipdata = ""
        if proc.returncode:     # zip spit out an error
            errmsg = "Error while creating archive\n" + proc.stdout.read()
            raise ZipFileError, errmsg
        elif not waitSeconds:   # Process timed out
            proc.terminate()
            raise ZipFileError, "Error:\nProcess failed to terminate"
        else:
            with open(dumpdir + "/" + zipname, "rb") as fh:
                zipdata = fh.read()
        if not len(zipdata):
            raise ZipFileError, "Error:\nThe zip archive contains no data"
        return zipdata

    except ZipFileError:
        raise
    except Exception, ex:
        errmsg = ""
        for err in ex.args:
            errmsg = errmsg + " " + unicode(err)
        raise ZipFileError, errmsg
    finally:
        if os.path.isdir(dumpdir):
            shutil.rmtree(dumpdir)

def format_file(data, file_format):
    """
    Format data into the provided format. Acceptable formats are:
        - base64
        - zlib
        - raw
        - invert

    :param data: The data to format.
    :type data: str