########################################################################
# File: MetaQuery.py
# Author: A.T.
# Date: 24.02.2015
# $HeadID$
########################################################################

""" Utilities for managing metadata based queries
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__RCSID__ = "$Id$"

from DIRAC import S_OK, S_ERROR
import DIRAC.Core.Utilities.Time as Time

import json

FILE_STANDARD_METAKEYS = {
    "SE": "VARCHAR",
    "CreationDate": "DATETIME",
    "ModificationDate": "DATETIME",
    "LastAccessDate": "DATETIME",
    "User": "VARCHAR",
    "Group": "VARCHAR",
    "Path": "VARCHAR",
    "Name": "VARCHAR",
    "FileName": "VARCHAR",
    "CheckSum": "VARCHAR",
    "GUID": "VARCHAR",
    "UID": "INTEGER",
    "GID": "INTEGER",
    "Size": "INTEGER",
    "Status": "VARCHAR",
}

FILES_TABLE_METAKEYS = {
    "Name": "FileName",
    "FileName": "FileName",
    "Size": "Size",
    "User": "UID",
    "Group": "GID",
    "UID": "UID",
    "GID": "GID",
    "Status": "Status",
}

FILEINFO_TABLE_METAKEYS = {
    "GUID": "GUID",
    "CheckSum": "CheckSum",
    "CreationDate": "CreationDate",
    "ModificationDate": "ModificationDate",
    "LastAccessDate": "LastAccessDate",
}


class MetaQuery(object):
    def __init__(self, queryDict=None, typeDict=None):

        self.__metaQueryDict = {}
        if queryDict is not None:
            self.__metaQueryDict = queryDict
        self.__metaTypeDict = {}
        if typeDict is not None:
            self.__metaTypeDict = typeDict

    def setMetaQuery(self, queryList, metaTypeDict=None):
        """Create the metadata query out of the command line arguments"""
        if metaTypeDict is not None:
            self.__metaTypeDict = metaTypeDict
        metaDict = {}
        contMode = False
        value = ""
        for arg in queryList:
            if not contMode:
                operation = ""
                for op in [">=", "<=", ">", "<", "!=", "="]:
                    if op in arg:
                        operation = op
                        break
                if not operation:
                    return S_ERROR("Illegal query element %s" % arg)

                name, value = arg.split(operation)
                if name not in self.__metaTypeDict:
                    return S_ERROR("Metadata field %s not defined" % name)

                mtype = self.__metaTypeDict[name]
            else:
                value += " " + arg
                value = value.replace(contMode, "")
                contMode = False

            if value[0] in ['"', "'"] and value[-1] not in ['"', "'"]:
                contMode = value[0]
                continue

            if "," in value:
                valueList = [x.replace("'", "").replace('"', "") for x in value.split(",")]
                mvalue = valueList
                if mtype[0:3].lower() == "int":
                    mvalue = [int(x) for x in valueList if x not in ["Missing", "Any"]]
                    mvalue += [x for x in valueList if x in ["Missing", "Any"]]
                if mtype[0:5].lower() == "float":
                    mvalue = [float(x) for x in valueList if x not in ["Missing", "Any"]]
                    mvalue += [x for x in valueList if x in ["Missing", "Any"]]
                if operation == "=":
                    operation = "in"
                if operation == "!=":
                    operation = "nin"
                mvalue = {operation: mvalue}
            else:
                mvalue = value.replace("'", "").replace('"', "")
                if value not in ["Missing", "Any"]:
                    if mtype[0:3].lower() == "int":
                        mvalue = int(value)
                    if mtype[0:5].lower() == "float":
                        mvalue = float(value)
                if operation != "=":
                    mvalue = {operation: mvalue}

            if name in metaDict:
                if isinstance(metaDict[name], dict):
                    if isinstance(mvalue, dict):
                        op, value = list(mvalue.items())[0]
                        if op in metaDict[name]:
                            if isinstance(metaDict[name][op], list):
                                if isinstance(value, list):
                                    metaDict[name][op] = list(set(metaDict[name][op] + value))
                                else:
                                    metaDict[name][op] = list(set(metaDict[name][op].append(value)))
                            else:
                                if isinstance(value, list):
                                    metaDict[name][op] = list(set([metaDict[name][op]] + value))
                                else:
                                    metaDict[name][op] = list(set([metaDict[name][op], value]))
                        else:
                            metaDict[name].update(mvalue)
                    else:
                        if isinstance(mvalue, list):
                            metaDict[name].update({"in": mvalue})
                        else:
                            metaDict[name].update({"=": mvalue})
                elif isinstance(metaDict[name], list):
                    if isinstance(mvalue, dict):
                        metaDict[name] = {"in": metaDict[name]}
                        metaDict[name].update(mvalue)
                    elif isinstance(mvalue, list):
                        metaDict[name] = list(set((metaDict[name] + mvalue)))
                    else:
                        metaDict[name] = list(set(metaDict[name].append(mvalue)))
                else:
                    if isinstance(mvalue, dict):
                        metaDict[name] = {"=": metaDict[name]}
                        metaDict[name].update(mvalue)
                    elif isinstance(mvalue, list):
                        metaDict[name] = list(set([metaDict[name]] + mvalue))
                    else:
                        metaDict[name] = list(set([metaDict[name], mvalue]))
            else:
                metaDict[name] = mvalue

        self.__metaQueryDict = metaDict
        return S_OK(metaDict)

    def getMetaQuery(self):

        return self.__metaQueryDict

    def getMetaQueryAsJson(self):

        return json.dumps(self.__metaQueryDict)

    def applyQuery(self, userMetaDict):
        """Return a list of tuples with tables and conditions to locate files for a given user Metadata"""

        def getOperands(value):
            if isinstance(value, list):
                return [("in", value)]
            elif isinstance(value, dict):
                resultList = []
                for operation, operand in value.items():
                    resultList.append((operation, operand))
                return resultList
            else:
                return [("=", value)]

        def getTypedValue(value, mtype):
            if mtype[0:3].lower() == "int":
                return int(value)
            elif mtype[0:5].lower() == "float":
                return float(value)
            elif mtype[0:4].lower() == "date":
                return Time.fromString(value)
            else:
                return value

        for meta, value in self.__metaQueryDict.items():

            # Check if user dict contains all the requested meta data
            userValue = userMetaDict.get(meta, None)
            if userValue is None:
                if str(value).lower() == "missing":
                    continue
                else:
                    return S_OK(False)
            elif str(value).lower() == "any":
                continue

            mtype = self.__metaTypeDict[meta]
            try:
                userValue = getTypedValue(userValue, mtype)
            except ValueError:
                return S_ERROR("Illegal type for metadata %s: %s in user data" % (meta, str(userValue)))

            # Check operations
            for operation, operand in getOperands(value):
                try:
                    if isinstance(operand, list):
                        typedValue = [getTypedValue(x, mtype) for x in operand]
                    else:
                        typedValue = getTypedValue(operand, mtype)
                except ValueError:
                    return S_ERROR("Illegal type for metadata %s: %s in filter" % (meta, str(operand)))

                # Apply query operation
                if operation in [">", "<", ">=", "<="]:
                    if isinstance(typedValue, list):
                        return S_ERROR("Illegal query: list of values for comparison operation")
                    elif operation == ">" and typedValue >= userValue:
                        return S_OK(False)
                    elif operation == "<" and typedValue <= userValue:
                        return S_OK(False)
                    elif operation == ">=" and typedValue > userValue:
                        return S_OK(False)
                    elif operation == "<=" and typedValue < userValue:
                        return S_OK(False)
                elif operation == "in" or operation == "=":
                    if isinstance(typedValue, list) and userValue not in typedValue:
                        return S_OK(False)
                    elif not isinstance(typedValue, list) and userValue != typedValue:
                        return S_OK(False)
                elif operation == "nin" or operation == "!=":
                    if isinstance(typedValue, list) and userValue in typedValue:
                        return S_OK(False)
                    elif not isinstance(typedValue, list) and userValue == typedValue:
                        return S_OK(False)

        return S_OK(True)
