from __future__ import absolute_import, unicode_literals

import base64
import json
import random

import warnings
import websocket

from c8 import constants
from c8.api import APIWrapper
from c8.apikeys import APIKeys
from c8.c8ql import C8QL
from c8.collection import StandardCollection
from c8.exceptions import (
    CollectionCreateError,
    CollectionDeleteError,
    CollectionFindError,
    CollectionListError,
    CollectionPropertiesError,
    EventCreateError,
    EventGetError,
    FabricCreateError,
    FabricDeleteError,
    FabricGetMetadataError,
    FabricListError,
    FabricPropertiesError,
    FabricSetMetadataError,
    FabricUpdateMetadataError,
    GetAPIKeys,
    GetDcDetailError,
    GetDcListError,
    GetLocalDcError,
    GraphCreateError,
    GraphDeleteError,
    GraphListError,
    RestqlCreateError,
    RestqlCursorError,
    RestqlDeleteError,
    RestqlExecuteError,
    RestqlImportError,
    RestqlListError,
    RestqlUpdateError,
    RestqlValidationError,
    ServerConnectionError,
    ServerVersionError,
    SpotRegionAssignError,
    SpotRegionUpdateError,
    StreamAppGetSampleError,
    StreamCommunicationError,
    StreamConnectionError,
    StreamCreateError,
    StreamDeleteError,
    StreamListError,
    StreamPermissionError,
)
from c8.executor import AsyncExecutor, BatchExecutor, DefaultExecutor
from c8.graph import Graph
from c8.keyvalue import KV
from c8.request import Request
from c8.search import Search
from c8.stream_apps import StreamApps
from c8.stream_collection import StreamCollection

__all__ = [
    "StandardFabric",
    "AsyncFabric",
    "BatchFabric",
]
ENDPOINT = "/streams"

def raise_timeout(signum, frame):
    raise TimeoutError

