import base64
import os
import jsonpickle

from fastapi import UploadFile, HTTPException

from uuid import UUID, uuid4

from database.neo4j import MyDB, driver

from models.bcf_request import *
from models.bcf_response import *
from models.request import *

class BCFDB(MyDB):

    # implemented
    def get_projects(self, current_user: User) -> List[ProjectGET]:
        def get_projects_work(tx) -> List[ProjectGET]:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)
                WHERE u.username = $username
                AND r1.read = True
                RETURN p AS project
                """
            results = tx.run(cypher, username=current_user.username)
            project_model_list = list()
            for result in results:
                project_json = self.node_to_json(result.get("project"))
                project_model = ProjectGET(**project_json)
                project_model_list.append(project_model)
            return project_model_list

        with self.driver.session() as session:
            return session.execute_read(get_projects_work)

    # implemented
    def get_project(self, project_id: UUID, current_user: User) -> ProjectGET:
        def get_project_work(tx) -> ProjectGET:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                RETURN p AS project
                """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id))
            first = result.single()
            if first is None:
                raise HTTPException(status_code=404, detail="Item not found.")
            project_node = first.get("project")
            project_dict = self.node_to_json(project_node)
            return ProjectGET(**project_dict)

        with self.driver.session() as session:
            return session.execute_read(get_project_work)

    # implemented
    def put_project(self, project_id: UUID, project: ProjectPUT, current_user: User) -> ProjectGET:
        def put_project_work(tx) -> ProjectGET:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)
                WHERE u.username = $username
                AND r1.update = True
                AND p.project_id = $project_id
                SET p.name = $project_name
                RETURN p AS project
                """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            project_name=project.name)
            first = result.single()
            if first is None:
                raise HTTPException(status_code=404, detail="Item not found.")
            project_node = first.get("project")
            project_dict = self.node_to_json(project_node)
            return ProjectGET(**project_dict)

        with self.driver.session() as session:
            return session.execute_write(put_project_work)

    # implemented
    def get_project_extensions(self, project_id: UUID, current_user: User) -> ExtensionsGET:
        def get_project_extensions_work(tx) -> ExtensionsGET:
            cypher = """
                    MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(e:Extensions)
                    WHERE u.username = $username
                    AND r1.read = True
                    AND p.project_id = $project_id
                    RETURN e AS extensions
                    """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id))
            first = result.single()
            if first is None:
                raise HTTPException(status_code=404, detail="Item not found.")
            extensions_node = first.get("extensions")
            extensions_dict = self.node_to_json(extensions_node)
            return ExtensionsGET(**extensions_dict)

        with self.driver.session() as session:
            return session.execute_read(get_project_extensions_work)

    # implemented
    # returns a collection
    def get_topics(self, project_id: str, current_user: User) -> List[TopicGET]:
        def get_topics_work(tx) -> List[TopicGET]:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                RETURN t AS topic, ID(t) AS server_assigned_id
                """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id))
            topic_model_list = list()
            for result in results:
                topic_json = self.node_to_json(result.get("topic"))
                topic_json["server_assigned_id"] = result.get("server_assigned_id")
                topic_model = TopicGET(**topic_json)
                topic_model_list.append(topic_model)
            return topic_model_list

        with self.driver.session() as session:
            return session.execute_read(get_topics_work)

    # implemented
    def get_topic(self, project_id: UUID, topic_id: UUID, current_user: User) -> TopicGET:
        def get_topic_work(tx) -> TopicGET:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                RETURN t AS topic, ID(t) AS server_assigned_id
                """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id))
            first = result.single()
            if first is None:
                raise HTTPException(status_code=404, detail="Item not found.")
            topic_node = first.get("topic")
            topic_dict = self.node_to_json(topic_node)
            topic_dict["server_assigned_id"] = first.get("server_assigned_id")
            return TopicGET(**topic_dict)

        with self.driver.session() as session:
            return session.execute_read(get_topic_work)

    # implemented
    def post_topic(self, project_id: UUID, topic: TopicPOST, current_user: User) -> TopicGET:
        def post_topic_work(tx) -> UUID:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)
                WHERE u.username = $username
                AND r1.createTopic = True
                AND p.project_id = $project_id
                MERGE (p)-[r2:HAS]->(t:Topic {guid: $guid})
                SET t.creation_date         = $creation_date,
                    t.modified_date         = $creation_date,
                    t.creation_author       = $username,
                    t.modified_author       = $username,
                    t.topic_type            = $topic_type,
                    t.topic_status          = $topic_status,
                    t.title                 = $title,
                    t.priority              = $priority,
                    t.index                 = $index,
                    t.assigned_to           = $assigned_to,
                    t.stage                 = $stage,
                    t.description           = $description,
                    t.due_date              = $due_date
                RETURN t AS topic, ID(t) AS server_assigned_id
                """
            if not hasattr(topic, 'guid') or topic.guid is None:
                topic.guid = uuid4()
            creation_date = self.timestamp()
            print(cypher)
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            guid=str(topic.guid),
                            creation_date=creation_date,
                            topic_type=topic.topic_type,
                            topic_status=topic.topic_status,
                            title=topic.title,
                            priority=topic.priority,
                            index=topic.index,
                            assigned_to=topic.assigned_to,
                            stage=topic.stage,
                            description=topic.description,
                            due_date=topic.due_date)
            summary = result.consume()
            if summary.counters.nodes_created < 1:
                raise HTTPException(status_code=400, detail="Item not added.")
            return topic.guid

        with self.driver.session() as session:
            topic_guid = session.execute_write(post_topic_work)
            return self.get_topic(project_id, topic_guid, current_user)

    # implemented
    def put_topic(self, project_id: UUID, topic_id: UUID, topic: TopicPUT, current_user: User) -> TopicGET:
        def put_topic_work(tx) -> bool:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)
                WHERE u.username = $username
                AND r1.update = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                SET t.topic_type        = $topic_type,
                    t.topic_status      = $topic_status,
                    t.title             = $title,
                    t.priority          = $priority,
                    t.index             = $index,
                    t.modified_date     = $modified_date,
                    t.modified_author   = $username,
                    t.assigned_to       = $assigned_to,
                    t.stage             = $stage,
                    t.description       = $description,
                    t.due_date          = $due_date
                RETURN t AS topic, ID(t) AS server_assigned_id
                """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            topic_type=topic.topic_type,
                            topic_status=topic.topic_status,
                            title=topic.title,
                            priority=topic.priority,
                            index=topic.index,
                            modified_date=self.timestamp(),
                            assigned_to=topic.assigned_to,
                            stage=topic.stage,
                            description=topic.description,
                            due_date=topic.due_date)
            summary = result.consume()
            if summary.counters.properties_set < 1:
                raise HTTPException(status_code=404, detail="Item not found.")
            return True

        with self.driver.session() as session:
            session.execute_write(put_topic_work)
            return self.get_topic(project_id, topic_id, current_user)

        # implemented

    def set_topic_modified_date(self, project_id: UUID, topic_id: UUID, current_user: User) -> bool:
        def set_topic_modified_date_work(tx) -> bool:
            cypher = """
                   MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)
                   WHERE u.username = $username
                   AND r1.update = True
                   AND p.project_id = $project_id
                   AND t.guid = $topic_id
                   SET t.modified_date     = $modified_date
                """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            modified_date=self.timestamp())
            summary = result.consume()
            if summary.counters.properties_set < 1:
                raise HTTPException(status_code=404, detail="Item not found.")
            return True

        with self.driver.session() as session:
            return session.execute_write(set_topic_modified_date_work)

    # implemented
    def delete_topic(self, project_id: UUID, topic_id: UUID, current_user: User) -> int:
        def delete_topic_work(tx) -> int:
            cypher = """
                    MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)
                    WHERE u.username = $username
                    AND r1.update = True
                    AND p.project_id = $project_id
                    AND t.guid = $topic_id
                    DETACH DELETE t
                    """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id))
            result_summary = result.consume()
            if result_summary.counters.nodes_deleted < 1:
                raise HTTPException(status_code=404, detail="Item not found.")
            return result_summary.counters.nodes_deleted

        with self.driver.session() as session:
            return session.execute_write(delete_topic_work)

    # implemented
    # returns a collection
    def get_files_information(self, project_id: UUID, current_user: User) -> List[ProjectFileInformation]:
        def get_files_information_work(tx) -> List[ProjectFileInformation]:
            cypher = """
                 MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:CONTAINS]->(d:Document)
                 WHERE u.username = $username
                 AND r1.read = True
                 AND p.project_id = $project_id
                 RETURN d AS document
                 """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id))

            project_file_informations = list()
            for result in results:
                document_json = self.node_to_json(result.get("document"))
                display_information_list = list()
                display_information_json = {
                    'field_display_name': 'File',
                    'field_value': document_json['title']
                }
                display_information_list.append(display_information_json)
                base = os.environ['KONTROLL_BASE_URL'] + "documents/1.0/document/"
                base.replace('http://', 'https://')
                base.replace('https://', 'open-cde-documents://')
                document_id = document_json['document_id']
                version_index = document_json['version_index']
                extra_path = document_id + "/version/" + str(version_index)
                reference = base + extra_path
                file_get_json = {
                    'ifc_project': document_json['ifc_project'],
                    'filename': document_json['name'],
                    'reference': reference
                }
                project_file_information = ProjectFileInformation(**{
                    'display_information': display_information_list,
                    'file': file_get_json
                })
                project_file_informations.append(project_file_information)
            return project_file_informations

            #
            # BCF API compatible servers offer endpoints to list project model files, which are stored on a CDE.
            # To indicate that models provided by BCF API file-references can be downloaded from the CDE using
            # the Documents API, the reference part of a file_GET response from the BCF server will include
            # the URL scheme (protocol) open-cde-documents and a link to the document_version URL on the CDE.
            #
            # The actual URL scheme (protocol) that a Client Application should
            # use to call the document_version URL should be https.
            #
            # BCF API project_file_information example:
            #
            # {
            #   "display_information": [
            #     {
            #       "field_display_name": "Model Name",
            #       "field_value": "ARCH-Z100-051"
            #     }
            #   ],
            #   "file": {
            #     "ifc_project": "0J$yPqHBD12v72y4qF6XcD",
            #     "file_name": "OfficeBuilding_Architecture_0001.ifc",
            #     "reference": "open-cde-documents://<document_version_url>"
            #   }
            # }
            #
            # In the example above, the CDE returns a value of
            # open-cde-documents://<document_version_url> for the file reference part,
            # indicating that a Documents API capable client can call https://<document_version_url>
            # to get the document version information. From there on, compatible
            # clients can retrieve metadata and the binary content of the file.
            #
            # class ProjectFileInformation(BaseModel):
            # display_information: Optional[List[ProjectFileDisplayInformation]] = None
            # file: Optional[FileGET] = None
            #
            # class ProjectFileDisplayInformation(BaseModel):
            # field_display_name: str
            # field_value: str
            #
            # class FileGET(BaseModel):
            # ifc_project: Optional[str] = None
            # ifc_spatial_structure_element: Optional[str] = None
            # filename: Optional[str] = None
            # date: Optional[str] = None
            # reference: Optional[str] = None
            #

        with self.driver.session() as session:
            return session.execute_read(get_files_information_work)

    # implemented
    # returns a collection
    def get_files(self, project_id: UUID, topic_id: UUID, current_user: User) -> List[FileGET]:
        def get_files_work(tx) -> List[FileGET]:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:REFERS_TO]->(f:Document:Model)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                RETURN f AS file
                """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id))
            file_model_list = list()
            for result in results:
                file_json = self.node_to_json(result.get("file"))
                file_model = FileGET(**file_json)
                file_model_list.append(file_model)
            return file_model_list

        with self.driver.session() as session:
            return session.execute_read(get_files_work)

    # implemented
    def put_files(self, project_id: UUID, topic_id: UUID, files: List[FilePUT], current_user: User) -> List[FileGET]:
        def put_files_work(tx) -> bool:
            cypher_delete_references = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:REFERS_TO]->(f:Document:Model)
                WHERE u.username = $username
                AND r1.updateFiles = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                DELETE r3
            """
            tx.run(cypher_delete_references,
                   username=current_user.username,
                   project_id=str(project_id),
                   topic_id=str(topic_id))
            cypher_update_and_attach_references = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(f:Document:Model)
                MATCH (u)-[r1]->(p)-[r3:HAS]->(t:Topic)
                WHERE u.username = $username
                AND r1.updateFiles = True
                AND p.project_id = $project_id
                AND f.reference = $reference
                AND t.guid = $topic_id
                SET f.filename                          = $filename,
                    f.date                              = $date,
                    f.ifc_project                       = $ifc_project,
                    f.ifc_spatial_structure_element     = $ifc_spatial_structure_element
                MERGE (t)-[r4:REFERS_TO]->(f)
                RETURN f
                """
            for file in files:
                result = tx.run(cypher_update_and_attach_references,
                                username=current_user.username,
                                project_id=str(project_id),
                                reference=file.reference,
                                topic_id=str(topic_id),
                                filename=file.filename,
                                date=file.date,
                                ifc_project=file.ifc_project,
                                ifc_spatial_structure_element=file.ifc_spatial_structure_element)
                first = result.single()
                if first is None:
                    raise HTTPException(status_code=404, detail="Item not found.")
                return True

        with self.driver.session() as session:
            session.execute_write(put_files_work)
            return self.get_files(project_id, topic_id, current_user)

    # implemented
    def get_comments(self, project_id: UUID, topic_id: UUID, current_user: User) -> List[CommentGET]:
        def get_comments_work(tx) -> List[CommentGET]:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(c:Comment)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                RETURN c AS comment
                """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id))
            comment_model_list = list()
            for result in results:
                comment_json = self.node_to_json(result.get("comment"))
                comment_json['topic_guid'] = str(topic_id)
                comment_model = CommentGET(**comment_json)
                comment_model_list.append(comment_model)
            return comment_model_list

        with self.driver.session() as session:
            return session.execute_read(get_comments_work)

    # implemented
    def get_comment(self, project_id: UUID, topic_id: UUID, comment_id: UUID, current_user: User) -> CommentGET:
        def get_comment_work(tx) -> CommentGET:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(c:Comment)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                AND c.guid = $comment_id
                RETURN c AS comment
                """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            comment_id=str(comment_id))
            first = result.single()
            if first is None:
                raise HTTPException(status_code=404, detail="Item not found.")
            comment_node = first.get("comment")
            comment_dict = self.node_to_json(comment_node)
            comment_dict['topic_guid'] = str(topic_id)
            comment = CommentGET(**comment_dict)
            return comment

        with self.driver.session() as session:
            return session.execute_read(get_comment_work)
        # date
        # author
        # topic_guid

    def post_comment(self, project_id: UUID, topic_id: UUID, comment: CommentPOST, current_user: User) -> CommentGET:
        def post_comment_work(tx) -> UUID:
            if not hasattr(comment, 'guid') or comment.guid is None:
                this_comment_guid = uuid4()
            else:
                this_comment_guid = UUID(comment.guid)
            if comment.viewpoint_guid:
                # make sure that given viewpoint_guid exists in database
                viewpoints = self.get_viewpoints(project_id, topic_id, current_user)
                viewpoint_guids = [viewpoint.guid for viewpoint in viewpoints]
                if comment.viewpoint_guid not in viewpoint_guids:
                    raise HTTPException(status_code=400, detail="Item not added.")
                injection_viewpoint = '-[r3:HAS]->(v:Viewpoint)'
                injection_viewpoint_selection = 'AND v.guid = $viewpoint_id'
                injection_viewpoint_relation = 'MERGE (c2)-[r7:RELATED_TO]->(v)'
            else:
                injection_viewpoint = ''
                injection_viewpoint_selection = ''
                injection_viewpoint_relation = ''
            if hasattr(comment, 'reply_to_comment_guid'):
                # make sure that given reply_to_comment_guid exists in database
                get_comments = self.get_comments(project_id, topic_id, current_user)
                comment_guids = [get_comment.guid for get_comment in get_comments]
                if comment.reply_to_comment_guid not in comment_guids:
                    raise HTTPException(status_code=400, detail="Item not added.")
                injection_existing_comment = 'MATCH (u)-[r1]->(p)-[r2]->(t)-[r4:HAS]->(c1:Comment)'
                injection_existing_comment_selection = 'AND c1.guid = $reply_to_comment_id'
                injection_existing_comment_relation = 'MERGE (c1)-[r5:THEN]->(c2)'
            else:
                injection_existing_comment = ''
                injection_existing_comment_selection = ''
                injection_existing_comment_relation = ''
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)%s
                %s
                WHERE u.username = $username
                AND r1.createComment = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                %s
                %s
                MERGE (t)-[r5:HAS]->(c2:Comment {guid: $comment_id})
                %s
                %s
                SET c2.date                  = $creation_date,
                    c2.modified_date         = $creation_date,
                    c2.author                = $username,
                    c2.modified_author       = $username,
                    c2.comment               = $comment
                """ % (injection_viewpoint,
                       injection_existing_comment,
                       injection_viewpoint_selection,
                       injection_existing_comment_selection,
                       injection_viewpoint_relation,
                       injection_existing_comment_relation)
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            viewpoint_id=str(comment.viewpoint_guid),
                            reply_to_comment_id=str('comment.reply_to_comment_guid'),
                            comment_id=str(this_comment_guid),
                            creation_date=self.timestamp(),
                            comment=comment.comment)
            summary = result.consume()
            if summary.counters.nodes_created < 1:
                raise HTTPException(status_code=400, detail="Item not added.")
            return this_comment_guid

        with self.driver.session() as session:
            comment_guid = session.execute_write(post_comment_work)
            return self.get_comment(project_id, topic_id, comment_guid, current_user)

    def put_comment(self, project_id: UUID, topic_id: UUID, comment_id: UUID, comment: CommentPUT,
                    current_user: User) -> CommentGET:
        def put_comment_work(tx) -> bool:
            if comment.viewpoint_guid:
                viewpoints = self.get_viewpoints(project_id, topic_id, current_user)
                viewpoint_guids = [viewpoint.guid for viewpoint in viewpoints]
                if comment.viewpoint_guid not in viewpoint_guids:
                    raise HTTPException(status_code=400, detail="Item not added.")
                injection_new_viewpoint = 'MATCH (t)-[r5:HAS]->(v_new:Viewpoint)'
                injection_new_viewpoint_selection = 'WHERE v_new.guid = $new_rtv_id'
                injection_new_viewpoint_relation = 'MERGE (c)-[r5:RELATED_TO]->(v)'
            else:
                injection_new_viewpoint = ''
                injection_new_viewpoint_selection = ''
                injection_new_viewpoint_relation = ''
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(c:Comment)
                WHERE u.username = $username
                AND r1.updateComment = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                AND c.guid = $comment_id
                OPTIONAL MATCH (c)-[r4:RELATED_TO]->(v_existing:Viewpoint)
                %s
                %s
                SET c.comment = $comment
                SET c.modified_date = $modified_date
                SET c.modified_author = $username
                DELETE r4
                %s
            """ % (injection_new_viewpoint,
                   injection_new_viewpoint_selection,
                   injection_new_viewpoint_relation)
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            comment_id=str(comment_id),
                            new_rtv_id=str(comment.viewpoint_guid),
                            comment=comment.comment,
                            modified_date=self.timestamp())
            summary = result.consume()
            if summary.counters.properties_set < 1:
                raise HTTPException(status_code=400, detail="Item not added.")
            return True

        with self.driver.session() as session:
            session.execute_write(put_comment_work)
            return self.get_comment(project_id, topic_id, comment_id, current_user)

    # implemented
    def delete_comment(self, project_id: UUID, topic_id: UUID, comment_id: UUID, current_user: User) -> int:
        def delete_comment_work(tx) -> int:
            cypher = """
                    MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(c:Comment)
                    WHERE u.username = $username
                    AND r1.update = True
                    AND p.project_id = $project_id
                    AND t.guid = $topic_id
                    AND c.guid = $comment_id
                    DETACH DELETE c
                """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            comment_id=str(comment_id))
            result_summary = result.consume()
            if result_summary.counters.nodes_deleted < 1:
                raise HTTPException(status_code=404, detail="Item not found.")
            return result_summary.counters.nodes_deleted

        with self.driver.session() as session:
            self.set_topic_modified_date(project_id, topic_id, current_user)
            return session.execute_write(delete_comment_work)

    @staticmethod
    def enum_to_mime(enum_type) -> str:
        if str(enum_type) == 'png':
            mime_type = 'image/png'
        elif str(enum_type) == 'jpg' or str(enum_type) == 'jpeg':
            mime_type = 'image/jpeg'
        else:
            mime_type = 'image/' + str(enum_type)
        return mime_type

    # implementing
    def post_viewpoint(self, project_id: UUID, topic_id: UUID, viewpoint: ViewpointPOST,
                       current_user: User) -> ViewpointGET:
        def post_viewpoint_work(tx) -> str:
            if hasattr(viewpoint, 'snapshot'):
                snapshot_type = self.enum_to_mime(viewpoint.snapshot.snapshot_type.value)
                snapshot = True
                set_snapshot = 'v.snapshot = $snapshot, v.snapshot_type = $snapshot_type,'
            else:
                snapshot_type = ''
                snapshot = False
                set_snapshot = ''
            cypher_viewpoint = """
                 MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)
                 WHERE u.username = $username
                 AND r1.createViewpoint = True
                 AND p.project_id = $project_id
                 AND t.guid = $topic_id
                 MERGE (t)-[r3:HAS]->(v:Viewpoint {guid: $guid, index: $index})
                 SET v.camera_definition = $camera_definition,
                     v.camera_view_point   = point({x: $cvp_x, y: $cvp_y, z: $cvp_z}),
                     v.camera_direction    = point({x: $cad_x, y: $cad_y, z: $cad_z}),
                     v.camera_up_vector    = point({x: $cuv_x, y: $cuv_y, z: $cuv_z}),
                     v.view_tws_or_fo_view = $view_tws_or_fo_view,
                     v.aspect_ratio        = $aspect_ratio,
                     %s
                     v.default_visibility = $default_visibility,
                     v.spaces_visible = $spaces_visible,
                     v.space_boundaries_visible = $space_boundaries_visible,
                     v.openings_visible = $openings_visible
                 """ % set_snapshot
            if viewpoint.guid is None:
                viewpoint.guid = uuid4()
            if viewpoint.orthogonal_camera is None:
                camera_definition = 'perspective_camera'
                view_tws_or_fo_view = viewpoint.perspective_camera.field_of_view
                aspect_ratio = viewpoint.perspective_camera.aspect_ratio
                cvp = viewpoint.perspective_camera.camera_view_point
                cad = viewpoint.perspective_camera.camera_direction
                cuv = viewpoint.perspective_camera.camera_up_vector
            else:
                camera_definition = 'orthogonal_camera'
                view_tws_or_fo_view = viewpoint.orthogonal_camera.view_to_world_scale
                aspect_ratio = viewpoint.orthogonal_camera.aspect_ratio
                cvp = viewpoint.orthogonal_camera.camera_view_point
                cad = viewpoint.orthogonal_camera.camera_direction
                cuv = viewpoint.orthogonal_camera.camera_up_vector
            result = tx.run(cypher_viewpoint,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            guid=str(viewpoint.guid),
                            index=viewpoint.index,
                            camera_definition=camera_definition,
                            cvp_x=cvp.x, cvp_y=cvp.y, cvp_z=cvp.z,
                            cad_x=cad.x, cad_y=cad.y, cad_z=cad.z,
                            cuv_x=cuv.x, cuv_y=cuv.y, cuv_z=cuv.z,
                            view_tws_or_fo_view=view_tws_or_fo_view,
                            aspect_ratio=aspect_ratio,
                            snapshot=snapshot,
                            snapshot_type=snapshot_type,
                            default_visibility=viewpoint.components.visibility.default_visibility,
                            spaces_visible=viewpoint.components.visibility.view_setup_hints.spaces_visible,
                            space_boundaries_visible=viewpoint.components.visibility.view_setup_hints.space_boundaries_visible,
                            openings_visible=viewpoint.components.visibility.view_setup_hints.openings_visible)
            summary = result.consume()
            if summary.counters.nodes_created < 1:
                print(cypher_viewpoint)
                raise HTTPException(status_code=400, detail="Item not added.")

            # LINES
            for line in viewpoint.lines:
                cypher_line = """
                    MATCH (v:Viewpoint)
                    WHERE v.guid = $guid
                    MERGE (v)-[r1:HAS]->(l:Line)
                    SET l.start_point = point({x: $lsp_x, y: $lsp_y, z: $lsp_z}),
                        l.end_point = point({x: $lep_x, y: $lep_y, z: $lep_z})
                """
                result = tx.run(cypher_line,
                                guid=str(viewpoint.guid),
                                lsp_x=line.start_point.x, lsp_y=line.start_point.y, lsp_z=line.start_point.z,
                                lep_x=line.end_point.x, lep_y=line.end_point.y, lep_z=line.end_point.z)
                summary = result.consume()
                if summary.counters.nodes_created < 1:
                    print(cypher_line)
                    raise HTTPException(status_code=400, detail="Item not added.")

            # CLIPPING PLANES
            for clipping_plane in viewpoint.clipping_planes:
                cypher_clipping_plane = """
                    MATCH (v:Viewpoint)
                    WHERE v.guid = $guid
                    MERGE (v)-[r1:HAS]->(cp:ClippingPlane)
                    SET cp.location = point({x: $cpl_x, y: $cpl_y, z: $cpl_z}),
                        cp.direction = point({x: $cpd_x, y: $cpd_y, z: $cpd_z})
                """
                result = tx.run(cypher_clipping_plane,
                                guid=str(viewpoint.guid),
                                lsp_x=clipping_plane.location.x, lsp_y=clipping_plane.location.y,
                                lsp_z=clipping_plane.location.z,
                                lep_x=clipping_plane.direction.x, lep_y=clipping_plane.direction.y,
                                lep_z=clipping_plane.direction.z)
                summary = result.consume()
                if summary.counters.nodes_created < 1:
                    print(cypher_clipping_plane)
                    raise HTTPException(status_code=400, detail="Item not added.")

            # BITMAPS
            for bitmap in viewpoint.bitmaps:
                cypher_bitmap = """
                    MATCH (v:Viewpoint)
                    WHERE v.guid = $guid
                    MERGE (v)-[r1:HAS]->(b:Bitmap)
                    SET b.type = $type,
                        b.location = point({x: $bml_x, y: $bml_y, z: $bml_z}),
                        b.normal = point({x: $bmn_x, y: $bmn_y, z: $bmn_z}),
                        b.up = point({x: $bmu_x, y: $bmu_y, z: $bmu_z}),
                        b.height = $height
                """
                bitmap_type = self.enum_to_mime(bitmap.bitmap_type)
                result = tx.run(cypher_bitmap,
                                guid=str(viewpoint.guid),
                                type=bitmap_type,
                                bml_x=bitmap.location.x, bml_y=bitmap.location.y, bml_z=bitmap.location.z,
                                bmn_x=bitmap.direction.x, bmn_y=bitmap.direction.y, bmn_z=bitmap.direction.z,
                                bmu_x=bitmap.direction.x, bmu_y=bitmap.direction.y, bmu_z=bitmap.direction.z,
                                height=bitmap.height)
                summary = result.consume()
                if summary.counters.nodes_created < 1:
                    print(bitmap_type)
                    raise HTTPException(status_code=400, detail="Item not added.")

            # COMPONENTS
            components = {}
            for selected_component in viewpoint.components.selection:
                if selected_component.ifc_guid not in components:
                    components[selected_component.ifc_guid] = {}
                components[selected_component.ifc_guid]['selected'] = True
                components[selected_component.ifc_guid]['originating_system'] = selected_component.originating_system
                components[selected_component.ifc_guid]['authoring_tool_id'] = selected_component.authoring_tool_id
            for color in viewpoint.components.coloring:
                for colored_component in color.components:
                    if colored_component.ifc_guid not in components:
                        components[colored_component.ifc_guid] = {}
                    components[colored_component.ifc_guid]['color'] = color.color
            for exception_component in viewpoint.components.visibility.exceptions:
                if exception_component.ifc_guid not in components:
                    components[exception_component.ifc_guid] = {}
                components[exception_component.ifc_guid]['visibility_exception'] = True
            set_list = list()

            for ifc_guid in components.keys():
                set_list.append('c.ifc_guid = $ifc_guid')
                if 'selected' in components[ifc_guid]:
                    selected = True
                    originating_system = components[ifc_guid]['originating_system']
                    authoring_tool_id = components[ifc_guid]['authoring_tool_id']
                    set_list.append('c.selected = $selected')
                    set_list.append('c.originating_system = $originating_system')
                    set_list.append('c.authoring_tool_id = $authoring_tool_id')
                else:
                    selected = False
                    originating_system = False
                    authoring_tool_id = False
                if 'color' in components[ifc_guid]:
                    color = components[ifc_guid]['color']
                    set_list.append('c.color = $color')
                else:
                    color = False
                if 'visibility_exception' in components[ifc_guid]:
                    visibility_exception = True
                    set_list.append('c.visibility_exception = $visibility_exception')
                else:
                    visibility_exception = False
                set_string = ', '.join(set_list)
                cypher_component = """
                    MATCH (v:Viewpoint)
                    WHERE v.guid = $viewpoint_id
                    MERGE (v)-[r1:HAS]->(c:Component {ifc_guid: $ifc_guid})
                    SET
                """
                cypher_component += set_string
                tx.run(cypher_component,
                       viewpoint_id=str(viewpoint.guid),
                       ifc_guid=str(ifc_guid),
                       selected=selected,
                       originating_system=originating_system,
                       authoring_tool_id=authoring_tool_id,
                       color=color,
                       visibility_exception=visibility_exception)
                summary = result.consume()

            # Look for component UUIDs in IFC-graph and relate them
            for ifc_guid in components.keys():
                cypher_link = """
                    MATCH (prod:IfcProduct) WHERE prod.GlobalId = $uuid
                    MATCH (c:Component) WHERE c.ifc_guid = $uuid
                    MERGE (c)-[r:SAME_AS]->(prod)
                """
                tx.run(cypher_link, uuid=str(ifc_guid))
                result.consume()

                cypher_link = """
                    MATCH (prod:IfcProduct)
                        -[r1:IsDefinedBy]->(rs:IfcRelationship)
                        -[r2:RelatingPropertyDefinition]->(ps:IfcPropertySet)
                        -[r3:HasProperties]->(prop:IfcProperty)
                        -[r4:NominalValue]->(root:Root)
                        WHERE prod.GlobalId = $uuid
                        AND ps.Name = 'Reuse'
                        AND prop.Name = 'GTIN'
                    MATCH (c:Component) WHERE c.ifc_guid = $uuid
                    SET c.GTIN = root.wrappedValue
                """
                tx.run(cypher_link, uuid=str(ifc_guid))
                result.consume()

            if summary.counters.nodes_created < 1:
                print(cypher_component)
                raise HTTPException(status_code=400, detail="Item not added.")
            else:
                snapshot_path = ''
                bitmap_path = ''
                try:
                    snapshot_name = 'snapshot_' + str(viewpoint.guid)
                    file_ending = '.' + str(viewpoint.snapshot.snapshot_type.value)
                    snapshot_path = 'data/snapshots/' + snapshot_name + file_ending
                    with open(snapshot_path, "wb") as snapshot_file:
                        snapshot_image = base64.b64decode(viewpoint.snapshot.snapshot_data, validate=True)
                        snapshot_file.write(snapshot_image)
                        snapshot_file.close()
                    for bitmap in viewpoint.bitmaps:
                        bitmap_name = 'bitmap_' + str(bitmap.guid)
                        file_ending = '.' + str(bitmap.bitmap_type.value)
                        bitmap_path = 'data/bitmaps/' + bitmap_name + file_ending
                        with open(bitmap_path, "wb") as bitmap_file:
                            bitmap_image = base64.b64decode(bitmap.bitmap_data, validate=True)
                            bitmap_file.write(bitmap_image)
                            bitmap_file.close()
                except Exception as e:
                    print('could not create file ' + snapshot_path + ' ' + bitmap_path + ' ')
                    print(e)
                    raise HTTPException(status_code=400, detail="Item not added.")
            return viewpoint.guid

        with self.driver.session() as session:
            viewpoint.guid = session.execute_write(post_viewpoint_work)
            viewpoint.guid = UUID(viewpoint.guid)
            self.set_topic_modified_date(project_id, topic_id, current_user)
            return self.get_viewpoint(project_id, topic_id, viewpoint.guid, current_user)

    # implemented
    # returns a collection
    def get_viewpoints(self, project_id: UUID, topic_id: UUID, current_user: User) -> List[ViewpointGET]:
        def get_viewpoints_work(tx) -> List[ViewpointGET]:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                RETURN v.guid AS viewpoint_id
                """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id))
            viewpoint_model_list = list()
            for result in results:
                viewpoint_id = result.get("viewpoint_id")
                viewpoint_model = self.get_viewpoint(project_id, topic_id, viewpoint_id, current_user)
                viewpoint_model_list.append(viewpoint_model)
            return viewpoint_model_list

        with self.driver.session() as session:
            return session.execute_read(get_viewpoints_work)

    # pending
    def get_viewpoint(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID, current_user: User) -> ViewpointGET:
        def get_viewpoint_work(tx) -> ViewpointGET:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                AND v.guid = $viewpoint_id
                RETURN v AS viewpoint
                """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            viewpoint_id=str(viewpoint_id))
            first = result.single()
            if first is None:
                raise HTTPException(status_code=404, detail="Item not found.")
            v_node = first.get("viewpoint")
            print(v_node)
            v = self.node_to_json(v_node)
            print(v)
            lines = self.get_viewpoint_lines(project_id, topic_id, viewpoint_id, current_user)
            clipping_planes = self.get_viewpoint_clipping_planes(project_id, topic_id, viewpoint_id, current_user)
            bitmaps = self.get_viewpoint_bitmaps(project_id, topic_id, viewpoint_id, current_user)
            snapshot_get = {
                'snapshot_type': v['snapshot_type'].split('/', 2)[1]
            }
            viewpoint_get = {
                'guid': v['guid'],
                'index': v['index'],
                'lines': lines,
                'clipping_planes': clipping_planes,
                'bitmaps': bitmaps,
                'snapshot': snapshot_get
            }
            cvp = v['camera_view_point']
            cad = v['camera_direction']
            cuv = v['camera_up_vector']
            camera = {
                'camera_view_point': {'x': cvp.x, 'y': cvp.y, 'z': cvp.z},
                'camera_direction': {'x': cad.x, 'y': cad.y, 'z': cad.z},
                'camera_up_vector': {'x': cuv.x, 'y': cuv.y, 'z': cuv.z},
                'aspect_ratio': v['aspect_ratio']
            }
            if v['camera_definition'] == 'orthogonal_camera':
                camera['view_to_world_scale'] = v['view_tws_or_fo_view']
                viewpoint_get['orthogonal_camera'] = camera
            elif v['camera_definition'] == 'perspective_camera':
                camera['field_of_view'] = v['view_tws_or_fo_view']
                viewpoint_get['perspective_camera'] = camera
            jsonpickle.dumps(v)
            jsonpickle.dumps(viewpoint_get)
            return ViewpointGET(**viewpoint_get)

        with self.driver.session() as session:
            return session.execute_read(get_viewpoint_work)

    def get_viewpoint_lines(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
                            current_user: User) -> List[Line]:
        def get_viewpoint_lines_work(tx) -> List[Line]:
            cypher = """
                    MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)-[r4:HAS]->(l:Line)
                    WHERE u.username = $username
                    AND r1.read = True
                    AND p.project_id = $project_id
                    AND t.guid = $topic_id
                    AND v.guid = $viewpoint_id
                    RETURN l AS line
                    """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id),
                             viewpoint_id=str(viewpoint_id))
            line_model_list = list()
            for result in results:
                line_node = result.get("line")
                line_json = {
                    'start_point': line_node.start_point,
                    'end_point': line_node.end_point
                }
                line_model = Line(**line_json)
                line_model_list.append(line_model)
            return line_model_list

        with self.driver.session() as session:
            return session.execute_read(get_viewpoint_lines_work)

    def get_viewpoint_clipping_planes(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
                                      current_user: User) -> List[ClippingPlane]:
        def get_viewpoint_clipping_planes_work(tx) -> List[ClippingPlane]:
            cypher = """
                    MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)-[r4:HAS]->(cp:ClippingPlane)
                    WHERE u.username = $username
                    AND r1.read = True
                    AND p.project_id = $project_id
                    AND t.guid = $topic_id
                    AND v.guid = $viewpoint_id
                    RETURN cp AS clipping_plane
                    """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id),
                             viewpoint_id=str(viewpoint_id))
            clipping_plane_model_list = list()
            for result in results:
                clipping_plane_json = self.node_to_json(result.get("clipping_plane"))
                clipping_plane_model = ClippingPlane(**clipping_plane_json)
                clipping_plane_model_list.append(clipping_plane_model)
            return clipping_plane_model_list

        with self.driver.session() as session:
            return session.execute_read(get_viewpoint_clipping_planes_work)

    def get_viewpoint_bitmaps(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
                              current_user: User) -> List[BitmapGET]:
        def get_viewpoint_bitmaps_work(tx) -> List[BitmapGET]:
            cypher = """
                    MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)-[r4:HAS]->(b:Bitmap)
                    WHERE u.username = $username
                    AND r1.read = True
                    AND p.project_id = $project_id
                    AND t.guid = $topic_id
                    AND v.guid = $viewpoint_id
                    RETURN b AS bitmap
                """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id),
                             viewpoint_id=str(viewpoint_id))
            bitmap_model_list = list()
            for result in results:
                bitmap_json = self.node_to_json(result.get("bitmap"))
                bitmap_model = BitmapGET(**bitmap_json)
                bitmap_model_list.append(bitmap_model)
            return bitmap_model_list

        with self.driver.session() as session:
            return session.execute_read(get_viewpoint_bitmaps_work)

    def get_viewpoint_bitmap(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID, bitmap_id: UUID,
                             current_user: User) -> BitmapGET:
        def get_viewpoint_bitmap_work(tx) -> BitmapGET:
            cypher = """
                    MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)-[r4:HAS]->(b:Bitmap)
                    WHERE u.username = $username
                    AND r1.read = True
                    AND p.project_id = $project_id
                    AND t.guid = $topic_id
                    AND v.guid = $viewpoint_id
                    AND b.guid = $bitmap_id
                    RETURN b AS bitmap
                """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            viewpoint_id=str(viewpoint_id),
                            bitmap_id=str(bitmap_id))
            bitmap_json = self.node_to_json(result.get("bitmap"))
            return BitmapGET(**bitmap_json)

        with self.driver.session() as session:
            return session.execute_read(get_viewpoint_bitmap_work)

    def get_viewpoint_snapshot(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID, current_user: User) -> str:
        def get_viewpoint_snapshot_work(tx) -> str:
            cypher = """
                 MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)
                 WHERE u.username = $username
                 AND r1.read = True
                 AND p.project_id = $project_id
                 AND t.guid = $topic_id
                 AND v.guid = $viewpoint_id
                 AND v.snapshot IS NOT NULL
                 RETURN v.snapshot_type AS snapshot_type
                 """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            viewpoint_id=str(viewpoint_id))
            first = result.single()
            if first is None:
                raise HTTPException(status_code=404, detail="Item not found.")
            snapshot_type = first.get("snapshot_type")
            return snapshot_type

        with self.driver.session() as session:
            return session.execute_read(get_viewpoint_snapshot_work)

    def get_viewpoint_colored_components(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
                                         current_user: User) -> ColoringGET:
        def get_viewpoint_colored_components_work(tx) -> ColoringGET:
            cypher = """
                 MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)-[r4:HAS]->(c:Component)
                 WHERE u.username = $username
                 AND r1.read = True
                 AND p.project_id = $project_id
                 AND t.guid = $topic_id
                 AND v.guid = $viewpoint_id
                 AND c.color IS NOT NULL
                 RETURN c AS component ORDER BY c.color
                 """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id),
                             viewpoint_id=str(viewpoint_id))
            coloring_list = list()
            color = ''
            coloring = {}
            occasion = 0
            for result in results:
                component_json = self.node_to_json(result.get("component"))
                if component_json['color'] != color:
                    if occasion > 0:
                        coloring_list.append(coloring)
                        occasion += 1
                    color = component_json['color']
                    coloring = {
                        'color': color,
                        'components': []
                    }
                coloring['components'].append(component_json)
            coloring_list.append(coloring)
            coloring_get = {'coloring': coloring_list}
            return ColoringGET(**coloring_get)

        with self.driver.session() as session:
            return session.execute_read(get_viewpoint_colored_components_work)

    def get_viewpoint_selected_components(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
                                          current_user: User) -> SelectionGET:
        def get_viewpoint_selected_components_work(tx) -> SelectionGET:
            cypher = """
                     MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)-[r4:HAS]->(c:Component)
                     WHERE u.username = $username
                     AND r1.read = True
                     AND p.project_id = $project_id
                     AND t.guid = $topic_id
                     AND v.guid = $viewpoint_id
                     AND c.selected IS NOT NULL
                     RETURN c AS component
                """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id),
                             viewpoint_id=str(viewpoint_id))
            component_list = list()
            for result in results:
                component_json = self.node_to_json(result.get("component"))
                component_list.append(component_json)
            selection_get = {'selection': component_list}
            return SelectionGET(**selection_get)

        with self.driver.session() as session:
            return session.execute_read(get_viewpoint_selected_components_work)

    def get_viewpoint_components_visibility(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
                                            current_user: User) -> VisibilityGET:
        def get_viewpoint_components_visibility_work(tx) -> VisibilityGET:
            cypher = """
                 MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)
                 WHERE u.username = $username
                 AND r1.read = True
                 AND p.project_id = $project_id
                 AND t.guid = $topic_id
                 AND v.guid = $viewpoint_id
                 RETURN v AS viewpoint
                 """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            viewpoint_id=str(viewpoint_id))
            first = result.single()
            if first is None:
                raise HTTPException(status_code=404, detail="Item not found.")
            viewpoint_json = self.node_to_json(first.get("viewpoint"))
            view_setup_hints = {
                'spaces_visible': viewpoint_json['spaces_visible'],
                'space_boundaries_visible': viewpoint_json['space_boundaries_visible'],
                'openings_visible': viewpoint_json['openings_visible']
            }
            visibility = {
                'default_visibility': viewpoint_json['default_visibility'],
                'view_setup_hints': view_setup_hints
            }
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)-[r4:HAS]->(c:Component)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                AND v.guid = $viewpoint_id
                AND c.visibility_exception IS NOT NULL
                RETURN c AS component
            """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id),
                             viewpoint_id=str(viewpoint_id))
            component_list = list()
            for result in results:
                component_json = self.node_to_json(result.get("component"))
                component_list.append(component_json)
            visibility['exceptions'] = component_list
            visibility_get = {'visibility': visibility}
            return VisibilityGET(**visibility_get)

        with self.driver.session() as session:
            return session.execute_read(get_viewpoint_components_visibility_work)

    # implemented
    def delete_viewpoint(self, project_id: UUID, topic_id: UUID, viewpoint_id: UUID, current_user: User) -> int:
        def delete_viewpoint_work(tx) -> int:
            cypher = """
                    MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:HAS]->(v:Viewpoint)-[r4:HAS*]->(n)
                    WHERE u.username = $username
                    AND r1.update = True
                    AND p.project_id = $project_id
                    AND t.guid = $topic_id
                    AND v.guid = $viewpoint_id
                    DETACH DELETE v, n
                    """
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            viewpoint_id=str(viewpoint_id))
            result_summary = result.consume()
            if result_summary.counters.nodes_deleted < 1:
                raise HTTPException(status_code=404, detail="Item not found.")
            return result_summary.counters.nodes_deleted

        with self.driver.session() as session:
            self.set_topic_modified_date(project_id, topic_id, current_user)
            return session.execute_write(delete_viewpoint_work)

    # implemented
    # returns a collection
    def get_related_topics(self, project_id: UUID, topic_id: UUID, current_user: User) -> List[TopicGET]:
        def get_related_topics_work(tx) -> List[TopicGET]:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t1:Topic)-[r3:RELATED_TO]->(t2:Topic)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                AND t1.guid = $topic_id
                RETURN t2 AS topic, ID(t2) AS server_assigned_id
                """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id))
            topic_model_list = list()
            for result in results:
                topic_json = self.node_to_json(result.get("topic"))
                topic_json["server_assigned_id"] = result.get("server_assigned_id")
                topic_model = TopicGET(**topic_json)
                topic_model_list.append(topic_model)
            return topic_model_list

        with self.driver.session() as session:
            return session.execute_read(get_related_topics_work)

    def put_related_topics(self, project_id: UUID, topic_id: UUID, related_topics: List[RelatedTopicPUT],
                           current_user: User) -> List[TopicGET]:
        def put_related_topics_work(tx) -> bool:
            for related_topic in related_topics:
                cypher = """
                    MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t1:Topic)
                    MATCH (p)-[r2:HAS]->(t2:Topic)
                    WHERE u.username = $username
                    AND r1.updateRelatedTopics = True
                    AND p.project_id = $project_id
                    AND t1.guid = $topic_id
                    AND t2.guid = $related_topic_id
                    MERGE (t1)-[r3:RELATED_TO]->(t2:Topic)
                """
                tx.run(cypher,
                       username=current_user.username,
                       project_id=str(project_id),
                       topic_id=topic_id,
                       related_topic_id=related_topic.related_topic_guid)
            return True

        with self.driver.session() as session:
            session.execute_write(put_related_topics_work)
            self.set_topic_modified_date(project_id, topic_id, current_user)
            return self.get_related_topics(project_id, topic_id, current_user)

    # returns a collection
    def get_topic_document_references(self, project_id: UUID, topic_id: UUID,
                                      current_user: User) -> List[DocumentReferenceGET]:
        def get_topic_document_references_work(tx) -> List[DocumentReferenceGET]:
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)-[r3:REFERS_TO]->(d:Document)
                WHERE u.username = $username
                AND r1.read = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                RETURN
                    r3.guid AS reference_guid,
                    d.guid AS document_guid,
                    d.url AS document_url,
                    d.description AS document_description
                """
            results = tx.run(cypher,
                             username=current_user.username,
                             project_id=str(project_id),
                             topic_id=str(topic_id))
            document_reference_model_list = list()
            for result in results:
                document_reference_model = DocumentReferenceGET(**result)
                document_reference_model_list.append(document_reference_model)
            return document_reference_model_list

        with self.driver.session() as session:
            return session.execute_read(get_topic_document_references_work)

    def post_topic_document_reference(self, project_id: UUID, topic_id: UUID, document_reference: DocumentReferencePOST,
                                      current_user: User) -> List[DocumentReferenceGET]:
        def post_topic_document_reference_work(tx) -> bool:
            if document_reference.guid is None:
                document_reference.guid = uuid4()
            if document_reference.document_guid is None or len(document_reference.url) > 4 > len(
                    document_reference.document_guid):
                document_reference.document_guid = uuid4()
                document_url = 'd.url = $url,'
            else:
                document_url = ''
                document_reference.url = ''
            cypher = """
                MATCH (u:User)-[r1:HAS_ACTIONS_ON]->(p:Project)-[r2:HAS]->(t:Topic)
                WHERE u.username = $username
                AND r1.updateDocumentReferences = True
                AND p.project_id = $project_id
                AND t.guid = $topic_id
                MERGE (t)-[r3:REFERS_TO]->(d:Document {guid: $document_id})
                SET r3.guid: $document_reference_id,
                    %s
                    d.description = $description
            """ % document_url
            result = tx.run(cypher,
                            username=current_user.username,
                            project_id=str(project_id),
                            topic_id=str(topic_id),
                            document_id=str(document_reference.document_guid),
                            document_reference_id=str(document_reference.guid),
                            url=document_reference.url,
                            description=document_reference.description)
            summary = result.consume()
            if summary.counters.nodes_created < 1:
                raise HTTPException(status_code=400, detail="Item not added.")
            return True

        with self.driver.session() as session:
            session.execute_write(post_topic_document_reference_work)
            self.set_topic_modified_date(project_id, topic_id, current_user)
            return self.get_topic_document_references(project_id, topic_id, current_user)

    def put_topic_document_references(self, project_id: UUID, topic_id: UUID, reference_id: UUID,
                                      document_reference: DocumentReferencePUT,
                                      current_user: User) -> List[DocumentReferenceGET]:
        document_reference.guid = reference_id
        document_reference_post = DocumentReferencePOST(document_reference)
        topic_document_references_response = self.post_topic_document_reference(project_id, topic_id,
                                                                                document_reference_post, current_user)
        self.set_topic_modified_date(project_id, topic_id, current_user)
        return topic_document_references_response

    # returns a collection
    def get_documents(self, project_id: UUID, current_user: User):
        return True

    def post_document(self, project_id: UUID, file: UploadFile, current_user: User):
        return True

    def get_document(self, project_id: UUID, document_id: UUID, current_user: User):
        return True

    # returns a collection
    def get_topics_events(self, project_id: UUID, topic_id: UUID, current_user: User):
        return True

    # returns a collection
    def get_topic_events(self, project_id: UUID, topic_id: UUID, current_user: User):
        return True

    # returns a collection
    def get_comments_events(self, project_id: UUID, UUID, current_user: User):
        return True

    # returns a collection
    def get_comment_events(self, project_id: UUID, topic_id: UUID, comment_id: UUID, current_user: User):
        return True

bcf_db = BCFDB(driver)


from matplotlib import pyplot as plt
import numpy as np
import math

# function f(x)
def fun(x):
    return np.exp(np.sin(x)**3) + pow(x, 6) - 2*pow(x, 4) - pow(x, 3) - 1

# derivative of f(x)
def der_fun(x):
    return 3*np.exp(np.sin(x)**3)*np.cos(x)*(np.sin(x)**2) + 6*(pow(x, 5)) - 8*(pow(x, 3)) - 3*(pow(x, 2))

'''
# Newton Raphson Method
Receives as arguments:
////////// x0(A random number from the domain of the function)
////////// e (The error)
////////// it (Number of iterations)
'''

def newton(x0, e, it):
    print("Newton Raphson Function:")
    for num_iter in range(it):
        x1 = x0 - fun(x0)/der_fun(x0)
        if(abs(x1-x0)<e):
            print ('Root is at: ', "{0:.6f}".format(x0))
            print ('f(x) at root is: ', fun(x0))
            print ('Number of iterations ', num_iter)
            if der_fun(x0) != 0:
                print("The function's convergence is square, f'(x0) = ", "{0:.6f}".format(der_fun(x0)))
            else:
                print("The function's convergence is not square, f'(x0) = ", "{0:.6f}".format(der_fun(x0)))
            return x1
        else:
            x0 = x1
            print("Checked possible root: ", "{0:.6f}".format(x0))
    return False

'''
# Secant Method
Receives as arguments:
////////// x0,x1(The edges of the function)
////////// e (The error)
////////// it (Number of iterations)
'''

def secant(x0, x1, e, it):
    print("Secant Function:")
    for num_iter in range(it):
        x2 = x1 - fun(x1)*((x1-x0)/(fun(x1)-fun(x0)))
        if abs (x2 - x1) < e:
            print('Root is at: ', "{0:.6f}".format(x0))
            print('f(x) at root is: ', "{0:.6f}".format(fun(x0)))
            print('Number of iterations ', num_iter)
            if der_fun(x0) != 0:
                print("The function's convergence is square, f'(x0) = ", "{0:.6f}".format(der_fun(x2)))
            else:
                print("The function's convergence is not square, f'(x0) = ", "{0:.6f}".format(der_fun(x2)))
            return x2
        else:
            x0 = x1
            x1 = x2
            print("Checked possible root: ", "{0:.6f}".format(x1))
    print("Solution was not found in ", num_iter ," iterations")
    return False

'''

# function that returns the middle between 2 edges

'''

def mean(begin, end):
    return (begin + end)/2

'''
# Bisection Method
It just receives the two edges of the function
'''

def bisect(edge):
    print("Bisection Function:")
    num_iter = math.ceil((np.log(4) - np.log(0.0000005))/np.log(2))
    for i in range(1,num_iter):
         print(i," Itteration")
         m = mean(edge[0], edge[1])
         print("Mean = ",m)
         if fun(edge[0])*fun(m)<0:
             edge = [edge[0], m]
             print("New Edge ",edge)
         elif fun(edge[1])*fun(m) < 0:
             edge = [m, edge[1]]
             print("New Edge ",edge)
         elif fun(m) == 0:
             print("The root is","{0:.6f}".format(fun(m))," at ","{0:.6f}".format(m),"and was found on ", i , " Itteration")
             if der_fun(m) != 0:
                 print("The function's convergence is square, f'(x0) = ", "{0:.6f}".format(der_fun(m)))
             else:
                 print("The function's convergence is not square, f'(x0) = ", "{0:.6f}".format(der_fun(m)))
             break
    return m

# The two edges of the function
edge = [-2.0, 2.0]
y1 = newton(0.0003, 0.0000005, 100)
print("")
y2=secant(-2.0, 2.0, 0.0000005, 1000)
print("")
y3 = bisect(edge)
x = np.linspace(-2.0, 2.0, 1000)
plt.plot(x, fun(x), 'r', label = 'Function')
plt.plot()
plt.xlabel('Domain')
plt.ylabel("F(x)")
plt.legend
plt.show()


import flet as ft
import pandas as pd

import Main.service.user.login_enc as cc
from .staff_add import staff_add_page
import Main.service.scr.election_scr as ee
from .candidate_add import candidate_add_page
from ..functions.dialogs import message_dialogs
from ..service.scr.loc_file_scr import file_data

old_data = None

def menubar_page(page: ft.Page, user_type: bool):

    main_column = ft.Column(expand=True)

    def add_candidate_page_fun(e):
        ele_ser = pd.read_json(ee.current_election_path + fr"/{file_data['election_settings']}", orient='table')
        if not ele_ser.loc['lock_data'].values[0]:
            candidate_add_page(page)
        else:
            message_dialogs(page, "Option is locked")

    add_candidate_button = ft.FloatingActionButton(
        icon=ft.icons.PERSON_ADD_ALT_1_ROUNDED,
        tooltip="Add new Candidate",
        on_click=add_candidate_page_fun,
    )

    add_staff_button = ft.FloatingActionButton(
        icon=ft.icons.PERSON_ADD_ALT_1_ROUNDED,
        tooltip="Add new Staff",
        on_click=lambda _: staff_add_page(page),
    )

    def on_option_click(e):
        page.splash = ft.ProgressBar()
        global old_data

        if e != 5:
            main_column.clean()
            page.update()
            if old_data == 0:
                container.image_src = "/images/background-3.png"
                home.icon = None
            elif old_data == 1:
                page.remove(add_candidate_button)
                candidate.icon = None
            elif old_data == 2:
                page.remove(add_staff_button)
                staff.icon = None
            elif old_data == 3:
                election.icon = None
            elif old_data == 4:
                settings.icon = None
        old_data = e
        page.update()

        if e == 0:
            container.image_src = "/images/background-2.png"
            home.icon = ft.icons.HOME_ROUNDED
            from .home import home_page
            home_page(page, main_column)
        elif e == 1:
            candidate.icon = ft.icons.SUPERVISED_USER_CIRCLE
            from .candidate_home import candidate_home_page
            candidate_home_page(page, main_column)
            page.add(add_candidate_button)
        elif e == 2:
            staff.icon = ft.icons.ADMIN_PANEL_SETTINGS
            from .staff_home import staff_home_page
            staff_home_page(page, main_column)
            page.add(add_staff_button)
        elif e == 3:
            election.icon = ft.icons.HOW_TO_VOTE
            from .election_settings import election_settings_page
            election_settings_page(page, main_column)
        elif e == 4:
            settings.icon = ft.icons.SETTINGS
            from .settings import settings_page
            settings_page(page, main_column)
        elif e == 5:
            from main import main
            from ..functions.dialogs import loading_dialogs
            loading_dialogs(page, "Logging out...", 2)
            old_data = None
            page.clean()
            page.splash = None
            page.update()
            main(page)
        page.update()

    home = ft.TextButton(
        text='Home',
        data=0,
        on_click=lambda e: on_option_click(e.control.data)
    )

    candidate = ft.TextButton(
        text="Candidate",
        data=1,
        on_click=lambda e: on_option_click(e.control.data)
    )

    staff = ft.TextButton(
        text="Staff",
        data=2,
        on_click=lambda e: on_option_click(e.control.data)
    )

    election = ft.TextButton(
        text="Election",
        data=3,
        on_click=lambda e: on_option_click(e.control.data)
    )

    settings = ft.TextButton(
        text="Settings",
        data=4,
        on_click=lambda e: on_option_click(e.control.data)
    )

    log_out = ft.TextButton(
        icon=ft.icons.LOGOUT_OUTLINED,
        text="Logout",
        data=5,
        on_click=lambda e: on_option_click(e.control.data)
    )

    if user_type:
        row_menu_list = [
            home,
            candidate,
            staff,
            election,
            settings,
        ]
    else:
        row_menu_list = [
            home,
            candidate,
            election,
        ]

    appbar = ft.Container(
        border_radius=9,
        bgcolor=ft.colors.with_opacity(0.5, '#FFFFFF'),
        blur=ft.Blur(10, 10, ft.BlurTileMode.MIRROR),
        content=ft.Row(
            [
                ft.Row(
                    [
                        ft.Container(
                            ft.Row(
                                [
                                    ft.CircleAvatar(
                                        content=ft.Text(
                                            value=cc.teme_data[1][0].upper(),
                                            size=25,
                                        ),
                                    ),
                                    ft.Text(
                                        value=cc.teme_data[1].capitalize(),
                                        weight=ft.FontWeight.BOLD,
                                        size=15,
                                    )
                                ]
                            ),
                            margin=5,
                        )
                    ],
                    expand=True,
                    alignment=ft.MainAxisAlignment.START,
                    scroll=ft.ScrollMode.ADAPTIVE,
                ),
                ft.Row(row_menu_list),
                ft.VerticalDivider(color=ft.colors.PRIMARY, thickness=2),
                log_out,
            ],
            alignment=ft.MainAxisAlignment.END,
            height=50
        ),
        margin=ft.margin.only(left=5, top=5, right=5),

    )

    container = ft.Container(
        image_fit=ft.ImageFit.COVER,
        image_src="/images/background-2.png",
        margin=-10,
        expand=True,
        content=ft.Column(
            [
                appbar,
                main_column
            ],
        )
    )

    page.add(container)
    page.update()
    on_option_click(0)

def update():
    global old_data
    old_data = None


"""
lsvtool is a code for filtering and intersecting Large Structural Variants
from different technologies (NAIBR, LinkedSV, 10X and Pacbio)
"""

import sys
import logging
import pkgutil
import importlib
from argparse import ArgumentParser

import lsvtool.cli as cli_package

logger = logging.getLogger(__name__)

def main(commandline_arguments=None) -> int:
    logging.basicConfig(level=logging.INFO,
                        format="%(asctime)s - %(module)s - %(levelname)s: %(message)s",
                        datefmt='%Y-%m-%d %H:%M:%S')
    parser = ArgumentParser(description=__doc__, prog="lsvtool")
    parser.add_argument("--version", action="version", version="%(prog)s 0.1")
    parser.add_argument("--debug", action="store_true", default=False,
                        help="Print debug messages")
    subparsers = parser.add_subparsers()

    # Import each module that implements a subcommand and add a subparser for it.
    # Each subcommand is implemented as a module in the cli subpackage.
    # It needs to implement an add_arguments() and a main() function.
    modules = pkgutil.iter_modules(cli_package.__path__)
    for _, module_name, _ in modules:
        module = importlib.import_module("." + module_name, cli_package.__name__)
        help_message = module.__doc__.strip().split("\n", maxsplit=1)[0]
        subparser = subparsers.add_parser(
            module_name, help=help_message, description=module.__doc__
        )
        subparser.set_defaults(module=module)
        module.add_arguments(subparser)

    # Module 'run' needs to accept addition arguments
    args, extra_args = parser.parse_known_args(commandline_arguments)

    # For module 'run' extra_args are added to existing snakemake_args in namespace
    if hasattr(args, "snakemake_args"):
        args.snakemake_args += extra_args

    if args.debug:
        root = logging.getLogger()
        root.setLevel(logging.DEBUG)

    if not hasattr(args, "module"):
        parser.error("Please provide the name of a subcommand to run")
    else:
        module = args.module
        del args.module
        module_name = module.__name__.split('.')[-1]

        # Re-parse extra arguments if module is not "run" to raise the expected error
        if module_name != "run" and extra_args:
            parser.parse_args(extra_args)

        # Print settings for module
        sys.stderr.write(f"SETTINGS FOR: {module_name}\n")
        for object_variable, value in vars(args).items():
            sys.stderr.write(f" {object_variable}: {value}\n")

        module.main(args)

    return 0

if __name__ == "__main__":
    sys.exit(main())


import pygame
import sys
from crypto import get_data
from objects import Colliders

W, H = 1000, 600
FPS = 60

def main():
    btc_data = get_data('BTC-USD')
    eth_data = get_data('ETH-USD')
    # print(btc_data)

    # Game
    pygame.init()

    screen = pygame.display.set_mode((W, H))
    clock = pygame.time.Clock()

    bg_surface = pygame.image.load('assets/bg.jpg').convert()
    bg_surface = pygame.transform.scale(bg_surface, (W, H))

    bars = Colliders('btc', btc_data)
    bars.normalize()

    bars_up = Colliders('eth', eth_data)
    bars_up.normalize()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        screen.blit(bg_surface, (0, 0))

        bars.render(screen, H, W)
        bars_up.render(screen, H, W)

        pygame.display.update()
        clock.tick(FPS)

if __name__ == '__main__':
    main()


# stdlib
import argparse
from pathlib import Path, PurePath
import pickle

import yaml

# third party
# first party
from runs.database import DataBase
from runs.logger import Logger
from runs.run_entry import RunEntry
from runs.subcommands import ls

def yaml_to_run_entry(node, *parts):
    parts += (node["name"],)
    try:
        for child in node["children"]:
            for run in yaml_to_run_entry(child, *parts):
                yield run
    except KeyError:
        yield RunEntry(
            path=PurePath(*parts),
            command=node["command"],
            commit=node["commit"],
            datetime=node["datetime"],
            description=node["description"],
        )

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("yaml_file", help="input file", type=str)
    parser.add_argument(
        "db_file", nargs="?", default="runs.db", help="output file", type=str
    )
    parser.add_argument("--column-width", default=100, help="output file", type=int)

    args = parser.parse_args()
    if args.yaml_file.endswith("yml") or args.yaml_file.endswith("yaml"):
        with Path(args.yaml_file).open() as f:
            data = yaml.load(f)
    elif args.yaml_file.endswith("pkl"):
        with Path(args.yaml_file).open("rb") as f:
            data = pickle.load(f)
    else:
        raise RuntimeError("This script works on yaml or pickle files only")

    with DataBase(args.db_file, Logger(quiet=False)) as db:
        for run in yaml_to_run_entry(data):
            db.append(run)

        print(ls.string(db=db))

if __name__ == "__main__":
    main()


# multiline = """Hechos sobre la luna:
# no hay atmosfera.
# no hay sonido."""

