from pathlib import Path
from pprint import PrettyPrinter

import numpy as np

FILENAME = "rosalind_lcsm.txt"

def get_values(data):
    values = dict()
    for i in range(1, len(data)):
        value = data[i].split("\n")
        values[value[0]] = "".join(value[1::])
    return values

def get_shortest_string(values):
    a = None
    length = 2000
    for key, value in values.items():
        if len(value) < length:
            length = len(value)
            a = key

    b = None
    length = 2000
    for key, value in values.items():
        if key != a:
            if len(value) < length:
                length = len(value)
                b = key
    return a, b

def main(filename):
    data_path = Path().absolute().parent.parent / "data"
    full_path = data_path / filename

    pp = PrettyPrinter()
    values = None
    with open(full_path, 'r') as f:
        data = f.read().rstrip()
        data = data.split(">")
        values = get_values(data)
        f.close()
    #pp.pprint(values)
    choice_a, choice_b = get_shortest_string(values)
    a = values[choice_a]
    b = values[choice_b]
    table = np.zeros((len(b)+1, len(a)+1), dtype=int)
    lcs_list = longest_common_substring(a, b, table)
    values.pop(choice_a, None)
    values.pop(choice_b, None)
    lcs = check_other_strings(lcs_list, values)
    print("The longest common substring is:", lcs)

def longest_common_substring(a, b, table):
    lcs_list = list()
    for row in range(1, len(table)):
        for col in range(1, len(table[row])):
            if b[row-1] == a[col-1]:
                table[row][col] = table[row-1][col-1] + 1
            else:
                table[row][col] = 0

    max_value = np.amax(table) # get largest value
    row, col = np.where(table == max_value) # get indices of the largest value
    row = row[0]
    col = col[0]
    end = table[row][col]
    longest_substring = b[row-1]
    while end != 1 and row > 0 and col > 0:
        left = table[row][col-1]
        up = table[row-1][col]
        diag = table[row-1][col-1]
        index = np.argmax([left, up, diag])
        if index == 2:  # diag
            end = end - 1
            row = row - 1
            col = col - 1
            longest_substring += b[row-1]
            if end == 1:
                lcs_list.append(longest_substring[::-1])
                if row > 0  and col > 0:
                    slice = table[0:row+1, 0:col+1]
                    max_value = np.amax(slice)
                    row, col = np.where(slice == max_value)
                    row = row[0]
                    col = col[0]
                    end = table[row][col]
                    longest_substring = b[row-1]
        elif index == 1:  # up
            row = row - 1
        else:  # left
            col = col - 1

    return lcs_list

def check_other_strings(lcs_list, values):
    common = None
    for lcs in lcs_list:
        temp_lcs = lcs
        while len(temp_lcs) > 1:
            in_all = True
            for key, value in values.items():
                if temp_lcs not in value:
                    in_all = False
                    break
            if in_all:
                if common is None:
                    common = temp_lcs
                elif len(temp_lcs) > len(common):
                    common = temp_lcs
            temp_lcs = temp_lcs[0:-1]
    return common

if __name__ == '__main__':
    main(filename=FILENAME)


# https://www.acmicpc.net/problem/15650
import sys
sys.stdin = open('input.txt')
def input():
    return sys.stdin.readline().rstrip()

# 1~n 자연수 중 중복없이 m개 고른 모든 오름차순 수열을 오름차순으로 한 줄에 수열 하나씩 출력
# 각 수열의 원소는 공백으로 구분
def dfs(count, data):
    if count == m:
        ans.append(data)
        return
    st = 1 if not data else data[-1] + 1  # 수열을 오름차순으로 유지시키기 위함.
    for i in range(st, n + 1):
        if not visited[i]:
            visited[i] = 1
            dfs(count + 1, data + [i])
            visited[i] = 0

n, m = map(int, input().split())
visited = [0] * (n+1)
ans = []
dfs(0, [])
for an in ans:
    print(*an)

