from flask import Flask, render_template, request
from sklearn.cluster import KMeans
import ast
import json
import pandas as pd
from flask_cors import CORS
import plotly
import plotly.graph_objs as go
import plotly.express as px

app = Flask(__name__, template_folder="templates")
cors = CORS(app)
df = pd.read_csv('./winequality-red.csv')

@app.route('/')
def home():
    json_response = json.loads(df.to_json(orient='records'))
    print(json_response)
    return render_template('home.html', data=json.dumps(json_response))

@app.route('/displaycsv', methods=['POST', 'GET'])
def displaycsv():
    global df
    df = pd.read_csv('./winequality-white.csv')
    json_response = json.loads(df.to_json(orient='records'))
    return json.dumps(json_response)

@app.route('/labelUpdate', methods=['POST', 'GET'])
def updateLabel():
    arguments = request.args
    df_a1 = pd.read_csv('./dataset1_processed_updated.csv')
    numcols =['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week']
    labelcol = arguments['label']
    numcols.append(labelcol)
    global df_al
    df_a1_filtered = df_a1[numcols]
    json_response = json.loads(df_a1_filtered.to_json(orient='records'))
    return json.dumps(json_response)

@app.route('/applyAlgoParameters', methods=['POST', 'GET'])
def applyAlgoParamsKMeans():
    arguments = request.args
    rstate = arguments['randomstate']
    init = str(arguments['init'])
    if('means' in init):
        init =init.strip()
        init+="++"
    csvname = arguments['name']
    colormode = arguments['mode']
    print(csvname+":"+colormode)

    global df
    df = pd.read_csv('./' + csvname + '.csv')
    labelcol = df.columns
    labelcol = labelcol[-1]
    classes = df[labelcol].unique()

    if colormode != "Class-Based":
        del df[labelcol]
        kmeans = KMeans(n_clusters=len(classes),init=init,random_state=int(rstate))
        kmeans.fit(df)
        y_kmeans = kmeans.predict(df)
        labels = list(y_kmeans)
        labels = kmeans.labels_
        df['labels'] = labels
    json_response = json.loads(df.to_json(orient='records'))
    return json.dumps(json_response)

@app.route('/load', methods=['POST', 'GET'])
def readcsv():
    arguments = request.args
    csvname = arguments['name']
    print(csvname)
    global df
    df = pd.read_csv('./' + csvname + '.csv')
    json_response = json.loads(df.to_json(orient='records'))
    return json.dumps(json_response)

@app.route('/correlationMatrix', methods=['POST', 'GET'])
def calculateCorrelationMatrix():
    arguments = request.args
    try:
        cid = int(arguments['clusterid'])
    except ValueError:
        cid = arguments['clusterid']

    global df
    labelcol = df.columns
    labelcol = labelcol[-1]
    df_filtered = df[df[labelcol] == cid]
    x = list(df_filtered.columns)
    x.pop()
    fig = go.Figure(data=go.Heatmap(
        z=df_filtered.corr(),
        x=x,
        y=x,
        colorscale='Reds'
    ))

    fig['layout'].update(title="Correlation Matrix")
    json_response=json.loads(fig.to_json())
    return json.dumps(json_response)

@app.route('/visualizeA1Assignment', methods=['POST', 'GET'])
def getAssignment1Data():

    df_a1 = pd.read_csv('./dataset1_processed_updated.csv')
    df_a1_filtered = df_a1[['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week', 'salary']]
    json_response = json.loads(df_a1_filtered.to_json(orient='records'))
    return render_template('a1_dataset.html', data=json.dumps(json_response))

@app.route('/clusterizeA1', methods=['POST', 'GET'])
def clusterizeA1Data():
    df_a1 = pd.read_csv('./dataset1_processed_updated.csv')
    arguments = request.args
    lbl = arguments['label']
    numcols = ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week', lbl]
    colormode = arguments['mode']
    df_a1_filtered = df_a1[numcols]
    classes = df_a1_filtered[lbl].unique()

    if colormode != "Class-Based":
        del df_a1_filtered[lbl]
        kmeans = KMeans(n_clusters=len(classes))
        kmeans.fit(df_a1_filtered)
        y_kmeans = kmeans.predict(df_a1_filtered)
        labels =list(y_kmeans)
        labels = kmeans.labels_
        df_a1_filtered['labels'] = labels
    json_response = json.loads(df_a1_filtered.to_json(orient='records'))
    return json.dumps(json_response)

@app.route('/clusterize', methods=['POST', 'GET'])
def clusterizeData():
    arguments = request.args
    csvname = arguments['name']
    colormode = arguments['mode']
    print(csvname+":"+colormode)
    global df
    df = pd.read_csv('./' + csvname + '.csv')
    labelcol = df.columns
    labelcol = labelcol[-1]
    classes = df[labelcol].unique()

    if colormode != "Class-Based":
        del df[labelcol]
        kmeans = KMeans(n_clusters=len(classes))
        kmeans.fit(df)
        y_kmeans = kmeans.predict(df)
        labels =list(y_kmeans)
        labels = kmeans.labels_
        df['labels'] = labels
    json_response = json.loads(df.to_json(orient='records'))
    return json.dumps(json_response)

if __name__ == '__main__':
    app.run(debug=True)


import os

import matplotlib.pyplot as plt

def load_instance_from_file(filename):
  with open(filename, 'r') as file:
    instance_data = file.readlines()

    w = int(instance_data[0])
    n = int(instance_data[1])

    M = []
    for line in instance_data[2:]:
      row = list(map(int, line.split()))
      M.append(row)

    return w, n, M

def write_file_times(times, path):
  with open(path+".txt", 'w') as f:
    f.writelines("COMPUTATION TIME")
    f.writelines("\n%s" % t for t in times)
    f.writelines("\n"+"-"*5)
  f.close()

def write_file_output(solution, path):
  f = open(path, "w+")
  if not solution=={}:
    f.write("{} {}\n".format(*solution['sizes_plate']))
    f.write("{}\n".format(solution['n_circuits']))
    for i in range(len(solution['pos_circuits'])):
      f.write("{} {} {} {}\n".format(*solution['sizes_circuits'][i], *solution['pos_circuits'][i]))
  f.close()

def display_solution(solution, instance_index, folder):
  fig, ax = plt.subplots()
  ax.set_aspect('equal')
  cmap = plt.colormaps['rainbow']
  ax = plt.gca()
  plt.title(f"Solution {instance_index}")
  if len(solution['pos_circuits']) > 0:
    for i in range(solution['n_circuits']):
      rect = plt.Rectangle(solution['pos_circuits'][i], *solution['sizes_circuits'][i], edgecolor="#333", facecolor=cmap(i / (solution['n_circuits'] - 1)))
      ax.add_patch(rect)
  ax.set_xlim(0, solution['sizes_plate'][0])
  ax.set_ylim(0, solution['sizes_plate'][1] + 1)
  ax.set_xticks(range(solution['sizes_plate'][0] + 1))
  ax.set_yticks(range(solution['sizes_plate'][1] + 1))
  ax.set_xlabel('width_plate')
  ax.set_ylabel('height_plate')

  if not os.path.exists(folder):
    os.makedirs(folder)
  plt.savefig(f"{folder}/solution_{instance_index}.png")
  #plt.show()
  plt.close()

def format_solution(W, N, H, X, Y, widths, heights):
  solution = {}
  solution['sizes_plate'] = (W, H)
  solution['n_circuits'] = N
  solution['sizes_circuits'] = list(zip(widths, heights))
  solution['pos_circuits'] = list(zip(X, Y))
  return solution


from dataclasses import dataclass
from ipaddress import IPv4Address, IPv6Address
from itertools import chain, islice
from typing import (
    AbstractSet,
    Iterable,
    Iterator,
    Mapping,
    MutableMapping,
    MutableSet,
    Sequence,
    Tuple,
    cast,
)

from std2.ipaddress import IPAddress

from .consts import SERVER_NAME
from .leases import leases
from .options.parser import settings
from .options.types import Accessible, PortForward, Protocol
from .types import DualStack, Networks

@dataclass(frozen=True)
class _Addrs:
    V4: IPv4Address
    V6: IPv6Address

@dataclass(frozen=True)
class _Dest:
    NAME: str
    ADDR: _Addrs

@dataclass(frozen=True)
class _Ported(_Dest):
    PROTO: Protocol
    PORT: int

@dataclass(frozen=True)
class _Forwarded(_Ported):
    FROM_PORT: int

@dataclass(frozen=True)
class _Available(_Ported):
    ...

@dataclass(frozen=True)
class Split(_Dest):
    DOMAIN: str

def _leased(networks: Networks) -> MutableMapping[str, MutableSet[IPAddress]]:
    trusted_ifs = len(settings().interfaces.trusted)
    guest_ifs = len(settings().interfaces.guest)

    leased: MutableMapping[str, MutableSet[IPAddress]] = {}
    for name, addr in leases():
        addrs = leased.setdefault(name, set())
        addrs.add(addr)

    addrs = leased.setdefault(SERVER_NAME, set())
    for addr in cast(
        Iterator[IPAddress],
        chain(
            islice(networks.guest.v4.hosts(), guest_ifs),
            islice(networks.guest.v6.hosts(), guest_ifs),
            islice(networks.trusted.v4.hosts(), trusted_ifs),
            islice(networks.trusted.v6.hosts(), trusted_ifs),
        ),
    ):
        addrs.add(addr)
    return leased

def _pick(
    leased: MutableMapping[str, MutableSet[IPAddress]], stack: DualStack, hostname: str
) -> Tuple[IPv4Address, IPv6Address]:
    lease_addrs = leased.setdefault(hostname, set())
    others = {addr for hn, addrs in leased.items() if hn != hostname for addr in addrs}

    v4 = next(
        chain(
            (addr for addr in lease_addrs if isinstance(addr, IPv4Address)),
            (addr for addr in stack.v4.hosts() if addr not in others),
        )
    )
    v6 = next(
        chain(
            (addr for addr in lease_addrs if isinstance(addr, IPv6Address)),
            (addr for addr in stack.v6.hosts() if addr not in others),
        )
    )

    lease_addrs.add(v4)
    lease_addrs.add(v6)
    return v4, v6

def forwarded_ports(
    networks: Networks,
) -> Tuple[AbstractSet[_Forwarded], AbstractSet[_Available], AbstractSet[Split]]:
    leased = _leased(networks)

    def c1(
        stack: DualStack, forwards: Mapping[str, Sequence[PortForward]]
    ) -> Iterator[_Forwarded]:
        for hostname, fws in forwards.items():
            for fwd in fws:
                for proto in fwd.protocols:
                    v4, v6 = _pick(leased, stack=stack, hostname=hostname)
                    spec = _Forwarded(
                        NAME=hostname,
                        PROTO=proto,
                        FROM_PORT=fwd.from_port or fwd.port,
                        PORT=fwd.port,
                        ADDR=_Addrs(V4=v4, V6=v6),
                    )
                    yield spec

    def c2(
        stack: DualStack, available: Mapping[str, Sequence[Accessible]]
    ) -> Iterator[_Available]:
        for hostname, accessible in available.items():
            for acc in accessible:
                for proto in acc.protocols:
                    v4, v6 = _pick(leased, stack=stack, hostname=hostname)
                    spec = _Available(
                        NAME=hostname,
                        PROTO=proto,
                        PORT=acc.port,
                        ADDR=_Addrs(V4=v4, V6=v6),
                    )
                    yield spec

    def c3(stack: DualStack, split: Mapping[str, AbstractSet[str]]) -> Iterator[Split]:
        for hostname, domains in split.items():
            for domain in domains:
                v4, v6 = _pick(leased, stack=stack, hostname=hostname)
                spec = Split(
                    NAME=hostname,
                    DOMAIN=domain,
                    ADDR=_Addrs(V4=v4, V6=v6),
                )
                yield spec

    fwd = {
        *c1(networks.wireguard, forwards=settings().port_forwards.wireguard),
        *c1(networks.trusted, forwards=settings().port_forwards.trusted),
        *c1(networks.guest, forwards=settings().port_forwards.guest),
    }
    available = {
        *c2(networks.wireguard, available=settings().guest_accessible.wireguard),
        *c2(networks.trusted, available=settings().guest_accessible.trusted),
    }
    split = {
        *c3(networks.wireguard, split=settings().dns.split_horizon.wireguard),
        *c3(networks.trusted, split=settings().dns.split_horizon.trusted),
        *c3(networks.guest, split=settings().dns.split_horizon.guest),
    }
    return fwd, available, split

