<commit_before># -*- coding: utf-8 -*-
"""
Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U

This file is part of telefonica-iot-qa-tools

iot-qa-tools is free software: you can redistribute it and/or
modify it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.

iot-qa-tools is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public
License along with iot-qa-tools.
If not, seehttp://www.gnu.org/licenses/.

For those usages not covered by the GNU Affero General Public License
please contact with::[<EMAIL>]
"""

__author__ = 'Jon & xvc'

import pystache
import requests
import json

from iotqatools.templates.cb_templates import *
from iotqatools.iot_logger import get_logger
from requests.exceptions import RequestException
from iotqatools.iot_tools import PqaTools


# Utilities
def check_valid_json(payload):
    """
    Checks if the argument is a JSON transformation valid, and return it in JSON
    :param json:
    :return:
    """
    if type(payload) is str or type(payload) is unicode:
        try:
            return json.loads(str(payload).replace('\'', '"'))
        except Exception as e:
            raise ValueError('JSON parse error in your string, check it: {string}'.format(string=payload))
    elif type(payload) is dict:
        return payload
    else:
        raise ValueError('The payloads can be only a string or a dict')


def pretty_json(json_to_print):
    """
    Fucntion that return a json with tabs to a pretty print
    :param json_to_print:
    :return:
    """
    return json.dumps(json_to_print, sort_keys=True, indent=4, separators=(',', ': '))


def check_minimal_attrs_payload(atrs_list, payload, log):
    """
    Check if the payload dict has the minimal attrs needed
    :param atrs_list:
    :param payload:
    :param log:
    :return:
    """
    if type(atrs_list) is not list:
        raise ValueError("The attributes to check has to be a list, but is: {attrs_list_type}".format(
            attrs_list_type=str(type(atrs_list))))
    if type(payload) is not dict:
        raise ValueError(
            "The payload has to be a python dict, but is: {payload_type}".format(payload_type=str(type(payload))))
    for attr in atrs_list:
        if attr not in payload:
            log.warn(
                "The payload to sent to CB has not \"{attr}\" attribute. \n Check the payload: \n {payload}".format(
                    attr=attr, payload=pretty_json(payload)))


class EntitiesConsults(object):
    """
    Class that represent the entities to build the payload to send to a contextBroker.
    The format created is:
    [
        {
            "type": "Room",
            "isPattern": "false",
            "id": "Room1"
        },
        {
            "type": "Room",
            "isPattern": "false",
            "id": "Room2"
        }
    ]
    """

    def __init__(self):
        self.entities_list = []

    def add_entity(self, entity_id, entity_type, is_pattern="false"):
        self.entities_list.append({'type': entity_type, 'isPattern': is_pattern, 'id': entity_id})

    def get_entities(self):
        return self.entities_list


class AttributesConsults(object):
    """
    Class that represent the attributes to build the payload to send to a contextBroker
    The format created is:
    [
        {
            "name": "temperature",
            "type": "float",
            "isDomain": "false"
        },
        {
            "name": "pressure",
            "type": "integer",
            "isDomain": "false"
        }
    ]
    """

    def __init__(self):
        self.attributes_list = []

    def add_attribute(self, attribute_name, attribute_type, is_domain="false"):
        self.attributes_list.append({'name': attribute_name, 'type': attribute_type, 'isDomain': is_domain})

    def get_attributes(self):
        return self.attributes_list


class MetadatasCreation(object):
    """
    Class that represent the metadata to build the attribute
    The format created is:
    [
            {
                "name": "temperature",
                "type": "float",
                "value": "23"
            },
            {
                "name": "pressure",
                "type": "integer",
                "value": "720"
            }
    ]
    """

    def __init__(self):
        self.metadatas_list = []

    def add_metadata(self, metadata_name, metadata_type, value):
        self.metadatas_list.append({'name': metadata_name, 'type': metadata_type, 'value': value})

    def get_metadatas(self):
        return self.metadatas_list


class AttributesCreation(object):
    """
    Class that represent the attributes to build the payload to send to a contextBroker
    The format created is:
    [
            {
                "name": "temperature",
                "type": "float",
                "value": "23"
            },
            {
                "name": "pressure",
                "type": "integer",
                "value": "720"
            }
    ]
    """

    def __init__(self):
        self.attributes_list = []

    def add_attribute(self, attribute_name, attribute_type, value, metadatas=None):
        if metadatas is not None:
            if not isinstance(metadatas, MetadatasCreation):
                raise ValueError('The metadatas argument has to be an instance of MetadatasCreation class')
            self.attributes_list.append({'name': attribute_name, 'type': attribute_type, 'value': value,
                                         'metadatas': metadatas.get_metadatas()})
        else:
            self.attributes_list.append({'name': attribute_name, 'type': attribute_type, 'value': value})

    def get_attributes(self):
        return self.attributes_list


class ContextElements(object):
    """
    Class that represent the attributes to build the payload to send to a contextBroker
    The format created is:
    [
        {
            "type": "Room",
            "isPattern": "false",
            "id": "Room1",
            "attributes": [
            {
                "name": "temperature",
                "type": "float",
                "value": "23"
            },
            {
                "name": "pressure",
                "type": "integer",
                "value": "720"
            }
            ]
        }
    ]
    """

    def __init__(self):
        self.context_elements_list = []

    def add_context_element(self, entity_id, entity_type, attributes, is_pattern="false"):
        if not isinstance(attributes, AttributesCreation):
            raise ValueError('The attributes argument has to be an instance of AttributesCreation class')
        self.context_elements_list.append(
            {'type': entity_type, 'isPattern': is_pattern, 'id': entity_id, 'attributes': attributes.get_attributes()})

    def get_context_elements(self):
        return self.context_elements_list


class NotifyConditions(object):
    """
    Class that represent the notify conditions to the subscriptions in ContextBroker
    The format is for ontime:
     [
        {
            "type": "ONTIMEINTERVAL",
            "condValues": [
                "PT10S"
            ]
        }
    ]
    The format is for onchange:
     [
        {
            "type": "ONCHANGE",
            "condValues": [
                "pressure", "temparature"
            ]
        }
    ]
    """

    def __init__(self):
        self.notify_conditions = []

    def add_notify_condition_ontime(self, time):
        self.notify_conditions.append({"type": "ONTIMEINTERVAL", "condValues": [time]})

    def add_notify_condition_onchange(self, attributes):
        if type(attributes) is not list:
            raise ValueError('The attributes argument has to be a list of strings with attributes')
        self.notify_conditions.append({"type": "ONCHANGE", "condValues": attributes})

    def get_notify_conditions(self):
        return self.notify_conditions


class ContextRegistrations(object):
    """
    Class that represents the registration to the Contexts Providers in the Context Broker
    The format is:
    [
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"
                },
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room2"
                }
            ],
            "attributes": [
                {
                    "name": "temperature",
                    "type": "float",
                    "isDomain": "false"
                },
                {
                    "name": "pressure",
                    "type": "integer",
                    "isDomain": "false"
                }
            ],
            "providingApplication": "http://mysensors.com/Rooms"
        }
    ]
    """

    def __init__(self):
        self.context_registrations = []

    def add_context_registration(self, entities, attributes, providing_application):
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The "entities" argument has to be an instance of EntitiesConsults')
        if not isinstance(attributes, AttributesConsults):
            raise ValueError('The "attributes" argument has to be an instance of AttributesConsults')
        self.context_registrations.append(
            {"entities": entities.get_entities(), "attributes": attributes.get_attributes(),
             "providingApplication": providing_application})

    def get_context_registrations(self):
        return self.context_registrations