class Fabric(APIWrapper):
    """Base class for Fabric API wrappers.

    :param connection: HTTP connection.
    :type connection: c8.connection.Connection
    :param executor: API executor.
    :type executor: c8.executor.Executor
    """

    def enum(**enums):
        return type("Enum", (), enums)

    SPOT_CREATION_TYPES = enum(
        AUTOMATIC="automatic", NONE="none", SPOT_REGION="spot_region"
    )

    def __init__(self, connection, executor):
        self.url = connection.url
        self.header = connection.headers
        self.stream_port = constants.STREAM_PORT
        super(Fabric, self).__init__(connection, executor)

    def __getitem__(self, name):
        """Return the collection API wrapper.

        :param name: Collection name.
        :type name: str | unicode
        :returns: Collection API wrapper.
        :rtype: c8.collection.StandardCollection
        """
        return self.collection(name)

    @property
    def name(self):
        """Return fabric name.

        :returns: Fabric name.
        :rtype: str | unicode
        """
        return self.fabric_name

    @property
    def c8ql(self):
        """Return C8QL (C8Db Query Language) API wrapper.

        :returns: C8QL API wrapper.
        :rtype: c8.c8ql.C8QL
        """
        return C8QL(self._conn, self._executor)

    @property
    def key_value(self):
        """Return KV (Key Value) API wrapper.

        :returns: KV API wrapper.
        :rtype: c8.keyvalue.KV
        """
        return KV(self._conn, self._executor)

    def on_change(self, collection, callback, timeout=60):
        """Execute given input function on receiving a change.

        :param collection: Collection name(s) regex to listen for
        :type collection: str
        :param timeout: timeout value
        :type timeout: int
        :param callback: Function to execute on a change
        :type callback: function
        """
        if not callback:
            raise ValueError("You must specify a callback function")

        if not collection:
            raise ValueError(
                "You must specify a collection on which realtime "
                "data is to be watched!"
            )

        namespace = constants.STREAM_LOCAL_NS_PREFIX + self.fabric_name

        subscription_name = "%s-%s-subscription-%s" % (
            self.tenant_name,
            self.fabric_name,
            str(random.randint(1, 1000)),
        )

        url = self.url.split("//")[1].split(":")[0]

        topic = "wss://{}/_ws/ws/v2/consumer/persistent/{}/{}/{}/{}".format(
            url, self.tenant_name, namespace, collection, subscription_name
        )

        ws = websocket.create_connection(topic, header=self.header, timeout=timeout)

        try:
            # "pyC8 Realtime: Begin monitoring realtime updates for " + topic
            while True:
                msg = json.loads(ws.recv())
                data = base64.b64decode(msg["payload"])
                ws.send(json.dumps({"messageId": msg["messageId"]}))
                callback(data)
        except websocket.WebSocketTimeoutException:
            pass
        except Exception as e:
            raise Exception(e)
        finally:
            ws.close()

    def properties(self):
        """Return fabric properties.

        :returns: Fabric properties.
        :rtype: dict
        :raise c8.exceptions.FabricPropertiesError: If retrieval fails.
        """
        request = Request(
            method="get",
            endpoint="/database/current",
        )

        def response_handler(resp):
            if not resp.is_success:
                raise FabricPropertiesError(resp, request)
            result = resp.body["result"]
            result["system"] = result.pop("isSystem")
            return result

        return self._execute(request, response_handler)

    def update_spot_region(self, tenant, fabric, new_dc):
        """Updates spot primary region for the geo-fabric

        :param tenant: tenant name
        :type tenant: str
        :param fabric: fabric name
        :type fabric: str
        :param new_dc: New spot region
        :type new_dc: str
        :returns: True if request successful,false otherwise
        :rtype: bool
        :raise c8.exceptions.SpotRegionUpdateError: If updation fails.
        """

        request = Request(
            method="put", endpoint="/_fabric/{}/database/{}".format(fabric, new_dc)
        )

        def response_handler(resp):
            if not resp.is_success:
                raise SpotRegionUpdateError(resp, request)
            return True

        return self._execute(request, response_handler)

    def fabrics_detail(self):
        request = Request(method="get", endpoint="/database/user")

        def response_handler(resp):
            if not resp.is_success:
                raise FabricListError(resp, request)
            return [
                {"name": col["name"], "options": col["options"]}
                for col in map(dict, resp.body["result"])
            ]

        return self._execute(request, response_handler)

    def version(self):
        """Return C8Db server version.

        :returns: Server version.
        :rtype: str | unicode
        :raise c8.exceptions.ServerVersionError: If retrieval fails.
        """
        request = Request(method="get", endpoint="/version", params={"details": False})

        def response_handler(resp):
            if not resp.is_success:
                raise ServerVersionError(resp, request)
            return resp.body["version"]

        return self._execute(request, response_handler)

    def ping(self):
        """Ping the C8Db server by sending a test request.

        :returns: Response code from server.
        :rtype: int
        :raise c8.exceptions.ServerConnectionError: If ping fails.
        """
        request = Request(
            method="get",
            endpoint="/collection",
        )

        def response_handler(resp):
            code = resp.status_code
            if code in {401, 403}:
                raise ServerConnectionError("bad username and/or password")
            if not resp.is_success:
                raise ServerConnectionError(resp.error_message or "bad server response")
            return code

        return self._execute(request, response_handler)

    #########################
    # Datacenter Management #
    #########################

    def dclist(self, detail=False):
        """Return the list of names of Datacenters

        :param detail: detail list of DCs if set to true else only DC names
        :type: boolean
        :returns: DC List.
        :rtype: [str | unicode ]
        :raise c8.exceptions.GetDcListError: If retrieval fails.
        """
        properties = self.properties()
        if not detail:
            return properties["options"]["dcList"].split(",")

        tenant_name = properties["options"]["tenant"]

        request = Request(
            method="get", endpoint="/datacenter/_tenant/{}".format(tenant_name)
        )

        def response_handler(resp):
            if not resp.is_success:
                raise GetDcListError(resp, request)
            dc_list = resp.body[0]["dcInfo"]
            for dc in dc_list:
                if dc["name"] not in properties["options"]["dcList"]:
                    dc_list.remove(dc)
            return dc_list

        return self._execute(request, response_handler, custom_prefix="")

    def localdc(self, detail=True):
        """Fetch data for a local/regional the data center

        :param detail: Details of local DC if set to true else only DC name.
        :type: boolean
        :returns: Local DC details.
        :rtype: str | dict
        :raise c8.exceptions.GetLocalDcError: If retrieval fails.
        """
        request = Request(method="get", endpoint="/datacenter/local")

        def response_handler(resp):
            if not resp.is_success:
                raise GetLocalDcError(resp, request)
            if detail:
                return resp.body
            return resp.body["name"]

        return self._execute(request, response_handler, custom_prefix="")

    def get_dc_detail(self, dc):
        """Fetch data for data center, identified by dc-name

        :param dc: DC name
        :type: str
        :returns: DC details.
        :rtype: dict
        :raise c8.exceptions.GetDcDetailError: If retrieval fails.
        """
        request = Request(method="get", endpoint="/datacenter/{}".format(dc))

        def response_handler(resp):
            if not resp.is_success:
                raise GetDcDetailError(resp, request)
            return resp.body

        return self._execute(request, response_handler, custom_prefix="")

    def dclist_all(self):
        """Fetch data about all the data centers

        :returns: DC List.
        :rtype: [str | unicode ]
        :raise c8.exceptions.GetDcListError: If retrieval fails.
        """

        request = Request(method="get", endpoint="/datacenter/all")

        def response_handler(resp):
            if not resp.is_success:
                raise GetDcListError(resp, request)
            return resp.body

        return self._execute(request, response_handler, custom_prefix="")

    def assign_dc_spot(self, dc, spot_region=False):
        """Assigns spot region of a fed

        :param dc: dc name
        :type dc: str
        :param spot_region: If True, makes the region a spot region
        :type spot_region: bool
        :returns: True if request successful, False otherwise
        :rtype: bool
        :raise c8.exceptions.SpotRegionAssignError: If assignment fails.
        """
        data = json.dumps(spot_region)
        request = Request(method="put", endpoint="/datacenter/{}/{}".format(dc, data))

        def response_handler(resp):
            if not resp.is_success:
                raise SpotRegionAssignError(resp, request)
            return True

        return self._execute(request, response_handler, custom_prefix="")

    #######################
    # Fabric Management #
    #######################

    def fabrics(self):
        """Return the names all fabrics.

        :returns: Fabric names.
        :rtype: [str | unicode]
        :raise c8.exceptions.FabricListError: If retrieval fails.
        """
        request = Request(method="get", endpoint="/database")

        def response_handler(resp):
            if not resp.is_success:
                raise FabricListError(resp, request)
            return resp.body["result"]

        return self._execute(request, response_handler)

    def has_fabric(self, name):
        """Check if a fabric exists.

        :param name: Fabric name.
        :type name: str | unicode
        :returns: True if fabric exists, False otherwise.
        :rtype: bool
        """
        return name in self.fabrics()

    def create_fabric(
        self,
        name,
        spot_dc=None,
        users=None,
        dclist=None,
        spot_creation_type=SPOT_CREATION_TYPES.AUTOMATIC,
    ):
        """Create a new fabric.

        :param name: Fabric name.
        :type name: str | unicode
        :param spot_creation_type: Specifying the mode of creating geo-fabric.
                                   If you use AUTOMATIC, a random spot region
                                   will be assigned by the system. If you
                                   specify NONE, a geo-fabric is created
                                   without the spot properties. If you specify
                                   SPOT_REGION,pass the corresponding spot
                                   region in the spot_dc parameter.
        :type name: Enum containing spot region creation types
        :param name: Spot Region name, if spot_creation_type is set to
                     SPOT_REGION
        :type name: str
        :param users: List of users with access to the new fabric
        :type users: [str | unicode]
        :param dclist: list of strings of datacenters
        :type dclist: [str | unicode]
        :returns: True if fabric was created successfully.
        :rtype: bool
        :raise c8.exceptions.FabricCreateError: If create fails.
        """
        data = {"name": name}
        if users is not None:
            data["users"] = users

        options = {}
        dcl = ""
        if dclist:
            # Process dclist param (type list) to build up comma-separated
            # string of DCs
            for dc in dclist:
                if len(dcl) > 0:
                    dcl += ","
                dcl += dc
        options["dcList"] = dcl

        if spot_creation_type == self.SPOT_CREATION_TYPES.NONE:
            options["spotDc"] = ""
        elif spot_creation_type == self.SPOT_CREATION_TYPES.SPOT_REGION and spot_dc:
            options["spotDc"] = spot_dc

        data["options"] = options

        request = Request(method="post", endpoint="/database", data=data)

        def response_handler(resp):
            if not resp.is_success:
                raise FabricCreateError(resp, request)
            return True

        return self._execute(request, response_handler)

    def get_fabric_metadata(self):
        """Fetch information about a GeoFabric.

        :returns: Fabric information.
        :rtype: dict
        :raise c8.exceptions.FabricGetMetadataError: If retrieval fails.
        """
        request = Request(method="get", endpoint="/database/metadata")

        def response_handler(resp):
            if not resp.is_success:
                raise FabricGetMetadataError(resp, request)
            return resp.body["result"]

        return self._execute(request, response_handler)

    def set_fabric_metadata(self, metadata):
        """Set the GeoFabric Metadata.

        :param metadata: Fabric metadata.
        :type metadata: dict
        :returns: True if metadata was set successfully.
        :rtype: bool
        :raise c8.exceptions.FabricSetMetadataError: If set fails.
        """

        data = {"metadata": metadata}
        request = Request(method="put", endpoint="/database/metadata", data=data)

        def response_handler(resp):
            if not resp.is_success:
                raise FabricSetMetadataError(resp, request)
            return resp.body["result"]

        return self._execute(request, response_handler)

    def update_fabric_metadata(self, metadata):
        """Modfiy the GeoFabric metadata.

        :param metadata: Fabric metadata.
        :type metadata: dict
        :returns: True if metadata was set successfully.
        :rtype: bool
        :raise c8.exceptions.FabricUpdateMetadataError: If update fails.
        """
        data = {"metadata": metadata}
        request = Request(method="patch", endpoint="/database/metadata", data=data)

        def response_handler(resp):
            if not resp.is_success:
                raise FabricUpdateMetadataError(resp, request)
            return resp.body["result"]

        return self._execute(request, response_handler)

    def delete_fabric(self, name, ignore_missing=False):
        """Delete the fabric.

        :param name: Fabric name.
        :type name: str | unicode
        :param ignore_missing: Do not raise an exception on missing fabric.
        :type ignore_missing: bool
        :returns: True if fabric was deleted successfully, False if fabric
            was not found and **ignore_missing** was set to True.
        :rtype: bool
        :raise c8.exceptions.FabricDeleteError: If delete fails.
        """
        request = Request(method="delete", endpoint="/database/{}".format(name))

        def response_handler(resp):
            if resp.error_code == 1228 and ignore_missing:
                return False
            if not resp.is_success:
                raise FabricDeleteError(resp, request)
            return resp.body["result"]

        return self._execute(request, response_handler)

    #########################
    # Collection Management #
    #########################

    def collection(self, name):
        """Return the standard collection API wrapper.

        :param name: Collection name.
        :type name: str | unicode
        :returns: Standard collection API wrapper.
        :rtype: c8.collection.StandardCollection
        """
        if self.has_collection(name):
            return StandardCollection(self._conn, self._executor, name)
        else:
            raise CollectionFindError("Collection not found")

    def has_collection(self, name):
        """Check if collection exists in the fabric.

        :param name: Collection name.
        :type name: str | unicode
        :returns: True if collection exists, False otherwise.
        :rtype: bool
        """
        return any(col["name"] == name for col in self.collections())

    def collections(self, collectionModel=None):
        """Return the collections in the fabric.

        :returns: Collections in the fabric and their details.
        :rtype: [dict]
        :raise c8.exceptions.CollectionListError: If retrieval fails.
        """
        request = Request(method="get", endpoint="/collection")

        def response_handler(resp):
            if not resp.is_success:
                raise CollectionListError(resp, request)
            if collectionModel is not None:
                docs = [
                    col
                    for col in map(dict, resp.body["result"])
                    if col["collectionModel"] == collectionModel
                ]
            else:
                docs = [col for col in map(dict, resp.body["result"])]
            collections = []

            for col in docs:
                c = {
                    "id": col["id"],
                    "name": col["name"],
                    "system": col["isSystem"],
                    "type": StandardCollection.types[col["type"]],
                    "status": StandardCollection.statuses[col["status"]],
                    "collectionModel": col["collectionModel"],
                }
                if "isSpot" in col.keys():
                    c["isSpot"] = col["isSpot"]
                else:
                    c["strongConsistency"] = col.get("strongConsistency", False)
                collections.append(c)
            return collections

        return self._execute(request, response_handler)

    def create_collection(
        self,
        name,
        sync=False,
        edge=False,
        user_keys=True,
        key_increment=None,
        key_offset=None,
        key_generator="traditional",
        shard_fields=None,
        index_bucket_count=None,
        sync_replication=None,
        enforce_replication_factor=None,
        strong_consistency=None,
        local_collection=False,
        is_system=False,
        stream=False,
        *,
        spot_collection=None,
    ):
        """Create a new collection.

        :param name: Collection name.
        :type name: str | unicode
        :param sync: If set to True, document operations via the collection
            will block until synchronized to disk by default.
        :type sync: bool
        :param edge: If set to True, an edge collection is created.
        :type edge: bool
        :param key_generator: Used for generating document keys. Allowed values
            are "traditional" or "autoincrement".
        :type key_generator: str | unicode
        :param user_keys: If set to True, users are allowed to supply document
            keys. If set to False, the key generator is solely responsible for
            supplying the key values.
        :type user_keys: bool
        :param key_increment: Key increment value. Applies only when value of
            **key_generator** is set to "autoincrement".
        :type key_increment: int
        :param key_offset: Key offset value. Applies only when value of
            **key_generator** is set to "autoincrement".
        :type key_offset: int
        :param shard_fields: Field(s) used to determine the target shard.
        :type shard_fields: [str | unicode]
        :param index_bucket_count: Number of buckets into which indexes using
            hash tables are split. The default is 16, and this number has to be
            a power of 2 and less than or equal to 1024. For large collections,
            one should increase this to avoid long pauses when the hash table
            has to be initially built or re-sized, since buckets are re-sized
            individually and can be initially built in parallel. For instance,
            64 may be a sensible value for 100 million documents.
        :type index_bucket_count: int
        :param sync_replication: If set to True, server reports success only
            when collection is created in all replicas. You can set this to
            False for faster server response, and if full replication is not a
            concern.
        :type sync_replication: bool
        :param enforce_replication_factor: Check if there are enough replicas
            available at creation time, or halt the operation.
        :type enforce_replication_factor: bool
        :param strong_consistency: If True, strong consistency is enabled
        :type strong_consistency: bool
        :param is_system: If True, able to create system collections
        :type is_system: bool
        :param stream: If True, create a local stream for collection.
        :type stream: bool
        :param spot_collection: If True, it is a spot collection.
            Deprecated. Use If strong_consistency instead.
        :type spot_collection: bool
        :returns: Standard collection API wrapper.
        :rtype: c8.collection.StandardCollection
        :raise c8.exceptions.CollectionCreateError: If create fails.
        """
        # Newer versions of GDN has renamed the isSpot property to strongConsistency.
        # We are using a keyword only argument to keep the backward compatibility of the SDK
        if spot_collection is not None and strong_consistency is not None:
            raise TypeError("create_collection recieved both spot_collection and strong_consistency")
        elif spot_collection is not None:
            warnings.simplefilter("once", DeprecationWarning)
            warnings.warn("spot_collection is deprecated. Use strong_consistency instead.", DeprecationWarning, 2)
            warnings.simplefilter("default", DeprecationWarning)
            isSpot = spot_collection
        elif strong_consistency is not None:
            isSpot = strong_consistency
        else:
            isSpot = False

        key_options = {"type": key_generator, "allowUserKeys": user_keys}
        if key_increment is not None:
            key_options["increment"] = key_increment
        if key_offset is not None:
            key_options["offset"] = key_offset
        if spot_collection and local_collection:
            return "Collection can either be spot or local"
        else:
            # Both strong_consistency and isSpot is added to the request body.
            # Correct value will be picked by the GDN depending on the supported property.
            data = {
                "name": name,
                "waitForSync": sync,
                "keyOptions": key_options,
                "type": 3 if edge else 2,
                "isSpot": isSpot,
                "strongConsistency": isSpot,
                "isLocal": local_collection,
                "isSystem": is_system,
                "stream": stream,
            }

        if shard_fields is not None:
            data["shardKeys"] = shard_fields
        if index_bucket_count is not None:
            data["indexBuckets"] = index_bucket_count

        params = {}
        if sync_replication is not None:
            params["waitForSyncReplication"] = sync_replication
        if enforce_replication_factor is not None:
            params["enforceReplicationFactor"] = enforce_replication_factor

        request = Request(
            method="post", endpoint="/collection", params=params, data=data
        )

        def response_handler(resp):
            if resp.is_success:
                return self.collection(name)
            raise CollectionCreateError(resp, request)

        return self._execute(request, response_handler)

    def update_collection_properties(
        self, collection_name, has_stream=None, wait_for_sync=None
    ):
        """Changes the properties of a collection.
           Note: except for waitForSync and hasStream, collection properties cannot be changed once a collection is created.
        :param collection_name: Collection name.
        :type collection_name: str | unicode
        :param has_stream: True if creating a live collection stream.
        :type has_stream: bool
        :param wait_for_sync: True if all data must be synced to storage before operation returns.
        :type wait_for_sync: bool
        """

        data = {}
        if has_stream is not None:
            data["hasStream"] = has_stream
        if wait_for_sync is not None:
            data["waitForSync"] = wait_for_sync

        request = Request(
            method="put",
            endpoint="/collection/{}/properties".format(collection_name),
            data=data,
        )

        def response_handler(resp):
            if resp.is_success:
                return resp.body
            raise CollectionPropertiesError(resp, request)

        return self._execute(request, response_handler)

    def delete_collection(self, name, ignore_missing=False, system=None):
        """Delete the collection.

        :param name: Collection name.
        :type name: str | unicode
        :param ignore_missing: Do not raise an exception on missing collection.
        :type ignore_missing: bool
        :param system: Whether the collection is a system collection.
        :type system: bool
        :returns: True if collection was deleted successfully, False if
            collection was not found and **ignore_missing** was set to True.
        :rtype: bool
        :raise c8.exceptions.CollectionDeleteError: If delete fails.
        """
        params = {}
        if system is not None:
            params["isSystem"] = system

        request = Request(
            method="delete", endpoint="/collection/{}".format(name), params=params
        )

        def response_handler(resp):
            if resp.error_code == 1203 and ignore_missing:
                return False
            if not resp.is_success:
                raise CollectionDeleteError(resp, request)
            return True

        return self._execute(request, response_handler)

    ####################
    # Graph Management #
    ####################

    def graph(self, name):
        """Return the graph API wrapper.

        :param name: Graph name.
        :type name: str | unicode
        :returns: Graph API wrapper.
        :rtype: c8.graph.Graph
        """
        return Graph(self._conn, self._executor, name)

    def has_graph(self, name):
        """Check if a graph exists in the fabric.

        :param name: Graph name.
        :type name: str | unicode
        :returns: True if graph exists, False otherwise.
        :rtype: bool
        """
        for graph in self.graphs():
            if graph["name"] == name:
                return True
        return False

    def graphs(self):
        """List all graphs in the fabric.

        :returns: Graphs in the fabric.
        :rtype: [dict]
        :raise c8.exceptions.GraphListError: If retrieval fails.
        """
        request = Request(method="get", endpoint="/graph")

        def response_handler(resp):
            if not resp.is_success:
                raise GraphListError(resp, request)
            return [
                {
                    "id": body["_id"],
                    "name": body["_key"],
                    "revision": body["_rev"],
                    "orphan_collections": body["orphanCollections"],
                    "edge_definitions": [
                        {
                            "edge_collection": definition["collection"],
                            "from_vertex_collections": definition["from"],
                            "to_vertex_collections": definition["to"],
                        }
                        for definition in body["edgeDefinitions"]
                    ],
                    "shard_count": body.get("numberOfShards"),
                    "replication_factor": body.get("replicationFactor"),
                }
                for body in resp.body["graphs"]
            ]

        return self._execute(request, response_handler)

    def create_graph(
        self, name, edge_definitions=None, orphan_collections=None, shard_count=None
    ):
        """Create a new graph.

        :param name: Graph name.
        :type name: str | unicode
        :param edge_definitions: List of edge definitions, where each edge
            definition entry is a dictionary with fields "edge_collection",
            "from_vertex_collections" and "to_vertex_collections" (see below
            for example).
        :type edge_definitions: [dict]
        :param orphan_collections: Names of additional vertex collections that
            are not in edge definitions.
        :type orphan_collections: [str | unicode]
        :param shard_count: Number of shards used for every collection in the
            graph. To use this, parameter **smart** must be set to True and
            every vertex in the graph must have the smart field. This number
            cannot be modified later once set. Applies only to enterprise
            version of C8Db.
        :type shard_count: int
        :returns: Graph API wrapper.
        :rtype: c8.graph.Graph
        :raise c8.exceptions.GraphCreateError: If create fails.

        Here is an example entry for parameter **edge_definitions**:

        .. code-block:: python

            {
                'edge_collection': 'teach',
                'from_vertex_collections': ['teachers'],
                'to_vertex_collections': ['lectures']
            }
        """
        data = {"name": name}
        if edge_definitions is not None:
            data["edgeDefinitions"] = [
                {
                    "collection": definition["edge_collection"],
                    "from": definition["from_vertex_collections"],
                    "to": definition["to_vertex_collections"],
                }
                for definition in edge_definitions
            ]
        if orphan_collections is not None:
            data["orphanCollections"] = orphan_collections
        if shard_count is not None:  # pragma: no cover
            data["numberOfShards"] = shard_count

        request = Request(method="post", endpoint="/graph", data=data)

        def response_handler(resp):
            if resp.is_success:
                return Graph(self._conn, self._executor, name)
            raise GraphCreateError(resp, request)

        return self._execute(request, response_handler)

    def delete_graph(self, name, ignore_missing=False, drop_collections=None):
        """Drop the graph of the given name from the fabric.

        :param name: Graph name.
        :type name: str | unicode
        :param ignore_missing: Do not raise an exception on missing graph.
        :type ignore_missing: bool
        :param drop_collections: Drop the collections of the graph also. This
            is only if they are not in use by other graphs.
        :type drop_collections: bool
        :returns: True if graph was deleted successfully, False if graph was not
            found and **ignore_missing** was set to True.
        :rtype: bool
        :raise c8.exceptions.GraphDeleteError: If delete fails.
        """
        params = {}
        if drop_collections is not None:
            params["dropCollections"] = drop_collections

        request = Request(
            method="delete", endpoint="/graph/{}".format(name), params=params
        )

        def response_handler(resp):
            if resp.error_code == 1924 and ignore_missing:
                return False
            if not resp.is_success:
                raise GraphDeleteError(resp, request)
            return True

        return self._execute(request, response_handler)

    ########################
    # Async Job Management #
    ########################

    # Pratik: APIs not supported in documentation. Waiting for verification
    # def async_jobs(self, status, count=None):
    #     """Return IDs of async jobs with given status.
    #
    #     :param status: Job status (e.g. "pending", "done").
    #     :type status: str | unicode
    #     :param count: Max number of job IDs to return.
    #     :type count: int
    #     :returns: List of job IDs.
    #     :rtype: [str | unicode]
    #     :raise c8.exceptions.AsyncJobListError: If retrieval fails.
    #     """
    #     params = {}
    #     if count is not None:
    #         params['count'] = count
    #
    #     request = Request(
    #         method='get',
    #         endpoint='/job/{}'.format(status),
    #         params=params
    #     )
    #
    #     def response_handler(resp):
    #         if resp.is_success:
    #             return resp.body
    #         raise AsyncJobListError(resp, request)
    #
    #     return self._execute(request, response_handler)
    #
    # def clear_async_jobs(self, threshold=None):
    #     """Clear async job results from the server.
    #
    #     Async jobs that are still queued or running are not stopped.
    #
    #     :param threshold: If specified, only the job results created prior to
    #         the threshold (a unix timestamp) are deleted. Otherwise, all job
    #         results are deleted.
    #     :type threshold: int
    #     :returns: True if job results were cleared successfully.
    #     :rtype: bool
    #     :raise c8.exceptions.AsyncJobClearError: If operation fails.
    #     """
    #     if threshold is None:
    #         url = '/job/all'
    #         params = None
    #     else:
    #         url = '/job/expired'
    #         params = {'stamp': threshold}
    #
    #     request = Request(
    #         method='delete',
    #         endpoint=url,
    #         params=params
    #     )
    #
    #     def response_handler(resp):
    #         if resp.is_success:
    #             return True
    #         raise AsyncJobClearError(resp, request)
    #
    #     return self._execute(request, response_handler)

    ########################
    # Streams Management   #
    ########################

    def stream(self, operation_timeout_seconds=30):
        """Return the stream collection API wrapper.

        :returns: stream collection API wrapper.
        :rtype: c8.stream_collection.StreamCollection
        """
        return StreamCollection(
            self,
            self._conn,
            self._executor,
            self.url,
            self.stream_port,
            operation_timeout_seconds,
        )

    def streams(self, local=None):
        """Get list of all streams under given fabric

        :returns: List of streams under given fabric.
        :rtype: json
        :raise c8.exceptions.StreamListError: If retrieving streams fails.
        """
        if local is False:
            url_endpoint = "/streams?global=true"

        elif local is True:
            url_endpoint = "/streams?global=false"

        elif local is None:
            url_endpoint = "/streams"

        request = Request(method="get", endpoint=url_endpoint)

        def response_handler(resp):
            code = resp.status_code
            if resp.is_success:
                return [
                    {
                        "name": col["topic"],
                        "topic": col["topic"],
                        "local": col["local"],
                        "db": col["db"],
                        "tenant": col["tenant"],
                        "type": StreamCollection.types[col["type"]],
                        "status": "terminated"
                        if "terminated" in col
                        else "active",  # noqa
                    }
                    for col in map(dict, resp.body["result"])
                ]
            elif code == 403:
                raise StreamPermissionError(resp, request)
            raise StreamListError(resp, request)

        return self._execute(request, response_handler)

    def has_stream(self, stream, isCollectionStream=False, local=False):
        """Check if the list of streams has a stream with the given name.

        :param stream: The name of the stream for which to check in the list
                       of all streams.
        :type stream: str | unicode
        :returns: True=stream found; False=stream not found.
        :rtype: bool
        """
        if isCollectionStream is False:
            if local is False and "c8globals" not in stream:
                stream = "c8globals." + stream
            elif local is True and "c8locals" not in stream:
                stream = "c8locals." + stream
        return any(mystream["name"] == stream for mystream in self.streams(local=local))

    def create_stream(self, stream, local=False):
        """
        Create the stream under the given fabric

        :param stream: name of stream
        :param local: Operate on a local stream instead of a global one.
        :returns: 200, OK if operation successful
        :raise: c8.exceptions.StreamCreateError: If creating streams fails.
        """
        if local is True:
            endpoint = "{}/{}?global=False".format(ENDPOINT, stream)
        elif local is False:
            endpoint = "{}/{}?global=True".format(ENDPOINT, stream)

        request = Request(method="post", endpoint=endpoint)

        def response_handler(resp):
            code = resp.status_code
            if resp.is_success:
                return resp.body["result"]
            elif code == 502:
                raise StreamCommunicationError(resp, request)
            raise StreamCreateError(resp, request)

        return self._execute(request, response_handler)

    def delete_stream(self, stream, force=False):
        """
        Delete the streams under the given fabric

        :param stream: name of stream
        :param force:
        :returns: 200, OK if operation successful
        :raise: c8.exceptions.StreamDeleteError: If deleting streams fails.
        """
        endpoint = f"{ENDPOINT}/{stream}"
        if force:
            endpoint = endpoint + "?force=true"

        request = Request(method="delete", endpoint=endpoint)

        def response_handler(resp):
            code = resp.status_code
            if resp.is_success:
                return True
            elif code == 403:
                raise StreamPermissionError(resp, request)
            elif code == 412:
                raise StreamDeleteError(resp, request)
            raise StreamConnectionError(resp, request)

        return self._execute(request, response_handler)

    #####################
    # Restql Management #
    #####################

    def save_restql(self, data):
        """Save restql by name.

        :param data: data to be used for restql POST API
        :type data: dict
        :returns: Results of restql API
        :rtype: dict
        :raise c8.exceptions.RestqlCreateError: if restql operation failed
        """

        query_name = data["query"]["name"]
        if " " in query_name:
            raise RestqlValidationError("White Spaces not allowed in Query " "Name")

        request = Request(method="post", endpoint="/restql", data=data)

        def response_handler(resp):
            if not resp.is_success:
                raise RestqlCreateError(resp, request)
            return resp.body["result"]

        return self._execute(request, response_handler)

    def import_restql(self, queries, details=False):
        """Import custom queries.

        :param queries: queries to be imported
        :type queries: [dict]
        :param details: Whether to include details
        :type details: bool
        :returns: Results of importing restql
        :rtype: dict
        :raise c8.exceptions.RestqlImportError: if restql operation failed
        """

        data = {"queries": queries, "details": details}

        request = Request(method="post", endpoint="/restql/import", data=data)

        def response_handler(resp):
            if not resp.is_success:
                raise RestqlImportError(resp, request)
            return resp.body["result"]

        return self._execute(request, response_handler)

    def execute_restql(self, name, data=None):
        """Execute restql by name.

        :param name: restql name
        :type name: str | unicode
        :param data: restql data (optional)
        :type data: dict
        :returns: Results of execute restql
        :rtype: dict
        :raise c8.exceptions.RestqlExecuteError: if restql execution failed
        """

        if data is None or not ("bindVars" in data or "batchSize" in data):
            data = {}

        request = Request(
            method="post", data=data, endpoint="/restql/execute/{}".format(name)
        )

        def response_handler(resp):
            if not resp.is_success:
                raise RestqlExecuteError(resp, request)
            return resp.body

        return self._execute(request, response_handler)

    def read_next_batch_restql(self, id):
        """Read next batch from query worker cursor.

        :param id: the cursor-identifier
        :type id: int
        :returns: Results of execute restql
        :rtype: dict
        :raise c8.exceptions.RestqlCursorError: if fetch next batch failed
        """

        request = Request(method="put", endpoint="/restql/fetch/{}".format(id))

        def response_handler(resp):
            if not resp.is_success:
                raise RestqlCursorError(resp, request)
            return resp.body

        return self._execute(request, response_handler)

    def get_all_restql(self):
        """Get all restql associated for user.

        :returns: Details of all restql
        :rtype: list
        :raise c8.exceptions.RestqlListError: if getting restql failed
        """

        request = Request(method="get", endpoint="/restql/user")

        def response_handler(resp):
            if not resp.is_success:
                raise RestqlListError(resp, request)
            return resp.body["result"]

        return self._execute(request, response_handler)

    def update_restql(self, name, data):
        """Update restql by name.

        :param name: name of restql
        :type name: str | unicode
        :param data: restql data
        :type data: dict
        :returns: True if restql is updated
        :rtype: bool
        :raise c8.exceptions.RestqlUpdateError: if query update failed
        """
        request = Request(method="put", data=data, endpoint="/restql/" + name)

        def response_handler(resp):
            if not resp.is_success:
                raise RestqlUpdateError(resp, request)
            return True

        return self._execute(request, response_handler)

    def delete_restql(self, name):
        """Delete restql by name.

        :param name: restql name
        :type name: str | unicode
        :returns: True if restql is deleted
        :rtype: bool
        :raise c8.exceptions.RestqlDeleteError: if restql deletion failed
        """
        request = Request(method="delete", endpoint="/restql/" + name)

        def response_handler(resp):
            if not resp.is_success:
                raise RestqlDeleteError(resp, request)
            return True

        return self._execute(request, response_handler)

    ########################
    # Events #
    ########################

    def create_event(self, payload):
        """Create an event.

        :param payload: Payload to create event
        :type payload: dict
        :returns:  Dictionary containing the event id
        :rtype: dict
        :raise c8.exceptions.EventCreateError: if event creation failed

        Here is an example entry for parameter **payload**:

        .. code-block:: python

            {
            "action": "string",
            "attributes": {},
            "description": "string",
            "details": "string",
            "entityName": "string",
            "entityType": "string",
            "status": "string"
            }
        """
        request = Request(method="post", endpoint="/events", data=payload)

        def response_handler(resp):
            if not resp.is_success:
                raise EventCreateError(resp, request)
            return resp.body

        return self._execute(request, response_handler)

    def delete_event(self, eventIds):
        """Delete an event/s.

        :param eventIds: The event id for which you want to fetch the event details
        :type eventId: list of strings(event Ids)
        :returns: List containig all the information of existing events
        :rtype: list
        :raise c8.exceptions.EventDeleteError: if event creation failed

        """
        data = json.dumps((eventIds))

        request = Request(method="delete", endpoint="/events", data=data)

        def response_handler(resp):
            if not resp.is_success:
                raise EventGetError(resp, request)
            return True

        return self._execute(request, response_handler)

    def get_all_events(self):
        """Create an event.

        :returns: List containig all the information of existing events
        :rtype: list
        :raise c8.exceptions.EventGetError: if event creation failed

        """
        request = Request(method="get", endpoint="/events/tenant")

        def response_handler(resp):
            if not resp.is_success:
                raise EventGetError(resp, request)
            return resp.body

        return self._execute(request, response_handler)

    def get_event_by_Id(self, eventId):
        """Create an event.

        :param eventId: The event id for which you want to fetch the event details
        :returns: List containig all the information of existing events
        :rtype: list
        :raise c8.exceptions.EventGetError: if event creation failed

        """
        request = Request(method="get", endpoint="/events/" + str(eventId))

        def response_handler(resp):
            if not resp.is_success:
                raise EventGetError(resp, request)
            return resp.body

        return self._execute(request, response_handler)

    ########################
    # Stream Apps #
    ########################

    def stream_app(self, name):
        return StreamApps(self._conn, self._executor, name)

    def validate_stream_app(self, data):
        """validates a stream app by given data

        :param data: stream app defination string
        """
        body = {"definition": data}
        req = Request(
            method="post", endpoint="/streamapps/validate", data=json.dumps(body)
        )

        def response_handler(resp):
            if resp.is_success is True:
                return True
            return False

        return self._execute(req, response_handler)

    def retrieve_stream_app(self):
        """retrieves all the stream apps of a fabric"""
        req = Request(
            method="get",
            endpoint="/streamapps",
        )

        def response_handler(resp):
            if resp.is_success is True:
                return resp.body
            return False

        return self._execute(req, response_handler)

    def get_samples_stream_app(self):
        """gets samples for stream apps"""
        req = Request(
            method="get",
            endpoint="/streamapps/samples",
        )

        def response_handler(resp):
            if resp.is_success is not True:
                raise StreamAppGetSampleError(resp, req)
            return resp.body["streamAppSample"]

        return self._execute(req, response_handler)

    def create_stream_app(self, data, dclist=[]):
        """Creates a stream application by given data

        :param data: stream app definition
        :param dclist: regions where stream app has to be deployed
        """
        # create request body
        req_body = {"definition": data, "regions": dclist}
        # create request
        req = Request(method="post", endpoint="/streamapps", data=json.dumps(req_body))

        # create response handler

        def response_handler(resp):
            if resp.is_success is True:
                return True
            return False

        # call api
        return self._execute(req, response_handler)

    ########################
    # APIKeys #
    ########################

    def api_keys(self, keyid):
        """Return the API keys API wrapper.

        :param keyid: API Key id
        :type kaeyid: string | unicode
        :returns: API keys API wrapper.
        :rtype: c8.stream_collection.StreamCollection
        """
        return APIKeys(self._conn, self._executor, keyid)

    def list_all_api_keys(self):
        """List the API keys.

        :returns:list.
        :raise c8.exceptions.GetAPIKeys: If request fails
        """
        request = Request(
            method="get",
            endpoint="/key",
        )

        # create response handler

        def response_handler(resp):
            if not resp.is_success:
                raise GetAPIKeys(resp, request)
            else:
                return resp.body["result"]

        return self._execute(request, response_handler, custom_prefix="/_api")

    ##############################
    # Search, View and Analyzers #
    ##############################
    def search(self):
        """Returns the Search APIWrapper
        :returns: Search API Wrapper
        :rtype: c8.search.Search
        """
        return Search(self._conn, self._executor)