"""
현 시점 실버3. 제출 54696.  정답률 74.148 %
처음에 대충 코딩하고 제출했는데 틀려서 의아했는데
'고른 수열은 오름차순이어야 한다'가 '수열들을 오름차순으로 출력이 아니라
'수열 자체가 오름차순인 것만 골라야 한다'였다.
"""

BaekJoon/Backtracking/Silver/S3/N과 M (2)/N과 M (2).py


#Filename: q03_determine_grade.py
#Author: Tan Di Sheng
#Created: 20130129
#Modified: 20130129
#Description: This program prompts the user to enter a score between 0 and 100
#inclusive and returns the grade accordingly

print("""This program prompts the user to enter a score between
0 and 100 inclusive and returns the grade accordingly""")

#Creates a loop so that the user can keep using the application
#without having to keep restarting the application
while True:

    while True:
        numberInput = input("\nEnter number: ")
        try:
            float(numberInput)
        except:
            print("\nPlease input an integer between 0 and 100")
        else:
            if 0 <= float(numberInput) <= 100:
                break
            else:
                print("Please input an integer between 0 and 100")

    numberInput = float(numberInput)

    grades = ["A", "B", "C", "D", "E", "S", "U"]

    if numberInput >= 70:
        print(grades[0])
    elif numberInput >= 60:
        print(grades[1])
    elif numberInput >= 55:
        print(grades[2])
    elif numberInput >= 50:
        print(grades[3])
    elif numberInput >= 45:
        print(grades[4])
    elif numberInput >= 35:
        print(grades[5])
    else:
        print(grades[6])

    #gives the user an option to quit the application
    contorquit = input("\nContinue? Type no to quit: ")
    if contorquit == "no":
        quit()
    else:
        continue


import jmespath
from parameterized import parameterized

from tests.helm_template_generator import render_chart

class TestKeda:
    def test_keda_disabled_by_default(self):
        """disabled by default"""
        docs = render_chart(
            values={},
            show_only=["templates/workers/worker-kedaautoscaler.yaml"],
        )
        assert docs == []

    @parameterized.expand(
        [
            ('CeleryExecutor', True),
            ('CeleryKubernetesExecutor', True),
        ]
    )
    def test_keda_enabled(self, executor, is_created):
        """
        ScaledObject should only be created when set to enabled and executor is Celery or CeleryKubernetes
        """
        docs = render_chart(
            values={
                "workers": {"keda": {"enabled": True}, "persistence": {"enabled": False}},
                'executor': executor,
            },
            show_only=["templates/workers/worker-kedaautoscaler.yaml"],
        )
        if is_created:
            assert jmespath.search("metadata.name", docs[0]) == "RELEASE-NAME-worker"
        else:
            assert docs == []

    @parameterized.expand(
        [
            ("CeleryExecutor", 8),
            ("CeleryExecutor", 16),
            ("CeleryKubernetesExecutor", 8),
            ("CeleryKubernetesExecutor", 16),
        ]
    )
    def test_keda_concurrency(self, executor, concurrency):
        """
        ScaledObject should only be created when set to enabled and executor is Celery or CeleryKubernetes
        """
        docs = render_chart(
            values={
                "workers": {"keda": {"enabled": True}, "persistence": {"enabled": False}},
                "executor": executor,
                "config": {"celery": {"worker_concurrency": concurrency}},
            },
            show_only=["templates/workers/worker-kedaautoscaler.yaml"],
        )
        expected_query = (
            f"SELECT ceil(COUNT(*)::decimal / {concurrency}) "
            "FROM task_instance WHERE state='running' OR state='queued'"
        )
        assert jmespath.search("spec.triggers[0].metadata.query", docs[0]) == expected_query

    @parameterized.expand(
        [
            ('enabled', 'StatefulSet'),
            ('not_enabled', 'Deployment'),
        ]
    )
    def test_persistence(self, enabled, kind):
        """
        If worker persistence is enabled, scaleTargetRef should be StatefulSet else Deployment.
        """
        is_enabled = enabled == 'enabled'
        docs = render_chart(
            values={
                "workers": {"keda": {"enabled": True}, "persistence": {"enabled": is_enabled}},
                'executor': 'CeleryExecutor',
            },
            show_only=["templates/workers/worker-kedaautoscaler.yaml"],
        )
        assert jmespath.search("spec.scaleTargetRef.kind", docs[0]) == kind


