#!/usr/bin/env python3

print('''
helper_functions.py - a script from the AQUAMIS pipeline:
https://gitlab.com/bfr_bioinformatics/AQUAMIS.git
''')

# %% Packages

# Python Standard Packages
import os
import json
import urllib.parse
from copy import deepcopy

# Other Packages
import pandas as pd
import numpy as np

# %% Dictionaries

defaultValues_dict = {
    'string' : str(),
    'int'    : int(0),
    'integer': int(0),
    'float'  : float(0),
    'number' : int(0),
    'array'  : [],
    'list'   : [],
    'tuple'  : [],
    'dict'   : {},
    'object' : {},
    'boolean': False,
    'null'   : None
}

defaultTypes_dict = {
    'string' : 'string',
    'object' : 'dict',
    'integer': 'integer',
    'number' : 'float',
    'array'  : 'list',
    'list'   : 'list',
    'boolean': 'boolean',
    'null'   : None
}


# %% Functions

def printjs(jsonObj: str, indent=4, sort_keys=False, allow_nan=False):
    print(json.dumps(obj=jsonObj, indent=indent, sort_keys=sort_keys, allow_nan=allow_nan))


def merge_dict(original: dict, patch: dict, path: list = None) -> dict:
    if path is None:
        path = []
    for key in patch:
        if key in original:
            if isinstance(original[key], dict) and isinstance(patch[key], dict):
                merge_dict(original[key], patch[key], path + [str(key)])
            elif original[key] == patch[key]:
                pass  # same leaf value
            else:
                raise Exception('Conflict at %s' % '.'.join(path + [str(key)]))
        else:
            original[key] = patch[key]
    return original


def nestedDict_to_json(nestedObj: (dict, list), name: str = None):
    jsonstring = str()
    try:
        jsonstring += json.dumps(nestedObj, ensure_ascii=False, allow_nan=True)
    except:
        if isinstance(nestedObj, dict):
            for k, v in nestedObj.items():
                if isinstance(v, pd.DataFrame):
                    jsonstring += '"' + k + '"' + ": " + v.to_json(orient='records', force_ascii=False)
                else:
                    try:
                        jsonstring += '"' + k + '"' + ": " + json.dumps(v, ensure_ascii=False, allow_nan=True)
                    except:
                        jsonstring += nestedDict_to_json(nestedObj=v, name=k)
                if k == list(nestedObj.keys())[-1]:
                    jsonstring = '{' + jsonstring + '}'
                else:
                    jsonstring += ','
        elif isinstance(nestedObj, list):
            for element in nestedObj:
                try:
                    jsonstring += json.dumps(element, ensure_ascii=False, allow_nan=True)
                except:
                    jsonstring += nestedDict_to_json(nestedObj=element)
            jsonstring = '[' + jsonstring + ']'
    if name is not None:
        jsonstring = '"' + name + '": ' + jsonstring
    return jsonstring


def strictJsonObj(nestedObj, uriCompliant: bool):
    if isinstance(nestedObj, list):
        new_list = []
        for element in nestedObj:
            if element is not None and isinstance(element, float):
                if not np.isfinite(element):
                    new_list.append(None)
            elif isinstance(element, (list, dict)):
                new_list.append(strictJsonObj(nestedObj=element, uriCompliant=uriCompliant))
            else:
                new_list.append(element)
        nestedObj = new_list
    if isinstance(nestedObj, dict):
        nullStrings = ['-', '--', 'NA', 'ND']
        for key in nestedObj.keys():
            if nestedObj[key] is not None and isinstance(nestedObj[key], float):
                if not np.isfinite(nestedObj[key]):
                    nestedObj[key] = None
            if nestedObj[key] is not None and isinstance(nestedObj[key], str):
                if nestedObj[key] in nullStrings:
                    nestedObj[key] = None
            elif isinstance(nestedObj[key], (list, dict)):
                nestedObj[key] = strictJsonObj(nestedObj=nestedObj[key], uriCompliant=uriCompliant)
        if uriCompliant:
            nestedObj = {urllib.parse.quote(key): value for (key, value) in nestedObj.items()}
    return nestedObj


def unquote_hook(quotedObj):
    obj = {urllib.parse.unquote(key): value for (key, value) in dict(quotedObj).items()}
    return obj