class StandardFabric(Fabric):
    """Standard fabric API wrapper.

    :param connection: HTTP connection.
    :type connection: c8.connection.Connection
    """

    def __init__(self, connection):
        super(StandardFabric, self).__init__(
            connection=connection, executor=DefaultExecutor(connection)
        )

    def __repr__(self):
        return "<StandardFabric {}>".format(self.name)

    def begin_async_execution(self, return_result=True):
        """Begin async execution.

        :param return_result: If set to True, API executions return instances
            of :class:`c8.job.AsyncJob`, which you can use to retrieve
            results from server once available. If set to False, API executions
            return None and no results are stored on server.
        :type return_result: bool
        :returns: Fabric API wrapper built specifically for async execution.
        :rtype: c8.fabric.AsyncFabric
        """
        return AsyncFabric(self._conn, return_result)

    def begin_batch_execution(self, return_result=True):
        """Begin batch execution.

        :param return_result: If set to True, API executions return instances
            of :class:`c8.job.BatchJob` that are populated with results on
            commit. If set to False, API executions return None and no results
            are tracked client-side.
        :type return_result: bool
        :returns: Fabric API wrapper built specifically for batch execution.
        :rtype: c8.fabric.BatchFabric
        """
        return BatchFabric(self._conn, return_result)