# multilinea = """hola \n crayola como esta \nlola"""
# #divide cadenas por linea, despues de salto de linea
# print(multilinea.split('\n'))

#buscar palabras en cadenas

from itertools import count

temperatures = "Saturn has a daytime temperature of -170 degrees Celsius, while Mars has -28 Celsius"

temperatures,count('moon')
print(temperatures)


'''

     View source on GitHub
    Notebook Viewer
    Run in binder
     Run in Google Colab

'''

# %%
'''
## Install Earth Engine API
Install the [Earth Engine Python API](https://developers.google.com/earth-engine/python_install) and [geehydro](https://github.com/giswqs/geehydro). The **geehydro** Python package builds on the [folium](https://github.com/python-visualization/folium) package and implements several methods for displaying Earth Engine data layers, such as `Map.addLayer()`, `Map.setCenter()`, `Map.centerObject()`, and `Map.setOptions()`.
The magic command `%%capture` can be used to hide output from a specific cell.
'''

# %%
# %%capture
# !pip install earthengine-api
# !pip install geehydro

# %%
'''
Import libraries
'''

# %%
import ee
import folium
import geehydro

# %%
'''
Authenticate and initialize Earth Engine API. You only need to authenticate the Earth Engine API once. Uncomment the line `ee.Authenticate()`
if you are running this notebook for this first time or if you are getting an authentication error.
'''