import numpy as np

from winapi_deobf_experiments.util.io import read_filepath_of_delimited_ints_as_vector
from winapi_deobf_experiments.supervised.dataset import Dataset

def get_bag_of_words_dataset_for_classification(corpus, K, label_tracker, seed=1234):

	np.random.seed(seed=seed)
	train_pct=_get_train_pct(label_tracker)
	X = _get_bag_of_words_matrix(corpus)
	Xproj = _rotate_bag_of_words_onto_dominant_right_singular_vectors(X, K)

	N=len(corpus.numeric_corpus)
	train_idxs=np.random.choice(N, int(N*train_pct))
	test_idxs=np.setdiff1d(range(N),train_idxs)
	features_train=Xproj[train_idxs,:]
	features_test=Xproj[test_idxs,:]
	labels_train=[corpus.get_numeric_label(x) for x in train_idxs]
	labels_test=[corpus.get_numeric_label(x) for x in test_idxs]

	#TD: add assertion that # train idxs + # test idxs has right size.
	dataset=Dataset(features_train=features_train, features_test=features_test,
		labels_train=labels_train, labels_test=labels_test, train_idxs=train_idxs,
		test_idxs=test_idxs)
	return dataset

def _get_bag_of_words_matrix(corpus):
	W=len(corpus.vocab_dict)
	N=len(corpus.numeric_corpus)
	X=np.zeros((N,W))
	for (i,numeric_words) in enumerate(corpus.numeric_corpus):
		for numeric_word in numeric_words:
			X[i,numeric_word]=1.0
	return X

def _rotate_bag_of_words_onto_dominant_right_singular_vectors(X, K):
	U, d, V = np.linalg.svd(X, full_matrices=True)
	Xproj=np.matmul(X,np.transpose(V[1:(K+3),:]))
	return Xproj

def _get_train_pct(label_tracker):
	train_pcts=[]
	for label, label_info in label_tracker.iteritems():
		train_pcts.append(label_info["train_pct"])
	if np.sum(np.diff(train_pcts))!=0.0:
		raise ValueError("Experiment 1 assumes a uniform training pct across labels")
	return train_pcts[0]


# -*- coding: utf-8 -*-
"""
Created on Tue Jul 28 01:36:37 2020

@author: Izadi
"""

import os
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
os.chdir(r'D:\desktop')
Data=pd.read_csv("BCG Matrix Data.csv")
Data.shape
Data.head()
# create data
x = Data['Value Share of Total F&N']
y = Data['Value Growth % YA']
z = Data.Value
value = pd.Series(Data['Items'])
colors = np.random.rand(10)
# use the scatter function
i = [0, 25]
j = [5, 5]
k= [12.5,12.5]
m =[-15,25]
plt.plot(i,j, color= 'black')
plt.plot(k,m, color= 'black')
plt.title("BCG Matrix")
plt.scatter(x, y, s=z/10000, c=colors, label=value)
plt.xlabel("Shares")
plt.ylabel("Growth")
plt.legend(loc=6)
plt.show()


import Vector2