class PayloadUtils(object):
    """
    Class who construct the payloads
    """
    # TODO: geographical scopes
    # TODO: Pagination

    @staticmethod
    def build_standard_entity_creation_payload(context_elements):
        """
        Build the payload to send to context broker to create a new entity with the standard api
        The format is:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "APPEND"
        }
        :return:
        """
        if not isinstance(context_elements, ContextElements):
            raise ValueError('The context_elements argument has to be an instance of ContestElements')
        payload = {
            "contextElements": context_elements.get_context_elements(),
            "updateAction": "APPEND"
        }
        return payload

    @staticmethod
    def build_convenience_entity_creation_payload(attributes, entity_id=None, entity_type=""):
        """
        Build the payload to sed to context broker so create a new entity with the convenience api
        :param entity_id:
        :param attributes:
        :param entity_type:
        :return:
        There are two kind of payloads possibles
        Only atrributes:
        {
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "23"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "720"
            }
          ]
        }
        ------
        Attributes and entity with type
        {
          "id": "Room1",
          "type": "Room",
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "23"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "720"
            }
          ]
        }
        """
        if not isinstance(attributes, AttributesCreation):
            raise ValueError('The context_elements argument has to be an instance of ContestElements')
        payload = {'attributes': attributes.get_attributes()}
        if entity_id != None:
            payload.update({"id": entity_id})
            payload.update({"type": entity_type})
        return payload

    @staticmethod
    def build_standard_query_context_payload(entities, attributes=None):
        """
        Build the payload to send to consult for some entitys to cb
        :param entities:
        :param attributes:
        :return:
        The attributes is optional.
        The format is:
        {
          "entities": [
            {
                "type": "Room",
                "isPattern": "false",
                "id": "Room1"
            }
            ],
            "attributes" : [
                "temperature"
            ]
        }
        """
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The entities argument has to be an instance of EntitiesConsults')
        if attributes != None:
            if type(attributes) is not list:
                raise ValueError('The attributes argument has to be a list of strings')
        payload = {"entities": entities.get_entities()}
        if attributes is not None:
            payload.update({"attributes": attributes})
        return payload

    @staticmethod
    def build_standard_entity_update_payload(context_elements):
        """
        Build the payload to send to context broker to create a new entity with the standard api
        The format is:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "UPDATE"
        }
        :return:
        """
        if not isinstance(context_elements, ContextElements):
            raise ValueError('The context_elements argument has to be an instance of ContestElements')
        payload = {
            "contextElements": context_elements.get_context_elements(),
            "updateAction": "UPDATE"
        }
        return payload

    @staticmethod
    def build_standard_entity_delete_payload(context_elements):
        """
        Build the payload to send to context broker to create a new entity with the standard api
        The format is:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "DELETE"
        }
        :return:
        """
        if not isinstance(context_elements, ContextElements):
            raise ValueError('The context_elements argument has to be an instance of ContestElements')
        payload = {
            "contextElements": context_elements.get_context_elements(),
            "updateAction": "DELETE"
        }
        return payload

    @staticmethod
    def build_convenience_entity_update_payload(attributes, entity_id=None, entity_type=""):
        """
        Is the same payload as in the Creation
        :param attributes:
        :param entity_id:
        :param entity_type:
        :return:
        """
        return PayloadUtils.build_convenience_entity_creation_payload(attributes, entity_id, entity_type)

    @staticmethod
    def build_convenience_entity_delete_payload(attributes, entity_id=None, entity_type=""):
        """
        Is the same payload as in the Creation
        :param attributes:
        :param entity_id:
        :param entity_type:
        :return:
        """
        return PayloadUtils.build_convenience_entity_creation_payload(attributes, entity_id, entity_type)

    @staticmethod
    def build_standard_subscribe_context_payload(entities, attributes, reference, duration, notify_conditions,
                                                 throttling=None):
        """
        Build the payload to send to subscribe to an entity and his attributes
        :return:
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"
                }
            ],
            "attributes": [
                "temperature"
            ],
            "reference": "http://localhost:1028/accumulate",
            "duration": "P1M",
            "notifyConditions": [
                {
                    "type": "ONTIMEINTERVAL",
                    "condValues": [
                        "PT10S"
                    ]
                }
            ],
            "throttling": ""
        }
        """
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The entities argument has to be an instance of EntitiesConsults')
        if type(attributes) is not list:
            raise ValueError('The attributes argument has to be a list of strings')
        if not isinstance(notify_conditions, NotifyConditions):
            raise ValueError('The notify_conditions has to be an instance of NotifyConditions')

        payload = {
            "entities": entities.get_entities(),
            "attributes": attributes,
            "reference": reference,
            "duration": duration,
            "notifyConditions": notify_conditions.get_notify_conditions()
        }
        if throttling is not None:
            payload.update({'throttling': throttling})
        return payload

    @staticmethod
    def build_standard_update_subscribe_context_payload(subscription_id, duration=None, notify_conditions=None,
                                                        throttling=None):
        """
        Build the payload to send to subscribe to an entity and his attributes
        :return:
        {
            "subscriptionId": "51c04a21d714fb3b37d7d5a7",
            "duration": "P1M",
            "notifyConditions": [
                {
                    "type": "ONTIMEINTERVAL",
                    "condValues": [
                        "PT10S"
                    ]
                }
            ],
            "throttling": "PT10S"
        }
        """
        if notify_conditions is not None:
            if not isinstance(notify_conditions, NotifyConditions):
                raise ValueError('The notify_conditions has to be an instance of NotifyConditions')
        payload = {"subscriptionId": subscription_id}
        if duration is not None:
            payload.update({"duration": duration})
        if notify_conditions is not None:
            payload.update({"notifyConditions": notify_conditions.get_notify_conditions()})
        if throttling is not None:
            payload.update({"throttling": throttling})
        return payload

    @staticmethod
    def build_standard_unsubscribe_context_payload(subscription_id):
        """
        Build the payload to send to unsubscribe from context Broker
        :param subscription_id:
        :return:
        The format is:
        {
          "subscriptionId": "51c04a21d714fb3b37d7d5a7"
        }
        """
        payload = {"subscriptionId": subscription_id}
        return payload

    # NGSI 9 -----------------------------------------------------------------------------------------------

    @staticmethod
    def build_context_registration_payload(context_registrations, duration):
        """
        Build the payload to send to register a context provider.
        Entities and attributes has to be list of entities or attributes
        The format is:
        {
            "contextRegistrations": [
                {
                    "entities": [
                        {
                            "type": "Room",
                            "isPattern": "false",
                            "id": "Room1"
                        },
                        {
                            "type": "Room",
                            "isPattern": "false",
                            "id": "Room2"
                        }
                    ],
                    "attributes": [
                        {
                            "name": "temperature",
                            "type": "float",
                            "isDomain": "false"
                        },
                        {
                            "name": "pressure",
                            "type": "integer",
                            "isDomain": "false"
                        }
                    ],
                    "providingApplication": "http://mysensors.com/Rooms"
                }
            ],
            "duration": "P1M"
        }
        """
        if not isinstance(context_registrations, ContextRegistrations):
            raise ValueError('The context_registrations argument has to be a instance of ContextRegistrations')
        payload = {
            'contextRegistrations': context_registrations.get_context_registrations(),
            'duration': duration
        }
        return payload

    @staticmethod
    def build_context_update_payload(context_registrations, duration, registration_id):
        """
        Build the payload to send to update a registration of a context provider.
        Entities and attributes has to be list of entities or attributes, ant the registration_id is needed
        The format is:
        {
            "contextRegistrations": [
                {
                    "entities": [
                        {
                            "type": "Room",
                            "isPattern": "false",
                            "id": "Room1"
                        },
                        {
                            "type": "Room",
                            "isPattern": "false",
                            "id": "Room2"
                        }
                    ],
                    "attributes": [
                        {
                            "name": "temperature",
                            "type": "float",
                            "isDomain": "false"
                        },
                        {
                            "name": "pressure",
                            "type": "integer",
                            "isDomain": "false"
                        }
                    ],
                    "providingApplication": "http://mysensors.com/Rooms"
                }
            ],
            "duration": "P1M",
            "registrationId": "aaaa"
        }
        """
        if not isinstance(context_registrations, ContextRegistrations):
            raise ValueError('The context_registrations argument has to be a instance of ContextRegistrations')
        payload = {
            'contextRegistrations': context_registrations.get_context_registrations(),
            'duration': duration,
            'registrationId': registration_id
        }

        return payload

    @staticmethod
    def build_subscribe_context_availability_payload(entities, attributes, reference, duration):
        """
        Build the subscribe context availability payload
        The format is:
        {
            "entities": [
            {
                "type": "Room",
                "isPattern": "true",
                "id": ".*"
            }
            ],
            "attributes": [
            "temperature"
            ],
            "reference": "http://localhost:1028/accumulate",
            "duration": "P1M"
        }
        """
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The entities argument has to be an instance of EntitiesConsults')
        if type(attributes) is not list:
            raise ValueError('The attributes argument has to be a list of strings')
        payload = {
            "entities": entities.get_entities(),
            "attributes": attributes,
            "reference": reference,
            "duration": duration
        }
        return payload

    @staticmethod
    def build_update_context_availability_subscription_payload(entities, subscription_id, attributes=None,
                                                               duration=None):
        """
        Build the update context availability subscription
        The format is
        {
            "entities": [
            {
                "type": "Car",
                "isPattern": "true",
                "id": ".*"
            }
            ],
            "attributes": ["temperature"]
            "duration": "P1M",
            "subscriptionId": "52a745e011f5816465943d59"
        }
        """
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The entities argument has to be an instance of EntitiesConsults')
        if attributes != None:
            if type(attributes) is not list:
                raise ValueError('The attributes argument has to be a list of strings')
        payload = {
            "entities": entities.get_entities(),
            "subscriptionId": subscription_id
        }
        if attributes is not None:
            payload.update({"attributes": attributes})
        if duration is not None:
            payload.update({"duration": duration})
        return payload

    @staticmethod
    def build_unsubscribe_context_availability_payload(subscription_id):
        """
        Build the unsubscribe context availability subscription
        The format is:
        {
            "subscriptionId": "52a745e011f5816465943d59"
        }
        """
        payload = {"subscriptionId": subscription_id}
        return payload

    @staticmethod
    def build_discover_context_availability_payload(entities):
        """
        Build the unsubscribe context availability subscription
        The format is:
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"
                }
            ]
        }
        """
        payload = {'entities': entities.get_entities()}
        return payload

    @staticmethod
    def build_convenience_register_context_payload(duration, providing_application):
        """
        Build the CONVENIENCE register context payload
        The format is_
        {
          "duration" : "P1M",
          "providingApplication" : "http://mysensors.com/Rooms"
        }
        """
        payload = {'duration': duration, 'providingApplication': providing_application}
        return payload