# %%
# ee.Authenticate()
ee.Initialize()

# %%
'''
## Create an interactive map
This step creates an interactive map using [folium](https://github.com/python-visualization/folium). The default basemap is the OpenStreetMap. Additional basemaps can be added using the `Map.setOptions()` function.
The optional basemaps can be `ROADMAP`, `SATELLITE`, `HYBRID`, `TERRAIN`, or `ESRI`.
'''

# %%
Map = folium.Map(location=[40, -100], zoom_start=4)
Map.setOptions('HYBRID')

# %%
'''
## Add Earth Engine Python script

'''

# %%
# Use an elevation dataset and terrain functions to create
# a custom visualization of topography.

# Load a global elevation image.
elev = ee.Image('USGS/GMTED2010')

# Zoom to an area of interest.
Map.setCenter(-121.069, 50.709, 6)

# Add the elevation to the map.
Map.addLayer(elev, {}, 'elev')

# Use the terrain algorithms to compute a hillshade with 8-bit values.
shade = ee.Terrain.hillshade(elev)
Map.addLayer(shade, {}, 'hillshade', False)

# Create a "sea" variable to be used for cartographic purposes
sea = elev.lte(0)
Map.addLayer(sea.mask(sea), {'palette':'000022'}, 'sea', False)

# Create a custom elevation palette from hex strings.
elevationPalette = ['006600', '002200', 'fff700', 'ab7634', 'c4d0ff', 'ffffff']
# Use these visualization parameters, customized by location.
visParams = {'min': 1, 'max': 3000, 'palette': elevationPalette}