class mesh :
    '''Drawable objects with points and lines'''

    def __init__ (self, points = [], vertCount = 0) :
        self.points = []
        self.vertCount = 0

        if (len(points) > 0) :
            self.add(points)

    def add (self, insertPoints) :
        print(insertPoints[0].x, insertPoints[0].y)

        for point in insertPoints :
            self.points.append( point )
            self.vertCount += 1

        self.points.append( insertPoints[0] )

    def draw (self, c, position, size, color, debug = False) :

        pointCounter = 0
        for point in self.points :

            if (debug) :
                c.create_text(point.x * size.x + position.x, point.y * size.y + position.y, text = pointCounter)

            pointCounter += 1

            if (pointCounter < self.vertCount) :
                nextPoint = self.points[pointCounter]

            c.create_line(
                int(point.x * size.x + position.x),     int(point.y * size.y + position.y),
                int(nextPoint.x * size.x + position.x), int(nextPoint.y * size.y + position.y),
                fill = color, width = 2
            )


import sys
import math

result = {}
result[1] = 1
result[2] = 0
result[3] = 0
result[4] = 2
result[5] = 10
result[6] = 4
result[7] = 40
result[8] = 92
result[9] = 352
result[10] = 724
result[11] = 2680

n = int(input())

# Write an action using print
# To debug: print("Debug messages...", file=sys.stderr)

print(result[n])


#
# A very simple (not the most efficient !) implementation
# to solve the Poisson's equation
#
# Vincent Legat - 2018
# Ecole Polytechnique de Louvain
#

from numpy import *
from numpy.linalg import solve
from scipy.sparse import dok_matrix
from scipy.sparse.linalg import spsolve

import matplotlib
import matplotlib.pyplot as plt
matplotlib.rcParams['toolbar'] = 'None'
myColorMap = matplotlib.cm.jet

from timeit import default_timer as timer

# =========================================================================

def poissonSolve(nx,ny):
  n = nx*ny; h = 2/(ny-1)
  A = eye(n); B = zeros(n)
  for i in range(1,nx-1):
    for j in range(1,ny-1):
      index = i + j*nx
      A[index,index]    =  4.0
      A[index,index-1]  = -1.0
      A[index,index+1]  = -1.0
      A[index,index-nx] = -1.0
      A[index,index+nx] = -1.0
      B[index] = 1
  return solve(A,B).reshape(ny,nx)

# =========================================================================

def poissonSolveSparse(nx,ny):
  n = nx*ny; h = 2/(ny-1)
  A = dok_matrix((n,n),dtype=float32)
  B = zeros(n)
  for i in range(n):
    A[i,i] = 1.0
  for i in range(1,nx-1):
    for j in range(1,ny-1):
      index = i + j*nx
      A[index,index]    =  4.0
      A[index,index-1]  = -1.0
      A[index,index+1]  = -1.0
      A[index,index-nx] = -1.0
      A[index,index+nx] = -1.0
      B[index] = 1
  A = A / (h*h)
  return spsolve(A.tocsr(),B).reshape(ny,nx)

# ============================= mainProgram ===============================

nx = ny = 100;
print(' === Considering nx=ny=%d' % nx)
tic = timer()
U = poissonSolve(nx,ny)
toc = timer() - tic
print(' === Full solver   : elapsed time is %f seconds.' % toc)
tic = timer()
U = poissonSolveSparse(nx,ny)
toc = timer() - tic
print(' === Sparse solver : elapsed time is %f seconds.' % toc)

plt.figure("Temperature")
Xg,Yg = meshgrid(linspace(-1,1,nx),linspace(-1,1,ny))
plt.contourf(Xg,Yg,U,10,cmap=myColorMap)
plt.contour(Xg,Yg,U,10,colors='k',linewidths=1)
plt.axis("equal"); plt.axis("off")
plt.show()

# =========================================================================


from django.contrib import admin
from django.urls import path, include
from . import views

urlpatterns = [
    path('', views.index, name='home'),
    path('about/', views.about, name='about'),
    path('contact/', views.contact, name='contact'),
    path('website', views.website, name='website'),
    path('social/', views.social, name='social'),
    path('crm/', views.crm, name='crm'),
    path('seo/', views.seo, name='seo'),
    path('deck/', views.pitch_deck, name='pitch-deck'),
    path('deck2/', views.pitch_deck2, name='pitch-deck2'),
    path('backlinks/', views.backlinks, name='backlinks'),
]