def dhcp_fixed(fwds: Iterable[_Dest]) -> Iterator[_Dest]:
    seen: MutableSet[str] = set()
    for fwd in fwds:
        name = fwd.NAME
        if name not in seen:
            seen.add(name)
            yield fwd


from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
    # url(r'^index/(?P<page>[0-9]+)/$', views.index, name='index'),
    url(r'^login/$', views.login, name='login'),
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
    url(r'^ques/$', views.ques, name='ques'),
    url(r'^ques/(?P<question_id>[0-9]+)/$', views.ques, name='ques'),
    url(r'^signup/$', views.signup, name='signup'),
    url(r'^logout/$', views.logout, name='logout')
]


"""
Class for monitoring expiration information in HashiCorp Vault.
"""
from abc import ABC, abstractmethod
from typing import Dict, List, Type, TypeVar

import hvac
from prometheus_client import Gauge

from vault_monitor.expiration_monitor.vault_time import ExpirationMetadata

ExpirationMonitorType = TypeVar("ExpirationMonitorType", bound="ExpirationMonitor")  # pylint: disable=invalid-name

class ExpirationMonitor(ABC):
    """
    Monitors and updates custom metadata in HashiCorp Vault for expiration based on custom metadata.
    """

    secret_last_renewal_timestamp_gauge: Gauge
    secret_expiration_timestamp_gauge: Gauge

    last_renewal_gauge_name: str
    last_renewal_gauge_description: str
    expiration_gauge_name: str
    expiration_gauge_description: str

    def __init__(self, mount_point: str, monitored_path: str, vault_client: hvac.Client, service: str, prometheus_labels: Dict[str, str] = None, metadata_fieldnames: Dict[str, str] = None) -> None:
        """
        Creates an instance of the ExpirationMonitor class.
        """
        self.mount_point = mount_point
        self.monitored_path = monitored_path
        self.vault_client = vault_client
        self.service = service
        # Add the secret specific labels to the provided labels
        self.prometheus_labels = {"monitored_path": monitored_path, "mount_point": mount_point, "service": service}

        if prometheus_labels is not None:
            self.prometheus_labels.update(prometheus_labels)

        prometheus_label_keys = list(self.prometheus_labels.keys())

        if metadata_fieldnames is None:
            metadata_fieldnames = {}
        self.last_renewed_timestamp_fieldname = metadata_fieldnames.get("last_renewal_timestamp", "last_renewal_timestamp")
        self.expiration_timestamp_fieldname = metadata_fieldnames.get("expiration_timestamp", "expiration_timestamp")

        self.create_metrics(prometheus_label_keys)

    @classmethod
    def create_metrics(cls: Type[ExpirationMonitorType], prometheus_label_keys: List[str]) -> None:
        """
        Create the metrics, only happens once during the entire lifetime of the exporter (not with every object creation.)
        """
        # Only create the metric once
        if not hasattr(cls, "secret_last_renewal_timestamp_gauge"):
            cls.secret_last_renewal_timestamp_gauge = Gauge(cls.last_renewal_gauge_name, cls.last_renewal_gauge_description, prometheus_label_keys)
        if not hasattr(cls, "secret_expiration_timestamp_gauge"):
            cls.secret_expiration_timestamp_gauge = Gauge(cls.expiration_gauge_name, cls.expiration_gauge_description, prometheus_label_keys)

    @abstractmethod
    def get_expiration_info(self) -> ExpirationMetadata:
        """
        Abstract method for getting the expiration information
        """

    def update_metrics(self) -> None:
        """
        Update the current value for the metrics.
        """

        expiration_info = self.get_expiration_info()

        self.secret_last_renewal_timestamp_gauge.labels(**self.prometheus_labels).set(expiration_info.get_last_renewal_timestamp())
        self.secret_expiration_timestamp_gauge.labels(**self.prometheus_labels).set(expiration_info.get_expiration_timestamp())


import os
import re
from modules.engine import SimplePol

out_name = "encrypted"
header_name = "header"
main_name = "all_nop"
temp_key = b'\x8eNT\xdee\x96\xda\xec'

def compile_file(file):
    compile_str = f"nasm -fbin -o in/{file} src/{file}.asm"
    os.system(compile_str)
    return os.path.getsize(f"in/{file}")

def change_asm_file(file, template, to):
    with open(file, 'r+', encoding='utf8') as asm:
        asm_txt = asm.read()
        asm_txt = re.sub(template, to, asm_txt)
        asm.seek(0)
        asm.write(asm_txt)
        asm.truncate()

a = SimplePol(f"src/{header_name}.asm")
a.polymorph()
# HERE polymorphism header_name function
header_name += "_pol"
header_size = compile_file(header_name)

change_asm_file(f"src/{main_name}.asm", r'(org)\s+(0[xX])?[a-fA-F0-9]+', f"org    {hex(header_size + 0x00400000)}")

file_size = compile_file(main_name)

change_asm_file(f"src/{header_name}.asm", r'(mov)\s+(rax)\s*(,)\s*(0[xX])?[a-fA-F0-9]+',
                f"mov rax, {hex(header_size + file_size + 0x00400000)}")
change_asm_file(f"src/{header_name}.asm", r'(mov)\s+(rcx)\s*(,)\s*(0[xX])?[a-fA-F0-9]+', f"mov rcx, {hex(file_size)}")
compile_file(header_name)

print(f"header size: {header_size}")
print(f"file size: {file_size}")
print(f"header_size + file_size: {header_size + file_size}")

i = 0
with open(f"in/{main_name}", 'r+b') as out:
    main = list(out.read())
# XOR
while i < file_size:
    main[i] = main[i] ^ temp_key[len(temp_key) - 1 - (i % 8)]
    i += 1

with open(f"out/{out_name}", "wb+") as out, open(f"in/{header_name}", "rb") as header:
    out.write(header.read())
    out.write(bytearray(main))

a_bytes_little = (header_size + file_size).to_bytes(8, 'little')
with open(f"out/{out_name}", 'r+b') as out:
    out.seek(0x60)
    out.write(a_bytes_little)
    out.write(a_bytes_little)


import pickle
import random
from collections import namedtuple
from typing import Tuple

import cv2
import lmdb
import numpy as np
from path import Path

Sample = namedtuple('Sample', 'gt_text, file_path')
Batch = namedtuple('Batch', 'imgs, gt_texts, batch_size')

class DataLoaderIAM:
    """
    Loads data which corresponds to IAM format,
    see: http://www.fki.inf.unibe.ch/databases/iam-handwriting-database
    """

    def __init__(self,
                 data_dir: Path,
                 batch_size: int,
                 data_split: float = 0.95,
                 fast: bool = True) -> None:
        """Loader for dataset."""

        assert data_dir.exists()

        self.fast = fast
        if fast:
            self.env = lmdb.open(str(data_dir / 'lmdb'), readonly=True)

        self.data_augmentation = False
        self.curr_idx = 0
        self.batch_size = batch_size
        self.samples = []

        f = open(data_dir / 'gt/words.txt')
        chars = set()
        bad_samples_reference = ['a01-117-05-02', 'r06-022-03-05']  # known broken images in IAM dataset
        for line in f:
            # ignore empty and comment lines
            line = line.strip()
            if not line or line[0] == '#':
                continue

            line_split = line.split(' ')
            assert len(line_split) >= 9

            # filename: part1-part2-part3 --> part1/part1-part2/part1-part2-part3.png
            file_name_split = line_split[0].split('-')
            file_name_subdir1 = file_name_split[0]
            file_name_subdir2 = f'{file_name_split[0]}-{file_name_split[1]}'
            file_base_name = line_split[0] + '.png'
            file_name = data_dir / 'img' / file_name_subdir1 / file_name_subdir2 / file_base_name

            if line_split[0] in bad_samples_reference:
                print('Ignoring known broken image:', file_name)
                continue

            # GT text are columns starting at 9
            gt_text = ' '.join(line_split[8:])
            chars = chars.union(set(list(gt_text)))

            # put sample into list
            self.samples.append(Sample(gt_text, file_name))

        # split into training and validation set: 95% - 5%
        split_idx = int(data_split * len(self.samples))
        self.train_samples = self.samples[:split_idx]
        self.validation_samples = self.samples[split_idx:]

        # put words into lists
        self.train_words = [x.gt_text for x in self.train_samples]
        self.validation_words = [x.gt_text for x in self.validation_samples]

        # start with train set
        self.train_set()

        # list of all chars in dataset
        self.char_list = sorted(list(chars))

    def train_set(self) -> None:
        """Switch to randomly chosen subset of training set."""
        self.data_augmentation = True
        self.curr_idx = 0
        random.shuffle(self.train_samples)
        self.samples = self.train_samples
        self.curr_set = 'train'

    def validation_set(self) -> None:
        """Switch to validation set."""
        self.data_augmentation = False
        self.curr_idx = 0
        self.samples = self.validation_samples
        self.curr_set = 'val'

    def get_iterator_info(self) -> Tuple[int, int]:
        """Current batch index and overall number of batches."""
        if self.curr_set == 'train':
            num_batches = int(np.floor(len(self.samples) / self.batch_size))  # train set: only full-sized batches
        else:
            num_batches = int(np.ceil(len(self.samples) / self.batch_size))  # val set: allow last batch to be smaller
        curr_batch = self.curr_idx // self.batch_size + 1
        return curr_batch, num_batches

    def has_next(self) -> bool:
        """Is there a next element?"""
        if self.curr_set == 'train':
            return self.curr_idx + self.batch_size <= len(self.samples)  # train set: only full-sized batches
        else:
            return self.curr_idx < len(self.samples)  # val set: allow last batch to be smaller

    def _get_img(self, i: int) -> np.ndarray:
        if self.fast:
            with self.env.begin() as txn:
                basename = Path(self.samples[i].file_path).basename()
                data = txn.get(basename.encode("ascii"))
                img = pickle.loads(data)
        else:
            img = cv2.imread(self.samples[i].file_path, cv2.IMREAD_GRAYSCALE)

        return img

    def get_next(self) -> Batch:
        """Get next element."""
        batch_range = range(self.curr_idx, min(self.curr_idx + self.batch_size, len(self.samples)))

        imgs = [self._get_img(i) for i in batch_range]
        gt_texts = [self.samples[i].gt_text for i in batch_range]

        self.curr_idx += self.batch_size
        return Batch(imgs, gt_texts, len(imgs))


from typing import final
import pyhik.hikvision as hikvision
import asyncio
import json
import websockets
import time

class VehicleInfo:
    def findPlate(xml):
        start = xml.find("<licensePlate>") + len("<licensePlate>")
        end = xml.find("</licensePlate>")
        plate = xml[start:end]
        return plate

    def findVehicleType(xml):
        start = xml.find("<vehicleType>") + len("<vehicleType>")
        end = xml.find("</vehicleType>")
        Type = xml[start:end]
        return Type

    def findVehicleColor(xml):
        start = xml.find("<color>") + len("<color>")
        end = xml.find("</color>")
        Color = xml[start:end]
        return Color

    def findVehicleDirect(xml):
        start = xml.find("<direction>") + len("<direction>")
        end = xml.find("</direction>")
        Color = xml[start:end]
        return Color

