import h5py
import hashlib
import validators
import json
import sys
import os
import time
import uuid
# log is the same as standard_sdks log
from mmkfeatures.fusion import log
from mmkfeatures.fusion.metadataconfigs import *
from mmkfeatures.fusion.integrity_check import *

from mmkfeatures.fusion.blank import *
from mmkfeatures.fusion.file_ops import *
from mmkfeatures.fusion.download_ops import *

# computational sequence class
# main attributes:
#       main_file: where the location of the heirarchical data binary file is
#       resource: where the heirarchical data comes from (i.e. a URL)
#       h5handle: handle to the file
#       data: the data in a dictionary format
#       metadata: the metadata in a dictionary format
#
# main function:
#       completeAllMissingMetadata: a helper to complete all the missing metadata
#       deploy: deploy the computational sequence - you can also notify multicomp about your computational sequence

class computational_sequence():

    def __init__(self, resource, destination=None, validate=True):
        # initializing the featureset
        self.__initialize(resource, destination, validate)

        # BACKWARD: backward compatibility
        self.rootName = self.root_name

    def _compare_entries(self, entry1, entry2):
        return entry1.split('[')[0] == entry2.split('[')[0]

    def __getitem__(self, key):
        return self.data[key]

    def __setitem__(self, key, value):
        self.data[key] = {}
        self.data[key]["intervals"] = value["intervals"]
        self.data[key]["features"] = value["features"]

    def keys(self):
        return self.data.keys()

    def _remove_id(self, entry_id, purge=False):
        if purge == False:
            if entry_id in list(self.data.keys()):
                del self.data[entry_id]
        elif purge == True:
            keys_to_del = [key for key in list(self.keys()) if key[:len(entry_id)] == entry_id]
            for key_to_del in keys_to_del:
                del self.data[key_to_del]
        else:
            log.error("Purge received wrong argument type. Exiting ...!", error=True)

    def __initialize_from_csd(self, resource, destination, validate):
        if validators.url(resource):
            # user would like to store to the current directory
            if destination is None or destination == '':
                destination = os.path.join('./', resource.split('/')[-1])
            # user has chosen a different directory
            elif '.csd' not in destination:
                destination = os.path.join(destination, resource.split('/')[-1])
            # .csd cannot be in the destination as we don't allow name changes when downloading
            else:
                log.error(
                    "Destination needs to be a folder where the downloaded computational sequence is stored. Exiting ...!",
                    error=True)

            read_URL(resource, destination)
            self.resource = resource
            self.main_file = destination
        else:
            self.main_file = resource

        print("self.mainfile=",self.main_file)

        h5handle, data,_,_, metadata = read_CSD(self.main_file)
        if type(metadata) is not dict:
            log.error("Metadata not in correct format for %s. Exiting ...!" % destination, error=True)

        self.data = data
        self.metadata = metadata
        self.root_name = self.metadata["root name"]
        self.h5handle = h5handle

        if validate:
            self.__check_format()
        else:
            log.warning("Validation of the computational sequence skipped by user request.")

    def __initialize_blank(self, root_name):
        self.main_file = None
        self.h5handle = None
        self.root_name = root_name
        self.data = {}
        self.metadata = {}
        self.metadata["root name"] = root_name
        log.success("Initialized empty <%s> computational sequence." % self.metadata["root name"])

    # TODO: try and excepts to be added to this code
    def __initialize(self, resource, destination, validate):
        # computational sequence is already initialized
        if hasattr(self, 'h5handle'): log.error(
            "<%s> computational sequence already initialized ..." % self.metadata["root name"], error=True)

        # initializing blank - main_file is where to initialize the data and resource is None since the data comes from nowhere
        if '.csd' not in resource:
            self.__initialize_blank(resource)
        # reading from url or folder - main_file is where the data should go and resource is the url
        else:
            self.__initialize_from_csd(resource, destination, validate)

    # checking if the data and metadata are in correct format
    # stops the program if the integrity is not ok
    def __check_format(self, error=True):
        if not hasattr(self, 'metadata') or not hasattr(self, 'data'):
            log.error("computational sequence is blank (data or metadata is missing)")
        log.status("Checking the integrity of the <%s> computational sequence ..." % self.metadata["root name"])
        # TODO: hash check not implemented yet
        datavalid = validate_data_format(self.data, self.metadata["root name"], verbose=False)
        metadatavalid = validate_metadata_format(self.metadata, self.metadata["root name"], verbose=False)
        if datavalid and metadatavalid:
            log.success("<%s> computational sequence is valid!" % self.metadata["root name"])

    # set the metadata for all the missing information in the metadata. If the key is not set then the assumption is that metadata is not available. Note that if the metadata is set to a dummy variable like None then it is still considered set.
    def complete_all_missing_metadata(self):

        missings = [x for (x, y) in zip(featuresetMetadataTemplate,
                                        [metadata in self.metadata.keys() for metadata in featuresetMetadataTemplate])
                    if y is False]
        # python2 vs python 3
        # TODO: Add read from file
        root_name_ext = ''
        if hasattr(self, "root_name"):
            root_name_ext = " for <%s> computational sequence" % self.root_name
        for missing in missings:
            self.metadata[missing] = log.status("Please input %s%s: " % (missing, root_name_ext), require_input=True)

    # BACKWARD: _ for backward compability
    def set_data(self, data, _=None, __=None):
        validate_data_format(data, self.root_name, verbose=True)
        self.data = data

    # BACKWARD: _ for backward compability
    def set_metadata(self, metadata, _=None, __=None):
        validate_metadata_format(metadata, self.root_name, verbose=False)
        self.metadata = metadata

    # writing the file to the output
    def deploy(self, destination, compression="gzip", compression_opts=9, full_chunk_shape=True,my_metadata=None):
        if my_metadata == None:
            self.complete_all_missing_metadata()
        else:
            self.metadata = my_metadata

        self.__check_format()
        log.status("Deploying the <%s> computational sequence to %s" % (destination, self.metadata['root name']))
        # generating the unique identifiers
        self.metadata['uuid'] = str(uuid.uuid4())
        # TODO: add SHA256 check + midification should not be possible without private key
        self.metadata['md5'] = None
        log.status("Your unique identifier for <%s> computational sequence is %s" % (
        self.metadata["root name"], self.metadata['uuid']))
        write_CSD(self.data,{}, {}, self.metadata, self.metadata["root name"], destination, compression=compression,
                  compression_opts=compression_opts, full_chunk_shape=full_chunk_shape)
        self.main_file = destination

    def _get_entries_stripped(self):
        return list(set([entry.split('[')[0] for entry in list(self.data.keys())]))

    def bib_citations(self, outfile=None):
        outfile = sys.stdout if outfile is None else outfile
        if self.metadata is None or self.metadata == {}:
            log.error("Metadata is not set for <%s> computational sequence" % self.root_name)
        outfile.write(
            'Computational Sequence <%s> bib: ' % self.root_name + self.metadata['featureset bib citation'] + '\n\n')
        outfile.write(
            'Dataset <%s> bib: ' % self.metadata["dataset name"] + self.metadata['dataset bib citation'] + '\n\n')

# some backward compatibility stuff.
computational_sequence.setData = computational_sequence.set_data
computational_sequence.setMetadata = computational_sequence.set_metadata
computational_sequence.completeAllMissingMetadata = computational_sequence.complete_all_missing_metadata