import sys,os

# Full path to your django directory
pathname = os.path.dirname(os.path.abspath(__file__))
path     = os.path.abspath(os.path.join(pathname, '..'))
sys.path.insert(0, pathname)
sys.path.insert(0, os.path.abspath(os.path.join(pathname, '..')))

os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

from inventory.utils import createDataStores, createVirtualHosts, createVirtualMachines, createIPs, clearAll

def ds():
  datastore_file  = path+"/data/DataStore.csv"
  datastore_names = ('name', 'capacityMB', 'freespaceMB', 'filesystemVersion')
  createDataStores(datastore_file, datastore_names)

def vmh():
  vmhost_file  = path+'/data/VMHost.csv'
  vmhost_names = ('Name','Manufacturer','Model','NumCpu','CpuTotalMhz','CpuUsageMhz','ProcessorType')
  createVirtualHosts(vmhost_file, vmhost_names)

def vm():
  vm_file  = path+'/data/VirtualMachine.csv'
  vm_names = ('Name','PowerState','NumCpu','MemoryMB','Notes','VMHost','Datastore')
  createVirtualMachines(vm_file, vm_names)

def ip():
  ip_file  = path+"/data/IpAddress.csv"
  ip_names =  ('vm','ip')
  createIPs(ip_file, ip_names)

if __name__ == '__main__':
  clearAll()
  ds()
  vmh()
  vm()
  ip()


# Exercise extracted from Crash Courser on Python (Google/Cousera).
# This exercise is inside the "Iterating over Lists and Tuples" video, at Week 4.
# I just found it interesting and am putting this code here.

'''Try out the enumerate function for yourself in this quick exercise. Complete the skip_elements function to return every other element from the list, this time using the enumerate function to check if an element is in an even position or an odd position.
'''

def skip_elements(elements):
	# code goes here
	list = []
	for index, element in enumerate(elements):
		if int(index) % 2 == 0:
			list.append(element)
	return list

print(skip_elements(["a", "b", "c", "d", "e", "f", "g"])) # Should be ['a', 'c', 'e', 'g']
print(skip_elements(['Orange', 'Pineapple', 'Strawberry', 'Kiwi', 'Peach'])) # Should be ['Orange', 'Strawberry', 'Peach']


 # -*- coding: utf-8; py-indent-offset: 2 -*-

from __future__ import absolute_import, division, print_function
from mmtbx.ions.environment import ChemicalEnvironment
import mmtbx.ions.identify
from mmtbx import ions
import mmtbx.monomer_library.pdb_interpretation
from mmtbx import monomer_library
from mmtbx.ions.environment import chem_carboxy, chem_amide, chem_backbone, \
     chem_water, chem_phosphate, \
     chem_nitrogen_primary, chem_nitrogen_secondary, \
     chem_chloride, chem_oxygen, chem_nitrogen, chem_sulfur
import libtbx.load_env
from collections import OrderedDict, Counter
import os
import sys
from six.moves import zip
from six.moves import range