class PlateDetector:
    def __init__(self, callback = None):
        self.token = None
        self.camHost = "192.168.1.64"
        self.cam = hikvision.HikCamera(f'http://{self.camHost}', 80, 'admin', 'Solid321')
        self.callback = callback

    def refresh_token(self):
        try:
            response = self.cam.hik_request.get(self.cam.root_url + "/ISAPI/Security/token?format=json")
            resobj = json.loads(response.text)
            self.token = resobj["Token"]["value"]
        except Exception as e:
            print(e)

    async def listen_ws(self):
        while True:
            try:
                uri = f"ws://{self.camHost}:7681/ISAPI/Event/notification/subscribeEventCap?token=" + self.token
                async with websockets.connect(uri) as websocket:
                    greeting = await websocket.recv()

                    with open('hikmessage.xml', 'r') as file:
                        init = file.read().replace('\n', '')
                        await websocket.send(init)

                    while True: # plaka loop
                        greeting = await websocket.recv()
                        if type(greeting)is str and greeting.startswith('<?xml version="1.0"'):
                            InfoClass = VehicleInfo
                            plate = VehicleInfo.findPlate(greeting)
                            VehicleType = VehicleInfo.findVehicleType(greeting)
                            VehicleColor = VehicleInfo.findVehicleColor(greeting)
                            VehicleDirect= VehicleInfo.findVehicleDirect(greeting)

                            if self.callback is not None:
                                self.callback(plate,VehicleType,VehicleColor,VehicleDirect)

            except Exception as e:
                print(e)
                self.refresh_token()

            # time.sleep(1)

    def run(self):
        self.refresh_token()
        asyncio.get_event_loop().run_until_complete(self.listen_ws())


# 在本节中，我们将学习
#
# 使用OpenCV查找图像的傅立叶变换
# 利用Numpy中可用的FFT功能
# 傅立叶变换的一些应用
# 我们将看到以下函数：cv.dft() ，cv.idft() 等

#Numpy中的傅立叶变换
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('./images/test.jpg', 0)
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20 * np.log(np.abs(fshift))
plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('Input Image')
plt.xticks([])
plt.yticks([])
plt.subplot(122)
plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('Magnitude Spectrum')
plt.xticks([])
plt.yticks([])
plt.show()