class AsyncFabric(Fabric):
    """Fabric API wrapper tailored specifically for async execution.

    See :func:`c8.fabric.StandardFabric.begin_async_execution`.

    :param connection: HTTP connection.
    :type connection: c8.connection.Connection
    :param return_result: If set to True, API executions return instances of
        :class:`c8.job.AsyncJob`, which you can use to retrieve results
        from server once available. If set to False, API executions return None
        and no results are stored on server.
    :type return_result: bool
    """

    def __init__(self, connection, return_result):
        super(AsyncFabric, self).__init__(
            connection=connection, executor=AsyncExecutor(connection, return_result)
        )

    def __repr__(self):
        return "<AsyncFabric {}>".format(self.name)

class BatchFabric(Fabric):
    """Fabric API wrapper tailored specifically for batch execution.

    See :func:`c8.fabric.StandardFabric.begin_batch_execution`.

    :param connection: HTTP connection.
    :type connection: c8.connection.Connection
    :param return_result: If set to True, API executions return instances of
        :class:`c8.job.BatchJob` that are populated with results on commit.
        If set to False, API executions return None and no results are tracked
        client-side.
    :type return_result: bool
    """

    def __init__(self, connection, return_result):
        super(BatchFabric, self).__init__(
            connection=connection, executor=BatchExecutor(connection, return_result)
        )

    def __repr__(self):
        return "<BatchFabric {}>".format(self.name)

    def __enter__(self):
        return self

    def __exit__(self, exception, *_):
        if exception is None:
            self._executor.commit()

    def queued_jobs(self):
        """Return the queued batch jobs.

        :returns: Queued batch jobs or None if **return_result** parameter was
            set to False during initialization.
        :rtype: [c8.job.BatchJob] | None
        """
        return self._executor.jobs

    def commit(self):
        """Execute the queued requests in a single batch API request.

        If **return_result** parameter was set to True during initialization,
        :class:`c8.job.BatchJob` instances are populated with results.

        :returns: Batch jobs, or None if **return_result** parameter was set to
            False during initialization.
        :rtype: [c8.job.BatchJob] | None
        :raise c8.exceptions.BatchStateError: If batch state is invalid
            (e.g. batch was already committed or the response size did not
            match expected).
        :raise c8.exceptions.BatchExecuteError: If commit fails.
        """
        return self._executor.commit()