# Create a mosaic of the sea and the elevation data
visualized = ee.ImageCollection([
  # Mask the elevation to get only land
  elev.mask(sea.Not()).visualize(**visParams),
  # Use the sea mask directly to display sea.
  sea.mask(sea).visualize(**{'palette':'000022'})
]).mosaic()

# Note that the visualization image doesn't require visualization parameters.
Map.addLayer(visualized, {}, 'elev palette', False)

# Convert the visualized elevation to HSV, first converting to [0, 1] data.
hsv = visualized.divide(255).rgbToHsv()
# Select only the hue and saturation bands.
hs = hsv.select(0, 1)
# Convert the hillshade to [0, 1] data, as expected by the HSV algorithm.
v = shade.divide(255)
# Create a visualization image by converting back to RGB from HSV.
# Note the cast to byte in order to export the image correctly.
rgb = hs.addBands(v).hsvToRgb().multiply(255).byte()
Map.addLayer(rgb, {}, 'styled')

# %%
'''
## Display Earth Engine data layers

'''

# %%
Map.setControlVisibility(layerControl=True, fullscreenControl=True, latLngPopup=True)
Map


from bs4 import BeautifulSoup

html= '''

 우리동네시장

                     바나나
                     3000원
                     500개
                     가나다상회
                     홈페이지

                     파인애플

'''