def exercise():
  if not libtbx.env.has_module("phenix_regression"):
    print("Skipping {}".format(os.path.split(__file__)[1]))
    return

  models = OrderedDict([
    ("2qng", [
      Counter({chem_oxygen: 7, chem_carboxy: 2, chem_water: 2,
               chem_backbone: 3}),
      Counter({chem_oxygen: 6, chem_carboxy: 3, chem_water: 1,
               chem_backbone: 2}),
      ]),
    ("3rva", [
      Counter({chem_oxygen: 6, chem_carboxy: 4, chem_water: 2}),
      Counter({chem_nitrogen: 1, chem_oxygen: 4, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 1}),
      Counter({chem_nitrogen: 4, chem_nitrogen_primary: 1,
               chem_nitrogen_secondary: 3, chem_backbone: 3}),
      ]),
    ("1mjh", [
      Counter({chem_oxygen: 6, chem_water: 3, chem_phosphate: 3}),
      Counter({chem_oxygen: 6, chem_water: 3, chem_phosphate: 3}),
      ]),
    ("4e1h", [
      Counter({chem_oxygen: 6, chem_carboxy: 4}),
      Counter({chem_oxygen: 6, chem_carboxy: 3}),
      Counter({chem_oxygen: 6, chem_carboxy: 3}),
      ]),
    ("2xuz", [
      Counter({chem_oxygen: 6}),
      ]),
    ("3zli", [
      Counter({chem_nitrogen: 2, chem_oxygen: 4, chem_nitrogen_secondary: 2,
               chem_carboxy: 1, chem_water: 1}),
      Counter({chem_sulfur: 4}),
      Counter({chem_nitrogen: 2, chem_oxygen: 4, chem_nitrogen_secondary: 2,
               chem_carboxy: 1, chem_water: 1}),
      Counter({chem_sulfur: 4}),
      ]),
    ("3e0f", [
      Counter({chem_nitrogen: 2, chem_oxygen: 4, chem_nitrogen_secondary: 2,
               chem_carboxy: 2, chem_phosphate: 2}),
      Counter({chem_nitrogen: 2, chem_oxygen: 2, chem_nitrogen_secondary: 2,
               chem_carboxy: 1, chem_phosphate: 1}),
      Counter({chem_nitrogen: 2, chem_oxygen: 3, chem_nitrogen_secondary: 2,
               chem_carboxy: 2, chem_phosphate: 1}),
      ]),
    ("3dkq", [
      Counter({chem_nitrogen: 4, chem_oxygen: 1, chem_nitrogen_secondary: 4,
               chem_carboxy: 1}),
      Counter({chem_nitrogen: 2, chem_oxygen: 1, chem_nitrogen_secondary: 2,
               chem_carboxy: 1}),
      Counter({chem_nitrogen: 4, chem_oxygen: 1, chem_nitrogen_secondary: 4,
               chem_carboxy: 1}),
      ]),
    ("2o8q", [
      Counter({chem_nitrogen: 3, chem_oxygen: 3, chem_nitrogen_secondary: 3,
               chem_water: 3}),
      Counter({chem_nitrogen: 3, chem_oxygen: 3, chem_nitrogen_secondary: 3,
               chem_water: 3}),
      ]),
    ("1tgg", [
      Counter({chem_oxygen: 5, chem_chloride: 1, chem_carboxy: 4,
               chem_water: 1}),
      Counter({chem_oxygen: 3, chem_chloride: 2, chem_carboxy: 3}),
      Counter({chem_oxygen: 4, chem_chloride: 2, chem_carboxy: 4}),
      ]),
    ("3zu8", [
      Counter({chem_oxygen: 7, chem_carboxy: 3, chem_water: 1,
                      chem_backbone: 2}),
      Counter({chem_nitrogen: 4, chem_oxygen: 1, chem_nitrogen_primary: 1,
               chem_nitrogen_secondary: 3, chem_carboxy: 1, chem_backbone: 3}),
      ]),
    ("1ofs", [
      Counter({chem_nitrogen: 1, chem_oxygen: 4, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 1}),
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_water: 2,
               chem_backbone: 1}),
      Counter({chem_nitrogen: 1, chem_oxygen: 5, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 2}),
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_water: 2,
               chem_backbone: 1}),
      ]),
    ("3ul2", [
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_water: 2,
               chem_backbone: 1}),
      Counter({chem_nitrogen: 1, chem_oxygen: 5, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 2}),
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_backbone: 1,
               chem_water: 2}),
      Counter({chem_nitrogen: 1, chem_oxygen: 5, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 2}),
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_water: 2,
               chem_backbone: 1}),
      Counter({chem_nitrogen: 1, chem_oxygen: 5, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 2}),
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_water: 2,
               chem_backbone: 1}),
      Counter({chem_nitrogen: 1, chem_oxygen: 5, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 2}),
      ]),
    ("3snm", [
      Counter({chem_oxygen: 5, chem_amide: 1, chem_carboxy: 3,
               chem_backbone: 1}),
      Counter({chem_nitrogen: 1, chem_oxygen: 3, chem_nitrogen_secondary: 1,
               chem_carboxy: 3}),
      ]),
    ("3qlq", [
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_water: 2,
               chem_backbone: 1}),
      Counter({chem_nitrogen: 1, chem_oxygen: 5, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 2}),
      Counter({chem_nitrogen: 1, chem_oxygen: 5, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 2}),
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_water: 2,
               chem_backbone: 1}),
      Counter({chem_nitrogen: 1, chem_oxygen: 5, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 2}),
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_water: 2,
               chem_backbone: 1}),
      Counter({chem_oxygen: 7, chem_amide: 1, chem_carboxy: 3, chem_water: 2,
               chem_backbone: 1}),
      Counter({chem_nitrogen: 1, chem_oxygen: 5, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 2}),
      ]),
    ("2gdf", [
      Counter({chem_nitrogen: 1, chem_oxygen: 4, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 1}),
      Counter({chem_oxygen: 6, chem_amide: 1, chem_carboxy: 3, chem_water: 1,
               chem_backbone: 1}),
      Counter({chem_nitrogen: 1, chem_oxygen: 4, chem_nitrogen_secondary: 1,
               chem_carboxy: 3, chem_water: 1}),
      Counter({chem_oxygen: 6, chem_amide: 1, chem_carboxy: 3, chem_water: 1,
               chem_backbone: 1}),
      ]),
    ("1q8h", [
      Counter({chem_oxygen: 7, chem_carboxy: 6, chem_water: 1}),
      Counter({chem_oxygen: 7, chem_carboxy: 4, chem_water: 3}),
      Counter({chem_oxygen: 8, chem_carboxy: 6, chem_water: 2}),
      ]),
  ])

  for model, expected_environments in models.items():
    pdb_path = libtbx.env.find_in_repositories(
      relative_path = os.path.join(
        "phenix_regression", "mmtbx", "ions", model + ".pdb"),
      test = os.path.isfile
      )

    mon_lib_srv = monomer_library.server.server()
    ener_lib = monomer_library.server.ener_lib()
    processed_pdb_file = monomer_library.pdb_interpretation.process(
      mon_lib_srv = mon_lib_srv,
      ener_lib = ener_lib,
      file_name = pdb_path,
      raw_records = None,
      force_symmetry = True,
      log = libtbx.utils.null_out()
      )

    geometry = \
      processed_pdb_file.geometry_restraints_manager(show_energies = False)
    xray_structure = processed_pdb_file.xray_structure()
    pdb_hierarchy = processed_pdb_file.all_chain_proxies.pdb_hierarchy
    connectivity = geometry.shell_sym_tables[0].full_simple_connectivity()

    manager = mmtbx.ions.identify.manager(
      fmodel = None,
      pdb_hierarchy = pdb_hierarchy,
      xray_structure = xray_structure,
      connectivity = connectivity)

    elements = set(ions.DEFAULT_IONS + ions.TRANSITION_METALS)
    elements.difference_update(["CL"])

    metals = [i_seq for i_seq, atom in enumerate(manager.pdb_atoms)
             if atom.fetch_labels().resname.strip().upper() in elements]
    assert len(metals) == len(expected_environments)

    for index, metal, expected_environment in \
      zip(range(len(metals)), metals, expected_environments):
      env = ChemicalEnvironment(
        metal,
        manager.find_nearby_atoms(metal, filter_by_two_fofc = False),
        manager
        )
      if env.chemistry != expected_environment:
        print("Problem detecting chemistry environment in", model, index)
        print("Found:    ", env.chemistry)
        print("Should be:", expected_environment)
        sys.exit()

  print("OK")

if __name__ == "__main__":
  exercise()


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)