def translateSchema_json2cerberus(schemaObj, defaultValues=None, objName='Root'):
    debug_switch = False
    newDict = dict()
    if isinstance(schemaObj['type'], list):
        newDict[objName] = {'type': [defaultTypes_dict[type] for type in schemaObj['type']]}
    else:
        newDict[objName] = {'type': defaultTypes_dict[schemaObj['type']]}
    try:
        schemaObj['$id']
    except KeyError:
        if debug_switch: print(objName + ' has no $id.')
        objId = objName
    else:
        objId = schemaObj['$id']
    try:
        schemaObj['properties']
    except KeyError:
        if debug_switch: print(objId + ' has no properties.')
        pass
    else:
        newDict[objName]['schema'] = {}
        for key in schemaObj['properties'].keys():
            newDict[objName]['schema'][key] = translateSchema_json2cerberus(schemaObj=schemaObj['properties'][key],
                                                                            defaultValues=defaultValues,
                                                                            objName=key)[key]
    if schemaObj['type'] == 'array':
        try:
            schemaObj['items']
        except KeyError:
            if debug_switch: print(objId + ' has no items.')
            pass
        else:
            try:
                schemaObj['properties']
            except KeyError:
                if debug_switch: print(objId + ' has no properties.')
                pass
            else:
                newDict[objName]['schema'] = {}
                for item in schemaObj['items']['properties'].keys():
                    newDict[objName]['schema'][item] = translateSchema_json2cerberus(schemaObj=schemaObj['items']['properties'][item],
                                                                                     defaultValues=defaultValues,
                                                                                     objName=item)[item]
    if defaultValues is not None:
        try:
            schemaObj[objName]['default']
        except KeyError:
            if debug_switch: print(objId + ' has no default value.')
            newDict[objName]['default'] = defaultValues_dict[newDict[objName]['type']]
        else:
            newDict[objName]['default'] = schemaObj[objName]['default']
    return newDict


def schemaSkeleton(schemaObj, insertField: dict = None, excludeField: list = None):
    exclusion_list = ['properties', 'items']  # these are branch points
    if insertField is not None:
        exclusion_list += list(insertField.keys())
    if excludeField is not None:
        exclusion_list += excludeField
    if isinstance(schemaObj, list):
        new_list = []
        for element in schemaObj:
            if isinstance(element, (list, dict)) and len(schemaSkeleton(schemaObj=element, insertField=insertField, excludeField=excludeField)) != 0:
                new_list.append(schemaSkeleton(schemaObj=element, insertField=insertField, excludeField=excludeField))
        new_obj = new_list
    if isinstance(schemaObj, dict):
        new_dict = deepcopy(schemaObj)
        for key in schemaObj.keys():
            if key == '$id' and insertField is not None:
                new_dict.update(insertField)
            if isinstance(schemaObj[key], (list, dict)):
                new_dict[key] = schemaSkeleton(schemaObj=schemaObj[key], insertField=insertField, excludeField=excludeField)
                if len(new_dict[key]) == 0:
                    new_dict.pop(key)
            else:
                if key not in exclusion_list:
                    new_dict.pop(key)
        new_obj = new_dict
    return new_obj


def sortSchema(target, template):
    if isinstance(target, list):
        new_list = []
        for num, element in enumerate(template):
            if isinstance(element, (list, dict)):
                new_list.append(sortSchema(target=target[num], template=template[num]))
            else:
                new_list.append(element)
        new_obj = new_list
    if isinstance(target, dict):
        new_dict = {}
        unionSet = list(template.keys()) + [x for x in target.keys() if x not in template.keys()]
        for key in unionSet:
            if isinstance(target[key], (list, dict)):
                new_dict[key] = sortSchema(target=target[key], template=template[key])
            elif key == 'title':
                new_dict[key] = urllib.parse.unquote(os.path.basename(target['$id'])).capitalize()
            else:
                new_dict[key] = target[key]
        new_obj = new_dict
    return new_obj


def docker_path_replace(nestedObj: dict, newPath: str):
    for key in nestedObj.keys():
        if isinstance(nestedObj[key], dict):
            nestedObj[key] = docker_path_replace(nestedObj=nestedObj[key], newPath=newPath)
        elif isinstance(nestedObj[key], str):
            nestedObj[key] = nestedObj[key].replace('/AQUAMIS/analysis', newPath)
    return nestedObj