soup= BeautifulSoup(html, 'html.parser')
print(soup)

tags_span= soup.select('span')
tags_p= soup.select('p')
ids_fruits1= soup.select('#fruits1')
class_price= soup.select('.price')
tags_span_class_price= soup.select('span.price')

# print(tags_span)
# print(tags_span_class_price)

tags_name= soup.select('span.name')
print(tags_name)

# 같은 태그를 찾더라도 여러가지 방식이 있다. (상위구조 등)
# 결과는 list 형태로 반환
tags_banana1= soup.select('#fruits1> span.name')
tags_banana2= soup.select('div.sale> #fruits1> span.name')
tags_banana3= soup.select('div.sale span.name')
print(tags_banana1)
print(tags_banana2)
print(tags_banana3)

# HTML에서 원하는 정보 가져오기
tags= soup.select('a')         # a 태그 내용 가져오기
print(tags, type(tags))
tag= tags[0]         # a 태그 내용 중 가장 첫 번째 항목 tag 변수 저장
print(tag, type(tag))
content= tag.text            # tag의 내용 중 txt 를 content 변수 저장
print(content, type(content))
link= tag['href']           # tag의 내용 중 URL 부분 link 변수 저장
print(link, type(link))


import requests
import time
import asyncio

def load_url(url):
    response = requests.get(url)
    return response.text
def sequential_load():
    urls = ["#"] * 100
    start_time = time.time()

    for url in urls:
        load_url(url)

    end_time = time.time()
    elapsed_time = end_time - start_time
    print(f"последовательно {elapsed_time}")

async def asynchron_load(url):
    loop = asyncio.get_event_loop()
    future = loop.run_in_executor(None, load_url, url)
    response = await future
    return response

async def asynchron_main():
    urls = ["#"] * 100
    start_time = time.time()

    tasks = []
    for url in urls:
        task = asyncio.ensure_future(asynchron_load(url))
        tasks.append(task)

    responses = await asyncio.gather(*tasks)

    end_time = time.time()
    elapsed_time = end_time - start_time
    print(f"асинхронно {elapsed_time}")

# Вызов функций
sequential_load()
asyncio.run(asynchron_main())