#OpenCV中的傅立叶变换
# OpenCV 为此提供了功能 cv.dft() 和 cv.idft() 。它返回与以前相同的结果，但是有两个通道。
# 第一个通道将具有结果的实部，第二个通道将具有结果的虚部。输入的图像应首先转换为np.float32 。
# 我们将看到如何做。
img_opencv = cv.imread('./images/test.jpg', 0)
dft = cv.dft(np.float32(img), flags=cv.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
magnitude_spectrum = 20*np.log(cv.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()


from nltk import WordNetLemmatizer, PorterStemmer, RegexpTokenizer
from nltk.corpus import stopwords

import warnings
import numpy as np
import pandas as pd
import seaborn as sns
import neattext
import re
import spacy
nlp = spacy.load("en_core_web_sm", disable=['parser', 'ner'])

sns.set()
warnings.filterwarnings('ignore')

# path = '/content/Insight_Stress_Analysis/data/'
path = '../data/'
train = pd.read_csv(path + 'dreaddit-train.csv', encoding = "ISO-8859-1")
test = pd.read_csv(path + 'dreaddit-test.csv', encoding = "ISO-8859-1")

def removeWordsWithNumbers(text):
    return re.sub(r'\S*\d\S*', '', text).strip()

def clean_data(content):
    content = str(content)
    content = removeWordsWithNumbers(content)
    docx = neattext.TextFrame(text=content)
    docx.remove_emojis()
    docx.remove_html_tags()
    docx.remove_puncts()
    docx.remove_urls()
    docx.remove_stopwords(lang='en')
    docx.remove_special_characters()
    docx.fix_contractions()
    docx.remove_numbers()
    content = nlp(content.strip())
    return " ".join([token.lemma_ for token in content])

### Tokenization & Remove punctuations

train['processed_text'] = train['text'].apply(lambda x: clean_data(x.lower()))
test['processed_text'] = test['text'].apply(lambda x: clean_data(x.lower()))

train.to_csv(path + 'dreaddit-train-processed.csv', encoding = "ISO-8859-1")
test.to_csv(path + 'dreaddit-test-processed.csv', encoding = "ISO-8859-1")


import pygame
from Chess_logic import logic
from GUI import menu
#from Speech_to_moves import VoiceControl
from Speech_to_moves_2 import SpeechToMoves
from Chess_AI import ChessAI
from Moves_to_commands import moves_to_commands
from Commands_to_serial import Serial_sender
from Moves_to_commands import solver
import time

"""Test function before chess logic is done"""

promotion_map = {(0, 0): "r", (0, 1): "n", (1, 0): "b", (1, 1): "q"}
_STOCKFISH_ENGINE_PATH = "/opt/homebrew/bin/stockfish"
_SERIAL_PORT = "/dev/tty.usbserial-1130"
_SERVO_OFF = 60
_SERVO_ON = 0

# HARDCODED CASTLE MOVES 0 - WHITE SHORT CASTLE, 1 - WHITE LONG, 2 - BLACK SHORT, 3 - BLACK LONG
_CASTLE_MOVES = [[[(7, 0), (6.5, 0.5)], [(4, 0), (6, 0)], [(6.5, 0.5), (5, 0.5), (5, 0)]],
                 # WHITE SHORT CASLTE
                 [[(0, 0), (0.5, 0.5)], [(4, 0), (2, 0)], [(0.5, 0.5), (3, 0.5), (3, 0)]],
                 # WHITE LONG CASTLE
                 [[(7, 7), (6.5, 6.5)], [(4, 7), (6, 7)], [(6.5, 6.5), (5, 6.5), (5, 7)]],
                 # BLACK SHORT CASTLE
                 [[(0, 7), (0.5, 6.5)], [(4, 7), (2, 7)], [(0.5, 6.5), (3, 6.5), (3, 7)]],
                 # BLACK LONG CASTLE
                 ]

_WHITE_SHORT = 0
_WHITE_LONG = 1
_BLACK_SHORT = 2
_BLACK_LONG = 3
_SERVO_WAIT = 0.3
_MOTOR_SPEED = 50
_MOTOR_ACCEL = 200
_MOTORS_ON = 5
_MOTOR_OFF = 4

class ChessGUI(object):
    pygame.init()
    pygame.mixer.init()
    _WIDTH = 480
    _HEIGHT = 480
    _NUM_PIECES = 12
    _RECT_SIZE = 60
    _PIECE_IMG_SIZE = 45

    _LIGHT_CHESS_COLOR = "#eeeed2"
    _DARK_CHESS_COLOR = "#769656"
    _BG_COLOR = "#2d3436"

    _VIABLE_MOVE_COLOR = (116, 125, 140, 75)
    _VIABLE_MOVE_SURF = pygame.Surface([_RECT_SIZE, _RECT_SIZE], pygame.SRCALPHA)
    pygame.draw.circle(_VIABLE_MOVE_SURF, _VIABLE_MOVE_COLOR, [_RECT_SIZE * 0.5, _RECT_SIZE * 0.5], _RECT_SIZE * 0.20)

    _BOARD_IMG_OFFSET = [0, 0]

    _SELECTED_RECT_COLOR = (186, 202, 68)

    _HOVER_COLOR = (241, 242, 246, 200)
    _HOVER_SURF = pygame.Surface([_RECT_SIZE, _RECT_SIZE], pygame.SRCALPHA)
    pygame.draw.rect(_HOVER_SURF, _HOVER_COLOR, [0, 0, _RECT_SIZE, _RECT_SIZE], 5)

    """---AUDIO STUFF---"""
    _NORMAL_MOVE_SOUND = pygame.mixer.Sound("GUI/src/normal_move.mp3")
    _CAPTURE_MOVE_SOUND = pygame.mixer.Sound("GUI/src/capture.mp3")
    _CHECK_MOVE_SOUND = pygame.mixer.Sound("GUI/src/move-check.mp3")
    _PROMOTION_MOVE_SOUND = pygame.mixer.Sound("GUI/src/promote.mp3")
    _CASTLE_MOVE_SOUND = pygame.mixer.Sound("GUI/src/castle.mp3")
    """--- GAME MODES ---"""
    _PVP = 1
    _PVAI = 2
    _AIVAI = 3

    promotion_x, promotion_y = _WIDTH / 2 - _PIECE_IMG_SIZE * 1.5, _HEIGHT / 2 - _PIECE_IMG_SIZE * 1.5

    _font = pygame.font.SysFont("Arial", 15, False)
    _font.render("a", True, "White")
    _letters_surf = pygame.Surface([_WIDTH, 20])
    _letters_surf.fill("#2d3436")
    _nums_surf = pygame.Surface([20, _HEIGHT])
    _nums_surf.fill("#2d3436")
    _letters = []
    for i in range(8):
        _letters.append(_font.render(chr(i + 97), True, "White"))
    _nums = []
    for i in range(8):
        _nums.append(_font.render(str(8 - i), True, "White"))
    for i, letter in enumerate(_letters):
        _letters_surf.blit(letter,
                           (i * _WIDTH / 8 + _WIDTH / 16 - letter.get_width() / 2, 10 - letter.get_height() / 2))
    for i, num in enumerate(_nums):
        _nums_surf.blit(num, (10 - num.get_width() / 2, i * _HEIGHT / 8 + _HEIGHT / 16 - num.get_height() / 2))

    def __init__(self):
        """---------- VISUAL STUFF --------"""
        self._PROMOTION_POPUP_WHITE = pygame.Surface([3 * self._PIECE_IMG_SIZE, 3 * self._PIECE_IMG_SIZE])
        self._PROMOTION_POPUP_BLACK = pygame.Surface([3 * self._PIECE_IMG_SIZE, 3 * self._PIECE_IMG_SIZE])
        self.piece_imgs = self.__load_images()
        self.chess_board = pygame.Surface([8 * self._RECT_SIZE, 8 * self._RECT_SIZE])
        self._mouse_down = False
        self._clicked_pos = None
        self._hover_pos = None
        self._choosing_promotion = False
        self._choosing_promotion_color = None
        self._promotion_move = None
        """----------- FUNCTIONAL STUFF -----------"""
        self.__logic = logic.ChessBoard()
        self.valid_moves = []
        self._speech_recog = None
        self._stockfish = None
        self._stockfish2 = None
        self.my_color = -1
        self._recently_played_move = ""
        self._recent_promotion = ""
        self._was_castle = False
        self._castle_type = 0
        self._was_capture = False
        self._captured_pos = None
        self._mode = self._PVP
        self._move_calc = moves_to_commands.MoveToCmds()
        self._move_calc_optimal = solver.A_star_solver()
        self._sender = None
        self._use_serial = False
        self.game_ended = False

    def __load_images(self):
        """
        Loads images from "/src" directory
        :return: dictionary of white and black images
        """
        images = {'b': dict(), 'w': dict()}
        for i in range(1, self._NUM_PIECES + 1):
            if i <= 6:
                img = pygame.image.load("GUI/src/w" + str(i) + ".svg")
                images["w"][i] = img
            else:
                img = pygame.image.load("GUI/src/b" + str(i - 6) + ".svg")
                images["b"][i - 6] = img

        self._PROMOTION_POPUP_WHITE.fill((45, 52, 54))
        self._PROMOTION_POPUP_BLACK.fill((241, 242, 246))
        self._PROMOTION_POPUP_WHITE.blit(images["w"][2], (0.5 * self._PIECE_IMG_SIZE, 0.5 * self._PIECE_IMG_SIZE))
        self._PROMOTION_POPUP_WHITE.blit(images["w"][3], (1.5 * self._PIECE_IMG_SIZE, 0.5 * self._PIECE_IMG_SIZE))
        self._PROMOTION_POPUP_WHITE.blit(images["w"][4], (0.5 * self._PIECE_IMG_SIZE, 1.5 * self._PIECE_IMG_SIZE))
        self._PROMOTION_POPUP_WHITE.blit(images["w"][5], (1.5 * self._PIECE_IMG_SIZE, 1.5 * self._PIECE_IMG_SIZE))

        self._PROMOTION_POPUP_BLACK.blit(images["b"][2], (0.5 * self._PIECE_IMG_SIZE, 0.5 * self._PIECE_IMG_SIZE))
        self._PROMOTION_POPUP_BLACK.blit(images["b"][3], (1.5 * self._PIECE_IMG_SIZE, 0.5 * self._PIECE_IMG_SIZE))
        self._PROMOTION_POPUP_BLACK.blit(images["b"][4], (0.5 * self._PIECE_IMG_SIZE, 1.5 * self._PIECE_IMG_SIZE))
        self._PROMOTION_POPUP_BLACK.blit(images["b"][5], (1.5 * self._PIECE_IMG_SIZE, 1.5 * self._PIECE_IMG_SIZE))
        return images

    @staticmethod
    def __get_piece_color(chess_piece):
        piece_color = None
        if chess_piece != 0:
            piece_color = "b"
            if chess_piece < 0:
                piece_color = "w"
        return piece_color

    def __draw_chess_board(self, surf: pygame.Surface):
        """
        Draws chess board with pieces
        :param surf: pygame.Surface where the board will be drawn
        :return:
        """
        for row in range(8):
            for col in range(8):
                square_col = self._DARK_CHESS_COLOR
                if (row + col) % 2 == 0:
                    square_col = self._LIGHT_CHESS_COLOR
                if [row, col] == self._clicked_pos:
                    square_col = self._SELECTED_RECT_COLOR
                pygame.draw.rect(surf, square_col,
                                 [col * self._RECT_SIZE, row * self._RECT_SIZE, self._RECT_SIZE, self._RECT_SIZE])
                chess_piece = self.__logic.get_piece(row, col)
                piece_color = self.__get_piece_color(chess_piece)
                if piece_color:
                    x = col * self._RECT_SIZE + self._RECT_SIZE * 0.5 - self.piece_imgs[piece_color][
                        abs(chess_piece)].get_width() * 0.5
                    y = row * self._RECT_SIZE + self._RECT_SIZE * 0.5 - self.piece_imgs[piece_color][
                        abs(chess_piece)].get_height() * 0.5
                    if [row, col] == self._clicked_pos and self._mouse_down:
                        pass
                    else:
                        surf.blit(self.piece_imgs[piece_color][abs(chess_piece)], (x, y))
            """--- DRAW IT SEPARATELY ON TOP TO NOT BE OBSTRUCTED ---"""
            if self._mouse_down:
                chess_piece = self.__logic.get_piece(*self._clicked_pos)
                piece_color = self.__get_piece_color(chess_piece)
                row, col = self.__get_row_col(self._hover_pos[0], self._hover_pos[1])
                surf.blit(self._HOVER_SURF, (col * self._RECT_SIZE, row * self._RECT_SIZE))
                if piece_color:
                    surf.blit(self.piece_imgs[piece_color][abs(chess_piece)],
                              [self._hover_pos[0] - self._RECT_SIZE / 2, self._hover_pos[1] - self._RECT_SIZE / 2])
                """--- DRAW ALL VIABLE MOVES ---"""
                for move in self.valid_moves:
                    surf.blit(self._VIABLE_MOVE_SURF, [move[1] * self._RECT_SIZE, move[0] * self._RECT_SIZE])
            if self._choosing_promotion:

                if self._choosing_promotion_color == 1:
                    surf.blit(self._PROMOTION_POPUP_BLACK, (self.promotion_x, self.promotion_y))
                else:
                    surf.blit(self._PROMOTION_POPUP_WHITE, (self.promotion_x, self.promotion_y))

    def __get_row_col(self, x, y):
        return [y // self._RECT_SIZE, x // self._RECT_SIZE]

    def __detect_special_moves(self, move, promotion):
        self._was_castle = False
        self._was_capture = False
        move_start_piece = self.__logic.get_piece(*move[0])
        move_end_piece = self.__logic.get_piece(*move[1])
        dr, dc = move[0][0] - move[1][0], move[0][1] - move[1][1]
        # castle detection
        if abs(move_start_piece) == 6 and (abs(dr) > 1 or abs(dc) > 1):
            self._was_castle = True
            if logic.sign(move_start_piece) == -1:  # white
                if move[1][1] < 3:
                    self._castle_type = _WHITE_LONG  # long
                elif move[1][1] > 5:
                    self._castle_type = _WHITE_SHORT  # short
            else:  # black
                if move[1][1] < 3:
                    self._castle_type = _BLACK_LONG  # long
                elif move[1][1] > 5:
                    self._castle_type = _BLACK_SHORT  # short
        # en passant detection
        elif abs(move_start_piece) == 1 and dc != 0 and move_end_piece == 0:
            self._was_capture = True
            self._captured_pos = move[1]
        else:
            if move_end_piece != 0:
                self._was_capture = True
                self._captured_pos = move[1]

    def __play_sound(self, promotion):
        if self._was_castle:
            pygame.mixer.Sound.play(self._CASTLE_MOVE_SOUND)
        elif promotion != "":
            pygame.mixer.Sound.play(self._PROMOTION_MOVE_SOUND)
        elif self._was_capture:
            pygame.mixer.Sound.play(self._CAPTURE_MOVE_SOUND)
        else:
            pygame.mixer.Sound.play(self._NORMAL_MOVE_SOUND)

    def _determine_move(self):
        new_row, new_col = self.__get_row_col(*self._hover_pos)
        move = [self._clicked_pos, [new_row, new_col]]
        if self.__logic.is_valid_move(move):
            if (self.__logic.get_piece(*self._clicked_pos) == 1 and new_row == 7) or (
                    self.__logic.get_piece(*self._clicked_pos) == -1 and new_row == 0):
                # SHOW POPUP
                self._choosing_promotion = True
                self._choosing_promotion_color = logic.sign(self.__logic.get_piece(*self._clicked_pos))
                self._mouse_down = False
                self._promotion_move = move
                return
            self.play_move(move)
        self._mouse_down = False
        self._clicked_pos = None

    def __init_speech(self) -> None:
        """
        Initializes speech recognition module and starts new thread
        :return: None
        """
        #self._speech_recog = VoiceControl.VoiceControl(debug=True, info=True, recordTime=4, lan="en-US")
        self._speech_recog = SpeechToMoves.SpeechToMoves("Speech_to_moves_2/coordinates.dict")
        self._speech_recog.start()

    def __init_stockfish(self, stockfish=1) -> None:
        """
        Initializes stockfish engine and sets parameters
        :return: None
        """
        if stockfish == 1:
            self._stockfish = ChessAI.ChessAI(_STOCKFISH_ENGINE_PATH)
            self._stockfish.set_parameters(skill_level=0)
        elif stockfish == 2:
            self._stockfish2 = ChessAI.ChessAI(_STOCKFISH_ENGINE_PATH)
            self._stockfish2.set_parameters(skill_level=20)

    def __init_serial(self) -> None:
        """
        Init serial obj for sending data over serial
        :return:
        """
        self._sender = Serial_sender.SerialSender(_SERIAL_PORT)
        self._sender.send_bare_command(_MOTORS_ON)
        self._sender.send_set_speed(_MOTOR_SPEED)
        self._sender.send_set_acceleraton(_MOTOR_ACCEL)
        self._sender.send_set_servo(_SERVO_OFF)

    def _eval_player_move(self, event):
        """
        Evaluates what move is being played based on users input
        :param event: pygame.event
        :return:
        """
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == pygame.BUTTON_LEFT:
                pos = pygame.mouse.get_pos()
                if not self._choosing_promotion:
                    self._clicked_pos = self.__get_row_col(pos[0] - self._BOARD_IMG_OFFSET[0],
                                                           pos[1] - self._BOARD_IMG_OFFSET[1])
                    if self.__logic.get_piece(*self._clicked_pos) != 0:
                        self._mouse_down = True
                        self.valid_moves = self.__logic.get_valid_moves(self._clicked_pos)
                    else:
                        self._clicked_pos = None

        if event.type == pygame.MOUSEBUTTONUP:
            if not self._choosing_promotion:
                if event.button == pygame.BUTTON_LEFT and self._mouse_down:
                    self._determine_move()
            else:
                if event.button == pygame.BUTTON_LEFT:
                    pos = pygame.mouse.get_pos()
                    x = round((pos[0] - self.promotion_x - self._PIECE_IMG_SIZE / 2 - self._BOARD_IMG_OFFSET[
                        0]) // self._PIECE_IMG_SIZE)
                    y = round((pos[1] - self.promotion_y - self._PIECE_IMG_SIZE / 2 - self._BOARD_IMG_OFFSET[
                        1]) // self._PIECE_IMG_SIZE)
                    if (x in [0, 1]) and (y in [0, 1]):
                        chosen_promotion = promotion_map[(y, x)]
                        self.play_move(self._promotion_move, chosen_promotion)
                        self._choosing_promotion = False

    def _play_stockfish_move(self, move, promotion="", stockfish=1):
        if self._was_castle:
            # castle
            if move[1][1] == 7:
                move[1][1] = 6
            elif move[1][1] == 0:
                move[1][1] = 2

        if stockfish == 1:
            if move:
                move = self._stockfish.get_move(logic.row_col_to_cmd(*move[0]) + logic.row_col_to_cmd(*move[1]) + promotion)
            else:
                move = self._stockfish.get_move("")
        elif stockfish == 2:
            if move:
                move = self._stockfish2.get_move(logic.row_col_to_cmd(*move[0]) + logic.row_col_to_cmd(*move[1]) + promotion)
            else:
                move = self._stockfish2.get_move("")
        move_rc = [logic.cmd_to_row_col(move[0:2]), logic.cmd_to_row_col(move[2:4])]

        if len(move) == 5:
            promotion = move[4]
        else:
            promotion = ""
        self.play_move(move_rc, promotion)

    def _get_coords(self, move):
        """
        # OLD VERSION - NOT OPTIMAL
        full_squares = []
        for row in range(8):
            for col in range(8):
                if self.__logic.get_piece(row, col) != 0:
                    full_squares.append([col, 7 - row])
        move = [[move[0][1], 7 - move[0][0]], [move[1][1], 7 - move[1][0]]]
        return self._move_calc.move(full_squares, move)
        """
        full_squares = []
        for row in range(8):
            for col in range(8):
                if self.__logic.get_piece(row, col) != 0:
                    full_squares.append((row, col))
        return self._move_calc_optimal.calc_path(full_squares, move)

    def __reset_board(self) -> None:
        print("PLAYER: ", self.__logic.get_player_playing() * -1, " WON")
        self.__logic.reset_board()
        self._recently_played_move = ""
        self._recent_promotion = ""
        if self._mode == self._PVAI:
            self.__init_stockfish()
        if self._mode == self._AIVAI:
            self.__init_stockfish(1)
            self.__init_stockfish(2)

    def _serial_capture(self):
        """
        Sends capture commands over serial
        :return:
        """
        """

        if move_recalc[1] > 3:
            dy = -0.5
        else:
            dy = 0.5
        if move_recalc[0] > 3:
            final_x = 0
        else:
            final_x = 7
        self._sender.send_move(move_recalc)
        self._sender.send_set_servo(_SERVO_ON)
        self._sender.wait_for_empty_buffer()
        time.sleep(_SERVO_WAIT)
        self._sender.send_move((move_recalc[0], move_recalc[1] + dy))
        # TODO: special capture command, currently only normal move
        self._sender.send_move((final_x, move_recalc[1] + dy))
        self._sender.send_set_servo(_SERVO_OFF)
        self._sender.wait_for_empty_buffer()
        time.sleep(_SERVO_WAIT)
        """
        coords = self._get_coords([(self._captured_pos[0], self._captured_pos[1]), (3.5, 7)])
        print("capture coords: ", coords)
        for i, coord in enumerate(coords):
            if i == 1:
                self._sender.send_set_servo(_SERVO_ON)
                self._sender.wait_for_empty_buffer()
                time.sleep(_SERVO_WAIT)
            self._sender.send_move(coord)

        self._sender.send_set_servo(_SERVO_OFF)
        self._sender.wait_for_empty_buffer()
        time.sleep(_SERVO_WAIT)
        self._sender.send_move((3.5, 3.5))
        self._sender.wait_for_empty_buffer()
        #Clear captured figure
        self._sender.send_remover()
        self._sender.wait_for_empty_buffer()
        time.sleep(0.5)
        print("move done")

    def _serial_castle(self):
        """
        Sends castle commands over serials
        :return:
        """
        for command_sequence in _CASTLE_MOVES[self._castle_type]:
            for i, command in enumerate(command_sequence):
                if i == 1:
                    self._sender.send_set_servo(_SERVO_ON)
                    self._sender.wait_for_empty_buffer()
                    time.sleep(_SERVO_WAIT)
                self._sender.send_move(command)
            self._sender.send_set_servo(_SERVO_OFF)
            self._sender.wait_for_empty_buffer()
            time.sleep(_SERVO_WAIT)

    def play_move(self, move, promotion="") -> None:
        """
        Plays move and handles all of the logic (sending to Arduino, updating board etc...)
        :param: move = [[start row, start col], [end row, end col]]
        :return: None
        """
        if self.__logic.is_valid_move(move, promotion=promotion):
            # play sound
            self.__detect_special_moves(move, promotion)
            self.__play_sound(promotion)
            print("playing move: ", move)
            if self._use_serial:
                cnc_coords = []
                if self._was_capture:
                    print("Capture move")
                    self._serial_capture()
                    cnc_coords = self._get_coords(move)
                elif self._was_castle:
                    print("Castle move: ", self._castle_type)
                    self._serial_castle()
                else:
                    print("move:", move)
                    cnc_coords = self._get_coords(move)

                if cnc_coords:
                    print("cnc coords:", cnc_coords)
                    for i, coord in enumerate(cnc_coords):
                        if i == 1:
                            self._sender.send_set_servo(_SERVO_ON)
                            self._sender.wait_for_empty_buffer()
                            time.sleep(_SERVO_WAIT)
                        self._sender.send_move(coord)

                    self._sender.send_set_servo(_SERVO_OFF)
                    self._sender.wait_for_empty_buffer()
                    time.sleep(_SERVO_WAIT)
                    print("move done")
            #print(self._get_coords(move))
            # Do the move
            self.__logic.play_move(move, promotion)
            self._recently_played_move = move
            self._recent_promotion = promotion
            if self.__logic.is_checkmate():
                print("PLAYER: ", "BLACK" if self.__logic.get_player_playing() * -1 == 1 else "WHITE", " WON")
                self.game_ended = True
                self._sender.send_set_servo(_SERVO_ON)
                self._sender.send_set_servo(_SERVO_OFF)
                self._sender.send_set_servo(_SERVO_ON)
                self._sender.send_set_servo(_SERVO_OFF)
                self._sender.send_set_servo(_SERVO_ON)
                self._sender.send_set_servo(_SERVO_OFF)
                self._sender.wait_for_empty_buffer()
                #self.__reset_board()
        else:
            print("Move not valid:", move, promotion)

    def draw(self, win: pygame.Surface) -> None:
        """
        Draws chess board to win
        :param win: pygame.Surface
        :return: None
        """
        win.fill(self._BG_COLOR)
        self.__draw_chess_board(self.chess_board)
        win.blit(self.chess_board, [0, 0])
        win.blit(self._letters_surf, [0, 480])
        win.blit(self._nums_surf, [480, 0])
        pygame.display.flip()

    def main(self) -> None:
        """---- SETUP ----"""
        win = pygame.display.set_mode((self._WIDTH + 20, self._HEIGHT + 20))
        pygame.display.set_caption("Speech Chess")
        """ --- SETTINGS SCREEN ---"""
        run = True
        q = False
        _menu = menu.Menu([self._WIDTH, self._HEIGHT])
        _speech_commands = False
        _serial_comm = False
        mode = self._PVP
        clock = pygame.time.Clock()
        while run:
            clock.tick(30)
            events = pygame.event.get()
            for event in events:
                if event.type == pygame.QUIT:
                    run = False
                    q = True
            if _menu.update(events):
                settings = _menu.get_settings()
                # [1v1, 1vAI, speech_commands]
                mode = None
                for i in range(3):
                    if settings[i]:
                        mode = i + 1

                _speech_commands = settings[3]
                _serial_comm = settings[4]
                print(_serial_comm)
                if mode is not None:
                    run = False
            win.fill("#2d3436")
            win.blit(_menu.screen, [10, 10])
            pygame.display.flip()

        """---- Start speech recog thread"""
        self._mode = mode
        if _speech_commands:
            self.__init_speech()
        if mode == self._PVAI:
            self.__init_stockfish()
        elif mode == self._AIVAI:
            self.__init_stockfish(1)
            self.__init_stockfish(2)
        if _serial_comm:
            self.__init_serial()
            self._use_serial = True
        """---- Main Loop ----"""
        run = not q

        """run = False
        self._castle_type = _WHITE_LONG
        self._serial_castle()
        """
        while run:
            clock.tick(30)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    run = False
                    if _speech_commands:
                        self._speech_recog.close()
                if not self.game_ended:
                    if (mode == self._PVAI and self.__logic.get_player_playing() != self.my_color) or mode == self._AIVAI:
                        pass
                    else:
                        self._eval_player_move(event)

            if not self.game_ended:

                if mode == self._PVAI and self.__logic.get_player_playing() != self.my_color:
                    self._play_stockfish_move(self._recently_played_move, self._recent_promotion)

                if mode == self._AIVAI:
                    if self.__logic.get_player_playing() == self.my_color:
                        self._play_stockfish_move(self._recently_played_move, self._recent_promotion, 1)
                    else:
                        self._play_stockfish_move(self._recently_played_move, self._recent_promotion, 2)

                if _speech_commands:
                    if self._speech_recog.available():
                        move = self._speech_recog.get_next_coordinate()
                        if move:
                            print(move)
                            move = [(7 - move[0][1], move[0][0]), (7 - move[1][1], move[1][0])]
                            if mode == self._PVAI and self.__logic.get_player_playing() != self.my_color:
                                pass
                            elif mode == self._PVAI and self.__logic.get_player_playing() == self.my_color:
                                if self.__logic.is_valid_move(move):
                                    print("Voice command valid- playing move: ", move)
                                    self.play_move(move)
                                else:
                                    print("voice command not valid!")

                            elif mode == self._PVP:
                                if self.__logic.is_valid_move(move):
                                    print("Voice command valid- playing move: ", move)
                                    self.play_move(move)
                                else:
                                    print("voice command not valid!")

                if self._mouse_down:
                    pos = pygame.mouse.get_pos()
                    self._hover_pos = [pos[0] - self._BOARD_IMG_OFFSET[0], pos[1] - self._BOARD_IMG_OFFSET[1]]
                self.draw(win)

        if self._use_serial:
            self._sender.send_move((0, 0))
            self._sender.wait_for_empty_buffer()
            self._sender.send_bare_command(4)
            self._sender.Serial.close()

if __name__ == "__main__":
    gui = ChessGUI()
    gui.main()


import sys

from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
from django.core.management.base import CommandParser

from django_sftp.compat import get_username_field
from django_sftp.models import SFTPUserAccount
from django_sftp.models import SFTPUserGroup

class Command(BaseCommand):
    """Django Command to creare SFTP account.

    Runs as ./manage.py createsftpuseraccount.
    """

    help = "Create SFTP user account"

    def add_arguments(self, parser: CommandParser) -> None:
        """Parse django command arguments.

        Args:
            parser (CommandParser): CommandParser instance
        """
        parser.add_argument("username")
        parser.add_argument("group")
        parser.add_argument("home_dir", nargs="?")

    def handle(self, *args, **options) -> None:
        """Django command handler."""
        username = options.get("username")
        group_name = options.get("group")
        home_dir = options.get("home_dir")

        if SFTPUserAccount.objects.filter(user__username=username).exists():
            raise CommandError(f'FTP user account "{username}" is already exists.')

        user_model = get_user_model()
        try:
            user = user_model.objects.get(**{get_username_field(): username})
        except user_model.DoesNotExist:
            raise CommandError(f'User "{username}" is not exists.')

        try:
            group = SFTPUserGroup.objects.get(name=group_name)
        except SFTPUserGroup.DoesNotExist:
            raise CommandError(f'FTP user group "{group_name}" is not exists.')

        account = SFTPUserAccount.objects.create(
            user=user, group=group, home_dir=home_dir
        )

        sys.stdout.write(
            f'FTP user account pk={account.pk}, "{username}" was created.\n',
        )


from augmentation import SegmentationGenerator
# from model_checkpoint_parallel import ModelCheckpoint
import unet
import segnet

from keras.callbacks import TensorBoard, ModelCheckpoint
from keras.optimizers import SGD, Adam
from keras.utils.training_utils import multi_gpu_model
import keras.backend as K

import cv2
from skimage.io import *

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

from os.path import join, basename, exists
from os import listdir, makedirs

SIZE = (256, 256)
N_GPUS = 1
BATCH_SIZE = 16*N_GPUS

SMOOTH = 1.

def dice(y_true, y_pred):
    y_true_f = K.flatten(y_true)
    y_pred_f = K.flatten(y_pred)
    intersection = K.sum(y_true_f * y_pred_f)
    return (2. * intersection + SMOOTH) / (K.sum(y_true_f) + K.sum(y_pred_f) + SMOOTH)

def dice_coef_loss(y_true, y_pred):
    return -dice(y_true, y_pred)

DATA_PATHS = {
    'TRAIN': {
        'IMAGES': 'data/train/images',
        'MASKS': 'data/train/masks',
    },
    'TEST': {
        'IMAGES': 'data/test/images',
        'MASKS': 'data/test/masks',
    }
}

train_datagen = SegmentationGenerator(
    horizontal_flip=True,
    vertical_flip=True,
    rotation_range=90.,
    width_shift_range=0.3,
    height_shift_range=0.3,
    zoom_range=0.2,
    shear_range=0.3,
    fill_mode='reflect'
)

train_iter = train_datagen.flow_from_directory(
    DATA_PATHS['TRAIN']['IMAGES'],
    DATA_PATHS['TRAIN']['MASKS'],
    target_size=SIZE, color_mode='grayscale',
    batch_size=BATCH_SIZE, shuffle=True
)

valid_datagen = SegmentationGenerator()

valid_iter = valid_datagen.flow_from_directory(
    DATA_PATHS['TEST']['IMAGES'],
    DATA_PATHS['TEST']['MASKS'],
    target_size=SIZE, color_mode='grayscale',
    batch_size=BATCH_SIZE, shuffle=False
)

model = segnet.build_model(*SIZE, n_channels=1)
if N_GPUS >= 2:
    model = multi_gpu_model(model, N_GPUS)

optimizer = SGD(lr=0.001, momentum=0.9, decay=0.0005, nesterov=False)
model.compile(loss=dice_coef_loss, optimizer=optimizer, metrics=[dice])

model_idx = 0

experiment_dir = 'segnet_{}x{}'.format(*SIZE)
models_dir = join(experiment_dir, 'models')
log_dir = join(experiment_dir, 'log')
if not exists(models_dir):
    makedirs(models_dir)
if not exists(log_dir):
    makedirs(log_dir)
modelname = 'segnet' + str(model_idx)
modelpath = join(models_dir, modelname)
tb_log_dir = join(log_dir, modelname)

model.fit_generator(
    train_iter,
    epochs=200,
    callbacks=[
        ModelCheckpoint(filepath=modelpath, monitor='val_dice', period=1),
        TensorBoard(log_dir=tb_log_dir, batch_size=BATCH_SIZE)
    ],
    validation_data=valid_iter,
    use_multiprocessing=False
)


from __future__ import annotations

import os
from datetime import datetime

from sqlalchemy import Column
from sqlalchemy import types
from sqlalchemy.engine import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

path_to_database = os.environ.get("PATH_TO_SQLITE")
if path_to_database:
    path_to_database = f"sqlite://{path_to_database}?cache=shared"
else:
    path_to_database = f"sqlite:///:memory:?cache=shared"

Base = declarative_base()

class Messages(Base):
    __tablename__ = 'messages'
    id = Column(types.Integer, primary_key=True)
    receive_time = Column('receive_time', types.DateTime)
    message_group = Column('message_group', types.String(255))
    message_url = Column('message_url', types.String(128))
    message_title = Column('message_title', types.String(255))
    message_full_url = Column('message_full_url', types.String(255))
    black_marker_reason = Column('black_marker_reason', types.String(64))
    content = Column('content', types.Text(), default=u'')

    def __str__(self):
        return f"id:{self.id}  receive_time: {self.receive_time} message_title: {self.message_title} " \
               f"message_url: {self.message_url}  message_full_url: {self.message_full_url}"

if __name__ == '__main__':
    engine = create_engine(path_to_database, echo=True)
    Base.metadata.create_all(engine, checkfirst=True)
    session_builder: sessionmaker = sessionmaker(bind=engine, autocommit=False, autoflush=False)

    session = session_builder()
    # session.begin()
    message = Messages()
    message.message_title = "hello"
    message.receive_time = datetime.now()
    session.add(message)
    session.commit()
    message = Messages()
    message.message_title = "hello2"
    message.receive_time = datetime.now()
    session.add(message)
    session.commit()

    session_another = session_builder()
    object_message: Messages = session_another.query(Messages).filter_by(message_title="hello").one_or_none()
    object_message.message_full_url = "https://this.is.some_url.com"
    session_another.add(object_message) # not possible to attach to another session session.add(object_message)
    session_another.commit()

    # read multiply objects, read updates from another transaction
    # alchemy operations "http://www.leeladharan.com/sqlalchemy-query-with-or-and-like-common-filters"
    for each_record in session.query(Messages).filter(Messages.message_title.isnot(None)):
        print(each_record)
    # connection = engine.connect()


from flask import Blueprint, request, make_response
from flask_login import login_required, current_user
from app.models import db, Bookmark, Bookmarks_List

bookmarks_routes = Blueprint("bookmarks", __name__)

@bookmarks_routes.route("")
def get_users_bookmarks():
    """ Get all of users bookmarks """
    user = current_user.to_dict()
    lists = Bookmarks_List.query.filter(Bookmarks_List.user_id == user["id"]).all()
    bookmarks = []
    for list in lists:
       for bookmark in list.to_dict_bookmark_tabs()["bookmarks"]:
           bookmarks.append(bookmark)
    return bookmarks

@bookmarks_routes.route("/<int:bookmark_id>")
def get_bookmark_by_id(bookmark_id):
    """" Get single bookmark by id """
    user = current_user.to_dict()

    bookmark = Bookmark.query.get(bookmark_id)
    #------------ validation -------------#
    if not bookmark:
        error = make_response("Bookmark does not exist")
        error.status_code = 404
        return error

    list = Bookmarks_List.query.get(bookmark.bookmarks_list_id)
    if list.user_id != user["id"]:
        error = make_response("Only the creator can view their Bookmark")
        error.status_code = 401
        return error
    #--------------------------------------#
    return bookmark.to_dict()

@bookmarks_routes.route("", methods=["PUT"])
def update_bookmark_status():
    """ update a bookmark's completion status """
    user = current_user.to_dict()

    bookmark_id = request.get_json()
    bookmark = Bookmark.query.get(bookmark_id)

    #------------ validation -------------#
    if not bookmark:
        error = make_response("Bookmark does not exist")
        error.status_code = 404
        return error
    #--------------------------------------#
    bookmark.completed = not bookmark.completed
    db.session.commit()
    return bookmark.to_dict()

@bookmarks_routes.route("", methods=["DELETE"])
def delete_bookmark():
    """ Delete a single bookmark """
    user = current_user.to_dict()
    data = request.get_json()

    #------------ validation -------------#
    bookmark = Bookmark.query.get(data["bookmarkId"])
    if not bookmark:
        error = make_response("Bookmark does not exist")
        error.status_code = 404
        return error

    list = Bookmarks_List.query.get(bookmark.bookmarks_list_id)
    if list.user_id != user["id"]:
        error = make_response("Only the creator of a Bookmark can delete a bookmark")
        error.status_code = 401
        return error
    # #--------------------------------------#

    db.session.delete(bookmark)
    db.session.commit()
    res = make_response({"message": "Successfully deleted"})
    res.status_code = 200
    return res


import copy
import typing

import networkx as nx
import numpy

import qtoolkit.data_structures.quantum_circuit.gate_hierarchy as qgate
import qtoolkit.data_structures.quantum_circuit.quantum_operation as qop

class QuantumCircuit:
    def __init__(self, qubit_number: int, cache_matrix: bool = True) -> None:
        """Initialise the :py:class:`~.QuantumCircuit` instance.

        For documentation about the :py:class:`~.QuantumCircuit` internals see
        the :py:mod:`.quantum_circuit.quantum_circuit` documentation.

        :param qubit_number: The number of qubits the instance will acts on.
        :param cache_matrix: A boolean flag indicating if the instance should
            keep in memory the current value of its representing matrix or if it
            should recompute this matrix at each call to
            :py:attr:`.QuantumCircuit.matrix`.
        """
        assert qubit_number > 0, (
            "A circuit with less than 1 qubit cannot be " "created."
        )

        self._qubit_number = qubit_number
        self._graph = nx.MultiDiGraph()
        self._node_counter = 0

        for qubit_id in range(qubit_number):
            self._graph.add_node(self._node_counter, type="input", key=qubit_id)
            self._node_counter += 1

        self._last_inserted_operations = numpy.arange(qubit_number)
        self._cache_matrix = cache_matrix
        self._matrix = None
        if self._cache_matrix:
            self._matrix = numpy.identity(2 ** self._qubit_number)

    def add_operation(self, operation: qop.QuantumOperation) -> None:
        """Add an operation to the circuit.

        :param operation: The operation to add to the
            :py:class:`~.QuantumCircuit` instance.
        """
        self._check_operation(operation)
        current_node_id = self._node_counter
        self._graph.add_node(self._node_counter, type="op", op=operation)
        self._node_counter += 1

        # Create the target wire
        self._create_edge(
            self._last_inserted_operations[operation.target],
            current_node_id,
            operation.target,
        )
        self._last_inserted_operations[operation.target] = current_node_id

        # Create the control wires
        for ctrl in operation.controls:
            self._create_edge(
                self._last_inserted_operations[ctrl], current_node_id, ctrl
            )
            self._last_inserted_operations[ctrl] = current_node_id

        # Compute the new matrix if needed and possible.
        if self._cache_matrix:
            self._matrix = self._matrix @ operation.matrix(self._qubit_number)

    def apply(
        self, gate: qgate.QuantumGate, target: int, controls: typing.Sequence[int] = ()
    ) -> None:
        """Apply a quantum operation to the circuit.

        :param gate: The quantum gate to apply.
        :param target: The target qubit. The quantum gate will be applied on
            this qubit.
        :param controls: The control qubit(s).
        """
        self.add_operation(qop.QuantumOperation(gate, target, controls))

    def _check_operation(self, operation: qop.QuantumOperation) -> None:
        """Check if the operation is valid. If not, raise an exception.

        :param operation: The operation to check for validity.
        :raise IndexError: if the qubits of the operation (target or control(s))
            are not within the range of the current instance.
        :raise RuntimeError: if one of the qubits on the operation (target or
            control(s)) is None or if the target qubit is also listed in the
            control qubit(s).
        """
        if operation.target is None or any(
            (ctrl is None for ctrl in operation.controls)
        ):
            raise RuntimeError(
                "At least one of the target or control qubit is None. Generic "
                "QuantumOperations are not supported in a QuantumCircuit "
                "instance."
            )
        if operation.target in operation.controls:
            raise RuntimeError(
                "The target qubit cannot be in the list of control qubits."
            )
        if operation.target >= self._qubit_number or operation.target < 0:
            raise IndexError(
                f"The operation's target ({operation.target}) is not valid "
                f"for the current quantum circuit with {self._qubit_number} "
                f"qubits."
            )
        for ctrl in operation.controls:
            if ctrl >= self._qubit_number or ctrl < 0:
                raise IndexError(
                    "One of the control qubit is not valid for the current "
                    "quantum circuit."
                )

    def pop(self) -> qop.QuantumOperation:
        """Deletes the last inserted operation from the instance and returns it.

        :return: The last inserted operation.
        """
        if self._node_counter <= self._qubit_number:
            raise RuntimeError(
                "Attempting to pop a QuantumOperation from an empty " "QuantumCircuit."
            )
        # Recover the last operation performed.
        op = self.last
        # Update the last_inserted structure
        for pred, _, key in self._graph.in_edges(
            nbunch=self._node_counter - 1, keys=True
        ):
            self._last_inserted_operations[key] = pred
        # Remove the node (and the edges associated to it).
        self._graph.remove_node(self._node_counter - 1)
        self._node_counter -= 1
        # Compute the new matrix if needed and possible.
        if self._cache_matrix:
            self._matrix = self._matrix @ op.matrix(self._qubit_number).T.conj()
        return op

    def _create_edge(self, from_id: int, to_id: int, qubit_id: int) -> None:
        """Create an edge between `from_id` and `to_id`.

        :param from_id: Source of the edge.
        :param to_id: Target of the edge.
        :param qubit_id: Identifier of the qubit concerned by the target
            operation.
        """
        self._graph.add_edge(from_id, to_id, key=qubit_id)

    def get_n_last_operations_on_qubit_reversed(
        self, n: int, qubit_id: int
    ) -> typing.Iterable[qop.QuantumOperation]:
        """Get the `n` last inserted operations involving `qubit_id`.

        The returned operations can have the qubit `qubit_id` either as target
        or control qubit.

        :param n: Number of quantum operation to retrieve.
        :param qubit_id: Identifier of the qubit we are interested in.
        :return: an iterable over the `n` last quantum operations involving
            `qubit_id` in the reverse order of insertion.
        :raise IndexError: if `qubit_id` is involved in less than `n`
            operations.
        """
        try:
            all_ops_gen = self.get_operations_on_qubit_reversed(qubit_id)
            for op_id in range(n):
                yield next(all_ops_gen)
        except StopIteration:
            raise IndexError(
                f"Cannot retrieve {n} operations on qubit n°{qubit_id}: only "
                f"{op_id} operation are available."
            )

    def get_n_last_operations_on_qubit(
        self, n: int, qubit_id: int
    ) -> typing.Iterable[qop.QuantumOperation]:
        """Get the `n` last inserted operations involving `qubit_id`.

        The returned operations can have the qubit `qubit_id` either as target
        or control qubit.

        :param n: Number of quantum operation to retrieve.
        :param qubit_id: Identifier of the qubit we are interested in.
        :return: an iterable over the `n` last quantum operations involving
            `qubit_id` in the order of insertion.
        :raise IndexError: if `qubit_id` is involved in less than `n`
            operations.
        """
        return list(self.get_n_last_operations_on_qubit_reversed(n, qubit_id))[::-1]

    def get_operations_on_qubit_reversed(self, qubit_id: int):
        """Get all the operations involving `qubit_id`.

        The returned operations can have the qubit `qubit_id` either as target
        or control qubit.

        :param qubit_id: Identifier of the qubit we are interested in.
        :return: an iterable over all the quantum operations involving
            `qubit_id` in the reverse order of insertion.
        """
        current = self._last_inserted_operations[qubit_id]
        while current >= self.qubit_number:
            yield self._graph.nodes[current]["op"]
            # Update the current node.
            current = next(
                filter(
                    lambda node_id: qubit_id
                    in self._graph.get_edge_data(node_id, current),
                    self._graph.predecessors(current),
                )
            )

    def get_operations_on_qubit(self, qubit_id: int):
        """Get all the operations involving `qubit_id`.

        The returned operations can have the qubit `qubit_id` either as target
        or control qubit.

        :param qubit_id: Identifier of the qubit we are interested in.
        :return: an iterable over all the quantum operations involving
            `qubit_id` in the order of insertion.
        """
        return list(self.get_operations_on_qubit_reversed(qubit_id))[::-1]

    def __getitem__(self, idx: int) -> qop.QuantumOperation:
        """Method used when []-indexing is used.

        :param idx: The position of the operation we want to retrieve.
        :return: The idx-th inserted operation.
        """
        return self._graph.nodes[idx + self._qubit_number]["op"]

    @property
    def last(self) -> qop.QuantumOperation:
        """Getter for the last inserted operation.

        :return: the last inserted operation.
        :raise IndexError: if the circuit is empty.
        """
        if self._node_counter == self._qubit_number:
            raise IndexError(
                "Trying to recover the last operation of an " "empty QuantumCircuit."
            )
        return self._graph.nodes[self._node_counter - 1]["op"]

    @property
    def operations(self) -> typing.Iterable[qop.QuantumOperation]:
        """Getter on the operations performed in this quantum circuit.

        :return: a generator that generates all the operations of the circuit.
        """
        return (
            self._graph.nodes[i]["op"]
            for i in range(self._qubit_number, self._node_counter)
        )

    def gates_on_qubit(self, qubit_index: int) -> typing.Iterable[qop.QuantumOperation]:
        """Getter for the gates applied on the qubit at the given index.

        :param qubit_index: the qubit we are interested in.
        :return: a generator yielding all the quantum gates in the circuit
            that involve the specified qubit.
        """
        return (op.gate for op in self.get_operations_on_qubit(qubit_index))

    @property
    def matrix(self) -> numpy.ndarray:
        """Getter on the unitary matrix representing the circuit.

        Depending on the value of `cache_matrix` given at initialisation, this
        method will either return the cached matrix or compute it.

        :return: the unitary matrix representing the current quantum circuit.
        """
        if self._cache_matrix:
            return self._matrix
        ret = numpy.identity(2 ** self._qubit_number)
        for operation in self.operations:
            ret = ret @ operation.matrix(self._qubit_number)
        return ret

    @property
    def qubit_number(self) -> int:
        """Getter on the number of qubits of the current instance."""
        return self._qubit_number

    @property
    def size(self) -> int:
        """Getter on the number of quantum gates in the current instance."""
        return self._node_counter - self._qubit_number

    def __iadd__(self, other: "QuantumCircuit") -> "QuantumCircuit":
        """Add all the operations contained in `other` to the current instance.

        :param other: the quantum circuit containing the operations to append
            to the current instance. `other` and the instance
            :py:meth:`~.__iadd__` is called on should have the same number of
            qubits.
        :return: The union of self and other.
        :raise RuntimeError: if `self` and `other` have a different number of
            qubits.
        """
        # 1. Checks
        if self.qubit_number != other.qubit_number:
            raise RuntimeError(
                f"The number of qubits of the first circuit "
                f"({self.qubit_number}) does not match the "
                f"number of qubits of the second circuit "
                f"({other.qubit_number})."
            )
        # 2. Update the graph
        # 2.1. First remove the "input" nodes from the other graph. We don't
        # want to change or copy the other graph so we take a view of the other
        # graph without the "input" nodes.
        other_subgraph = other._graph.subgraph(
            range(other.qubit_number, other._node_counter)
        )
        # 2.2. Regroup the two graphs into one graph.
        self._graph = nx.disjoint_union(self._graph, other_subgraph)
        # 2.3. Join the nodes if possible.
        for qubit_index in range(self.qubit_number):
            old_neighbor = list(other._graph.neighbors(qubit_index))
            if old_neighbor:
                new_neighbor = old_neighbor[0] - other.qubit_number + self._node_counter
                self._graph.add_edge(
                    self._last_inserted_operations[qubit_index], new_neighbor
                )
                # Only change the last inserted index if we joined the nodes.
                self._last_inserted_operations[qubit_index] = new_neighbor
        # 3. Update the other attributes:
        self._node_counter += other._node_counter - other.qubit_number
        if self._cache_matrix and other._matrix is not None:
            self._matrix = self.matrix @ other.matrix

        return self

    def __matmul__(self: "QuantumCircuit", other: "QuantumCircuit") -> "QuantumCircuit":
        """Wrapper around __iadd__ for the new '@' operator."""
        cpy = copy.copy(self)
        return cpy.__iadd__(other)

    def __copy__(self) -> "QuantumCircuit":
        """Override the default copy behaviour."""
        cpy = QuantumCircuit(self._qubit_number, cache_matrix=self._cache_matrix)
        if self.compressed:
            cpy._compressed_graph = copy.copy(self._compressed_graph)
        else:
            cpy._graph = self._graph.copy()
        cpy._node_counter = self._node_counter
        cpy._last_inserted_operations = self._last_inserted_operations.copy()
        if self._cache_matrix:
            cpy._matrix = self._matrix
        return cpy

    def compress(self) -> "QuantumCircuit":
        """Compress the instance to save some memory.

        This method is useful when a large number of small circuits needs to be
        stored in memory.

        .. warning:: Several methods of the :py:class:`~.QuantumCircuit` class
           will not work as expected (or will raise an exception) if called on
           a compressed circuit.
        """
        if not self.compressed:
            self._compressed_graph = CompressedMultiDiGraph(self._graph)
            del self._graph
        return self

    def uncompress(self) -> "QuantumCircuit":
        """Uncompress the instance."""
        if self.compressed:
            self._graph = self._compressed_graph.uncompress()
            del self._compressed_graph
        return self

    @property
    def compressed(self) -> bool:
        """Return True if the instance is compressed, else False."""
        return hasattr(self, "_compressed_graph")

    def inverse(self) -> "QuantumCircuit":
        """Create the inverse of the instance it is called on.

        This method will create a new :py:class:`~.QuantumCircuit` and construct
        in this new circuit the inverse of `self`.
        """
        inv = QuantumCircuit(self._qubit_number, cache_matrix=self._cache_matrix)
        for op in reversed(list(self.operations)):
            inv.add_operation(op.inverse())

        return inv

    def __str__(self) -> str:
        """Textual representation of the circuit.

        The representation used is very similar to OpenQASM.
        """
        return "\n".join(
            (
                "{Cs}{opname} {controls}{commaornot}{target}".format(
                    Cs="C" * len(op.controls),
                    opname=op.gate.name,
                    controls=",".join(map(str, op.controls)),
                    commaornot=(", " if op.controls else ""),
                    target=op.target,
                )
                for op in self.operations
            )
        )

class CompressedMultiDiGraph:
    def __init__(self, graph: nx.MultiDiGraph = None) -> None:
        """Initialise the :py:class:`~.CompressedMultiDiGraph` instance.

        Instances of :py:class:`~.CompressedMultiDiGraph` are just storing
        a :py:class:`networkx.MultiDiGraph` in a more memory efficient format.

        :param graph: The graph to compress.
        """
        if graph is None:
            self._qubit_number = 0
            return

        node_number = len(graph.nodes)
        edge_number = len(graph.edges)

        if node_number < 2 ** 8:
            data_type = numpy.uint8
        elif node_number < 2 ** 16:
            data_type = numpy.uint16
        else:
            data_type = numpy.uint32

        # We keep each edge with its corresponding qubit ID.
        self._from_arr = numpy.zeros((edge_number,), dtype=data_type)
        self._to_arr = numpy.zeros((edge_number,), dtype=data_type)
        self._data_arr = numpy.zeros((edge_number,), dtype=data_type)
        for idx, (u, v, qubit_id) in enumerate(graph.edges):
            self._from_arr[idx] = u
            self._to_arr[idx] = v
            self._data_arr[idx] = qubit_id

        # And the we keep each node
        self._qubit_number = 0
        self._is_op_node = numpy.zeros((node_number,), dtype=numpy.bool)
        self._operations = list()
        for node_id, node_data in graph.nodes.items():
            if node_data["type"] == "op":
                self._is_op_node[node_id] = True
                self._operations.append(node_data["op"])
            else:
                self._qubit_number += 1

    def __copy__(self) -> "CompressedMultiDiGraph":
        """Override the default copy behaviour."""
        cpy = CompressedMultiDiGraph()
        cpy._qubit_number = self._qubit_number
        cpy._from_arr = self._from_arr.copy()
        cpy._to_arr = self._to_arr.copy()
        cpy._data_arr = self._data_arr.copy()
        cpy._is_op_node = self._is_op_node.copy()
        cpy._operations = copy.copy(self._operations)
        return cpy

    def uncompress(self) -> nx.MultiDiGraph:
        """Uncompress the stored :py:class:`networkx.MultiDiGraph`.

        :return: the uncompressed :py:class:`networkx.MultiDiGraph`.
        """
        graph = nx.MultiDiGraph()
        if self._qubit_number == 0:
            return graph

        # Re-create the nodes.
        for i in range(self._qubit_number):
            graph.add_node(i, type="input", key=i)

        for node_id in range(self._qubit_number, len(self._is_op_node)):
            graph.add_node(
                node_id, type="op", op=self._operations[node_id - self._qubit_number]
            )

        # Re-create the edges
        for u, v, qubit_id in zip(self._from_arr, self._to_arr, self._data_arr):
            graph.add_edge(u, v, key=qubit_id)

        return graph

CircuitCostFunction = typing.Callable[[QuantumCircuit], float]


import json
import time

from flask import Blueprint, request
from flask_login import login_required, current_user
from werkzeug.exceptions import abort

from app.db_handler import db
from app.models import Story, Paragraph, User, Vote
from app.utils import create_response, RESPONSE_KEY_STORIES, RESPONSE_KEY_FIRST_PARAGRAPH, RESPONSE_KEY_TITLE, \
    RESPONSE_KEY_OWNER, RESPONSE_KEY_NAME, RESPONSE_KEY_PARAGRAPHS, RESPONSE_KEY_DEADLINE, RESPONSE_KEY_SUGGESTIONS, \
    RESPONSE_KEY_PARTICIPANTS, RESPONSE_KEY_VOTES

RELATIVE_DEADLINE = 2

MESSAGE_ALREADY_VOTED = "You have already voted to paragraph #{0}"

MESSAGE_NOT_VOTEABLE = "Paragraph #{0} is closed for votes."

MESSAGE_VOTE_SUCCESSFULLY = "Vote has been recorded to paragraph #{0}"

MESSAGE_RES_WAS_NOT_FOUND = "{0} with id #{1} was not found."
MESSAGE_MANDATORY_FIELD_WAS_NOT_SUPPLIED = "The mandatory field was not supplied: {0}"

MAX_SIZE_TITLE = 64

MAX_SIZE_PARAGRAPH = 1200

MESSAGE_CANNOT_BE_EMPTY = "The parameter {0} cannot be empty!"

MESSAGE_TOO_LONG = "The given {0} exceeds the max-size: {1}"

REQUEST_KEY_TITLE = 'title'

REQUEST_KEY_PARAGRAPH = 'paragraph'

REQUEST_KEY_STORY_ID = "story_id"

KEY_ID = "id"

main_blue_print = Blueprint('main', __name__)

@main_blue_print.route('/story', methods=['POST'])
@login_required
def begin_story():
    _validate_request_for_new_story()
    new_story = _add_new_story()
    _create_and_add_new_paragraph_to_story(new_story, request.form.get(REQUEST_KEY_PARAGRAPH))
    return create_response(True, {KEY_ID: new_story.id})

def _create_and_add_new_paragraph_to_story(story, paragraph_content=None):
    paragraph_entry = _create_and_add_to_db_new_paragraph(paragraph_content, story.id)
    _add_paragraph_to_story(paragraph_entry, story)
    return paragraph_entry

def _add_paragraph_to_story(paragraph_entry, story):
    story.paragraphs = '[]' if story.paragraphs is None else story.paragraphs
    paragraphs_order = json.loads(story.paragraphs)
    paragraphs_order.append(paragraph_entry.id)
    story.paragraphs = json.dumps(paragraphs_order)
    db.session.commit()

def _create_and_add_to_db_new_paragraph(paragraph_content, story_id, voteable=False):
    paragraph_entry = Paragraph(story_id, current_user.id, paragraph_content)
    paragraph_entry.voteable = voteable
    db.session.add(paragraph_entry)
    db.session.commit()
    return paragraph_entry

def _validate_request_for_new_story():
    failure_message = _check_parameter_length(REQUEST_KEY_TITLE, MAX_SIZE_TITLE)
    if failure_message is None:
        failure_message = _check_parameter_length(REQUEST_KEY_PARAGRAPH, MAX_SIZE_PARAGRAPH)
    if failure_message is not None:
        abort(400, description=failure_message)

def _check_parameter_length(parameter, max_size):
    failure_message = None
    if parameter not in request.form:
        failure_message = MESSAGE_MANDATORY_FIELD_WAS_NOT_SUPPLIED.format(parameter)
    elif len(request.form.get(parameter)) == 0:
        failure_message = MESSAGE_CANNOT_BE_EMPTY.format(parameter)
    elif len(request.form.get(parameter)) > max_size:
        failure_message = MESSAGE_TOO_LONG.format(parameter, max_size)
    return failure_message

def _add_new_story():
    title = request.form.get(REQUEST_KEY_TITLE)
    story = Story(title, current_user.id)
    db.session.add(story)
    db.session.commit()
    return story

@main_blue_print.route('/paragraph_suggestion', methods=['POST'])
@login_required
def suggest_new_paragraph():
    story_id = request.form.get(REQUEST_KEY_STORY_ID)
    story = Story.query.get(story_id)
    if story is None:
        abort(404, description=MESSAGE_RES_WAS_NOT_FOUND.format(Story.__class__.__name__, story_id))
    _set_deadline(story)
    paragraph = _add_new_suggestion_to_story(story, story_id, True)
    db.session.commit()
    return create_response(True, {KEY_ID: paragraph.id})

def _set_deadline(story, commit=False):
    dl = story.deadline
    if story.suggestions is None or len(story.suggestions) == 0 or dl is None or dl - time.time() <= 0:
        story.deadline = time.time() + RELATIVE_DEADLINE * 24 * 60 * 60
        if commit:
            db.session.commit()
    return story.deadline

def _add_new_suggestion_to_story(story, story_id, voteable=False):
    paragraph = _create_and_add_to_db_new_paragraph(request.form.get(REQUEST_KEY_PARAGRAPH), story_id, voteable)
    story.suggestions = '[]' if story.suggestions is None else story.suggestions
    suggestions_order = json.loads(story.suggestions)
    suggestions_order.append(paragraph.id)
    story.suggestions = json.dumps(suggestions_order)
    return paragraph

@main_blue_print.route('/paragraph_suggestion', methods=['GET'])
@login_required
def get_paragraphs_suggestions():
    suggestions = []
    if REQUEST_KEY_STORY_ID in request.args:
        suggestions = _get_suggested_paragraphs_by_story(suggestions)
    else:
        abort(400, description=MESSAGE_MANDATORY_FIELD_WAS_NOT_SUPPLIED.format(REQUEST_KEY_STORY_ID))
    return create_response(True, suggestions)

def _get_suggested_paragraphs_by_story(suggestions):
    story = Story.query.get(request.args.get(REQUEST_KEY_STORY_ID))
    suggestions_ids = json.loads(story.suggestions)
    for suggestion_id in suggestions_ids:
        suggestion = Paragraph.query.get(suggestion_id)
        suggestions.append(suggestion.as_dict())
    return suggestions

@main_blue_print.route('/story', methods=['GET'])
@login_required
def get_story():
    if KEY_ID in request.args:
        response_data = _get_story_by_id()
    else:
        response_data = _get_stories()
    return create_response(True, response_data)

def _get_stories():
    stories = db.session.query().with_entities(Story.id, Story.title, Story.owner_id, Story.paragraphs).all()
    owners_dict = _get_owners_as_dict()
    stories_dict = _get_stories_as_dict(owners_dict, stories)
    return {RESPONSE_KEY_STORIES: stories_dict}

def _get_stories_as_dict(owners_dict, stories):
    paragraphs_to_fetch = []
    stories_dict = {}
    for story in stories:
        _add_story_to_stories_dict(owners_dict, paragraphs_to_fetch, stories_dict, story)
    paragraphs = db.session.query(Paragraph).filter(Paragraph.id.in_(paragraphs_to_fetch)).all()
    for paragraph in paragraphs:
        stories_dict[paragraph.story_id][RESPONSE_KEY_FIRST_PARAGRAPH] = paragraph.as_dict()
    return stories_dict

def _add_story_to_stories_dict(owners_dict, paragraphs_to_fetch, stories_dict, story):
    owner = dict(zip(owners_dict[story.owner_id].keys(), owners_dict[story.owner_id]))
    stories_dict[story.id] = {RESPONSE_KEY_TITLE: story.title, RESPONSE_KEY_OWNER: owner}
    story_paragraphs = story.paragraphs
    if story_paragraphs is not None:
        paragraphs = json.loads(story_paragraphs)
        paragraphs_to_fetch.append(paragraphs[0])

def _get_owners_as_dict():
    owners = db.session.query().with_entities(User.id, User.name).all()
    owners_dict = {}
    for owner in owners:
        owners_dict[owner.id] = owner
    return owners_dict

def _get_story_by_id():
    story = Story.query.get(request.args.get(KEY_ID))
    owner = User.query.get(story.owner_id)
    paragraphs_order = json.loads(story.paragraphs)
    paragraphs = []
    participants_ids = set()
    for paragraph_id in paragraphs_order:
        paragraph = Paragraph.query.get(paragraph_id)
        paragraphs.append(paragraph.as_dict())
        participants_ids.add(paragraph.owner_id)

    response_data = {RESPONSE_KEY_TITLE: story.title,
                     RESPONSE_KEY_OWNER: {KEY_ID: story.owner_id, RESPONSE_KEY_NAME: owner.name},
                     RESPONSE_KEY_PARAGRAPHS: paragraphs}

    if story.suggestions is not None:
        deadline = story.deadline
        if deadline is not None:
            if deadline - time.time() > 0:
                response_data[RESPONSE_KEY_DEADLINE] = deadline
                suggestions = _get_suggestions(story, participants_ids)
                response_data[RESPONSE_KEY_SUGGESTIONS] = suggestions
            else:
                votes_counter = {}
                max_id = -1
                max_value = 0
                max_counter = 1
                suggestions_order = json.loads(story.suggestions)

                for suggestion_id in suggestions_order:
                    number_of_votes = Vote.query.filter(Vote.paragraph_id == suggestion_id).count()

                    max_id = suggestion_id if max_id == -1 else max_id
                    if max_value == number_of_votes:
                        max_counter += 1
                    if max_value < number_of_votes:
                        max_value = number_of_votes
                        max_id = suggestion_id
                        max_counter = 1
                    votes_counter[suggestion_id] = number_of_votes

                if max_counter > 1:
                    response_data[RESPONSE_KEY_DEADLINE] = _set_deadline(story, True)
                    suggestions = _get_suggestions(story, participants_ids)
                    response_data[RESPONSE_KEY_SUGGESTIONS] = suggestions
                else:
                    if max_id > -1:
                        paragraph = Paragraph.query.get(max_id)
                        _add_paragraph_to_story(paragraph, story)
                        story.deadline = None
                        story.suggestions = None
                        paragraph.voteable = False
                        db.session.commit()
                        paragraphs.append(paragraph.as_dict())

    if len(participants_ids) > 0:
        participants = []
        participants_objects = db.session.query(User).filter(User.id.in_(participants_ids))
        for participant in participants_objects:
            participants.append({KEY_ID: participant.id, RESPONSE_KEY_NAME: participant.name})
        if len(participants) > 0:
            response_data[RESPONSE_KEY_PARTICIPANTS] = participants

    return response_data

def _get_suggestions(story, participants_ids):
    suggestions_order = json.loads(story.suggestions)
    suggestions = []
    for suggestion_id in suggestions_order:
        paragraph = Paragraph.query.get(suggestion_id)
        as_dict = paragraph.as_dict()
        as_dict[RESPONSE_KEY_VOTES] = Vote.query.filter(Vote.paragraph_id == suggestion_id).count()
        suggestions.append(as_dict)
        if participants_ids is not None:
            participants_ids.add(paragraph.owner_id)
    return suggestions

@main_blue_print.route('/vote', methods=['PUT'])
@login_required
def vote():
    paragraph_id = request.form.get(KEY_ID)
    paragraph = Paragraph.query.filter_by(id=paragraph_id).first()
    response = None
    if paragraph:
        response = _add_vote_to_voteable_paragraph(paragraph)
    else:
        abort(404, description=MESSAGE_RES_WAS_NOT_FOUND.format(Paragraph.__class__.__name__, paragraph_id))
    return response

def _add_vote_to_voteable_paragraph(paragraph):
    if paragraph.voteable:
        if not Vote.query.filter_by(user_id=current_user.id, paragraph_id=paragraph.id).first():
            response = _add_vote(paragraph.id, current_user.id)
        else:
            response = create_response(False, message=MESSAGE_ALREADY_VOTED.format(paragraph.id))
    else:
        response = create_response(False, message=MESSAGE_NOT_VOTEABLE.format(paragraph.id))
    return response

def _add_vote(paragraph_id, user_id):
    vote_entry = Vote(user_id=user_id, paragraph_id=paragraph_id)
    db.session.add(vote_entry)
    db.session.commit()
    response = create_response(True, message=MESSAGE_VOTE_SUCCESSFULLY.format(paragraph_id))
    return response


from nose.tools import assert_equal, assert_raises
from StringIO import StringIO
from pbcore.io import GffWriter, Gff3Record, GffReader
from pbcore import data

class TestGff3Record:

    def setup(self):
        self.record = Gff3Record("chr1", 10, 11, "insertion",
                                 attributes=[("cat", "1"), ("dog", "2")])

    def test_str(self):
        assert_equal("chr1\t.\tinsertion\t10\t11\t.\t.\t.\tcat=1;dog=2",
                     str(self.record))

    def test_modification(self):
        record = self.record.copy()
        record.dog = 3
        record.cat = 4
        record.mouse = 5
        record.start = 100
        record.end = 110
        assert_equal("chr1\t.\tinsertion\t100\t110\t.\t.\t.\tcat=4;dog=3;mouse=5",
                     str(record))

    def test_fromString(self):
        newRecord = Gff3Record.fromString(str(self.record))
        assert_equal(str(self.record),  str(newRecord))

    def test_get(self):
        """
        Verify field access behavior
        """
        record = self.record
        record.dog = 3
        record.cat = 4
        record.mouse = 5
        record.start = 100
        record.end = 110

        assert_equal(3, record.dog)
        assert_equal(100, record.start)
        with assert_raises(AttributeError):
            record.god

        assert_equal(3, record.get("dog"))
        assert_equal(None, record.get("god"))
        assert_equal(100, record.get("start", 100))

class TestGffReader:
    def setup(self):
        self.rawFile = open(data.getGff3())
        self.reader = GffReader(data.getGff3())

    def test_headers(self):
        assert_equal(["##gff-version 3",
                      "##pacbio-variant-version 2.1",
                      "##date Sat Mar 22 12:16:13 2014",
                      "##feature-ontology http://song.cvs.sourceforge.net/*checkout*/song/ontology/sofa.obo?revision=1.12",
                      "##source GenomicConsensus 0.8.0",
                      "##source-commandline /Users/dalexander/.virtualenvs/VE/bin/variantCaller.py --algorithm=plurality -q20 -x5 pbcore/data/aligned_reads_1.cmp.h5 -r /Users/dalexander/Data/lambdaNEB.fa -o /tmp/v.gff",
                      "##source-alignment-file /Users/dalexander/Dropbox/Sources/git/pbcore/pbcore/data/aligned_reads_1.cmp.h5",
                      "##source-reference-file /Users/dalexander/Data/lambdaNEB.fa",
                      "##sequence-region lambda_NEB3011 1 48502"],
                     self.reader.headers)

    def test__iter__(self):
        records = list(self.reader)
        rawLines = self.rawFile.readlines()[9:]
        for record, rawLine in zip(records, rawLines):
            # No newlines or whitespace allowed in records
            assert_equal(str(record).strip(), str(record))
            # Make sure record matches line
            assert_equal(rawLine.strip(), str(record))

class TestGffWriter:
    def setup(self):
        self.outfile = StringIO()
        self.record1 = Gff3Record("chr1", 10, 11, "insertion",
                                  attributes=[("cat", "1"), ("dog", "2")])
        self.record2 = Gff3Record("chr1", 200, 201, "substitution",
                                  attributes=[("mouse", "1"), ("moose", "2")])
        self.gffWriter = GffWriter(self.outfile)

    def test_writeHeader(self):
        self.gffWriter.writeHeader("##foo bar")
        assert_equal("##gff-version 3\n##foo bar\n",
                     self.outfile.getvalue())

    def test_writeRecord(self):
        self.gffWriter.writeRecord(self.record1)
        self.gffWriter.writeRecord(self.record2)
        expected = ("##gff-version 3\n" +
                    "chr1\t.\tinsertion\t10\t11\t.\t.\t.\tcat=1;dog=2\n" +
                    "chr1\t.\tsubstitution\t200\t201\t.\t.\t.\tmouse=1;moose=2\n")
        assert_equal(expected, self.outfile.getvalue())