class CbNgsi10Utils(object):
    """
    Basic functionality for ContextBroker
    """

    def __init__(self, instance, service=None, subservice=None,
                 protocol="http",
                 port="1026",
                 path_context_entities="/v1/contextEntities",
                 path_context_entity_types="/v1/contextEntityTypes",
                 path_query_context="/v1/queryContext",
                 path_statistics="/statistics",
                 path_subscribe_context="/v1/subscribeContext",
                 path_update_context="/v1/updateContext",
                 path_update_context_subscription="/v1/updateContextSubscription",
                 path_unsubscribe_context="/v1/unsubscribeContext",
                 path_context_subscriptions="/v1/contextSubscriptions",
                 path_version="/version",
                 log_instance=None,
                 log_verbosity='DEBUG',
                 default_headers={"Accept": "application/json", 'content-type': 'application/json'},
                 check_json=True):
        """
        CB Utils constructor
        :param instance:
        :param service:
        :param subservice:
        :param protocol:
        :param port:
        :param path_context_entities:
        :param path_context_entity_types:
        :param path_query_context:
        :param path_statistics:
        :param path_subscribe_context:
        :param path_update_context:
        :param path_update_context_subscription:
        :param path_unsubscribe_context:
        :param path_context_subscriptions:
        :param path_version:
        :param log_instance:
        :param log_verbosity:
        :param default_headers:
        :param check_json:
        """
        # initialize logger
        if log_instance is not None:
            self.log = log_instance
        else:
            self.log = get_logger('CbNgsi10Utils', log_verbosity)

        # Assign the values
        self.default_endpoint = protocol + '://' + instance + ':' + port
        self.headers = self.__set_headers(default_headers, service, subservice)
        self.path_context_entities = self.default_endpoint + path_context_entities
        self.path_context_entity_types = self.default_endpoint + path_context_entity_types
        self.path_query_context = self.default_endpoint + path_query_context
        self.path_statistics = path_statistics
        self.path_subscribe_context = self.default_endpoint + path_subscribe_context
        self.path_update_context = self.default_endpoint + path_update_context
        self.path_update_context_subscription = self.default_endpoint + path_update_context_subscription
        self.path_unsubscribe_context = self.default_endpoint + path_unsubscribe_context
        self.path_context_subscriptions = self.default_endpoint + path_context_subscriptions
        self.path_version = path_version
        self.check_json = check_json

    def __send_request(self, method, url, headers=None, payload=None, verify=None, query=None):
        """
        Send a request to a specific url in a specifig type of http request
        """

        parameters = {
            'method': method,
            'url': url,
        }

        if headers is not None:
            parameters.update({'headers': headers})

        if payload is not None:
            if self.check_json:
                parameters.update({'data': json.dumps(payload)})
            else:
                parameters.update({'data': payload})

        if query is not None:
            parameters.update({'params': query})

        if verify is not None:
            parameters.update({'verify': verify})

        # Send the requests
        try:
            response = requests.request(**parameters)
        except RequestException, e:
            PqaTools.log_requestAndResponse(url=url, headers=headers, data=payload, comp='CB')
            assert False, 'ERROR: [NETWORK ERROR] {}'.format(e)

        # Log data
        PqaTools.log_fullRequest(comp='CB', response=response, params=parameters)

        return response

    def __set_headers(self, default_headers, service, subservice):
        """
        Function that set the service and subservice headers before sent. Add this headers to the defaults one
        :param service:
        :param subservice:
        :return:
        """
        headers = default_headers.copy()
        if service is not None:
            headers.update({'Fiware-Service': service})
        if subservice is not None:
            headers.update({'Fiware-Servicepath': subservice})
        return headers.copy()

    def set_service(self, service):
        self.headers['Fiware-Service'] = service

    def set_subservice(self, subservice):
        self.headers['Fiware-Servicepath'] = subservice

    def set_auth_token(self, auth_token):
        self.headers['x-auth-token'] = auth_token

    def version(self):
        """
        Get CB version
        """
        url = self.default_endpoint + self.path_version

        # send the request for the subscription
        response = self.__send_request('get', url, self.headers)
        return response

    def statistics(self):
        """
        Get CB statistics
        """
        url = self.default_endpoint + self.path_statistics

        # send the request for the subscription
        response = self.__send_request('get', url, self.headers)
        return response

    def standard_entity_creation(self, payload):
        """
        Create a entity in ContextBroker with the standard entity creation
        :param payload:
        :param service:
        :param subservice:
        :return:
        The payload has to be like:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "APPEND"
        }
        """
        if self.check_json:
            payload = check_valid_json(payload)
            # Checks the payload to send
            check_minimal_attrs_payload(["contextElements", "updateAction"], payload, self.log)
            if "updateAction" in payload:
                if payload['updateAction'] != "APPEND":
                    self.log.warn("The action of the creation is not \"APPEND\"")
        return self.__send_request('post', self.path_update_context, self.headers, payload)

    def convenience_entity_creation_url_method(self, entity_id, payload, entity_type=None):
        """
        Create an entity in Context Broker with the convenience entity creation.
        There are two ways to create:
            - Specify the Id in the url with no Type (.../contextEntity/<entity_id>)
            - Specify the Id and the Type in te url (.../contextEntity/type/<type>/id/<id>
        :param entity_id:
        :param payload:
        :param service:
        :param subservice:
        :param entity_type:
        :return:
        Payload
        {
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "21"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "711"
            }
          ]
        }
        """

        if entity_type is None:
            url = self.path_context_entities + '/{entity_id}'.format(entity_id=entity_id)
        else:
            url = self.path_context_entities + '/type/{entity_type}/id/{entity_id}'.format(entity_type=entity_type,
                                                                                           entity_id=entity_id)
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["attributes"], payload, self.log)
        return self.__send_request('post', url, self.headers, payload)

    def convenience_entity_creation_payload_method(self, payload):
        """
        Create an entity in Context Broker with the convenience entity creation.
        The entity and his type is indicated in the payload
        :param payload:
        :param service:
        :param subservice:
        :return:
        Payload with entity information
        {
          "id": "Room1",
          "type": "Room",
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "23"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "720"
            }
          ]
        }
        """
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["attributes", "id", "type"], payload, self.log)
        return self.__send_request('post', self.path_context_entities, self.headers, payload)

    def standard_query_context(self, payload):
        """
        Query an entity with the possibility of filtering for the attributes
        :param payload:
        :param service:
        :param subservice:
        :return:
        payload:
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"
                }
            ],
            "attributes" : [
                "temperature"
            ]
        }
        """


        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["entities"], payload, self.log)
        return self.__send_request('post', self.path_query_context, self.headers, payload)

    def convenience_query_context(self, entity_id=None, entity_type=None, attribute=None):
        """
        Query an entity with the possibility of filtering for the attribute, and the posiibility
        of not send entity retrieving all entities
        :
        :param entity_id:
        :param entity_type:
        :param attribute:
        :return:
        """

        if entity_id is not None:
            url = self.path_context_entities
            if entity_type is not None:
                url += '/type/{entity_type}/id/{entity_id}'.format(entity_type=entity_type, entity_id=entity_id)
            else:
                url += '/{entity_id}'.format(entity_type=entity_type, entity_id=entity_id)
            if attribute is not None:
                url += '/attributes/{attribute}'.format(attribute=attribute)
        else:
            if entity_type is not None:
                url = self.path_context_entity_types
                if attribute is not None:
                    url += '/{entity_type}/attributes/{attribute}'.format(entity_type=entity_type, attribute=attribute)
                else:
                    url += '/{entity_type}'.format(entity_type=entity_type)
            else:
                # Retrieve all entities with all attributes
                url = self.path_context_entities
        return self.__send_request('get', url, self.headers)

    def standard_entity_update(self, payload):
        """
        Create a entity in ContextBroker with the standard entity creation
        :param payload:
        :param service:
        :param subservice:
        :return:
        The payload has to be like:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "UPDATE"
        }
        """
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["contextElements", "updateAction"], payload, self.log)
            if "updateAction" in payload:
                if payload['updateAction'] != "UPDATE":
                    self.log.warn("The action of the creation is not \"UPDATE\", is: {updateaction}".format(
                        updateaction=payload['updateSction']))
        return self.__send_request('post', self.path_update_context, self.headers, payload)

    def convenience_entity_update_url_method(self, entity_id, payload, entity_type=None):
        """
        Update an entity in Context Broker with the convenience entity udpate.
        There are two ways to create:
            - Specify the Id in the url with no Type (.../contextEntity/<entity_id>)
            - Specify the Id and the Type in te url (.../contextEntity/type/<type>/id/<id>
        :param entity_id:
        :param payload:
        :param service:
        :param subservice:
        :param entity_type:
        :return:
        Payload
        {
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "21"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "711"
            }
          ]
        }
        """

        if entity_type is None:
            url = self.path_context_entities + '/{entity_id}'.format(entity_id=entity_id)
        else:
            url = self.path_context_entities + '/type/{entity_type}/id/{entity_id}'.format(entity_type=entity_type,
                                                                                           entity_id=entity_id)
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["attributes"], payload, self.log)
        return self.__send_request('put', url, self.headers, payload)

    def convenience_entity_update_payload_method(self, payload):
        """
        Update an entity in Context Broker with the convenience entity update.
        The entity and his type is indicated in the payload
        :param payload:
        :return:
        Payload with entity information
        {
          "id": "Room1",
          "type": "Room",
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "23"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "720"
            }
          ]
        }
        """
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["attributes", "id", "type"], payload, self.log)
        return self.__send_request('put', self.path_context_entities, self.headers, payload)

    # **
    def standard_entity_delete(self, payload):
        """
        Delete a entity in ContextBroker with the standard entity deletion
        :param payload:
        :return:
        The payload has to be like:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "DELETE"
        }
        """
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["contextElements", "updateAction"], payload, self.log)
            if "updateAction" in payload:
                if payload['updateAction'] != "DELETE":
                    self.log.warn("The action of the creation is not \"DELETE\", is: {updateaction}".format(
                        updateaction=payload['updateSction']))
        return self.__send_request('post', self.path_update_context, self.headers, payload)

    def convenience_entity_delete_url_method(self, entity_id, entity_type=None):
        """
        Delete an entity in Context Broker with the convenience entity deletion.
        There are two ways to create:
            - Specify the Id in the url with no Type (.../contextEntity/<entity_id>)
            - Specify the Id and the Type in te url (.../contextEntity/type/<type>/id/<id>
        :param entity_id:
        :param entity_type:
        :return:
        """

        if entity_type is None:
            url = self.path_context_entities + '/{entity_id}'.format(entity_id=entity_id)
        else:
            url = self.path_context_entities + '/type/{entity_type}/id/{entity_id}'.format(entity_type=entity_type,
                                                                                           entity_id=entity_id)
        return self.__send_request('delete', url, self.headers)

    # **

    def standard_subscribe_context_ontime(self, payload):
        """
        Create a subscription in the context broker
        :param payload
        The format of the payload is:
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"<commit_msg>add service and servicePath to def convenience_entity_delete_url_method<commit_after># -*- coding: utf-8 -*-
"""
Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U