#
# @lc app=leetcode.cn id=15 lang=python3
#
# [15] 三数之和
#

# @lc code=start
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        result = []
        n = len(nums)

        nums.sort()

        for i, first in enumerate(nums):
            if i > 0 and first == nums[i - 1]:
                continue
            k = n - 1
            for j, second in enumerate(nums[i+1:], i + 1):
                if j > i + 1 and second == nums[j - 1]:
                    continue

                while second + nums[k] > -first and j < k:
                    k -= 1
                if j == k:
                    break
                if second + nums[k] == -first:
                    result.append((first, second, nums[k]))

        return result

# @lc code=end


from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .routers import post, user, auth, vote

############################################

#models.Base.metadata.create_all(bind=engine)

app = FastAPI()

origins = ["*"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(post.router)
app.include_router(user.router)
app.include_router(auth.router)
app.include_router(vote.router)

@app.get("/")
def root():
    return {"message": "Hello World"}

"""
my_posts = [{"title": "title of post 1", "content": "content of post 1", "id":1},
            {"title": "favorite foods", "content": "I like pizza", "id":2}]

def find_post(id):
    for p in my_posts:
        if p["id"] == id:
            return p

def find_index_post(id):
    for i, p in enumerate(my_posts):
        if p['id'] == id:
            return i
"""

"""
#Path Operations
@app.get("/")
#IT IS THE DECORATOR: IT CREATES THE ENDPOINT OF THE FUNCTION. It is called on the already created FastAPI INSTANCE (app)
#Within the brackets we have the PATH, the path that we must access from the URL
#the get method: is one of the possible HTTP methods

def root():

    1): Async it is optional (so we can delete it)
    2): the name of the fun should be as much descriptive as possible
    3)RETURN:  is the message that is returned back to the user

    return {"message": "Hello World"}
"""


import os
import random
import math
import seaborn
import matplotlib.pyplot as plt

num_train_samples = 1
threshold = 0.25
dtw_window = 50

# thresholds: 0.15, 0.2, ...

def read_gesture(path):
    with open(path, "r") as file:
        lines = [line.rstrip() for line in file]
        gesture = [[float(value) for value in data.split(',')] for data in lines]
        return gesture

labels = ['circle_ccw', 'circle_cw', 'heart_cw', 'square_ccw', 'triangle_cw', 'junk']

paths = os.listdir('gestures')
circle_ccw = [('circle_ccw', read_gesture('gestures/' + path)) for path in paths if path.startswith('circle_ccw')]
circle_cw = [('circle_cw', read_gesture('gestures/' + path)) for path in paths if path.startswith('circle_cw')]
heart_cw = [('heart_cw', read_gesture('gestures/' + path)) for path in paths if path.startswith('heart_cw')]
square_ccw = [('square_ccw', read_gesture('gestures/' + path)) for path in paths if path.startswith('square_ccw')]
triangle_cw = [('triangle_cw', read_gesture('gestures/' + path)) for path in paths if path.startswith('triangle_cw')]
junk = [('junk', read_gesture('gestures/' + path)) for path in paths if path.startswith('junk')]

def fir_lowpass_first(a):
    q = 0.95
    b = [a[0]]
    for i in range(1, len(a)):
        x = (1.0 - q) * a[i - 1][0] + q * a[i][0]
        y = (1.0 - q) * a[i - 1][1] + q * a[i][1]
        z = (1.0 - q) * a[i - 1][2] + q * a[i][2]
        b.append([x, y, z])
    return b

def calc_distance(a, b) -> float:
    ax = a[0]
    ay = a[1]
    az = a[2]
    bx = b[0]
    by = b[1]
    bz = b[2]
    dir = (ax * bx + ay * by + az * bz) / (normalize(ax, ay, az) * normalize(bx, by, bz) + 0.0000001)

    return (1.0 - 0.5 * dir) * normalize(ax - bx, ay - by, az - bz)

def normalize(x, y, z) -> float:
    return math.sqrt(x * x + y * y + z * z)

def calc_dtw(a, b) -> float:
    a = fir_lowpass_first(a)
    b = fir_lowpass_first(b)

    dtw = [[0.0 for _ in range(50)] for _ in range(50)]
    dtw[0][0] = calc_distance(a[0], b[0])

    for i in range(1, 50):
        dtw[i][0] = calc_distance(a[i], b[0]) + dtw[i - 1][0]
        dtw[0][i] = calc_distance(a[0], b[i]) + dtw[0][i - 1]

    for i in range(1, 50):
        for j in range(1, 50):
            dtw[i][j] = calc_distance(a[i], b[j]) + min(dtw[i - 1][j], dtw[i][j - 1], dtw[i - 1][j - 1])

    i = 49
    j = 49
    distance = [0.0 for _ in range(100)]
    length = 0

    while i > 0 and j > 0:
        if dtw[i - 1][j] <= dtw[i][j - 1] and dtw[i - 1][j] <= dtw[i - 1][j - 1] and (j - i) <= dtw_window:
            distance[length] = dtw[i][j] - dtw[i - 1][j]
            i -= 1
        elif dtw[i][j - 1] < dtw[i - 1][j - 1] and (i - j) <= dtw_window:
            distance[length] = dtw[i][j] - dtw[i][j - 1]
            j -= 1
        else:
            distance[length] = dtw[i][j] - dtw[i - 1][j - 1]
            i -= 1
            j -= 1
        length += 1

    while i > 0:
        distance[length] = dtw[i][0] - dtw[i - 1][0]
        i -= 1
        length += 1

    while j > 0:
        distance[length] = dtw[0][j] - dtw[0][j - 1]
        j -= 1
        length += 1

    distance[length] = dtw[0][0]
    length += 1

    mean = 0.0

    for i in range(length):
        mean += distance[i]

    mean = mean / float(length)

    return mean

confusion_matrix = {}
num_trails = {}

for true_label in labels:
    confusion_matrix[true_label] = {}
    num_trails[true_label] = 0.0

    for predicted_label in labels:
        confusion_matrix[true_label][predicted_label] = 0.0

for _ in range(25):
    random.shuffle(circle_ccw)
    random.shuffle(circle_cw)
    random.shuffle(heart_cw)
    random.shuffle(square_ccw)
    random.shuffle(triangle_cw)

    circle_ccw_train = circle_ccw[:num_train_samples]
    circle_ccw_test = circle_ccw[num_train_samples:]

    circle_cw_train = circle_cw[:num_train_samples]
    circle_cw_test = circle_cw[num_train_samples:]

    heart_cw_train = heart_cw[:num_train_samples]
    heart_cw_test = heart_cw[num_train_samples:]

    square_ccw_train = square_ccw[:num_train_samples]
    square_ccw_test = square_ccw[num_train_samples:]

    triangle_cw_train = triangle_cw[:num_train_samples]
    triangle_cw_test = triangle_cw[num_train_samples:]

    train = circle_ccw_train + circle_cw_train + heart_cw_train + square_ccw_train + triangle_cw_train
    test = circle_ccw_test + circle_cw_test + heart_cw_test + square_ccw_test + triangle_cw_test + junk

    for (predicted_label, gesture) in test:
        means = [(a, calc_dtw(gesture, tr)) for (a, tr) in train]
        means.sort(key=lambda x:x[1])

        true_label = means[0][0]
        mean = means[0][1]

        if mean > threshold:
            true_label = 'junk'

        confusion_matrix[true_label][predicted_label] += 1.0
        num_trails[predicted_label] += 1.0

cf_plot = [[0 for _ in labels] for _ in labels]

for (i, true_label) in enumerate(['circle_ccw', 'circle_cw', 'heart_cw', 'square_ccw', 'triangle_cw', 'junk']):
    for (j, predicted_label) in enumerate(['circle_ccw', 'circle_cw', 'heart_cw', 'square_ccw', 'triangle_cw', 'junk']):
        cf_plot[j][i] = confusion_matrix[true_label][predicted_label] / num_trails[predicted_label]

plt.tick_params(labeltop=True, labelbottom=False)
seaborn.heatmap(cf_plot, cmap='rocket_r', annot=True, vmin=0.0, vmax=1.0, xticklabels=labels, yticklabels=labels, cbar=False)
plt.show()


# USAGE
# python knn.py --dataset ../../SolutionDL4CV/SB_code/datasets/animals
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from pyimagesearch.preprocessing import SimplePreprocessor
from pyimagesearch.datasets import SimpleDatasetLoader
from imutils import paths
import argparse

ap = argparse.ArgumentParser()
ap.add_argument('-d', '--dataset', required=True, help='path to input dataset')
ap.add_argument('-k', '--neighbors', type=int, default=1, help='# of nearest neighbors for classification')
ap.add_argument('-j', '--jobs', type=int, default=-1, help='# of jobs for k-NN distance (-1 uses all available cores)')
args = vars(ap.parse_args())

print('[INFO] loading images...')
img_paths = list(paths.list_images(args['dataset']))

sp = SimplePreprocessor(32, 32)
sdl = SimpleDatasetLoader(preprocessors=[sp])
data, labels = sdl.load(img_paths, verbose=500)
data = data.reshape((len(data), -1))

print(f'[INFO] feature matrix: {data.nbytes/(1024*1000.0):.1f}MB')

# Encode the labels as integer
le = LabelEncoder()
labels = le.fit_transform(labels)

# Split to train and test set
X_train, X_test, y_train, y_test = train_test_split(data, labels, test_size=0.25, random_state=42)

print("[INFO] evaluating k-NN classifier...")
model = KNeighborsClassifier(n_neighbors=args['neighbors'], n_jobs=args['jobs'])
model.fit(X_train, y_train)
print(classification_report(y_test, model.predict(X_test),  target_names=le.classes_))


import numpy as np
import matplotlib.pyplot as plt
import activation_functions as acfunc

inp_a = np.arange(-1.0, 1.0, 0.2)
inp_b = np.arange(-1.0, 1.0, 0.2)

outputs = np.zeros((10, 10))

weight_a = 2.5
weight_b = 3

bias = 0.1

for i in range(10):
    for j in range(10):
        u_single = inp_a[i] * weight_a + inp_b[j] * weight_b + bias
        outputs[i][j] = acfunc.sigmoid_func(u_single)

plt.imshow(outputs, "gray", vmin=0.0, vmax=1.0)
plt.colorbar()
plt.show()


'''
Loss functions.
'''

import copy
import torch
import torch.nn as nn
from torch.autograd import Variable
import numpy as np

import utils

class NLLLoss(nn.Module):
    """Self-Defined NLLLoss Function

    Args:
        weight: Tensor (num_class, )
    """
    def __init__(self, weight):
        super(NLLLoss, self).__init__()
        self.weight = weight

    def forward(self, prob, target):
        """
        Args:
            prob: (N, C)
            target : (N, )
        """
        N = target.size(0)
        C = prob.size(1)
        weight = Variable(self.weight).view((1, -1))
        weight = weight.expand(N, C)  # (N, C)
        if prob.is_cuda:
            weight = weight.cuda()
        prob = weight * prob

        one_hot = torch.zeros((N, C))
        if prob.is_cuda:
            one_hot = one_hot.cuda()
        one_hot.scatter_(1, target.data.view((-1,1)), 1)
        one_hot = one_hot.type(torch.ByteTensor)
        one_hot = Variable(one_hot)
        if prob.is_cuda:
            one_hot = one_hot.cuda()
        loss = torch.masked_select(prob, one_hot)

        return -torch.sum(loss)

class GANLoss(nn.Module):

    """Reward-Refined NLLLoss Function for adversial training of Generator"""

    def __init__(self):
        super(GANLoss, self).__init__()

    def forward_reinforce(self, prob, target, reward, cuda=False):
        """
        Forward function used in the SeqGAN implementation.
        Args:
            prob: (N, C), torch Variable
            target : (N, ), torch Variable
            reward : (N, ), torch Variable
        """
        N = target.size(0)
        C = prob.size(1)
        one_hot = torch.zeros((N, C))
        if cuda:
            one_hot = one_hot.cuda()
        one_hot.scatter_(1, target.data.view((-1,1)), 1)
        one_hot = one_hot.type(torch.ByteTensor)
        one_hot = Variable(one_hot)
        if cuda:
            one_hot = one_hot.cuda()
        loss = torch.masked_select(prob, one_hot)
        loss = loss * reward
        loss =  -torch.sum(loss)

        return loss

    def forward_reward(self, i, samples, prob, rewards, BATCH_SIZE, g_sequence_len, VOCAB_SIZE, cuda=False):
        """
        Returns what is used to get the gradient contribution of the i-th term of the batch.

        """
        conditional_proba = Variable(torch.zeros(BATCH_SIZE, VOCAB_SIZE))
        if cuda:
            conditional_proba = conditional_proba.cuda()
        for j in range(BATCH_SIZE):
            conditional_proba[j, int(samples[j, i])] = 1
            conditional_proba[j, :] = - (rewards[j]/BATCH_SIZE * conditional_proba[j, :])

        return conditional_proba

    def forward_reward_grads(self, samples, prob, rewards, g, BATCH_SIZE, g_sequence_len, VOCAB_SIZE, cuda=False):
        """
        Returns a list of gradient contribution of every term in the batch

        """
        conditional_proba = Variable(torch.zeros(BATCH_SIZE, g_sequence_len, VOCAB_SIZE))
        batch_grads = []
        if cuda:
            conditional_proba = conditional_proba.cuda()
        for j in range(BATCH_SIZE):
            for i in range(g_sequence_len):
                conditional_proba[j, i, int(samples[j, i])] = 1
            conditional_proba[j, :, :] = - (rewards[j] * conditional_proba[j, :, :])
        for j in range(BATCH_SIZE):
            j_grads = []
            # since we want to isolate each contribution, we have to zero the generator's gradients here.
            g.zero_grad()
            prob[j, :, :].backward(conditional_proba[j, :, :], retain_graph=True)
            for p in g.parameters():
                j_grads.append(p.grad.clone())
            batch_grads.append(j_grads)

        return batch_grads

class VarianceLoss(nn.Module):

    """Loss for the control variate annex network"""

    def __init__(self):
        super(VarianceLoss, self).__init__()

    def forward(self, grad, cuda = False):
        """
        Used to get the gradient of the variance.

        """
        bs = len(grad)
        ref = 0
        for j in range(bs):
            for i in range(len(grad[j])):
                ref += torch.sum(grad[j][i]**2).item()
        total_loss = np.array([ref/bs])
        total_loss = Variable(torch.Tensor(total_loss), requires_grad=True)
        if cuda:
            total_loss = total_loss.cuda()

        return total_loss

    def forward_variance(self, grad, cuda=False):
        """
        Used to get the variance of one single parameter.
        In this case, we take look at the last layer, then take the variance of the first parameter of this last layer in main.py

        """
        bs = len(grad)
        n_layers = len(grad[0])
        square_term = torch.zeros((grad[0][n_layers-1].size()))
        normal_term = torch.zeros((grad[0][n_layers-1].size()))
        if cuda:
            square_term = square_term.cuda()
            normal_term = normal_term.cuda()
        for j in range(bs):
            square_term = torch.add(square_term, grad[j][n_layers-1]**2)
            normal_term = torch.add(normal_term, grad[j][n_layers-1])
        square_term /= bs
        normal_term /= bs
        normal_term = normal_term ** 2

        return square_term - normal_term


def makeForms(verb):
    character = ('o', 'ch', 's', 'sh', 'x', 'z')
    if verb.endswith("y"):
        new = verb[:-1] + "ies"
    elif verb.endswith(character):
        new = verb + "es"
    else:
        new = verb + "s"
    return new

def main():
    verb = input("Enter your word: ")
    print("The third person singular form is", makeForms(verb))

main()


"""Control implementation for assignment 1.

The controller used the simulation in file `aer1216_fall2020_hw1_sim.py`.

Example
-------
To run the simulation, type in a terminal:

    $ python aer1216_fall2020_hw1_sim.py

Notes
-----
Tune the PD coefficients in `HW1Control.__init__()`.

"""
import numpy as np
from gym_pybullet_drones.envs.BaseAviary import BaseAviary

class HW1Control():
    """Control class for assignment 1."""

    ################################################################################

    def __init__(self,
                 env: BaseAviary
                 ):
        """ Initialization of class HW1Control.

        Parameters
        ----------
        env : BaseAviary
            The PyBullet-based simulation environment.

        """
        self.g = env.G
        """float: Gravity acceleration, in meters per second squared."""
        self.mass = env.M
        """float: The mass of quad from environment."""
        self.timestep = env.TIMESTEP
        """float: Simulation and control timestep."""
        self.kf_coeff = env.KF
        """float: RPMs to force coefficient."""
        self.km_coeff = env.KM
        """float: RPMs to torque coefficient."""

        ############################################################
        ############################################################
        #### HOMEWORK CODE (START) #################################
        ############################################################
        ############################################################
        self.p_coeff_position = 0.7 * 0.7
        """float: Proportional coefficient for position control."""
        self.d_coeff_position = 2 * 0.7 * 0.7
        """float: Derivative coefficient for position control."""
        ############################################################
        ############################################################
        #### HOMEWORK CODE (END) ###################################
        ############################################################
        ############################################################

        self.reset()

    ################################################################################

    def reset(self):
        """ Resets the controller counter."""
        self.control_counter = 0

    ################################################################################

    def compute_control(self,
                        current_position,
                        current_velocity,
                        target_position,
                        target_velocity=np.zeros(3),
                        target_acceleration=np.zeros(3),
                        ):
        """Compute the propellers' RPMs for the target state, given the current state.

        Parameters
        ----------
        current_position : ndarray
            (3,)-shaped array of floats containing global x, y, z, in meters.
        current_velocity : ndarray
            (3,)-shaped array of floats containing global vx, vy, vz, in m/s.
        target_position : ndarray
            (3,)-shaped array of float containing global x, y, z, in meters.
        target_velocity : ndarray, optional
            (3,)-shaped array of floats containing global, in m/s.
        target_acceleration : ndarray, optional
            (3,)-shaped array of floats containing global, in m/s^2.

        Returns
        -------
        ndarray
            (4,)-shaped array of ints containing the desired RPMs of each propeller.
        """
        self.control_counter += 1

        ############################################################
        ############################################################
        #### HOMEWORK CODE (START) #################################
        ############################################################
        ############################################################

        ##### Calculate position and velocity errors ###############
        current_pos_error = target_position[2] - current_position[2]
        current_vel_error = target_velocity[2] - current_velocity[2]

        #### Calculate input with a PD controller ##################
        # u = desired_acceleration + Kv * velocity_error + Kp * position_error
        u = target_acceleration[2] \
            + self.d_coeff_position * current_vel_error \
            + self.p_coeff_position * current_pos_error

        ##### Calculate propeller turn rates given the PD input ####
        # turn_rate = sqrt( (m*u + m*g) / (4*Kf) )
        propellers_rpm = np.sqrt((u*self.mass + self.g*self.mass) / (4 * self.kf_coeff))

        # For up-down motion, assign the same turn rates to all motors
        propellers_0_and_3_rpm, propellers_1_and_2_rpm = propellers_rpm, propellers_rpm
        ############################################################
        ############################################################
        #### HOMEWORK CODE (END) ###################################
        ############################################################
        ############################################################

        #### Print relevant output #################################
        if self.control_counter%(1/self.timestep) == 0:
            print("current_position", current_position)
            print("current_velocity", current_velocity)
            print("target_position", target_position)
            print("target_velocity", target_velocity)
            print("target_acceleration", target_acceleration)

        return np.array([propellers_0_and_3_rpm, propellers_1_and_2_rpm,
                         propellers_1_and_2_rpm, propellers_0_and_3_rpm])


from django import forms
from django.core.exceptions import ValidationError

from semester.models import Semester, CourseOffered, CourseDistribution, DistributedSectionDetail
from tempus_dominus.widgets import TimePicker, DatePicker
from django.contrib import messages
from django.shortcuts import redirect

class SemesterForm(forms.ModelForm):
    start_effect_date = forms.DateField(
        widget=DatePicker(
            options={
                'collapse': False,
                'format': 'L',
            },
            attrs={
                'append': 'fa fa-calendar',
                'icon_toggle': True,
                'input_toggle': True,
                'placeholder': 'Start Effect Date',
                'required': 'true',
                'autocomplete': 'off',
            },
        )
    )

    end_effect_date = forms.DateField(
        widget=DatePicker(
            options={
                'collapse': False,
                'format': 'L',
            },
            attrs={
                'append': 'fa fa-calendar',
                'icon_toggle': True,
                'input_toggle': True,
                'placeholder': 'End Effect Date',
                'required': 'true',
                'autocomplete': 'off',
            },
        )
    )

    class Meta:
        model = Semester
        fields = ('name', 'year', 'start_effect_date', 'end_effect_date')

class CourseOfferingForm(forms.ModelForm):
    class Meta:
        model = CourseOffered
        fields = '__all__'

class CourseDistributionForm(forms.ModelForm):
    starting_id = forms.CharField()
    ending_id = forms.CharField()

    class Meta:
        model = CourseDistribution
        fields = ('offered', 'section', 'teacher', 'parent_dist', 'starting_id', 'ending_id')

    def clean(self):
        cd = self.cleaned_data
        qs = CourseDistribution.objects.filter(offered=cd.get('offered'), section=cd.get('section'))
        if qs:
            for obj in qs:
                try:
                    ms = DistributedSectionDetail.objects.get(distribution=obj)
                    if ms.starting_id == "*" or cd.get('starting_id') == "*":
                        raise ValidationError("Course already distributed to Section.")
                    else:
                        if not (cd.get('ending_id') < ms.starting_id or cd.get('starting_id') > ms.ending_id):
                            raise ValidationError("Course already distributed to Section.")
                # raise ValidationError("Course already distributed to Section.")
                except:
                    return redirect("/distribution/create/")

        parent = cd.get('parent_dist')
        if parent:
            while parent.parent_dist != parent:
                cd['parent_dist'] = parent.parent_dist
                parent = cd.get('parent_dist')

        if parent and cd.get('teacher') != parent.teacher:
            raise ValidationError("Merged sections has different teacher")

        return cd

    def save(self, commit=True):
        cd = self.cleaned_data
        instance = super(CourseDistributionForm, self).save(commit=True)
        if not instance.parent_dist:
            instance.parent_dist = instance
            instance.save()

        DistributedSectionDetail.objects.update(
            distribution=instance,
            starting_id=cd.get('starting_id'),
            ending_id=cd.get('ending_id')
        )

        return instance

class CourseDistributionUpdateForm(forms.ModelForm):
    class Meta:
        model = CourseDistribution
        fields = ('offered', 'section', 'teacher', 'parent_dist')
    def clean(self):
        cd = self.cleaned_data
        parent = cd.get('parent_dist')
        if parent:
            while parent.parent_dist != parent:
                cd['parent_dist'] = parent.parent_dist
                parent = cd.get('parent_dist')

        if parent and cd.get('teacher') != parent.teacher:
            raise ValidationError("Merged sections has different teacher")

        return cd


from anomaly_detection import Amean
from multiprocessing import Process, Queue
from database import DataBase
from datetime import datetime, timedelta
import time
import traceback

class AnomalyDomain (Process):
    # initilize data
    def __init__(self, name, host) :
        super(AnomalyDomain, self).__init__()
        self.name = name
        self.db = DataBase(host=host, database="anomaly", collection="data")
        self.last_update = ""
        self.last_value = 0

        self.timeline = []
        for i in range(24) :
            hour_detection = Amean()
            self.timeline.append(hour_detection)

        self.timeline_weekend = []
        for i in range(24) :
            hour_detection = Amean()
            self.timeline_weekend.append(hour_detection)
        return

    def __predict (self, hour=0, current=0, angle=0, date="") :
        """predict and return value for new data point"""
        date = datetime.strptime(date, "%Y-%m-%d")
        code = self.timeline[hour].predict(current=current, angle=angle)
        if code == '' :
            return 10
        else :
            return code

    def __log (self, msg) :
        with open("log.txt","a") as f :
            f.write(msg + '\n')
        return

    def __check_new_data (self, name) :
        """check if there is new data in repo
            if yes, return all new data"""
        # check if repo is null (start-date = null)
        if self.last_update == "" :
            start_date = self.db.get_start_date(name=name)
            if start_date != '' :
                self.last_update = start_date

        # check last update
        # go to database and get last_update, then update data in anomaly class (this class)
        db_last_update = self.db.get_last_update(name=name)
        print("db_last_update: ",db_last_update)
        if db_last_update == '' or not db_last_update:
            return []
        else :
            db_last_update = datetime.strptime(db_last_update, "%Y-%m-%d %H")
        last_update = datetime.strptime(self.last_update,  "%Y-%m-%d %H")
        result = []
        while last_update < db_last_update :
            print("db_last_update: ", name," ", db_last_update)
            last_update += timedelta(seconds=3600)
            print("check last update :", last_update)
            date = last_update.strftime("%Y-%m-%d")
            hour = last_update.hour
            data_value = self.db.get_data_by_hour(name=name, date=date, hour=hour)
            self.__log(date + ' ' + str(hour) + ' ' + str(data_value))
            data = {'angle':float(data_value)-float(self.last_value),
                    'current':data_value,
                    'date':date,
                    'hour':hour}
            result.append(data)
            self.last_value = data_value

        self.last_update = datetime.strftime(last_update, '%Y-%m-%d %H')
        return result

    def __save_result (self, name, date, result) :
        self.db.insert_result(name=name, date=date, value=result)
        return

    #=========================  RUN   ==============================
    # Run process method
    # start per process by calling run()
    def run(self) :
        name = self.name
        try :
            while True :
                time.sleep(10)

                data = self.__check_new_data (name)
                # data :
                # [] : no new data
                # [ {date:, hour:, current:, angle:)]
                print("--------------AnomalyDomain is running1--------------")
                if data != [] :
                    print("--------------AnomalyDomain is running2--------------")
                    # predict new data
                    for hour_data in data :
                        result_prediction = self.__predict(hour=hour_data['hour'],
                                                           current=hour_data['current'],
                                                           angle=hour_data['angle'],
                                                           date=hour_data["date"])
                        # save result to db
                        self.__save_result(name=name,
                                           date=hour_data['date']+' '+str(hour_data['hour']),
                                           result=result_prediction)

                #continue waiting
        except Exception as e:
            with open("log.txt","a") as f :
                f.write(str(e) + '\n')
                traceback.print_exc()


# import transformers
# import datasets
# from pprint import pprint

# # with pipeline

# model = transformers.AutoModelForSequenceClassification.from_pretrained("") # load model from local directory
# tokenizer = transformers.AutoTokenizer.from_pretrained("TurkuNLP/bert-base-finnish-cased-v1")

# test_pipe = transformers.pipeline(task="text-classification", model=model, tokenizer=tokenizer, function_to_apply="sigmoid", top_k=None) # return_all_scores=True is deprecated

# test = [""] # add examples to test

# results = test_pipe(test)

# for zipped in zip(test, results):
#   pprint(zipped)

import transformers
import torch
import numpy as np
import argparse
from pprint import PrettyPrinter
import json
import datasets
import pandas as pd
import csv

""" This script is meant for looking at multi-label predictions for raw text data and saving the probabilities with id. """

# parse arguments
parser = argparse.ArgumentParser()
parser.add_argument('--model', required=True,
    help="the model name")
parser.add_argument('--data', required=True,
    help="the file name of the raw text to use for predictions")
parser.add_argument('--tokenizer', required=True,
    help="the tokenizer to use for tokenizing new text")
parser.add_argument('--filename', required=True,
    help="the file name to give file resulting from the predictions")
args = parser.parse_args()
print(args)

pprint = PrettyPrinter(compact=True).pprint

# read the data in
data = args.data

if ".json" in data:
    with open(data, 'r') as json_file:
            json_list = list(json_file)
    lines = [json.loads(jline) for jline in json_list]

    # use pandas to look at each column
    df=pd.DataFrame(lines)

# # TODO might have to change this depending on the data type
# elif ".tsv" in data:
#     with open(data, "rt", encoding="utf-8") as f:
#         lines = f.readlines()
#     lines = lines[1:]
#     for i in range(len(lines)):
#         lines[i] = lines[i].replace("\n", "")
#         lines[i] = lines[i].split("\t")
#         assert len(lines[i]) == 3

#     df=pd.DataFrame(lines, columns = ['id', 'label', 'text'])

elif ".tsv" in data:
    with open(data, "rt", encoding="utf-8") as f:
        lines = f.readlines()
    lines = lines[1:]
    for i in range(len(lines)):
        lines[i] = lines[i].replace("\n", "")
        lines[i] = lines[i].split("\t")
        assert len(lines[i]) == 2

    df=pd.DataFrame(lines, columns = ['label', 'text'])

# instantiate model, this is pretty simple
model=transformers.AutoModelForSequenceClassification.from_pretrained(args.model)

trainer = transformers.Trainer(
    model=model
)

tokenizer = transformers.AutoTokenizer.from_pretrained(args.tokenizer)

def tokenize(example):
    return tokenizer(
        example["text"],
        padding='max_length', # this got it to work, data_collator could have helped as well?
        max_length=512,
        truncation=True,
    )

dataset = datasets.Dataset.from_pandas(df)

#map all the examples
dataset = dataset.map(tokenize)

labels = dataset["label"]
# oh right I would have to change the labels for the test set to match the upper ones if I wanted easily readable results

dataset = dataset.remove_columns("label")
texts = dataset["text"]
#ids = dataset["id"]

# see how the labels are predicted
test_pred = trainer.predict(dataset)
predictions = test_pred.predictions # these are the logits

sigmoid = torch.nn.Sigmoid()
probs = sigmoid(torch.Tensor(predictions))
probs = probs.numpy()

unique_labels = ["IN", "NA", "HI", "LY", "IP", "SP", "ID", "OP", "QA_NEW"] # upper labels plus qa_new

with open(args.filename, 'w') as outfile:
    header = ["text", "gold_labels", *unique_labels] #maybe I should put the text last

    writer = csv.writer(outfile, delimiter="\t")
    writer.writerow(header)

    for i in range(len(texts)):

        text = texts[i]
        gold = labels[i]
        line = [text, gold]
        pred_list = [str(val) for val in probs[i]]
        line = [*line, *pred_list]

        writer.writerow(line)


#-------------------------------------------------------------------------
# Script en python que se encarga de conectarse a un recurso Event Hub de
# Microsoft Azure y leer todos los mensajes disponibles, al mismo tiempo
# que deja un checkpoint de lo que ha leído para no repetir mensajes
# la siguiente vez que arranque el programa.
#
# Instrucciones para utilizarla.
#   1-. Tener Python 3.4 o mayor.
#   2-. Tener el instalador de paquetes "pip".
#   3-. Ingresar el comando "pip install azure-eventhub"
#
# Autor: Noé Amador Campos Castillo.
# E-mail: noecampos@tec.mx
#--------------------------------------------------------------------------

import os
import sys
import json
import signal
import logging
import asyncio
import functools
from azure.eventprocessorhost import (
    AbstractEventProcessor,
    AzureStorageCheckpointLeaseManager,
    EventHubConfig,
    EventProcessorHost,
    EPHOptions)

class EventProcessor(AbstractEventProcessor):

    # Constructor de un Event Processor
    def __init__(self, params=None):
        super().__init__(params)
        self._msg_counter = 0

    # Función que se puede hacer override
    # Es para inicializar un Procesador de Eventos
    async def open_async(self, context):
        print("Connection established {}".format(context.partition_id))

    # Función que se puede hacer override
    # Sirve para detener el Procesador de Eventos.
    async def close_async(self, context, reason):

        print("Connection closed (reason {}, id {}, offset {}, sq_number {})".format(
            reason,
            context.partition_id,
            context.offset,
            context.sequence_number))

    # Función que se puede hacer override
    """
        Se llama cuando el EPH recibe un nuevo batch de eventos.
        Es donde se programa las acciones a realizar.
        Parametros:
            context     = Información sobre la partición
            messages    = El batch de eventos a procesar
    """
    async def process_events_async(self, context, messages):
        # Por cada evento...
        for Event in messages:
            # Se imprime el número de secuencia
            print("Mensaje: {}".format(Event.sequence_number))

            # Se parsea el json recibido en el mensaje del evento
            parsedMessage = json.loads(Event.body_as_str())
            # Se imprime de manera más estetica
            print(json.dumps(parsedMessage, indent=2, sort_keys=True))

        # Deja un checkpoint del evento recibido
        await context.checkpoint_async()

    # Función que se puede hacer override
    """
        Se llama cada que el cliente experimenta algún error al recibir eventos.
        El Event Proccessor Host se recupera recibiendo desde donde se quedo.
        ( A menos de que se haya matado el programa )
        Parametros:
            context     = Información sobre la partición
            messages    = El batch de eventos a procesar
    """
    async def process_error_async(self, context, error):
        print("Event Processor Error {!r}".format(error))

# Recibir eventos por dos minutos y luego apagarlo
async def wait_and_close(host):
    await asyncio.sleep(60)
    await host.close_async()

# Se conecta y recibe mensajes
try:
    # Regresa un loop asincrono
    ephLoop = asyncio.get_event_loop()

    # Nombre del Storage Account
    stgName = "-"
    # Key del storage
    stgKey = "-"
    # Nombre del Blob
    blobName = "-"

    # Nombre del namespace de Event Hubs
    ehNamespace = "-"
    # Nombre del Event Hub
    ehName = "-"
    # Nombre del SAS Policy del Event Hub
    SASUser = "-"
    # Llave del SAS Policy del Event Hub
    SASKey = "-"

    """
    Configuración del Event Hub
    Párametros:
        sb_name   = Nombre del namespace de Event Hubs
        eh_name   = Nombre del Event Hub
        policy    = Nombre del SAS Policy
        key       = Llave de la SAS Policy
    """
    ehConfig = EventHubConfig(ehNamespace, ehName, SASUser, SASKey)

    # Opciones por default
    ehOptions = EPHOptions()
    # Set algunas opciones
    ehOptions.release_pump_on_timeout = True
    ehOptions.debug_trace = False

    """
    Configuración del Storage
    Párametros:
        storage_account_name    = Nombre del storage
        storage_account_key     = Llave del storage
        lease_container_name    = Nombre del contenedor
    """
    stgManager = AzureStorageCheckpointLeaseManager(
        stgName, stgKey, blobName)

    # Host del Event Hub Processor
    ehHost = EventProcessorHost(
        EventProcessor,
        ehConfig,
        stgManager,
        ep_params = ["param1", "param2"],
        eph_options = ehOptions,
        loop = ephLoop)

    # Prepara los procedimientos a ejecutar en loop
    ephTasks = asyncio.gather(
        ehHost.open_async(),
        wait_and_close(ehHost))

    # Corre el loop
    ephLoop.run_until_complete(ephTasks)

# En caso de ocurrri excepciones de teclado
except KeyboardInterrupt:
    # Cancela las tareas y el loop
    for task in asyncio.Task.all_tasks():
        task.cancel()
    ephLoop.run_forever()
    ephTasks.exception()

# Cierra el loop
finally:
    ephLoop.stop()


from bokeh.layouts import column
from bokeh.models.widgets import RadioButtonGroup,Select, Div, Button,PreText
from bokeh.models import  TextInput, RadioGroup
from bokeh.plotting import curdoc

button_group = RadioButtonGroup(labels=["Physical parameters", "Geometric parameters", "Initial  conditions"], active=1)

## Add the elements inside each radio button

slope  = TextInput(value='25', title="slope:")

###### Drop down menus

Coefficient = Select(title="Coefficient:", value="chezy", options=["Manning", "chezy"])
Coefficient_value1  = TextInput(value=" ", title="chezy:" )
Coefficient_value2  = TextInput(value=" ", title="Manning:" )

#### Add geometric parameters
length  = TextInput(value=" ", title="Length:")
width  = TextInput(value=" ", title="Width:")

#LABELS = ["km", "m"]
#radio_group = RadioGroup(labels=LABELS, active=0)
#### Add ginitial conditiions
depth = TextInput(value=" ", title="Depth(m):")
Q = TextInput(value=" ", title="Discharge(m3/sec):")
texto = PreText(text="""Please click here""",width=500, height=100)
layout = ()
def button_group_change(active):
    ch = active
    if ch == 0:
       slope.visible=True
       Coefficient.visible=True
       Coefficient_value1.visible=True
       Coefficient_value2.visible=True
       length.visible=False
       width.visible=False
       depth.visible=False
       Q.visible=False
       # layout= column(slope, Coefficient,Coefficient_value1, Coefficient_value2)

    elif ch == 1:
         length.visible=True
         width.visible=True
         slope.visible=False
         Coefficient.visible=False
         Coefficient_value1.visible=False
         Coefficient_value2.visible=False
         depth.visible=False
         Q.visible=False
        # layout= column(length, width)

    elif ch == 2:
        depth.visible=True
        Q.visible=True
        slope.visible=False
        Coefficient.visible=False
        Coefficient_value1.visible=False
        Coefficient_value2.visible=False
        length.visible=False
        width.visible=False
        # layout= column(length, width )
    texto.text ='text' #str(layout)

    #curdoc().add_root(column(button_group,slope, Coefficient))
    #curdoc().add_root(row(button_group,length))
layout= column(slope, Coefficient,Coefficient_value1, Coefficient_value2,length, width,length)
button_group.on_click(button_group_change)
###show
curdoc().add_root(column(texto,button_group,layout))
#bokeh serve --show BWC.py


# Time: O(n)
# Space: O(1)
class Solution:
    def rob(self, nums: List[int]) -> int:
        """
        """

        if not nums:
            return 0
        if len(nums)==1:
            return nums[0]

        return max(self.util(nums[:-1]), self.util(nums[1:]))

    def util(self, nums):

        # Linear Space
        # max_arr = [0]*len(nums)
        # max_arr[0] = nums[0]

        # Constant Space
        prev_max = nums[0]
        pp_max = 0

        for i in range(1, len(nums)):

            cur_max = max(nums[i] + pp_max, prev_max)
            pp_max = prev_max
            prev_max = cur_max

            max_arr[i] = max(nums[i] + max_arr[i-2], max_arr[i-1])

        return prev_max


import sys
input = sys.stdin.readline

T = int(input())
for tc in range(1,T+1):
    L = list(map(int,input().split()))
    L.sort()
    a,b,c = L
    ans = "NO"
    if a**2 + b**2 == c**2:
        ans = "YES"
    print(f"Case #{tc}: {ans}")


import requests
import re
from bs4 import BeautifulSoup
from openpyxl import load_workbook

DIRECTORY_URL = "https://directory.tufts.edu/searchresults.cgi"
WORKBOOK_NAME = "DirectoryResults_2017-2018.xlsx"
NAME_SHEET = "DirectoryResults"

# This script works on Excel Sheets with a single column in the A column of
# a unique listing of names. It searches for every name in the tufts directory,
# then strips out a department if it is found and places it in the B column next
# to the search query.

def getDirectoryPage(name):
    rawPage = requests.post(DIRECTORY_URL, data={"type": "Faculty", "search": name})
    soup = BeautifulSoup(rawPage.text, "html.parser")
    for child in soup.find_all(href=re.compile("department.cgi")):
        return child.contents[0].strip()

dataBook = load_workbook(WORKBOOK_NAME)
nameSheet = dataBook[NAME_SHEET]

for index in range(2, nameSheet.max_row + 1):
    currentName = nameSheet["A{0}".format(index)].value
    affiliation = getDirectoryPage(currentName)
    nameSheet["B{0}".format(index)] = affiliation

    # Rudimentary backup every 100 entries
    if index % 100 == 0:
        print("Progress: {:.2%}".format(index / (nameSheet.max_row + 1)))
        dataBook.save(WORKBOOK_NAME)

dataBook.save(WORKBOOK_NAME)