This file is part of telefonica-iot-qa-tools

iot-qa-tools is free software: you can redistribute it and/or
modify it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.

iot-qa-tools is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public
License along with iot-qa-tools.
If not, seehttp://www.gnu.org/licenses/.

For those usages not covered by the GNU Affero General Public License
please contact with::[<EMAIL>]
"""

__author__ = 'Jon & xvc'

import pystache
import requests
import json

from copy import deepcopy
from iotqatools.templates.cb_templates import *
from iotqatools.iot_logger import get_logger
from requests.exceptions import RequestException
from iotqatools.iot_tools import PqaTools


# Utilities
def check_valid_json(payload):
    """
    Checks if the argument is a JSON transformation valid, and return it in JSON
    :param json:
    :return:
    """
    if type(payload) is str or type(payload) is unicode:
        try:
            return json.loads(str(payload).replace('\'', '"'))
        except Exception as e:
            raise ValueError('JSON parse error in your string, check it: {string}'.format(string=payload))
    elif type(payload) is dict:
        return payload
    else:
        raise ValueError('The payloads can be only a string or a dict')


def pretty_json(json_to_print):
    """
    Fucntion that return a json with tabs to a pretty print
    :param json_to_print:
    :return:
    """
    return json.dumps(json_to_print, sort_keys=True, indent=4, separators=(',', ': '))


def check_minimal_attrs_payload(atrs_list, payload, log):
    """
    Check if the payload dict has the minimal attrs needed
    :param atrs_list:
    :param payload:
    :param log:
    :return:
    """
    if type(atrs_list) is not list:
        raise ValueError("The attributes to check has to be a list, but is: {attrs_list_type}".format(
            attrs_list_type=str(type(atrs_list))))
    if type(payload) is not dict:
        raise ValueError(
            "The payload has to be a python dict, but is: {payload_type}".format(payload_type=str(type(payload))))
    for attr in atrs_list:
        if attr not in payload:
            log.warn(
                "The payload to sent to CB has not \"{attr}\" attribute. \n Check the payload: \n {payload}".format(
                    attr=attr, payload=pretty_json(payload)))


class EntitiesConsults(object):
    """
    Class that represent the entities to build the payload to send to a contextBroker.
    The format created is:
    [
        {
            "type": "Room",
            "isPattern": "false",
            "id": "Room1"
        },
        {
            "type": "Room",
            "isPattern": "false",
            "id": "Room2"
        }
    ]
    """

    def __init__(self):
        self.entities_list = []

    def add_entity(self, entity_id, entity_type, is_pattern="false"):
        self.entities_list.append({'type': entity_type, 'isPattern': is_pattern, 'id': entity_id})

    def get_entities(self):
        return self.entities_list


class AttributesConsults(object):
    """
    Class that represent the attributes to build the payload to send to a contextBroker
    The format created is:
    [
        {
            "name": "temperature",
            "type": "float",
            "isDomain": "false"
        },
        {
            "name": "pressure",
            "type": "integer",
            "isDomain": "false"
        }
    ]
    """

    def __init__(self):
        self.attributes_list = []

    def add_attribute(self, attribute_name, attribute_type, is_domain="false"):
        self.attributes_list.append({'name': attribute_name, 'type': attribute_type, 'isDomain': is_domain})

    def get_attributes(self):
        return self.attributes_list


class MetadatasCreation(object):
    """
    Class that represent the metadata to build the attribute
    The format created is:
    [
            {
                "name": "temperature",
                "type": "float",
                "value": "23"
            },
            {
                "name": "pressure",
                "type": "integer",
                "value": "720"
            }
    ]
    """

    def __init__(self):
        self.metadatas_list = []

    def add_metadata(self, metadata_name, metadata_type, value):
        self.metadatas_list.append({'name': metadata_name, 'type': metadata_type, 'value': value})

    def get_metadatas(self):
        return self.metadatas_list


class AttributesCreation(object):
    """
    Class that represent the attributes to build the payload to send to a contextBroker
    The format created is:
    [
            {
                "name": "temperature",
                "type": "float",
                "value": "23"
            },
            {
                "name": "pressure",
                "type": "integer",
                "value": "720"
            }
    ]
    """

    def __init__(self):
        self.attributes_list = []

    def add_attribute(self, attribute_name, attribute_type, value, metadatas=None):
        if metadatas is not None:
            if not isinstance(metadatas, MetadatasCreation):
                raise ValueError('The metadatas argument has to be an instance of MetadatasCreation class')
            self.attributes_list.append({'name': attribute_name, 'type': attribute_type, 'value': value,
                                         'metadatas': metadatas.get_metadatas()})
        else:
            self.attributes_list.append({'name': attribute_name, 'type': attribute_type, 'value': value})

    def get_attributes(self):
        return self.attributes_list


class ContextElements(object):
    """
    Class that represent the attributes to build the payload to send to a contextBroker
    The format created is:
    [
        {
            "type": "Room",
            "isPattern": "false",
            "id": "Room1",
            "attributes": [
            {
                "name": "temperature",
                "type": "float",
                "value": "23"
            },
            {
                "name": "pressure",
                "type": "integer",
                "value": "720"
            }
            ]
        }
    ]
    """

    def __init__(self):
        self.context_elements_list = []

    def add_context_element(self, entity_id, entity_type, attributes, is_pattern="false"):
        if not isinstance(attributes, AttributesCreation):
            raise ValueError('The attributes argument has to be an instance of AttributesCreation class')
        self.context_elements_list.append(
            {'type': entity_type, 'isPattern': is_pattern, 'id': entity_id, 'attributes': attributes.get_attributes()})

    def get_context_elements(self):
        return self.context_elements_list


class NotifyConditions(object):
    """
    Class that represent the notify conditions to the subscriptions in ContextBroker
    The format is for ontime:
     [
        {
            "type": "ONTIMEINTERVAL",
            "condValues": [
                "PT10S"
            ]
        }
    ]
    The format is for onchange:
     [
        {
            "type": "ONCHANGE",
            "condValues": [
                "pressure", "temparature"
            ]
        }
    ]
    """

    def __init__(self):
        self.notify_conditions = []

    def add_notify_condition_ontime(self, time):
        self.notify_conditions.append({"type": "ONTIMEINTERVAL", "condValues": [time]})

    def add_notify_condition_onchange(self, attributes):
        if type(attributes) is not list:
            raise ValueError('The attributes argument has to be a list of strings with attributes')
        self.notify_conditions.append({"type": "ONCHANGE", "condValues": attributes})

    def get_notify_conditions(self):
        return self.notify_conditions


class ContextRegistrations(object):
    """
    Class that represents the registration to the Contexts Providers in the Context Broker
    The format is:
    [
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"
                },
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room2"
                }
            ],
            "attributes": [
                {
                    "name": "temperature",
                    "type": "float",
                    "isDomain": "false"
                },
                {
                    "name": "pressure",
                    "type": "integer",
                    "isDomain": "false"
                }
            ],
            "providingApplication": "http://mysensors.com/Rooms"
        }
    ]
    """

    def __init__(self):
        self.context_registrations = []

    def add_context_registration(self, entities, attributes, providing_application):
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The "entities" argument has to be an instance of EntitiesConsults')
        if not isinstance(attributes, AttributesConsults):
            raise ValueError('The "attributes" argument has to be an instance of AttributesConsults')
        self.context_registrations.append(
            {"entities": entities.get_entities(), "attributes": attributes.get_attributes(),
             "providingApplication": providing_application})

    def get_context_registrations(self):
        return self.context_registrations


class PayloadUtils(object):
    """
    Class who construct the payloads
    """
    # TODO: geographical scopes
    # TODO: Pagination

    @staticmethod
    def build_standard_entity_creation_payload(context_elements):
        """
        Build the payload to send to context broker to create a new entity with the standard api
        The format is:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "APPEND"
        }
        :return:
        """
        if not isinstance(context_elements, ContextElements):
            raise ValueError('The context_elements argument has to be an instance of ContestElements')
        payload = {
            "contextElements": context_elements.get_context_elements(),
            "updateAction": "APPEND"
        }
        return payload

    @staticmethod
    def build_convenience_entity_creation_payload(attributes, entity_id=None, entity_type=""):
        """
        Build the payload to sed to context broker so create a new entity with the convenience api
        :param entity_id:
        :param attributes:
        :param entity_type:
        :return:
        There are two kind of payloads possibles
        Only atrributes:
        {
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "23"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "720"
            }
          ]
        }
        ------
        Attributes and entity with type
        {
          "id": "Room1",
          "type": "Room",
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "23"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "720"
            }
          ]
        }
        """
        if not isinstance(attributes, AttributesCreation):
            raise ValueError('The context_elements argument has to be an instance of ContestElements')
        payload = {'attributes': attributes.get_attributes()}
        if entity_id != None:
            payload.update({"id": entity_id})
            payload.update({"type": entity_type})
        return payload

    @staticmethod
    def build_standard_query_context_payload(entities, attributes=None):
        """
        Build the payload to send to consult for some entitys to cb
        :param entities:
        :param attributes:
        :return:
        The attributes is optional.
        The format is:
        {
          "entities": [
            {
                "type": "Room",
                "isPattern": "false",
                "id": "Room1"
            }
            ],
            "attributes" : [
                "temperature"
            ]
        }
        """
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The entities argument has to be an instance of EntitiesConsults')
        if attributes != None:
            if type(attributes) is not list:
                raise ValueError('The attributes argument has to be a list of strings')
        payload = {"entities": entities.get_entities()}
        if attributes is not None:
            payload.update({"attributes": attributes})
        return payload

    @staticmethod
    def build_standard_entity_update_payload(context_elements):
        """
        Build the payload to send to context broker to create a new entity with the standard api
        The format is:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "UPDATE"
        }
        :return:
        """
        if not isinstance(context_elements, ContextElements):
            raise ValueError('The context_elements argument has to be an instance of ContestElements')
        payload = {
            "contextElements": context_elements.get_context_elements(),
            "updateAction": "UPDATE"
        }
        return payload

    @staticmethod
    def build_standard_entity_delete_payload(context_elements):
        """
        Build the payload to send to context broker to create a new entity with the standard api
        The format is:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "DELETE"
        }
        :return:
        """
        if not isinstance(context_elements, ContextElements):
            raise ValueError('The context_elements argument has to be an instance of ContestElements')
        payload = {
            "contextElements": context_elements.get_context_elements(),
            "updateAction": "DELETE"
        }
        return payload

    @staticmethod
    def build_convenience_entity_update_payload(attributes, entity_id=None, entity_type=""):
        """
        Is the same payload as in the Creation
        :param attributes:
        :param entity_id:
        :param entity_type:
        :return:
        """
        return PayloadUtils.build_convenience_entity_creation_payload(attributes, entity_id, entity_type)

    @staticmethod
    def build_convenience_entity_delete_payload(attributes, entity_id=None, entity_type=""):
        """
        Is the same payload as in the Creation
        :param attributes:
        :param entity_id:
        :param entity_type:
        :return:
        """
        return PayloadUtils.build_convenience_entity_creation_payload(attributes, entity_id, entity_type)

    @staticmethod
    def build_standard_subscribe_context_payload(entities, attributes, reference, duration, notify_conditions,
                                                 throttling=None):
        """
        Build the payload to send to subscribe to an entity and his attributes
        :return:
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"
                }
            ],
            "attributes": [
                "temperature"
            ],
            "reference": "http://localhost:1028/accumulate",
            "duration": "P1M",
            "notifyConditions": [
                {
                    "type": "ONTIMEINTERVAL",
                    "condValues": [
                        "PT10S"
                    ]
                }
            ],
            "throttling": ""
        }
        """
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The entities argument has to be an instance of EntitiesConsults')
        if type(attributes) is not list:
            raise ValueError('The attributes argument has to be a list of strings')
        if not isinstance(notify_conditions, NotifyConditions):
            raise ValueError('The notify_conditions has to be an instance of NotifyConditions')

        payload = {
            "entities": entities.get_entities(),
            "attributes": attributes,
            "reference": reference,
            "duration": duration,
            "notifyConditions": notify_conditions.get_notify_conditions()
        }
        if throttling is not None:
            payload.update({'throttling': throttling})
        return payload

    @staticmethod
    def build_standard_update_subscribe_context_payload(subscription_id, duration=None, notify_conditions=None,
                                                        throttling=None):
        """
        Build the payload to send to subscribe to an entity and his attributes
        :return:
        {
            "subscriptionId": "51c04a21d714fb3b37d7d5a7",
            "duration": "P1M",
            "notifyConditions": [
                {
                    "type": "ONTIMEINTERVAL",
                    "condValues": [
                        "PT10S"
                    ]
                }
            ],
            "throttling": "PT10S"
        }
        """
        if notify_conditions is not None:
            if not isinstance(notify_conditions, NotifyConditions):
                raise ValueError('The notify_conditions has to be an instance of NotifyConditions')
        payload = {"subscriptionId": subscription_id}
        if duration is not None:
            payload.update({"duration": duration})
        if notify_conditions is not None:
            payload.update({"notifyConditions": notify_conditions.get_notify_conditions()})
        if throttling is not None:
            payload.update({"throttling": throttling})
        return payload

    @staticmethod
    def build_standard_unsubscribe_context_payload(subscription_id):
        """
        Build the payload to send to unsubscribe from context Broker
        :param subscription_id:
        :return:
        The format is:
        {
          "subscriptionId": "51c04a21d714fb3b37d7d5a7"
        }
        """
        payload = {"subscriptionId": subscription_id}
        return payload

    # NGSI 9 -----------------------------------------------------------------------------------------------

    @staticmethod
    def build_context_registration_payload(context_registrations, duration):
        """
        Build the payload to send to register a context provider.
        Entities and attributes has to be list of entities or attributes
        The format is:
        {
            "contextRegistrations": [
                {
                    "entities": [
                        {
                            "type": "Room",
                            "isPattern": "false",
                            "id": "Room1"
                        },
                        {
                            "type": "Room",
                            "isPattern": "false",
                            "id": "Room2"
                        }
                    ],
                    "attributes": [
                        {
                            "name": "temperature",
                            "type": "float",
                            "isDomain": "false"
                        },
                        {
                            "name": "pressure",
                            "type": "integer",
                            "isDomain": "false"
                        }
                    ],
                    "providingApplication": "http://mysensors.com/Rooms"
                }
            ],
            "duration": "P1M"
        }
        """
        if not isinstance(context_registrations, ContextRegistrations):
            raise ValueError('The context_registrations argument has to be a instance of ContextRegistrations')
        payload = {
            'contextRegistrations': context_registrations.get_context_registrations(),
            'duration': duration
        }
        return payload

    @staticmethod
    def build_context_update_payload(context_registrations, duration, registration_id):
        """
        Build the payload to send to update a registration of a context provider.
        Entities and attributes has to be list of entities or attributes, ant the registration_id is needed
        The format is:
        {
            "contextRegistrations": [
                {
                    "entities": [
                        {
                            "type": "Room",
                            "isPattern": "false",
                            "id": "Room1"
                        },
                        {
                            "type": "Room",
                            "isPattern": "false",
                            "id": "Room2"
                        }
                    ],
                    "attributes": [
                        {
                            "name": "temperature",
                            "type": "float",
                            "isDomain": "false"
                        },
                        {
                            "name": "pressure",
                            "type": "integer",
                            "isDomain": "false"
                        }
                    ],
                    "providingApplication": "http://mysensors.com/Rooms"
                }
            ],
            "duration": "P1M",
            "registrationId": "aaaa"
        }
        """
        if not isinstance(context_registrations, ContextRegistrations):
            raise ValueError('The context_registrations argument has to be a instance of ContextRegistrations')
        payload = {
            'contextRegistrations': context_registrations.get_context_registrations(),
            'duration': duration,
            'registrationId': registration_id
        }

        return payload

    @staticmethod
    def build_subscribe_context_availability_payload(entities, attributes, reference, duration):
        """
        Build the subscribe context availability payload
        The format is:
        {
            "entities": [
            {
                "type": "Room",
                "isPattern": "true",
                "id": ".*"
            }
            ],
            "attributes": [
            "temperature"
            ],
            "reference": "http://localhost:1028/accumulate",
            "duration": "P1M"
        }
        """
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The entities argument has to be an instance of EntitiesConsults')
        if type(attributes) is not list:
            raise ValueError('The attributes argument has to be a list of strings')
        payload = {
            "entities": entities.get_entities(),
            "attributes": attributes,
            "reference": reference,
            "duration": duration
        }
        return payload

    @staticmethod
    def build_update_context_availability_subscription_payload(entities, subscription_id, attributes=None,
                                                               duration=None):
        """
        Build the update context availability subscription
        The format is
        {
            "entities": [
            {
                "type": "Car",
                "isPattern": "true",
                "id": ".*"
            }
            ],
            "attributes": ["temperature"]
            "duration": "P1M",
            "subscriptionId": "52a745e011f5816465943d59"
        }
        """
        if not isinstance(entities, EntitiesConsults):
            raise ValueError('The entities argument has to be an instance of EntitiesConsults')
        if attributes != None:
            if type(attributes) is not list:
                raise ValueError('The attributes argument has to be a list of strings')
        payload = {
            "entities": entities.get_entities(),
            "subscriptionId": subscription_id
        }
        if attributes is not None:
            payload.update({"attributes": attributes})
        if duration is not None:
            payload.update({"duration": duration})
        return payload

    @staticmethod
    def build_unsubscribe_context_availability_payload(subscription_id):
        """
        Build the unsubscribe context availability subscription
        The format is:
        {
            "subscriptionId": "52a745e011f5816465943d59"
        }
        """
        payload = {"subscriptionId": subscription_id}
        return payload

    @staticmethod
    def build_discover_context_availability_payload(entities):
        """
        Build the unsubscribe context availability subscription
        The format is:
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"
                }
            ]
        }
        """
        payload = {'entities': entities.get_entities()}
        return payload

    @staticmethod
    def build_convenience_register_context_payload(duration, providing_application):
        """
        Build the CONVENIENCE register context payload
        The format is_
        {
          "duration" : "P1M",
          "providingApplication" : "http://mysensors.com/Rooms"
        }
        """
        payload = {'duration': duration, 'providingApplication': providing_application}
        return payload


class CbNgsi10Utils(object):
    """
    Basic functionality for ContextBroker
    """

    def __init__(self, instance, service=None, subservice=None,
                 protocol="http",
                 port="1026",
                 path_context_entities="/v1/contextEntities",
                 path_context_entity_types="/v1/contextEntityTypes",
                 path_query_context="/v1/queryContext",
                 path_statistics="/statistics",
                 path_subscribe_context="/v1/subscribeContext",
                 path_update_context="/v1/updateContext",
                 path_update_context_subscription="/v1/updateContextSubscription",
                 path_unsubscribe_context="/v1/unsubscribeContext",
                 path_context_subscriptions="/v1/contextSubscriptions",
                 path_version="/version",
                 log_instance=None,
                 log_verbosity='DEBUG',
                 default_headers={"Accept": "application/json", 'content-type': 'application/json'},
                 check_json=True):
        """
        CB Utils constructor
        :param instance:
        :param service:
        :param subservice:
        :param protocol:
        :param port:
        :param path_context_entities:
        :param path_context_entity_types:
        :param path_query_context:
        :param path_statistics:
        :param path_subscribe_context:
        :param path_update_context:
        :param path_update_context_subscription:
        :param path_unsubscribe_context:
        :param path_context_subscriptions:
        :param path_version:
        :param log_instance:
        :param log_verbosity:
        :param default_headers:
        :param check_json:
        """
        # initialize logger
        if log_instance is not None:
            self.log = log_instance
        else:
            self.log = get_logger('CbNgsi10Utils', log_verbosity)

        # Assign the values
        self.default_endpoint = protocol + '://' + instance + ':' + port
        self.headers = self.__set_headers(default_headers, service, subservice)
        self.path_context_entities = self.default_endpoint + path_context_entities
        self.path_context_entity_types = self.default_endpoint + path_context_entity_types
        self.path_query_context = self.default_endpoint + path_query_context
        self.path_statistics = path_statistics
        self.path_subscribe_context = self.default_endpoint + path_subscribe_context
        self.path_update_context = self.default_endpoint + path_update_context
        self.path_update_context_subscription = self.default_endpoint + path_update_context_subscription
        self.path_unsubscribe_context = self.default_endpoint + path_unsubscribe_context
        self.path_context_subscriptions = self.default_endpoint + path_context_subscriptions
        self.path_version = path_version
        self.check_json = check_json

    def __send_request(self, method, url, headers=None, payload=None, verify=None, query=None):
        """
        Send a request to a specific url in a specifig type of http request
        """

        parameters = {
            'method': method,
            'url': url,
        }

        if headers is not None:
            parameters.update({'headers': headers})

        if payload is not None:
            if self.check_json:
                parameters.update({'data': json.dumps(payload)})
            else:
                parameters.update({'data': payload})

        if query is not None:
            parameters.update({'params': query})

        if verify is not None:
            parameters.update({'verify': verify})

        # Send the requests
        try:
            response = requests.request(**parameters)
        except RequestException, e:
            PqaTools.log_requestAndResponse(url=url, headers=headers, data=payload, comp='CB')
            assert False, 'ERROR: [NETWORK ERROR] {}'.format(e)

        # Log data
        PqaTools.log_fullRequest(comp='CB', response=response, params=parameters)

        return response

    def __set_headers(self, default_headers, service, subservice):
        """
        Function that set the service and subservice headers before sent. Add this headers to the defaults one
        :param service:
        :param subservice:
        :return:
        """
        headers = default_headers.copy()
        if service is not None:
            headers.update({'Fiware-Service': service})
        if subservice is not None:
            headers.update({'Fiware-Servicepath': subservice})
        return headers.copy()

    def set_service(self, service):
        self.headers['Fiware-Service'] = service

    def set_subservice(self, subservice):
        self.headers['Fiware-Servicepath'] = subservice

    def set_auth_token(self, auth_token):
        self.headers['x-auth-token'] = auth_token

    def version(self):
        """
        Get CB version
        """
        url = self.default_endpoint + self.path_version

        # send the request for the subscription
        response = self.__send_request('get', url, self.headers)
        return response

    def statistics(self):
        """
        Get CB statistics
        """
        url = self.default_endpoint + self.path_statistics

        # send the request for the subscription
        response = self.__send_request('get', url, self.headers)
        return response

    def standard_entity_creation(self, payload):
        """
        Create a entity in ContextBroker with the standard entity creation
        :param payload:
        :param service:
        :param subservice:
        :return:
        The payload has to be like:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "APPEND"
        }
        """
        if self.check_json:
            payload = check_valid_json(payload)
            # Checks the payload to send
            check_minimal_attrs_payload(["contextElements", "updateAction"], payload, self.log)
            if "updateAction" in payload:
                if payload['updateAction'] != "APPEND":
                    self.log.warn("The action of the creation is not \"APPEND\"")
        return self.__send_request('post', self.path_update_context, self.headers, payload)

    def convenience_entity_creation_url_method(self, entity_id, payload, entity_type=None):
        """
        Create an entity in Context Broker with the convenience entity creation.
        There are two ways to create:
            - Specify the Id in the url with no Type (.../contextEntity/<entity_id>)
            - Specify the Id and the Type in te url (.../contextEntity/type/<type>/id/<id>
        :param entity_id:
        :param payload:
        :param service:
        :param subservice:
        :param entity_type:
        :return:
        Payload
        {
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "21"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "711"
            }
          ]
        }
        """

        if entity_type is None:
            url = self.path_context_entities + '/{entity_id}'.format(entity_id=entity_id)
        else:
            url = self.path_context_entities + '/type/{entity_type}/id/{entity_id}'.format(entity_type=entity_type,
                                                                                           entity_id=entity_id)
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["attributes"], payload, self.log)
        return self.__send_request('post', url, self.headers, payload)

    def convenience_entity_creation_payload_method(self, payload):
        """
        Create an entity in Context Broker with the convenience entity creation.
        The entity and his type is indicated in the payload
        :param payload:
        :param service:
        :param subservice:
        :return:
        Payload with entity information
        {
          "id": "Room1",
          "type": "Room",
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "23"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "720"
            }
          ]
        }
        """
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["attributes", "id", "type"], payload, self.log)
        return self.__send_request('post', self.path_context_entities, self.headers, payload)

    def standard_query_context(self, payload):
        """
        Query an entity with the possibility of filtering for the attributes
        :param payload:
        :param service:
        :param subservice:
        :return:
        payload:
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"
                }
            ],
            "attributes" : [
                "temperature"
            ]
        }
        """


        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["entities"], payload, self.log)
        return self.__send_request('post', self.path_query_context, self.headers, payload)

    def convenience_query_context(self, entity_id=None, entity_type=None, attribute=None):
        """
        Query an entity with the possibility of filtering for the attribute, and the posiibility
        of not send entity retrieving all entities
        :
        :param entity_id:
        :param entity_type:
        :param attribute:
        :return:
        """

        if entity_id is not None:
            url = self.path_context_entities
            if entity_type is not None:
                url += '/type/{entity_type}/id/{entity_id}'.format(entity_type=entity_type, entity_id=entity_id)
            else:
                url += '/{entity_id}'.format(entity_type=entity_type, entity_id=entity_id)
            if attribute is not None:
                url += '/attributes/{attribute}'.format(attribute=attribute)
        else:
            if entity_type is not None:
                url = self.path_context_entity_types
                if attribute is not None:
                    url += '/{entity_type}/attributes/{attribute}'.format(entity_type=entity_type, attribute=attribute)
                else:
                    url += '/{entity_type}'.format(entity_type=entity_type)
            else:
                # Retrieve all entities with all attributes
                url = self.path_context_entities
        return self.__send_request('get', url, self.headers)

    def standard_entity_update(self, payload):
        """
        Create a entity in ContextBroker with the standard entity creation
        :param payload:
        :param service:
        :param subservice:
        :return:
        The payload has to be like:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "UPDATE"
        }
        """
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["contextElements", "updateAction"], payload, self.log)
            if "updateAction" in payload:
                if payload['updateAction'] != "UPDATE":
                    self.log.warn("The action of the creation is not \"UPDATE\", is: {updateaction}".format(
                        updateaction=payload['updateSction']))
        return self.__send_request('post', self.path_update_context, self.headers, payload)

    def convenience_entity_update_url_method(self, entity_id, payload, entity_type=None):
        """
        Update an entity in Context Broker with the convenience entity udpate.
        There are two ways to create:
            - Specify the Id in the url with no Type (.../contextEntity/<entity_id>)
            - Specify the Id and the Type in te url (.../contextEntity/type/<type>/id/<id>
        :param entity_id:
        :param payload:
        :param service:
        :param subservice:
        :param entity_type:
        :return:
        Payload
        {
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "21"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "711"
            }
          ]
        }
        """

        if entity_type is None:
            url = self.path_context_entities + '/{entity_id}'.format(entity_id=entity_id)
        else:
            url = self.path_context_entities + '/type/{entity_type}/id/{entity_id}'.format(entity_type=entity_type,
                                                                                           entity_id=entity_id)
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["attributes"], payload, self.log)
        return self.__send_request('put', url, self.headers, payload)

    def convenience_entity_update_payload_method(self, payload):
        """
        Update an entity in Context Broker with the convenience entity update.
        The entity and his type is indicated in the payload
        :param payload:
        :return:
        Payload with entity information
        {
          "id": "Room1",
          "type": "Room",
          "attributes" : [
            {
              "name" : "temperature",
              "type" : "float",
              "value" : "23"
            },
            {
              "name" : "pressure",
              "type" : "integer",
              "value" : "720"
            }
          ]
        }
        """
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["attributes", "id", "type"], payload, self.log)
        return self.__send_request('put', self.path_context_entities, self.headers, payload)

    # **
    def standard_entity_delete(self, payload):
        """
        Delete a entity in ContextBroker with the standard entity deletion
        :param payload:
        :return:
        The payload has to be like:
        {
            "contextElements": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1",
                    "attributes": [
                    {
                        "name": "temperature",
                        "type": "float",
                        "value": "23"
                    },
                    {
                        "name": "pressure",
                        "type": "integer",
                        "value": "720"
                    }
                    ]
                }
            ],
            "updateAction": "DELETE"
        }
        """
        # Checks the payload to send
        if self.check_json:
            payload = check_valid_json(payload)
            check_minimal_attrs_payload(["contextElements", "updateAction"], payload, self.log)
            if "updateAction" in payload:
                if payload['updateAction'] != "DELETE":
                    self.log.warn("The action of the creation is not \"DELETE\", is: {updateaction}".format(
                        updateaction=payload['updateSction']))
        return self.__send_request('post', self.path_update_context, self.headers, payload)

    def convenience_entity_delete_url_method(self, entity_id, entity_type=None, service='', service_path=''):
        """
        Delete an entity in Context Broker with the convenience entity deletion.
        There are two ways to create:
            - Specify the Id in the url with no Type (.../contextEntity/<entity_id>)
            - Specify the Id and the Type in te url (.../contextEntity/type/<type>/id/<id>
        :param entity_id:
        :param entity_type:
        :return:
        """

        if entity_type is None:
            url = self.path_context_entities + '/{entity_id}'.format(entity_id=entity_id)
        else:
            url = self.path_context_entities + '/type/{entity_type}/id/{entity_id}'.format(entity_type=entity_type,
                                                                                           entity_id=entity_id)

        myheaders = copy.deepcopy(self.headers)
        if service:
            myheaders["Fiware-Service"]= service
        if service_path:
            myheaders["Fiware-ServicePath"]= service_path

        return self.__send_request('delete', url, myheaders)

    # **

    def standard_subscribe_context_ontime(self, payload):
        """
        Create a subscription in the context broker
        :param payload
        The format of the payload is:
        {
            "entities": [
                {
                    "type": "Room",
                    "isPattern": "false",
                    "id": "Room1"
{'fraction_non_alphanumeric': 0.09733863302514473, 'fraction_numerical': 0.004644431003197096, 'mean_word_length': 1.5376181992599698, 'pattern_counts': {'":': 510, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 40, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2934819', 'n_tokens_mistral': 23360, 'n_tokens_neox': 22437, 'n_words': 5929}