import os
import shutil
import numpy as np

import tensorflow as tf
from tensorflow import keras

from pathlib import Path
from IPython.display import display, Audio

import io
import csv
import random
import numpy as np
import pandas as pd
import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_io as tfio
from tensorflow import keras
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import shutil
from pathlib import Path
from IPython.display import display, Audio

from keras import Model
from keras.layers.core import Dense, Activation
from keras.layers import Conv2D, Conv1D, MaxPooling2D, Reshape, Concatenate, concatenate, Dropout , MaxPooling1D, Flatten
from keras.layers import Dense, Input

from keras.models import Sequential, Model
from keras.layers.core import Dense, Activation
from keras.layers.convolutional import Conv2D , Conv1D
from keras.layers import Conv2D, Conv1D,MaxPooling2D ,BatchNormalization, Reshape, Multiply, Dropout , MaxPooling1D

# Optimizers
from keras.optimizers import Adagrad
from keras.optimizers import Adam
from keras.optimizers import SGD
from keras.optimizers import RMSprop

# ----------------------- Accent Data ----------------------

SEED = 1337
EPOCHS = 5
BATCH_SIZE = 64
VALIDATION_RATIO = 0.1
MODEL_NAME = "uk_irish_accent_recognition"

# Location where the dataset will be downloaded.
# By default (None), keras.utils.get_file will use ~/.keras/ as the CACHE_DIR
#CACHE_DIR = None

# The location of the dataset
#URL_PATH = "https://www.openslr.org/resources/83/"

# List of datasets compressed files that contain the audio files
zip_files = {
    0: "irish_english_male",
    1: "midlands_english_female",
    2: "midlands_english_male",
    3: "northern_english_female",
    4: "northern_english_male",
    5: "scottish_english_female",
    6: "scottish_english_male",
    7: "southern_english_female",
    8: "southern_english_male",
    9: "welsh_english_female",
    10: "welsh_english_male",

}

# We see that there are 2 compressed files for each accent (except Irish):
# - One for male speakers
# - One for female speakers
# However, we will using a gender agnostic dataset.

# List of gender agnostic categories
gender_agnostic_categories = [
    "ir",  # Irish
    "mi",  # Midlands
    "no",  # Northern
    "sc",  # Scottish
    "so",  # Southern
    "we",  # Welsh

]

class_names = [
    "Irish",
    "Midlands",
    "Northern",
    "Scottish",
    "Southern",
    "Welsh",
    "Not a speech",
]

# Set all random seeds in order to get reproducible results
keras.utils.set_random_seed(SEED)

# Where to download the dataset
DATASET_DESTINATION = os.path.dirname(os.getcwd()) + '/data';

yamnet_model = hub.load("https://tfhub.dev/google/yamnet/1")

# CSV file that contains information about the dataset. For each entry, we have:
# - ID
# - wav file name
# - transcript
line_index_file = keras.utils.get_file(
    fname="line_index_file", origin=DATASET_DESTINATION + "line_index_all.csv"
)

# Download the list of compressed files that contains the audio wav files
for i in zip_files:
    fname = zip_files[i].split(".")[0]
    url = DATASET_DESTINATION + zip_files[i]

    #zip_file = keras.utils.get_file(fname=fname, origin=url, extract=True)
    #os.remove(zip_file)

dataframe = pd.read_csv(
    line_index_file, names=["id", "filename", "transcript"], usecols=["filename"]
)
dataframe.head()

# The purpose of this function is to preprocess the dataframe by applying the following:
# - Cleaning the filename from a leading space
# - Generating a label column that is gender agnostic i.e.
#   welsh english male and welsh english female for example are both labeled as
#   welsh english
# - Add extension .wav to the filename
# - Shuffle samples
def preprocess_dataframe(dataframe):
    # Remove leading space in filename column
    dataframe["filename"] = dataframe.apply(lambda row: row["filename"].strip(), axis=1)

    # Create gender agnostic labels based on the filename first 2 letters
    dataframe["label"] = dataframe.apply(
        lambda row: gender_agnostic_categories.index(row["filename"][:2]), axis=1
    )

    # Add the file path to the name
    dataframe["filename"] = dataframe.apply(
        lambda row: os.path.join(DATASET_DESTINATION, row["filename"] + ".wav"), axis=1
    )

    # Shuffle the samples
    dataframe = dataframe.sample(frac=1, random_state=SEED).reset_index(drop=True)

    return dataframe

dataframe = preprocess_dataframe(dataframe)
dataframe.head()

split = int(len(dataframe) * (1 - VALIDATION_RATIO))
train_df = dataframe[:split]
valid_df = dataframe[split:]

print(
    f"We have {train_df.shape[0]} training samples & {valid_df.shape[0]} validation ones"
)

@tf.function
def load_16k_audio_wav(filename):
    # Read file content
    file_content = tf.io.read_file(filename)

    # Decode audio wave
    audio_wav, sample_rate = tf.audio.decode_wav(file_content, desired_channels=1)
    audio_wav = tf.squeeze(audio_wav, axis=-1)
    sample_rate = tf.cast(sample_rate, dtype=tf.int64)

    # Resample to 16k
    audio_wav = tfio.audio.resample(audio_wav, rate_in=sample_rate, rate_out=16000)

    return audio_wav

def filepath_to_embeddings(filename, label):
    # Load 16k audio wave
    audio_wav = load_16k_audio_wav(filename)

    # Get audio embeddings & scores.
    # The embeddings are the audio features extracted using transfer learning
    # while scores will be used to identify time slots that are not speech
    # which will then be gathered into a specific new category 'other'
    scores, embeddings, _ = yamnet_model(audio_wav)

    # Number of embeddings in order to know how many times to repeat the label
    embeddings_num = tf.shape(embeddings)[0]
    labels = tf.repeat(label, embeddings_num)

    # Change labels for time-slots that are not speech into a new category 'other'
    labels = tf.where(tf.argmax(scores, axis=1) == 0, label, len(class_names) - 1)

    # Using one-hot in order to use AUC
    return (embeddings, tf.one_hot(labels, len(class_names)))

def dataframe_to_dataset(dataframe, batch_size=64):
    dataset = tf.data.Dataset.from_tensor_slices(
        (dataframe["filename"], dataframe["label"])
    )

    dataset = dataset.map(
        lambda x, y: filepath_to_embeddings(x, y),
        num_parallel_calls=tf.data.experimental.AUTOTUNE,
    ).unbatch()

    return dataset.cache().batch(batch_size).prefetch(tf.data.AUTOTUNE)

train_ds = dataframe_to_dataset(train_df)
valid_ds = dataframe_to_dataset(valid_df)

keras.backend.clear_session()

# ----------------------- Speaker Data ----------------------

# Get the data from https://www.kaggle.com/kongaevans/speaker-recognition-dataset/download
# and save it to the 'Downloads' folder in your HOME directory
DATASET_ROOT = os.path.join(os.path.expanduser("~"), "Downloads/16000_pcm_speeches")

# The folders in which we will put the audio samples and the noise samples
AUDIO_SUBFOLDER = "audio"
NOISE_SUBFOLDER = "noise"

DATASET_AUDIO_PATH = os.path.join(DATASET_ROOT, AUDIO_SUBFOLDER)
DATASET_NOISE_PATH = os.path.join(DATASET_ROOT, NOISE_SUBFOLDER)

# Percentage of samples to use for validation
VALID_SPLIT = 0.1

# Seed to use when shuffling the dataset and the noise
SHUFFLE_SEED = 43

# The sampling rate to use.
# This is the one used in all of the audio samples.
# We will resample all of the noise to this sampling rate.
# This will also be the output size of the audio wave samples
# (since all samples are of 1 second long)
SAMPLING_RATE = 16000

# The factor to multiply the noise with according to:
#   noisy_sample = sample + noise * prop * scale
#      where prop = sample_amplitude / noise_amplitude
SCALE = 0.5

BATCH_SIZ = 128
EPOCH = 5

# If folder `audio`, does not exist, create it, otherwise do nothing
if os.path.exists(DATASET_AUDIO_PATH) is False:
    os.makedirs(DATASET_AUDIO_PATH)

# If folder `noise`, does not exist, create it, otherwise do nothing
if os.path.exists(DATASET_NOISE_PATH) is False:
    os.makedirs(DATASET_NOISE_PATH)

for folder in os.listdir(DATASET_ROOT):
    if os.path.isdir(os.path.join(DATASET_ROOT, folder)):
        if folder in [AUDIO_SUBFOLDER, NOISE_SUBFOLDER]:
            # If folder is `audio` or `noise`, do nothing
            continue
        elif folder in ["other", "_background_noise_"]:
            # If folder is one of the folders that contains noise samples,
            # move it to the `noise` folder
            shutil.move(
                os.path.join(DATASET_ROOT, folder),
                os.path.join(DATASET_NOISE_PATH, folder),
            )
        else:
            # Otherwise, it should be a speaker folder, then move it to
            # `audio` folder
            shutil.move(
                os.path.join(DATASET_ROOT, folder),
                os.path.join(DATASET_AUDIO_PATH, folder),
            )

# Get the list of all noise files
noise_paths = []
for subdir in os.listdir(DATASET_NOISE_PATH):
    subdir_path = Path(DATASET_NOISE_PATH) / subdir
    if os.path.isdir(subdir_path):
        noise_paths += [
            os.path.join(subdir_path, filepath)
            for filepath in os.listdir(subdir_path)
            if filepath.endswith(".wav")
        ]

print(
    "Found {} files belonging to {} directories".format(
        len(noise_paths), len(os.listdir(DATASET_NOISE_PATH))
    )
)

command = (
    "for dir in `ls -1 " + DATASET_NOISE_PATH + "`; do "
    "for file in `ls -1 " + DATASET_NOISE_PATH + "/$dir/*.wav`; do "
    "sample_rate=`ffprobe -hide_banner -loglevel panic -show_streams "
    "$file | grep sample_rate | cut -f2 -d=`; "
    "if [ $sample_rate -ne 16000 ]; then "
    "ffmpeg -hide_banner -loglevel panic -y "
    "-i $file -ar 16000 temp.wav; "
    "mv temp.wav $file; "
    "fi; done; done"
)

os.system(command)

# Split noise into chunks of 16000 each
def load_noise_sample(path):
    sample, sampling_rate = tf.audio.decode_wav(
        tf.io.read_file(path), desired_channels=1
    )
    if sampling_rate == SAMPLING_RATE:
        # Number of slices of 16000 each that can be generated from the noise sample
        slices = int(sample.shape[0] / SAMPLING_RATE)
        sample = tf.split(sample[: slices * SAMPLING_RATE], slices)
        return sample
    else:
        print("Sampling rate for {} is incorrect. Ignoring it".format(path))
        return None

noises = []
for path in noise_paths:
    sample = load_noise_sample(path)
    if sample:
        noises.extend(sample)
noises = tf.stack(noises)

print(
    "{} noise files were split into {} noise samples where each is {} sec. long".format(
        len(noise_paths), noises.shape[0], noises.shape[-1] // SAMPLING_RATE
    )
)

def paths_and_labels_to_dataset(audio_paths, labels):
    """Constructs a dataset of audios and labels."""
    path_ds = tf.data.Dataset.from_tensor_slices(audio_paths)
    audio_ds = path_ds.map(lambda x: path_to_audio(x))
    label_ds = tf.data.Dataset.from_tensor_slices(labels)
    return tf.data.Dataset.zip((audio_ds, label_ds))

def path_to_audio(path):
    """Reads and decodes an audio file."""
    audio = tf.io.read_file(path)
    audio, _ = tf.audio.decode_wav(audio, 1, SAMPLING_RATE)
    return audio

def add_noise(audio, noises=None, scale=0.5):
    if noises is not None:
       # Create a random tensor of the same size as audio ranging from
       # 0 to the number of noise stream samples that we have.
        tf_rnd = tf.random.uniform(
            (tf.shape(audio)[0],), 0, noises.shape[0], dtype=tf.int32
        )
        noise = tf.gather(noises, tf_rnd, axis=0)

       # Get the amplitude proportion between the audio and the noise
        prop = tf.math.reduce_max(audio, axis=1) / tf.math.reduce_max(noise, axis=1)
        prop = tf.repeat(tf.expand_dims(prop, axis=1), tf.shape(audio)[1], axis=1)
        # Adding the rescaled noise to audio
        audio = audio + noise * prop * scale

    return audio

def audio_to_fft(audio):
    # Since tf.signal.fft applies FFT on the innermost dimension,
    # we need to squeeze the dimensions and then expand them again
    # after FFT
    audio = tf.squeeze(audio, axis=-1)
    fft = tf.signal.fft(
        tf.cast(tf.complex(real=audio, imag=tf.zeros_like(audio)), tf.complex64)
    )
    fft = tf.expand_dims(fft, axis=-1)

    # Return the absolute value of the first half of the FFT
    # which represents the positive frequencies
    return tf.math.abs(fft[:, : (audio.shape[1] // 2), :])

# Get the list of audio file paths along with their corresponding labels

class_name = os.listdir(DATASET_AUDIO_PATH)
print("Our class names: {}".format(class_names,))

audio_paths = []
labels = []
for label, name in enumerate(class_name):
    print("Processing speaker {}".format(name,))
    dir_path = Path(DATASET_AUDIO_PATH) / name
    speaker_sample_paths = [
        os.path.join(dir_path, filepath)
        for filepath in os.listdir(dir_path)
        if filepath.endswith(".wav")
    ]
    audio_paths += speaker_sample_paths
    labels += [label] * len(speaker_sample_paths)

print(
    "Found {} files belonging to {} classes.".format(len(audio_paths), len(class_names))
)

# Shuffle
rng = np.random.RandomState(SHUFFLE_SEED)
rng.shuffle(audio_paths)
rng = np.random.RandomState(SHUFFLE_SEED)
rng.shuffle(labels)

# Split into training and validation
num_val_samples = int(VALID_SPLIT * len(audio_paths))
print("Using {} files for training.".format(len(audio_paths) - num_val_samples))
train_audio_paths = audio_paths[:-num_val_samples]
train_labels = labels[:-num_val_samples]

print("Using {} files for validation.".format(num_val_samples))
valid_audio_paths = audio_paths[-num_val_samples:]
valid_labels = labels[-num_val_samples:]

# Create 2 datasets, one for training and the other for validation
train_dse = paths_and_labels_to_dataset(train_audio_paths, train_labels)
train_dse = train_dse.shuffle(buffer_size=BATCH_SIZ * 8, seed=SHUFFLE_SEED).batch(
    BATCH_SIZ
)

valid_dse = paths_and_labels_to_dataset(valid_audio_paths, valid_labels)
valid_dse = valid_dse.shuffle(buffer_size=32 * 8, seed=SHUFFLE_SEED).batch(32)

# Add noise to the training set
train_dse = train_dse.map(
    lambda x, y: (add_noise(x, noises, scale=SCALE), y),
    num_parallel_calls=tf.data.AUTOTUNE,
)

# Transform audio wave to the frequency domain using `audio_to_fft`
train_dse = train_dse.map(
    lambda x, y: (audio_to_fft(x), y), num_parallel_calls=tf.data.AUTOTUNE
)
train_dse = train_dse.prefetch(tf.data.AUTOTUNE)

valid_dse = valid_dse.map(
    lambda x, y: (audio_to_fft(x), y), num_parallel_calls=tf.data.AUTOTUNE
)
valid_dse = valid_dse.prefetch(tf.data.AUTOTUNE)

# ----------------------- Accent CNN ----------------------
in_1D = Sequential()

in_1D = keras.layers.Input(shape=(1024), name="embedding")
# 1
x = keras.layers.Dense(256, activation="relu", name="dense_1")(in_1D)
x = keras.layers.Dropout(0.15, name="dropout_1")(x)

x = keras.layers.Dense(384, activation="relu", name="dense_2")(x)
x = keras.layers.Dropout(0.2, name="dropout_2")(x)

x = keras.layers.Dense(192, activation="relu", name="dense_3")(x)
x = keras.layers.Dropout(0.25, name="dropout_3")(x)

x = keras.layers.Flatten()(x)

x = keras.layers.Dense(384, activation="relu", name="dense_4")(x)
x = keras.layers.Dropout(0.2, name="dropout_4")(x)

#class name = 7
outputs = keras.layers.Dense(len(class_names), activation="softmax", name="ouput")(
    x
)

x = keras.Model(inputs=in_1D, outputs=outputs, name="accent_recognition")

x.compile(
    optimizer=keras.optimizers.Adam(learning_rate=1.9644e-5),
    loss=keras.losses.CategoricalCrossentropy(),
    metrics=["accuracy", keras.metrics.AUC(name="auc")],
)

# ----------------------- Speaker CNN ----------------------

model = Sequential()

def residual_block(x, filters, conv_num=3, activation="relu"):
    # Shortcut
    s = keras.layers.Conv1D(filters, 1, padding="same")(x)
    for i in range(conv_num - 1):
        x = keras.layers.Conv1D(filters, 3, padding="same")(x)
        x = keras.layers.Activation(activation)(x)
    x = keras.layers.Conv1D(filters, 3, padding="same")(x)
    x = keras.layers.Add()([x, s])
    x = keras.layers.Activation(activation)(x)
    return keras.layers.MaxPool1D(pool_size=2, strides=2)(x)

in_2D = keras.layers.Input((SAMPLING_RATE // 2, 1), len(class_name))
print(in_2D, in_1D)
x = residual_block(in_2D, 16, 2)
x = residual_block(x, 32, 2)
x = residual_block(x, 64, 3)
x = residual_block(x, 128, 3)
x = residual_block(x, 128, 3)

x = keras.layers.AveragePooling1D(pool_size=3, strides=3)(x)
x = keras.layers.Flatten()(x)
x = keras.layers.Dense(256, activation="relu")(x)
x = keras.layers.Dense(128, activation="relu")(x)

#change num classes to 50
x = keras.layers.Dense(len(class_name), activation="softmax", name="output")(x)

model = x

model_save_filename = "model.h5"

earlystopping_cb = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)
mdlcheckpoint_cb = keras.callbacks.ModelCheckpoint(
    model_save_filename, monitor="val_accuracy", save_best_only=True)

# ----------------------- Merged CNN ----------------------

merged = Multiply()([model, x])
output = Dense(7, activation='softmax')(merged)

model_combined = Model(inputs=[in_1D, in_2D], outputs=[output])

model_combined.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

model = model_combined

model.summary()

from tensorflow.keras.utils import plot_model
plot_model(model, to_file='model_combined.png')

# ----------------------- training ----------------------

model = Sequential()
model.add(Dense(8, input_shape=(10,), activation="relu"))
model.add(Dense(4, activation="relu"))
model.add(Dense(1, activation="linear"))

inputs = Input(shape=(10,))
x = Dense(8, activation="relu")(inputs)
x = Dense(4, activation="relu")(x)
x = Dense(1, activation="linear")(x)
model = Model(inputs, x)

# define two sets of inputs
inputA = Input(shape=(1))
inputB = Input(shape=(1))

# the first branch operates on the first input
x = Dense(8, activation="relu")(inputA)
x = Dense(4, activation="relu")(x)
x = Model(inputs=inputA, outputs=x)

# the second branch opreates on the second input
y = Dense(64, activation="relu")(inputB)
y = Dense(32, activation="relu")(y)
y = Dense(4, activation="relu")(y)
y = Model(inputs=inputB, outputs=y)

# combine the output of the two branches
combined = concatenate([x.output, y.output])

# apply a FC layer and then a regression prediction on the
# combined outputs
z = Dense(2, activation="relu")(combined)
z = Dense(1, activation="linear")(z)

# our model will accept the inputs of the two branches and
# then output a single value
model = Model(inputs=[x.input, y.input], outputs=z)

model.summary()

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

model1 = tf.keras.Sequential(name="input_3")
model2 = tf.keras.Sequential(name="input_4")

x = tf.data.Dataset.from_tensors(({"input_3": [[(1024)], [(1)]],
                                     "input_4": [[(8000)], [(1)]]}))

print(model.evaluate(x))

history = model.fit(
    x,
    validation_data=x,
    callbacks=[earlystopping_cb, mdlcheckpoint_cb],
)


from rest_framework import permissions

from courses.models import Course, HomeworkAnswer
from users.models import User

class IsTeacherOrReadOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        return request.user.role == User.RoleChoices.TEACHER

class IsCourseTeacher(permissions.BasePermission):
    def has_permission(self, request, view):
        course = Course.objects.get(pk=view.kwargs['course_pk'])
        if course not in Course.objects.user_courses(request.user):
            return False
        if request.method in permissions.SAFE_METHODS:
            return True
        return request.user in course.teachers.all()

class IsCourseTeacherOrReadOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS and \
           view.get_object() in Course.objects.user_courses(request.user):
            return True
        return request.user in view.get_object().teachers.all()

class IsCourseStudent(permissions.BasePermission):
    def has_permission(self, request, view):
        course = Course.objects.get(pk=view.kwargs['course_pk'])
        return request.user in course.students.all()

class IsHomeworkAnswerOwnerOrCourseTeacher(permissions.BasePermission):
    def has_permission(self, request, view):
        course = Course.objects.get(pk=view.kwargs['course_pk'])
        return request.user == view.get_object().user or request.user in course.teachers.all()

class IsHomeworkAnswerOwner(permissions.BasePermission):
    def has_permission(self, request, view):
        return request.user == view.get_object().user

class IsCourseTeacherOrHomeworkAnswerOwner(permissions.BasePermission):
    def has_permission(self, request, view):
        course = Course.objects.get(pk=view.kwargs['course_pk'])
        homework_answer = HomeworkAnswer.objects.get(pk=view.kwargs['homework_answer_pk'])
        return request.user == homework_answer.user or request.user in course.teachers.all()

class IsCommentOwner(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return request.user == obj.user


from subprocess import *
import time

if __name__ == '__main__':
    p1 = Popen(['python', './ODESR_Generator_V2.py'])
    p2 = Popen(['python', './Finding_Frequencies_GUI.py'])
    try:
        time.sleep(3)
        while True:
            stop = str(input('[+] Do you want to stop [y/n] : '))
            if stop == 'y':
                p1.kill()
                p2.kill()
                break
    except KeyboardInterrupt:
        p1.kill()
        p2.kill()
        print('Press Ctrl-C to kill the program')


# Specify a name for the model to avoid overwriting old results
folder = 'My_Network'

from matplotlib import offsetbox
import matplotlib.pyplot as plt
import nest as sim # type: ignore
import numpy as np
import pandas
from collections import Counter
import time as tm
import scipy.stats
import os

# Used to generate non-constant AHVs
def smoothclamp(x, mi, mx): return mi + (mx-mi)*(lambda t: np.where(t < 0 , 0, np.where( t <= 1 , 3*t**2-2*t**3, 1 ) ) )( (x-mi)/(mx-mi) )

# Used to generate population weight matrices
def insertVec(m, v, x, y, axis = 0):
    if axis == 1:
        for i in range(len(v)):
            m[y,x+i]=v[i]
    else:
        for i in range(len(v)):
            m[y+i,x]=v[i]
    return m

# Used to calculate the current to be injected into the stationary ring
# c describes vertical scale
# s describes steepness
# b is vertical offset
def velCurve(x, c, s, b):
    y = np.empty_like(x)
    for i in range(len(x)):
        y[i] = c/(s*abs(x[i])+1) + b
    return y

deg_sign = u'\N{DEGREE SIGN}'

# misc params
use_stationary_ring = False # Creates a 5th ring. Connections are not currently set
vel_rescale = False     # Non-functional
long = False            # If true, runs a 720deg rotation instead of 180. Used for noisy models

# params describing number of cells in each population
N_ex = 180
N_in = N_ex
N_cj = N_ex # number of cj POPULATIONS, not neurons

# params for describing connection weights between populations
sigma = 0.12    # def 0.12
delay = 0.1     # def 0.1
base_ex = 4000  # def 4000    EX->IN
base_in = 450   # def 450     IN->EX
base_cj = 25    # def 169     CJ->EX   #narrow 25   #halfvel 36
base_ex_cj = 660        # def 660
I_ex = 450.0            # def 450 pA
vel_mult = 3500         # def 3500                  #halfvel 1750
sh = 150                # def 150
in_sigma_scale = 0.5    # def 1        #narrow 0.5                 # multiplier of Sigma for the IN->EX distribution

base_st_ex = 0   #      ST->EX
I_st = 0.0        # def ? pA   peak current into the stationary layer, (c in the curve function)

# params describing size , duration and excitatory cell number to initialize the bump
I_init = 300.0 #pA
I_init_dur = 100.0 #ms
I_init_pos = N_ex//2

# PopCoding parameters
cj_population_size = 1      # set to 1 to not use pop-coding
pop_input_vari = 0.5        # EX->CJ variance
pop_output_vari = 0.0       # CJ->EX variance

# figure rendering tweak
ringhist_pad = 0.01

# ensure file struct
if not os.path.exists(f'results/{folder}'):
    os.makedirs(f'results/{folder}')
if not os.path.exists(f'results/{folder}/ring_hist'):
    os.makedirs(f'results/{folder}/ring_hist')
if not os.path.exists(f'results/{folder}/drift_hist'):
    os.makedirs(f'results/{folder}/drift_hist')
# Record parameter details
f = open("results/"+folder+"/Parameters.txt", "w")
f.write("N_ex " + str(N_ex) + "\n")
f.write("N_in " + str(N_in) + "\n")
f.write("N_cj " + str(N_cj) + "\n")
f.write("sigma " + str(sigma) + "\n")
f.write("base_ex " + str(base_ex) + "\n")
f.write("base_in " + str(base_in) + "\n")
f.write("base_cj " + str(base_cj) + "\n")
f.write("base_ex_cj " + str(base_ex_cj) + "\n")
f.write("I_ex " + str(I_ex) + "\n")
f.write("vel_mult " + str(vel_mult) + "\n")
f.write("sh " + str(sh) + "\n")
f.write("base_st_ex " + str(base_st_ex) + "\n")
f.write("I_st " + str(I_st) + "\n")
f.write("cj_population_size " + str(cj_population_size) + "\n")
f.write("in_sigma_scale " + str(in_sigma_scale) + "\n")
f.write("pop_input_vari " + str(pop_input_vari) + "\n")
f.write("pop_output_vari " + str(pop_output_vari) + "\n")
f.write("long " + str(long))
f.close()

# scale the the CJ->EX weight value to be appropriate for pop-size
base_cj /= cj_population_size # CJ -> EX#

# Everything in one big function to reset the kernal just in case
def run(myVel, graph_end=20, smoothVel = False, save_data = True):
    sim.ResetKernel()

    # V_th default = -55
    exc = sim.Create("iaf_psc_alpha",N_ex, params={"I_e": I_ex})
    inh = sim.Create("iaf_psc_alpha",N_in)

    l = sim.Create("iaf_psc_alpha",N_cj * cj_population_size)
    r = sim.Create("iaf_psc_alpha",N_cj * cj_population_size)
    st = sim.Create("iaf_psc_alpha",N_cj * cj_population_size)    # stationary ring

    # connections between excitatory and inhibitory rings described in weight matrices
    w_ex = np.empty((N_in,N_ex))
    w_in = np.empty((N_ex,N_in))

    #loop through each excitatory and inhibitory cell
    for e in range(N_ex):
        for i in range(N_in):
            # find the smallest distance between the excitatory and inhibitory cell, looking both ways around the ring
            d1 = abs(e/N_ex - i/N_in)
            d2 = abs(e/N_ex - i/N_in -1)
            d3 = abs(e/N_ex - i/N_in +1)
            d = min(abs(d1),abs(d2),abs(d3))

            #gaussian function finds the distance dependent connection strength
            w_gauss = np.exp(-(d)**2/2/sigma**2)
            w_ring = np.exp(-(d - 0.5)**2/2/(sigma*in_sigma_scale)**2) #inhibitory connections are ofset by parameter mu  # reduce deviation on inhib
            # scale by base weight and add to matrix
            w_ex[i,e] = base_ex * w_gauss
            w_in[e,i] = base_in * w_ring# * in_sigma_scale    # account for increased distrib        # MISTAKE HERE

    # set all very small weights to zero to reduce total number of connections
    w_ex[w_ex<10]=0
    w_in[w_in<10]=0

    ###
    #
    #   Build CJ weights
    #
    ###

    w_ex_cj = np.identity(N_ex) * base_ex_cj
    if(cj_population_size > 1):
        w_ex_cj = np.repeat(w_ex_cj, cj_population_size, axis=0)
        vec = np.linspace(base_ex_cj * (1+pop_input_vari), base_ex_cj  * (1-pop_input_vari), cj_population_size)
        for i in range(N_ex):
            w_ex_cj = insertVec(w_ex_cj, vec, i, i * cj_population_size, axis = 0)

    w_st_ex = np.identity(N_cj) * base_st_ex
    w_st_ex = np.repeat(w_st_ex, cj_population_size, axis=1)

    # connections between conjuntive layers and excitatory ring
    w_l = np.empty((N_ex,N_cj))
    w_r = np.empty((N_ex,N_cj)) # (row major)
    for c in range(N_cj):
        for e in range(N_ex):
            d1 = abs((e-1)/N_cj - c/N_ex)
            d2 = abs((e-1)/N_cj - c/N_ex -1)
            d3 = abs((e-1)/N_cj - c/N_ex +1)
            d = min(abs(d1),abs(d2),abs(d3))
            w_l[e,c] = base_cj * (np.exp(-(d)**2/2/sigma**2))

            d1 = abs((e+1)/N_cj - c/N_ex)
            d2 = abs((e+1)/N_cj - c/N_ex -1)
            d3 = abs((e+1)/N_cj - c/N_ex +1)
            d = min(abs(d1),abs(d2),abs(d3))
            w_r[e,c] = base_cj * (np.exp(-(d)**2/2/sigma**2))
    #temp1 = w_l
    #for c in range(N_cj):
    #    for e in range(N_ex):
    #        if w_l[e,c] <= w_l[(e+1)%(N_ex),c]:
    #            temp1[e,c] = 0
            #if w_r[(e+1)%(N_ex),c] <= w_r[e,c]:
            #    temp2[(e+1)%(N_ex),c] = 0
    #w_l = temp1
    #w_r = np.transpose(w_l)

    # this only takes the highest value. Takes the blurred line and turns it into a single thin line
    m = np.amax(w_l)
    w_l[w_l<m] = 0
    m = np.amax(w_r)
    w_r[w_r<m] = 0

    if(cj_population_size > 1):
        w_r = np.repeat(w_r, cj_population_size, axis=1)
        w_l = np.repeat(w_l, cj_population_size, axis=1)
        w_r = np.zeros_like(w_l)
        w_l = np.zeros_like(w_r)
        vec = np.linspace(base_cj * (1+pop_output_vari), base_cj * (1-pop_output_vari), cj_population_size)
        for i in range(N_ex):
            w_r = insertVec(w_r, vec, i * cj_population_size, i-1, axis = 1)
        for i in range(N_ex):
            w_l = insertVec(w_l, vec, (i-1) * cj_population_size, i, axis = 1)

    c = plt.matshow(w_l)
    plt.colorbar(c)
    plt.xlabel("CJ neuron index")
    plt.ylabel("EX neuron index")
    plt.tick_params(labelbottom=False,labeltop=True)
    plt.savefig("W_cjL-ex.png", dpi=100*cj_population_size,bbox_inches="tight")
    plt.clf()
    c = plt.matshow(w_r)
    plt.colorbar(c)
    plt.xlabel("CJ neuron index")
    plt.ylabel("EX neuron index")
    plt.tick_params(labelbottom=False,labeltop=True)
    plt.savefig("W_cjR-ex.png", dpi=100*cj_population_size,bbox_inches="tight")
    plt.clf()
    c = plt.matshow(w_ex)
    plt.colorbar(c)
    plt.xlabel("EX neuron index")
    plt.ylabel("IN neuron index")
    plt.tick_params(labelbottom=False,labeltop=True)
    plt.savefig("W_ex-in.png", dpi=300,bbox_inches="tight")
    plt.clf()
    c = plt.matshow(-w_in, cmap=plt.get_cmap("viridis_r"))
    plt.colorbar(c)
    plt.xlabel("IN neuron index")
    plt.ylabel("EX neuron index")
    plt.tick_params(labelbottom=False,labeltop=True)
    plt.savefig("W_in-ex.png", dpi=300,bbox_inches="tight")
    plt.clf()
    c = plt.matshow(w_ex_cj)
    plt.colorbar(c)
    plt.xlabel("EX neuron index")
    plt.ylabel("CJ neuron index")
    plt.tick_params(labelbottom=False,labeltop=True)
    plt.savefig("W_ex_cj.png", dpi=300,bbox_inches="tight")
    plt.clf()
    print("weights vis generated")

    sim.Connect(exc,inh,'all_to_all',syn_spec={'weight': w_ex, 'delay': delay})     # connect Excitatory to Inhibitory
    sim.Connect(inh,exc,'all_to_all',syn_spec={'weight': -w_in, 'delay': delay})    # connect Inhibitory to Excitatory

    sim.Connect(exc,l,'all_to_all',syn_spec={'weight': w_ex_cj, 'delay': delay})    # connect Excitatory to AHV_L
    sim.Connect(exc,r,'all_to_all',syn_spec={'weight': w_ex_cj, 'delay': delay})    # connect Excitatory to AHV_R

    sim.Connect(l,exc,'all_to_all',syn_spec={'weight': w_l, 'delay': delay})        # connect AHV_L to Excitatory
    sim.Connect(r,exc,'all_to_all',syn_spec={'weight': w_r, 'delay': delay})        # connect AHV_R to Excitatory
    if(use_stationary_ring):
        sim.Connect(st,exc,'all_to_all',syn_spec={'weight': w_st_ex, 'delay': delay})   # connect Stationary to Excitatory

    #os._exit(0)

    exc_spikes = sim.Create("spike_detector", 1, params={"withgid": True,"withtime": True}) # write to variable
    sim.Connect(exc,exc_spikes)
    l_spikes = sim.Create("spike_detector", 1, params={"withgid": True,"withtime": True}) # write to variable
    sim.Connect(l,l_spikes)
    r_spikes = sim.Create("spike_detector", 1, params={"withgid": True,"withtime": True}) # write to variable
    sim.Connect(r,r_spikes)
    inh_spikes = sim.Create("spike_detector", 1, params={"withgid": True,"withtime": True}) # write to variable
    sim.Connect(inh,inh_spikes)
    st_spikes = sim.Create("spike_detector", 1, params={"withgid": True,"withtime": True}) # write to variable
    sim.Connect(st,st_spikes)

    ###
    #
    #   Create vel curve
    #
    ###

    # posedata describes an actual path, but I can just set vel to a desired value
    #posedata = pandas.read_csv(f'data/{folder}/{folder}.csv')

    dt = 20 # 20ms, 50Hz
    #theta = posedata['field.theta']
    #theta = np.array(theta)
    targetPos = np.pi #*4 # radians
    if(long):
        targetPos *= 4
    duration = (targetPos / (myVel * np.pi/180)) * 1000
    framecount = duration // dt
    theta = np.linspace(0,targetPos,int(framecount)) # in radians
    if(smoothVel): theta = smoothclamp(theta,0,targetPos) #smooth clamp velocity
    #print(theta)
    angle_per_cell = (2*np.pi)/N_ex
    I_init_pos = np.around((theta[0]//angle_per_cell)+(N_ex//2)).astype(int)

    t = np.arange(0,len(theta)*dt,dt*1.) #assume 20ms timestep
    sim_len = len(theta) * 20
    time = [i for i in t if i < sim_len]

    vel = np.diff(np.unwrap(theta))
    print(len(theta))

    #arbitary scaling
            #vel rescale
    #print("downscaled vel: " + str(vel[0]))
    #if(vel_rescale == True):
    #    vel = (vel + 14) / 26

    Ivel = (vel) * vel_mult
    #Ivel *= Ivel

    fig = plt.figure()
    ax = plt.axes()
    ax.plot(t[1:], Ivel)
    fig.savefig("base vel over time.png")
    plt.clf()

    #os._exit(0)
    #print(vel)
    #print(Ivel)

    #st_i = velCurve(Ivel, I_st, 1, 0)
    st_i = np.ones_like(Ivel)
    st_i *= I_st
    #Ivel = velCurve(Ivel, -I_st, 1, I_st)

    go_l,go_r = Ivel,-Ivel #separate into clockwise and anticlockwise movements
    go_l = go_l+sh
    go_r = go_r+sh
    go_l[go_l<=sh] = 0 # everything below the threshold set to 0pA
    go_r[go_r<=sh] = 0

    # Connect AV input to conjunctive layers
    l_input = sim.Create('step_current_generator', 1)
    sim.SetStatus(l_input,{'amplitude_times': t[1:],'amplitude_values': go_l})  # current in picoamperes
    r_input = sim.Create('step_current_generator', 1)
    sim.SetStatus(r_input,{'amplitude_times': t[1:],'amplitude_values': go_r})
    st_input = sim.Create('step_current_generator', 1)
    sim.SetStatus(st_input,{'amplitude_times': t[1:],'amplitude_values': st_i})

    sim.Connect(r_input,r,'all_to_all')
    sim.Connect(l_input,l,'all_to_all')
    sim.Connect(st_input,st,'all_to_all')

    #os._exit(0)

    bump_init = sim.Create('step_current_generator', 1, params = {'amplitude_times':[0.1,0.1+I_init_dur],'amplitude_values':[I_init,0.0]})
    sim.Connect(bump_init,[exc[I_init_pos]])

    tic = tm.time()
    sim.Simulate(sim_len)
    print(f'Simulation run time: {np.around(tm.time()-tic,2)} s  Simulated time: {np.around(sim_len/1000,2)} s')

    if myVel >= 5:
        r_st = 0
        # Render all neuron histories
        T = np.arange(0,(len(theta)*dt),dt)   # Column
        fig_h = 2*(4+r_st)
        fig_l = len(T)/180 * fig_h
        fig, ax=plt.subplots(4+r_st,1, figsize=(fig_l, fig_h), sharex=True,)
        plt.xlabel("Timestep (20ms)")

        offset = 1
        ex_mat = np.zeros((N_ex, len(T)))
        ev = sim.GetStatus(exc_spikes)[0]['events']
        t = ev['times'] # time of each spike (in simulation time)
        sp = ev['senders'] # sender of each spike (cell ID)
        for i in range(len(T)-1):
            idx = (t>T[i])*(t<T[i+1]) # find all spikes in the bin
            sender_list = sp[np.where(idx)] # get the senders of each spike in the bin#
            for j in sender_list:
                ex_mat[j-offset][i] += 1
        ax[2+r_st].set_title("Exitatory ring spike history")
        plot = ax[2+r_st].imshow(ex_mat, cmap='hot', interpolation='nearest')
        ax[2+r_st].set_ylabel("Neuron index")
        plt.colorbar(plot,ax=ax[2+r_st],pad=ringhist_pad)

        offset += N_ex
        in_mat = np.zeros((N_in, len(T)))
        ev = sim.GetStatus(inh_spikes)[0]['events']
        t = ev['times'] # time of each spike (in simulation time)
        sp = ev['senders'] # sender of each spike (cell ID)
        for i in range(len(T)-1):
            idx = (t>T[i])*(t<T[i+1]) # find all spikes in the bin
            sender_list = sp[np.where(idx)] # get the senders of each spike in the bin#
            for j in sender_list:
                in_mat[j-offset][i] += 1
        ax[3+r_st].set_title("Inhibitory ring spike history")
        plot = ax[3+r_st].imshow(in_mat, cmap='hot', interpolation='nearest')
        ax[3+r_st].set_ylabel("Neuron index")
        plt.colorbar(plot,ax=ax[3+r_st],pad=ringhist_pad)

        offset += N_in
        cjl_mat = np.zeros((N_cj, len(T)))
        ev = sim.GetStatus(l_spikes)[0]['events']
        t = ev['times'] # time of each spike (in simulation time)
        sp = ev['senders'] # sender of each spike (cell ID)
        for i in range(len(T)-1):
            idx = (t>T[i])*(t<T[i+1]) # find all spikes in the bin
            sender_list = sp[np.where(idx)] # get the senders of each spike in the bin#
            for j in sender_list:
                cjl_mat[(j-offset)//cj_population_size][i] += 1
        ax[1+r_st].set_title("Left Conjunctive ring spike history")
        plot = ax[1+r_st].imshow(cjl_mat, cmap='hot', interpolation='nearest')
        if(cj_population_size>1):
            ax[1+r_st].set_ylabel("Population index")
        else:
            ax[1+r_st].set_ylabel("Neuron index")
        plt.colorbar(plot,ax=ax[1+r_st],pad=ringhist_pad)

        offset += N_cj*cj_population_size
        cjr_mat = np.zeros((N_cj, len(T)))
        ev = sim.GetStatus(r_spikes)[0]['events']
        t = ev['times'] # time of each spike (in simulation time)
        sp = ev['senders'] # sender of each spike (cell ID)
        for i in range(len(T)-1):
            idx = (t>T[i])*(t<T[i+1]) # find all spikes in the bin
            sender_list = sp[np.where(idx)] # get the senders of each spike in the bin#
            for j in sender_list:
                cjr_mat[(j-offset)//cj_population_size][i] += 1
        ax[0+r_st].set_title("Right Conjunctive ring spike history")
        plot = ax[0+r_st].imshow(cjr_mat, cmap='hot', interpolation='nearest')
        if(cj_population_size>1):
            ax[0+r_st].set_ylabel("Population index")
        else:
            ax[0+r_st].set_ylabel("Neuron index")
        plt.colorbar(plot,ax=ax[0+r_st],pad=ringhist_pad)

        if(r_st == 1):
            offset += N_cj*cj_population_size
            st_mat = np.zeros((N_cj, len(T)))
            ev = sim.GetStatus(st_spikes)[0]['events']
            t = ev['times'] # time of each spike (in simulation time)
            sp = ev['senders'] # sender of each spike (cell ID)
            for i in range(len(T)-1):
                idx = (t>T[i])*(t<T[i+1]) # find all spikes in the bin
                sender_list = sp[np.where(idx)] # get the senders of each spike in the bin#
                for j in sender_list:
                    st_mat[(j-offset)//cj_population_size][i] += 1
            ax[0].set_title("Stationary Conjunctive ring spike history")
            plot = ax[0].imshow(st_mat, cmap='hot', interpolation='nearest')
            if(cj_population_size>1):
                ax[0].set_ylabel("Population index")
            else:
                ax[0].set_ylabel("Neuron index")
            plt.colorbar(plot,ax=ax[0],pad=ringhist_pad)

        fig.tight_layout()
        plt.savefig("results/"+folder+"/ring_hist/Ring histories " + str(myVel) +".png", dpi=150,bbox_inches="tight")
        plt.clf()

    T1 = np.arange(0,(len(theta)*dt),dt)   # 20ms bins
    offset = N_ex + N_in + 1
    ## Diagnose CJ spikes
    ev = sim.GetStatus(l_spikes)[0]['events']
    t = ev['times'] # time of each spike (in simulation time)
    sp = ev['senders'] # sender of each spike (cell ID)
    rate = np.zeros(len(T1)-1)
    for i in range(len(T1)-1):
        idx = (t>T1[i])*(t<T1[i+1]) # find all spikes in the bin
        sender_list = sp[np.where(idx)] # get the senders of each spike in the bin
        #print(sender_list)
        pop_list = np.zeros(N_cj)
        for j in sender_list:
            pop_list[(j-offset)//cj_population_size] += 1
        #print(pop_list)
        mode = pop_list.max() # find most common sender
        rate[i] = mode # spikerate in Hz
    #print("AHV spikes: " + str(rate))
    average_rate = rate.sum()/len(rate) #avg per timestep
    average_rate = average_rate * 50 #avg in seconds
    print("cj Hz: " + str(average_rate))
    average_cj_rate = average_rate

    ## ST spike rate
    ev = sim.GetStatus(st_spikes)[0]['events']
    t = ev['times'] # time of each spike (in simulation time)
    sp = ev['senders'] # sender of each spike (cell ID)
    modes = np.zeros(len(T1))
    modes[:] = np.nan
    rate = np.zeros(len(T1)-1)
    for i in range(len(T1)-1):
        idx = (t>T1[i])*(t<T1[i+1]) # find all spikes in the bin
        sender_list = sp[np.where(idx)] # get the senders of each spike in the bin
        occurence_count = Counter(sender_list)
        mode = occurence_count.most_common(1) # find most common sender
        if(len(mode)>0):
            rate[i] = mode[0][1] # spikerate in Hz
        else:
            rate[i] = 0
    average_rate = rate.sum()/len(rate) #avg per timestep
    average_rate = average_rate * 50 #avg in seconds
    print("st Hz: " + str(average_rate))

    ## ex spike rate
    ev = sim.GetStatus(exc_spikes)[0]['events']
    t = ev['times'] # time of each spike (in simulation time)
    sp = ev['senders'] # sender of each spike (cell ID)
    modes = np.zeros(len(T1))
    modes[:] = np.nan
    rate = np.zeros(len(T1)-1)
    for i in range(len(T1)-1):
        idx = (t>T1[i])*(t<T1[i+1]) # find all spikes in the bin
        sender_list = sp[np.where(idx)] # get the senders of each spike in the bin
        occurence_count = Counter(sender_list)
        mode = occurence_count.most_common(1) # find most common sender
        if(len(mode)>0):
            rate[i] = mode[0][1] # spikerate in Hz
        else:
            rate[i] = 0
    average_rate = rate.sum()/len(rate) #avg per timestep
    average_rate = average_rate * 50 #avg in seconds
    print("ex Hz: " + str(average_rate))
    average_ex_rate = average_rate

    T2 = np.arange(0,(len(theta)*dt),dt*2)   # 40ms bins

    # get the spike times of the cells from the spike recorder
    ev = sim.GetStatus(exc_spikes)[0]['events']
    t = ev['times'] # time of each spike (in simulation time)
    sp = ev['senders'] # sender of each spike (cell ID)
    #find the most active cell in each 40ms bin
    modes = np.zeros(len(T2))
    modes[:] = np.nan
    for i in range(len(T2)-1):
        idx = (t>T2[i])*(t<T2[i+1]) # find all spikes in the bin
        lst = sp[np.where(idx)] # get the senders of those spikes
        occurence_count = Counter(lst)
        mode = occurence_count.most_common(1) # find most common sender
        if len(mode):
            modes[i] = mode[0][0]
    step = (2*np.pi)/N_ex
    modes = (modes*step) - np.pi

    if  myVel >= 5:
        # fig, ax = plt.subplots(1, 1,figsize=(5, 2),facecolor='w')
        fig, ax = plt.subplots(1, 1,figsize=(8, 3),facecolor='w')
        ax.set_xlabel('Time (s)')
        ax.set_ylabel('Head angle (deg)')
        ax.set_title('AHV = ' + str(myVel) + " " + deg_sign + "/s")

        theta_wrap = (theta + np.pi) % (2*np.pi) - np.pi
        #print(theta_wrap)
        ax.plot(T2/1000,modes*(180/np.pi),'.',label='HD signal',color='steelblue')
        ax.plot(np.array(time)/1000,(theta_wrap[:len(time)])*(180/np.pi),'.',markersize=.5,label='ground truth',color='black')
        ax.set_xlim([0,sim_len/1000])
        ax.set_yticks([-180,-135,-90,-45,0,45,90,135,180])
        #ax.set_yticks([0,45,90,135,180])
        #ax.set_ylim([-10, 190])
        #ax2 = ax.twinx()
        #ax2.plot(T1[1:]/1000,rate, 'ro-', markersize=.5, alpha=0.6, linewidth=1)
        #ax2.set_ylabel("AHV cell firerate per timestep",color="red",fontsize=14)
        #ax2.set_ylim([0, 8])

        fig.legend(bbox_to_anchor=(1.05, 1),loc='upper left', borderaxespad=0.)
        plt.tight_layout()
        plt.savefig("results/"+folder+"/drift_hist/"+str(myVel)+"_drift.png", dpi=300,bbox_inches="tight")
        plt.clf()

    # Compare against ground truth
    nanidx = np.where(~np.isnan(modes[:-1]))
    modes = modes[nanidx]
    T2=T2[nanidx]

    est = np.unwrap(modes)
    groundTruth = np.unwrap(theta[:len(time)])

    estimate = np.interp(time, T2, est)

    hd_estimate = np.vstack([T2,modes])
    np.save(f'results/{folder}/{folder}_estimate.npy',hd_estimate)

    # Find net RMSE
    fig, ax = plt.subplots(1, 1,figsize=(5, 2),facecolor='w')

    d = (estimate-groundTruth)

    if  myVel >= 5:
        plt.plot(np.array(time)/1000,abs(d)*(180/np.pi),color='steelblue')
        plt.xlabel('Time (s)')
        plt.ylabel('Abs Error (deg)')
        plt.xlim([0,sim_len/1000])
        plt.yticks([-45,0,45,90,135,180])
        plt.tight_layout()
        plt.savefig(f'results/{folder}/{folder}_error.png', bbox_inches="tight")
        plt.clf

    RMSE = np.sqrt(np.sum(d**2)/len(d))
    print("RMSE: " + str(RMSE*(180/np.pi)))

    drift = d[-1]*(180/np.pi) * 50 / len(theta) # drift per second

    print("Vel: " + str(myVel))
    print("Ivel: " + str(Ivel[1]))
    print("Total drift: " + str(d[-1]*(180/np.pi)))
    print("Drift per second: " + str(drift))

    if not save_data:
        return
    veldrift_f = open("results/" + folder + "/vel_drift_data.txt", "a")
    veldrift_f.write(str(myVel) + " " + str(drift) + " " + str(average_ex_rate) + " " + str(average_cj_rate) + "\n")
    veldrift_f.close()

    # draw all drift against vel values
    X = []
    Y = []
    Z = []

    f = open("results/"+folder+"/vel_drift_data.txt")
    for row in f:
        row = row.split(' ')
        X.append(float(row[0]))
        Y.append(float(row[1]))
        Z.append(float(row[3]))

    Y = [y for _,y in sorted(zip(X,Y))]
    Z = [z for _,z in sorted(zip(X,Z))]
    X.sort()

    fig,ax = plt.subplots()
    ax.hlines(0, 0, 1000, 'k', alpha=0.5)
    ax.plot(X, Y, 'bo-', markersize=3)
    ax.set_xlim([0, 80])
    ax.set_ylabel("Rate of Drift (" + deg_sign + "/s)",color="Blue",fontsize=14)
    ax.set_xlabel("Fixed AHV (" + deg_sign + "/s)",fontsize=14)
    ax2=ax.twinx()
    ax2.plot(X, Z, 'ro-', markersize=0.5, alpha=0.7)
    ax2.set_ylabel("Average peak CJ firerate (Hz)",color="red",fontsize=14)
    ax2.set_ylim([20, 70])
    fig.savefig("results/"+folder+"/DriftThruVel.png")
    plt.clf()
    print("DONE")

    plt.close('all')

loop = True
if(loop):
    start = 5
    end = 80
    step = 1
    for i in np.arange(start, end+step, step):
        print("\n\nRunning vel " + str(i))
        run(i, graph_end = end)
    print("\nCompleted batch " + folder + "\n")
else:
    run(50, save_data = False)


import cv2
import mediapipe as mp
import math
from typing import List, Mapping, Optional, Tuple, Union
import numpy as np

from mediapipe.framework.formats import landmark_pb2
from mediapipe.python.solutions.drawing_utils import DrawingSpec, _normalized_to_pixel_coordinates

WHITE_COLOR = (224, 224, 224)
BLACK_COLOR = (0, 0, 0)
RED_COLOR = (0, 0, 255)
GREEN_COLOR = (0, 255, 0)
YELLOW_COLOR = (0, 255, 255)
_PRESENCE_THRESHOLD = 0.5
_VISIBILITY_THRESHOLD = 0.5
_RGB_CHANNELS = 3

class UtilFunctions:
    """
    Estimates Pose points of a human body using the mediapipe library.
    """

    def __init__(self, view, mode=False, smooth=True,
                 detectionCon=0.6, trackCon=0.6):
        """
        :param mode: In static mode, detection is done on each image: slower
        :param upBody: Upper boy only flag
        :param smooth: Smoothness Flag
        :param detectionCon: Minimum Detection Confidence Threshold
        :param trackCon: Minimum Tracking Confidence Threshold
        """
        self.min_angle = 180
        self.max_distance = -1
        self.view = view
        self.mode = mode
        self.smooth = smooth
        self.detectionCon = detectionCon
        self.trackCon = trackCon
        self.visibility = [-1 for i in range(34)]
        self.stable_values = [[0, 0, 0] for i in range(34)]

        self.mpDraw = mp.solutions.drawing_utils
        self.mpPose = mp.solutions.pose
        self.pose = self.mpPose.Pose(static_image_mode=self.mode,
                                     smooth_landmarks=self.smooth,
                                     min_detection_confidence=self.detectionCon,
                                     min_tracking_confidence=self.trackCon, model_complexity=1)

    def findPose(self, img, world=False):
        """
        Find the pose landmarks in an Image of BGR color space.
        :param img: Image to find the pose in.
        :param draw: Flag to draw the output on the image.
        :return: Image with or without drawings
        """
        imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        results = self.pose.process(imgRGB)
        if world:
            return np.array(img), results.pose_world_landmarks
        else:
            return np.array(img), results.pose_landmarks

    def draw(self, img, landmarks_list):
        if landmarks_list:
            self.mpDraw.draw_landmarks(img, landmarks_list,
                                       self.mpPose.POSE_CONNECTIONS,
                                       landmark_drawing_spec=mp.solutions.drawing_styles.get_default_pose_landmarks_style()
                                       )

    def findPosition(self, img, landmarks):
        lmList = []
        if landmarks:
            h, w, c = img.shape
            for id, lm in enumerate(landmarks.landmark):
                lmList.append([id, int(lm.x * w), int(lm.y * h), lm.visibility])
        return lmList

    def check_visibility(self, landmarks_list, check):
        if landmarks_list:
            return landmarks_list.landmark[check].visibility

    def update_landmark(self, landmarks_list, joint):
        """
        Find the pose landmarks in an Image of BGR color space.
        :param landmarks_list: A normalized landmark list which needs to be updated
        :param joint: Flag to draw the output on the image.
        :ret : updated Landmarks_list
        """
        # This function updates the more stable value to the join under consideration
        if landmarks_list:
            if self.check_visibility(landmarks_list, joint) >= (
                    self.visibility[joint] - self.visibility[joint] * 0.008):
                self.stable_values[joint][0] = landmarks_list.landmark[joint].x
                self.stable_values[joint][1] = landmarks_list.landmark[joint].y
                self.stable_values[joint][2] = landmarks_list.landmark[joint].z

                self.visibility[joint] = self.check_visibility(landmarks_list, joint)

        landmarks_list.landmark[joint].x = self.stable_values[joint][0]
        landmarks_list.landmark[joint].y = self.stable_values[joint][1]
        landmarks_list.landmark[joint].z = self.stable_values[joint][2]

        return landmarks_list

    def drawLine(self, img, pt1, pt2, ptcolor, line_color, draw_points=True, r=8, t=2):
        cv2.line(img, pt1, pt2, line_color, t)
        if draw_points:
            cv2.circle(img, pt1, r, ptcolor, cv2.FILLED)
            cv2.circle(img, pt2, r, ptcolor, cv2.FILLED)

    def findAngle(self, img, p1, p2, p3, lmlist, draw=True):
        """
        Finds angle between three points. Inputs index values of landmarks
        instead of the actual points.
        :param img: Image to draw output on.
        :param p1: Point1 - Index of Landmark 1.
        :param p2: Point2 - Index of Landmark 2.
        :param p3: Point3 - Index of Landmark 3.
        :param draw:  Flag to draw the output on the image.
        :return:
        """

        # Get the landmarks
        x1, y1 = lmlist[p1][1:3]
        x2, y2 = lmlist[p2][1:3]
        x3, y3 = lmlist[p3][1:3]

        # Calculate the Angle
        angle = math.degrees(math.atan2((y3 - y2), (x3 - x2)) -
                             math.atan2((y1 - y2), (x1 - x2)))
        if angle < 0:
            angle+=360
        # Draw
        if draw:
            self.drawLine(img, (x1, y1), (x2, y2), RED_COLOR, WHITE_COLOR)
            self.drawLine(img, (x3, y3), (x2, y2), RED_COLOR, RED_COLOR)
            cv2.putText(img, str(int(angle)), (x2 - 50, y2 + 50),
                        cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255), 2)
        return angle

    def findDistance(self, p1, p2, img, lmlist, draw=True, r=15, t=4):
        x1, y1 = lmlist[p1][1:3]
        x2, y2 = lmlist[p2][1:3]
        cx, cy = (x1 + x2) // 2, (y1 + y2) // 2

        if draw:
            self.drawLine(img, (x1, y1), (x2, y2), (255, 0, 255), (255, 0, 255))

        length = math.hypot(x2 - x1, y2 - y1)

        return length, img, [x1, y1, x2, y2, cx, cy]

    def checkCurvature(self, p1, p2, img, lmlist, draw=True):
        x1, y1 = lmlist[p1][1:3]
        x2, y2 = lmlist[p2][1:3]
        cx, cy = (x1 + x2) // 2, (y1 + y2) // 2

        length = math.hypot(x2 - x1, y2 - y1)
        self.max_distance = max(length, self.max_distance)
        #print(self.max_distance, length)
        if length + 25 >= self.max_distance:
            if draw:
                self.drawLine(img, (x1, y1), (cx, cy), GREEN_COLOR, GREEN_COLOR)
                self.drawLine(img, (x2, y2), (cx, cy), GREEN_COLOR, GREEN_COLOR)
            return GREEN_COLOR
        else:
            if draw:
                self.drawLine(img, (x1, y1), (cx, cy), RED_COLOR, RED_COLOR)
                self.drawLine(img, (x2, y2), (cx, cy), RED_COLOR, RED_COLOR)
            return RED_COLOR

    def angleCheck(self, img, p1, p2,p3, target, isleftView, lmlist, addOn=13, cutoff=45, draw=True):
        x1, y1 = lmlist[p1][1:3]
        x2, y2 = lmlist[p2][1:3]
        x3, y3 = x1, y2
        if isleftView:
            return self.angleCheckLeft(img, p1,p2,p3, target, lmlist,addOn, cutoff, draw)
        # Calculate the Angle
        angle = math.degrees(math.atan2((y3 - y2), (x3 - x2)) -
                             math.atan2((y1 - y2), (x1 - x2)))
        #print("here", angle)
        if -3 < angle < target + addOn and angle < cutoff:
            if draw:
                self.drawLine(img, (x3, y3), (x2, y2), GREEN_COLOR, GREEN_COLOR)
                cv2.putText(img, str(int(angle)), (x2 - 50, y2 + 50),
                            cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)
            return GREEN_COLOR, angle
        elif angle < -3:
            if draw:
                self.drawLine(img, (x3, y3), (x2, y2), YELLOW_COLOR, YELLOW_COLOR)
                cv2.putText(img, str(int(angle)), (x2 - 50, y2 + 50),
                            cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255), 2)
            return YELLOW_COLOR, angle
        elif angle < cutoff:
            if draw:
                self.drawLine(img, (x3, y3), (x2, y2), RED_COLOR, RED_COLOR)
                cv2.putText(img, str(int(angle)), (x2 - 50, y2 + 50),
                            cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255), 2)

            return RED_COLOR, angle
        else:
            return BLACK_COLOR, angle

    def generate_angle_Feedback(self, back_angle_color):
        result = []
        if back_angle_color == RED_COLOR:
            result.append("Don't raise your back too much")
        elif back_angle_color == YELLOW_COLOR:
            result.append("Keep your torso parallel to the floor")
        else:
            result.append("Good Form! keep you back in this range.")

        return result[0], back_angle_color

    def generate_back_feedback(self, back_color):
        result = []
        if back_color == RED_COLOR:
            result.append("Don't round/curve your spine!")
        else:
            result.append("Good form! spine is natural and not rounded")
        return result[0], back_color

    def display_feedback(self, img, result_back, result_back_angle, color_back, color_angle):
        cv2.rectangle(img, (0, 0), (560, 80), (0, 0, 0), -1)
        cv2.putText(img, result_back, (0, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.75, color_back, 2, cv2.LINE_AA)
        cv2.putText(img, result_back_angle, (0, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.75, color_angle, 2, cv2.LINE_AA)

    def angleCheckLeft(self, img, p1, p2, p3, target, lmlist, addOn, cutoff, draw):
        # Calculate the Angle
        angle = self.findAngle(img,p1,p2,p3,lmlist, draw = False)

        x1, y1 = lmlist[p1][1:3]
        x2, y2 = lmlist[p2][1:3]
        x3, y3 = lmlist[p3][1:3]

        if angle > self.min_angle == 180:
            self.min_angle = angle
        print(self.min_angle-angle)
        if -3 < self.min_angle-angle < target + addOn and self.min_angle-angle < cutoff:
            if draw:
                self.drawLine(img, (x3, y3), (x2, y2), GREEN_COLOR, GREEN_COLOR)
                cv2.putText(img, str(int(angle)), (x2 - 50, y2 + 50),
                            cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)
            return GREEN_COLOR, self.min_angle-angle
        elif self.min_angle - angle < -3:
            if draw:
                self.drawLine(img, (x3, y3), (x2, y2), YELLOW_COLOR, YELLOW_COLOR)
                cv2.putText(img, str(int(angle)), (x2 - 50, y2 + 50),
                            cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255), 2)
            return YELLOW_COLOR, self.min_angle-angle
        elif self.min_angle - angle < cutoff:
            if draw:
                self.drawLine(img, (x3, y3), (x2, y2), RED_COLOR, RED_COLOR)
                cv2.putText(img, str(int(angle)), (x2 - 50, y2 + 50),
                            cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255), 2)

            return RED_COLOR, self.min_angle-angle
        else:
            self.drawLine(img, (x3, y3), (x2, y2), (255,0,0), (255,0,0))
            return BLACK_COLOR, self.min_angle-angle

    def slopeCheckBarbell(self, img, landmarks, draw = True):
        img_h, img_w, _ = img.shape
        left_wrist = [int(landmarks.landmark[15].x*img_w), int(landmarks.landmark[15].y*img_h), int(landmarks.landmark[15].z*img_w)]
        right_wrist = [int(landmarks.landmark[16].x*img_w), int(landmarks.landmark[16].y*img_h), int(landmarks.landmark[16].z*img_w)]

        wrist_slope = pow((left_wrist[2] - right_wrist[2]), 2) / math.sqrt(
            pow((left_wrist[0] - right_wrist[0]), 2) + pow((left_wrist[1] - right_wrist[1]), 2))
        left_ankle = [landmarks.landmark[27].x, landmarks.landmark[27].y, landmarks.landmark[27].z]
        right_ankle = [landmarks.landmark[28].x, landmarks.landmark[28].y, landmarks.landmark[28].z]

        ankle_slope = pow((left_ankle[2] - right_ankle[2]), 2) / math.sqrt(
            pow((left_ankle[0] - right_ankle[0]), 2) + pow((left_ankle[1] - right_ankle[1]), 2))
        # print("slope of wrists:",wrist_slope)
        # print(wrist_slope - ankle_slope)
        if abs(wrist_slope - ankle_slope) < 2:
            if draw:
                self.drawLine(img, left_wrist[0:2], right_wrist[0:2], GREEN_COLOR, GREEN_COLOR, draw_points=False)
            return GREEN_COLOR
        else:
            if draw:
                self.drawLine(img, left_wrist[0:2], right_wrist[0:2], RED_COLOR, RED_COLOR, draw_points=False)
            return RED_COLOR

    def generate_barbell_feedback(self, barbell_color):
        result = []
        if barbell_color == RED_COLOR:
            result.append("The barbell is tilting keep it straight!")
        else:
            result.append("Good! Keep the barbell straight. ")
        return result[0], barbell_color


import tkinter as tk
import tkinter.scrolledtext
import tkinter.messagebox
import socket
import threading
import os

DISCONNECTED = 0
CONNECTED = 1

class GUI:
    def __init__(self, master):
        master.geometry('900x700')
        master.title('Sample Messagebox')
        master.resizable(width=False, height=False)
        self.master = master
        self.msg_dict = {
            'NEW_CLIENT_LOGGEDIN': self.new_client_loggedin_proc,
            'LOGGEDIN_CLIENT_LIST': self.loggedin_client_list_proc,
            'RECEIVE_MSG': self.receive_msg_proc,
            'LOGOUT_ACCEPTED': self.logout_accepted_proc,
            'CLIENT_LOGGEDOUT': self.client_loggedout_proc,
        }

        self.status_label_connect_var = tk.StringVar()
        self.status_label_connect_var.set('Not Connected')
        self.entry_chat_var = tk.StringVar()

        self.menu_bar = tk.Menu(master)
        self.client_popup = tk.Menu(tearoff=0)
        self.menu_bar.add_cascade(label='Client', menu=self.client_popup, underline=0)

        self.client_popup.add_command(label='Connect...', command=self.client_connect_handler, underline=1, accelerator='Ctrl+O')
        self.client_popup.add_command(label='Disconnect', command=self.client_disconnect_handler, underline=0, accelerator='Ctrl+D', state='disabled')
        master.bind('<Control-o>', lambda  event: self.client_connect_handler())
        master.bind('<Control-d>', lambda event: self.client_disconnect_handler())
        master.config(menu=self.menu_bar)

        master.protocol('WM_DELETE_WINDOW', self.window_close_handler)

        self.status_frame = tk.Frame(master)
        self.status_connection_label = tk.Label(self.status_frame, relief=tk.SUNKEN, anchor='w', padx=(3, ), textvariable=self.status_label_connect_var)
        self.status_connection_label.pack(side='left', fill='x', expand=True)
        self.status_frame.pack(side='bottom', fill='x')

        self.frame = tk.Frame(self.master)
        self.text_chat = tk.scrolledtext.ScrolledText(self.frame, font='Calibri 14', width=60)
        self.listbox_clients = tk.Listbox(self.frame, font='Calibri 14', width=25)
        self.entry_chat = tk.Entry(self.master, font='Calibri 14', textvariable=self.entry_chat_var)
        self.button_ok = tk.Button(self.master, text='Ok', width=10, command=self.button_ok_handler)

        self.master.bind('<Return>', lambda event: self.button_ok_handler())
        self.will_close = False
        self.status = DISCONNECTED

    def show_widgets(self):
        self.text_chat.pack(side='left', padx=(0, 20), fill='y', anchor='n')
        self.listbox_clients.pack(side='left', fill='y', anchor='n')
        self.frame.pack(side='top', fill='x', anchor='n', padx=(10, 10), pady=(10, 10))
        self.entry_chat.pack(side='top', fill='x', padx=(10, 10), pady=(10, 10))
        self.button_ok.pack(side='top', padx=(10, 10), pady=(10, 10))

        self.master.bind('<Return>', lambda event: self.button_ok_handler())
        self.entry_chat.focus()

    def hide_widgets(self):
        self.text_chat.delete('1.0', 'end')
        self.text_chat.pack_forget()
        self.listbox_clients.delete(0, 'end')
        self.listbox_clients.pack_forget()
        self.frame.pack_forget()
        self.entry_chat.pack_forget()
        self.button_ok.pack_forget()

    def button_ok_handler(self):
        self.fw_sock.write(f'SEND_MSG {self.entry_chat_var.get()}\n')
        self.fw_sock.flush()
        self.entry_chat_var.set('')
        self.entry_chat.focus()

    def client_connect_handler(self):
        cd = ConnectDialog(self.master)
        self.master.wait_window(cd)
        if cd.result:
            self.thread = threading.Thread(target=self.thread_proc, args=(cd.server_name, cd.server_port, cd.nick), daemon=True)
            self.thread.start()

    def client_disconnect_handler(self):
        self.fw_sock.write('LOGOUT \n')
        self.fw_sock.flush()

    def thread_proc(self, server_name, server_port, nick):
        try:
            self.client_sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP)
            self.client_sock.connect((server_name, server_port))
            self.fr_sock = self.client_sock.makefile('r', encoding='UTF-8')
            self.fw_sock = self.client_sock.makefile('w', encoding='UTF-8')
            self.server_name = server_name
            self.server_port = server_port
            self.nick = nick

            self.login_proc()
        except Exception as e:
            tk.messagebox.showerror(title='Error', message=e)

    def login_proc(self):
        self.fw_sock.write(f'LOGIN {self.nick}\n')
        self.fw_sock.flush()

        response = self.fr_sock.readline().strip()
        if not response:
            self.socket_closed()
            return

        msg, args = self.cmd_split(response)
        if msg == 'LOGIN_ACCEPTED':
            self.status_label_connect_var.set('Connected')
            self.client_popup.entryconfig(0, state='disabled')
            self.client_popup.entryconfig(1, state='normal')
            self.show_widgets()
            self.status = CONNECTED
            self.msg_proc()
            return

        if msg == 'LOGIN_REJECTED':
            tk.messagebox.showerror(title='Error', message=args)
            return

    def msg_proc(self):
        try:
            while True:
                msg_line = self.fr_sock.readline().strip()
                msg, params = self.cmd_split(msg_line)
                f = self.msg_dict.get(msg)
                if not f:
                   break

                f(params)
        except:
            pass

    def new_client_loggedin_proc(self, params):
       self.listbox_clients.insert(tk.END, params)

    def loggedin_client_list_proc(self, params):
        for nick in params.split(','):
            self.listbox_clients.insert(tk.END, nick)

    def receive_msg_proc(self, params):
        self.text_chat.insert('end', params + os.linesep)
        self.text_chat.yview('end')

    def logout_accepted_proc(self, params):
        self.fw_sock.close()
        if self.will_close:
            self.master.destroy()
        self.status_label_connect_var.set('Not Connected')
        self.client_popup.entryconfig(0, state='normal')
        self.client_popup.entryconfig(1, state='disabled')
        self.hide_widgets()
        self.status = DISCONNECTED

    def client_loggedout_proc(self, params):
        index = self.listbox_clients.get(0, 'end').index(params)
        self.listbox_clients.delete(index)

    @staticmethod
    def cmd_split(msg_line):
        index = msg_line.find(' ')
        if index == -1:
            return msg_line, ''
        return msg_line[:index], msg_line[index + 1:]

    def socket_closed(self):
        self.status_label_connect_var.set('Connected')
        tk.messagebox.showwarning(title='Warninh', message='connection closed')

    def window_close_handler(self):
        if self.status == CONNECTED:
            self.will_close = True
            self.master.after(3000, lambda: self.master.destroy())
            self.client_disconnect_handler()
        else:
            self.master.destroy()

class ConnectDialog(tk.Toplevel):
    def __init__(self, master):
        super(ConnectDialog, self).__init__()
        self.geometry('380x170+300+400')
        self.title('Connection Dialog')
        self.resizable(width=False, height=False)
        self.master = master

        self.entry_host_var = tk.StringVar()
        self.entry_port_var = tk.StringVar()
        self.entry_nick_var = tk.StringVar()

        self.entry_host_var.set('localhost')
        self.entry_port_var.set('50050')

        self.transient(master)
        self.grab_set()

        self.label_host = tk.Label(self, text='Host', width=6, font='Arial 14')
        self.label_host.grid(row=0, column=0, pady=5)

        self.entry_host = tk.Entry(self, width=25, textvariable=self.entry_host_var, font='Arial 14')
        self.entry_host.grid(row=0, column=1)

        self.label_port = tk.Label(self, text='Port', width=6, font='Arial 14')
        self.label_port.grid(row=1, column=0, pady=5)

        self.entry_port = tk.Entry(self, width=25, textvariable=self.entry_port_var, font='Arial 14')
        self.entry_port.grid(row=1, column=1)

        self.label_nick = tk.Label(self, text='Nick', width=6, font='Arial 14')
        self.label_nick.grid(row=2, column=0, pady=5)

        self.entry_nick = tk.Entry(self, width=25, textvariable=self.entry_nick_var, font='Arial 14')
        self.entry_nick.grid(row=2, column=1)

        self.frame = tk.Frame(self)
        self.button_connect = tk.Button(self.frame, text='Connect', width=8, command=self.connect_proc)
        self.button_connect.pack(side='left', padx=(0, 10))
        self.button_cancel = tk.Button(self.frame, text='Cancel', width=8,  command=self.cancel_proc)
        self.button_cancel.pack(side='left')
        self.frame.grid(row=3, column=1, sticky='e', pady=(20, 0))

        self.bind('<Return>', lambda event: self.connect_proc())

        self.entry_nick.focus()

    def connect_proc(self):
        self.result = False
        try:
            nick = self.entry_nick_var.get().strip()
            if not nick:
                tk.messagebox.showerror(title='Error', message='Nick name must be specified!')
                self.entry_nick.focus()
                return

            self.server_name = self.entry_host_var.get()
            self.server_port = int(self.entry_port_var.get())
            self.nick = self.entry_nick_var.get()
            self.result = True
            self.destroy()

        except Exception as e:
            tk.messagebox.showerror(title='Error', message=e)

    def cancel_proc(self):
        self.result = False
        self.destroy()

root = tk.Tk()
gdb = GUI(root)
root.mainloop()


# by gaoxin
from django.urls import path
from .views import CategoryView, CourseView, CourseDetailView, CourseChapterView, CourseCommentView, QuestionView
from .video_view import PolyvView

urlpatterns = [
    path('category', CategoryView.as_view()),
    path('list', CourseView.as_view()),
    path('detail/<int:pk>', CourseDetailView.as_view()),
    path('chapter/<int:pk>', CourseChapterView.as_view()),
    path('comment/<int:pk>', CourseCommentView.as_view()),
    path('question/<int:pk>', QuestionView.as_view()),
    path('polyv', PolyvView.as_view()),

]


import numpy as np
import cv2
import sys
import os
sys.path.append(os.path.join(os.getcwd()))
import img_adjust_tous_points

def draw_polygon(img, matrice, input_type = 'image', output = None,):
    if input_type == 'image':
        img=cv2.imread(img)

    img=img.shape
    size = (img[0], img[1])
    # 全黑.可以用在屏保
    img = np.zeros(size)
    # Create a black image
    #####img = np.zeros((1080,1920,3), np.uint8) #y,x
    #####l1=img_adjust_tous_points.region_cal("./edge_enhanse.png")
    #pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
    #pts = np.array([[1161,441],[954,713],[999,750],[1207,482],[1101,396],[893,667],[938,704],[1148,434],[1222,487],[1016,756],[1060,796],[1267,528]], np.int32)
    pts=matrice
    pts = pts.reshape((-1,1,2))
    cv2.fillPoly(img, [pts], (255,255,255))
    cv2.polylines(img,[pts],True,(255,255,255))

    if input_type == 'image':
        cv2.imwrite(output,img)
    else:
        return img

if __name__=="__main__":
    input='./14.png'
    output='./14.png'
    l1=img_adjust_tous_points.region_cal(input)
    draw_polygon(input,output,l1)


from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from datetime import date

from schemas import Item
from application import TodoApplication

app: FastAPI = FastAPI()

origins = [
    "http://localhost:3001",
    "https://localhost:3001",
]

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

db: TodoApplication = TodoApplication()

@app.post("/add")
async def add_item(item: Item):
  result = db.add_item(item)
  return {"id": result}

@app.get("/read_all")
async def read_all_items():
  result = db.read_all_items()
  return {"items": result}


def pascal(n):
    space = n-1
    for i in range(n):
        print(' '*space,end="")
        out = str(pow(11,i))
        for _ in out:
            print(_+" ",end="")
        space-=1
        print()

n = int(input())
pascal(n)


import pandas as pd
import numpy as np
from collections import defaultdict
from collections import Counter
import networkx as nx
from matplotlib import pyplot as plt

def import_facebook_data(file):
    E = np.loadtxt(file, dtype=int)
    E=np.unique(E, axis=0)
    return E

def import_bitcoin_data(path):

    data = pd.read_csv(path)
    df1 = data.iloc[:,0:2]
    E=df1.to_numpy()
    E=np.subtract(E,1)
    E=np.unique(E,axis=0)
    return E

def spectralDecomp_OneIter(edge_list):

    size=edge_list.max()+1
    adjacency = [[0]*size for _ in range(size)]
    for sink, source in edge_list:
        adjacency[sink][source] = 1
        adjacency[source][sink]=1

    A=np.array(adjacency)

    size=len(A)
    nodes=np.arange(start=0,stop=size,step=1)
    temp=np.ones((size,1))
    diag=np.matmul(A,temp).T[0]
    D=np.diag(diag)
    L=D-A
    eig_val,eig_vec=np.linalg.eigh(L) # Since symmetric

    eig_val=np.real(eig_val)
    eig_vec=np.real(eig_vec)
    intm=np.sort(eig_val)

    part_index=0
    while intm[part_index]<pow(.5,5): # Note that number of partition chnages with this threshold
        part_index+=1

    index = np.argsort(eig_val)[part_index]
    fielder_vec= eig_vec[index]

    partition = -np.ones(len(A)) # Follows this structure because recursion follows this

    if(np.all(fielder_vec>=0)):# Edge case 1

        partition[np.intersect1d(np.where(fielder_vec>=0)[0],nodes)] = edge_list.min()
        graph_partition=[]
        graph_partition.append(nodes)
        graph_partition.append(partition)
        graph_partition=np.array(graph_partition).T
        return fielder_vec,A,partition

    elif(np.all(fielder_vec<0)): # edge case 2
        partition[np.intersect1d(np.where(fielder_vec<0)[0],nodes)] = edge_list.min()
        graph_partition=[]
        graph_partition.append(nodes)
        graph_partition.append(partition)
        graph_partition=np.array(graph_partition).T
        return fielder_vec,A,partition

    pos_id=np.where(fielder_vec>=0)[0].min()
    neg_id=np.where(fielder_vec<0)[0].min()
    operation = lambda x : pos_id if(x >= 0) else neg_id
    partition = np.array(list(map(operation,fielder_vec)))
    graph_partition=[]
    graph_partition.append(nodes)
    graph_partition.append(partition)
    graph_partition=np.array(graph_partition).T
    return fielder_vec, A, partition

def louvain_one_iter(nodes_connectivity_list_fb):
    def get_adj(nodes_connectivity_list_fb):
        size=nodes_connectivity_list_fb.max()+1
        # size = len(set([n for e in nodes_connectivity_list_fb for n in e]))
        # # make an empty adjacency list
        adjacency = [[0]*size for _ in range(size)]
        # populate the list for each edge
        for sink, source in nodes_connectivity_list_fb:
            adjacency[sink][source] = 1
            adjacency[source][sink]=1
        return np.array(adjacency)
    def get_parameters():
        A= get_adj(nodes_connectivity_list_fb)
        deg=np.sum(A, axis=0)
        node_to_commun_mapping =dict()
        degree_of_community=dict()
        degrees=dict()
        N=len(A)
        m=np.sum(A)
        nodes=np.arange(start=0,stop=N,step=1)

        Neigh = defaultdict(list)
        for i in range(N):
            flag=False
            for j in range(N):
                if A[i][j]==1:
                    Neigh.update({i:Neigh[i]+[j]})
                    flag=True
                else:
                    pass
            if flag==False:
                Neigh.update({i:[]})

        for i in nodes:
            node_to_commun_mapping[i] = i
            degrees[i] =deg[i]
            degree_of_community[i]=deg[i]
        return node_to_commun_mapping,degree_of_community,degrees,m,nodes,Neigh
    # Initial Condition

    node_to_commun_mapping,degree_of_community,degree_of_node,m,nodes,Neigh=get_parameters()

    flag=True

    while flag==True:
        flag = False

        for i in nodes:
            i_s_community = node_to_commun_mapping[i]
            Neighbours=Neigh[i]
            Neighbours_community=[]
            for x in Neighbours:
                Neighbours_community.append(node_to_commun_mapping[x])
            temp_dict=dict(Counter(Neighbours_community))
            k_i_in_list=[]
            for x in Neighbours_community:
                k_i_in_list.append(temp_dict[x])

            # Preparing
            degree_of_community[i_s_community] = degree_of_community[i_s_community]-degree_of_node[i]
            prev=node_to_commun_mapping[i]
            node_to_commun_mapping[i] = -1

            #setting init values
            opt_delta_Q = 0
            opt_comm=-2

            for neigh_commun, k_i_in_p in zip(Neighbours_community,k_i_in_list):
                delta_Q = 2 * k_i_in_p - degree_of_community[neigh_commun] * degree_of_node[i]/ m
                if delta_Q > opt_delta_Q:
                    opt_delta_Q = delta_Q
                    opt_comm=neigh_commun
            if opt_comm==-2:
                node_to_commun_mapping[i]=prev
                continue

            # update info

            degree_of_community[opt_comm] =degree_of_community[opt_comm]+ degree_of_node[i]
            node_to_commun_mapping[i] = opt_comm

            if opt_comm != i_s_community:
                flag = True

    classes=list(set(node_to_commun_mapping.values()))

    for x in classes:
        m=[k for k,v in node_to_commun_mapping.items() if v == x]
        id=min(m)
        for x in m:
            node_to_commun_mapping.update({x:id})

    graph_partition_vector=[]
    graph_partition_vector.append(list(node_to_commun_mapping.keys()))
    graph_partition_vector.append(list(node_to_commun_mapping.values()))
    graph_partition_vector=np.array(graph_partition_vector).T

    return graph_partition_vector

def Rec_Dcmps(edge_list, division, transform):
    def get_bin_partioned(edge_list):
        fielder_vec, _, _ = spectralDecomp_OneIter(edge_list)

        comm_neg= np.where(fielder_vec<0)[0]
        comm_pos = np.where(fielder_vec>=0)[0]

        return comm_neg,comm_pos
    def modify_partiton(pos,partition,nodelist):

        min=100000
        comm_pos_index=[]                       # Assigning min node number as the cluster id
        for i in nodelist[pos]:
            comm_pos_index.append(inverse_transform[i])
            if inverse_transform[i]<min:
                min=inverse_transform[i]
        comm_pos_index=np.array(comm_pos_index)

        partition[comm_pos_index,1]=min

        return partition
    def get_newEdgeList(souce_nodes,dest_nodes,map):
        nonlocal inverse_transform
        edge_list_zero=[]
        for x,y in zip(souce_nodes,dest_nodes):
            edge_list_zero.append([map[inverse_transform[x]],map[inverse_transform[y]]])
        edge_list_zero=np.array(edge_list_zero)
        return edge_list_zero

    check_list=division[:,1]
    stand_list=np.ones(len(check_list))*-1

    print("check")
    if (check_list!=stand_list).all():
        return division

    inverse_transform=dict()
    for i in range(len(transform.values())):
        inverse_transform[list(transform.values())[i]]=list(transform.keys())[i]

    comm_neg,comm_pos=get_bin_partioned(edge_list)
    node_list = np.array(list(transform.values()))

    ln=len(comm_neg)
    lp=len(comm_pos)

    if(ln==0 or lp==0): # No more recursion
        if ln==0:
            pos=comm_pos
        else:
            pos=comm_neg
        return modify_partiton(pos,division,node_list)

    map_pos=dict()
    map_neg=dict()

    pos_map_one=np.arange(lp)
    neg_map_one=np.arange(ln)

    l=len(node_list[comm_pos])

    for i in range(l):
        map_pos.update({inverse_transform[node_list[comm_pos][i]]:pos_map_one[i]})
    l=len(node_list[comm_neg])
    for i in range(l):
        map_neg.update({inverse_transform[node_list[comm_neg][i]]:neg_map_one[i]})

    new_source=[]
    new_dest=[]
    new_source_bar=[]
    new_dest_bar=[]

    for i in range(len(edge_list)):
        flag1=(edge_list[i][0] in comm_neg) and (edge_list[i][1] in comm_neg)
        flag2= (edge_list[i][0] in comm_pos) and ( edge_list[i][1] in comm_pos)
        if flag1:
            new_source.append(edge_list[i][0])
            new_dest.append(edge_list[i][1])
        if flag2:
            new_source_bar.append(edge_list[i][0])
            new_dest_bar.append(edge_list[i][1])

    edge_list_neg=get_newEdgeList(new_source,new_dest,map_neg)
    edge_list_pos=get_newEdgeList(new_source_bar,new_dest_bar,map_pos)
    l_n=len(edge_list_neg)
    l_p=len(edge_list_pos)

    if(l_n>0):
        Rec_Dcmps(edge_list_neg,division, map_neg)
    if(l_p>0):
        Rec_Dcmps(edge_list_pos,division, map_pos)
    return division

def spectralDecomposition(edge_list):
    def get_init_param(edge_list):
        size=edge_list.max()+1
        node_ids=np.arange(start=0,stop=size,step=1)
        init_pos=-1*np.ones_like(node_ids)
        partition=np.vstack([node_ids,init_pos]).T
        transform=dict()
        for i in node_ids:
            transform.update({i:i})     # each in it's own community
        return partition,transform
    def get_adj(nodes_connectivity_list_fb):
        size=nodes_connectivity_list_fb.max()+1
        # size = len(set([n for e in nodes_connectivity_list_fb for n in e]))
        # # make an empty adjacency list
        adjacency = [[0]*size for _ in range(size)]
        # populate the list for each edge
        for sink, source in nodes_connectivity_list_fb:
            adjacency[sink][source] = 1
            adjacency[source][sink]=1
        return np.array(adjacency)

    A = get_adj(edge_list)
    partition,map=get_init_param(edge_list)

    partition = Rec_Dcmps(edge_list,partition,map)
    positions=np.where(partition[:,1]==-1)[0]

    for i in positions:
        freq_dict=dict()
        pos=np.where(A[i]==1)[0]
        temp=[]
        for x in pos:
            temp.append(partition[:,1][x])
        templist=temp
        temp=np.array(temp)
        labels=np.unique(temp)
        for x in labels:
            freq_dict.update({x:templist.count(x)})
        unique=labels
        counts=np.array(list(freq_dict.values()))

        l_u=len(unique)
        l_c=len(counts)
        pos_sorted_count=np.argsort(counts)
        if(l_u==0):
            value=-1
        elif(unique[pos_sorted_count[l_c-1]] == -1):
            if(l_u>1):
                value=unique[pos_sorted_count[l_c-2]]
            else:
                # pass
                continue
        else:
            value=unique[pos_sorted_count[l_c-1]]
        partition[i,1] =value

    while(len(np.where(partition[:,0]<partition[:,1])[0])):
        idx = np.where(partition[:,0]<partition[:,1])[0][0]
        partition[np.where(partition[:,1]==partition[idx,1]),1] = idx

    return partition

def createSortedAdjMat(partition, edge_list):
    def get_adj(nodes_connectivity_list_fb):
        size=nodes_connectivity_list_fb.max()+1
        # size = len(set([n for e in nodes_connectivity_list_fb for n in e]))
        # # make an empty adjacency list
        adjacency = [[0]*size for _ in range(size)]
        # populate the list for each edge
        for sink, source in nodes_connectivity_list_fb:
            adjacency[sink][source] = 1
            adjacency[source][sink]=1
        return np.array(adjacency)
    def get_clustered_graph(G):
        nonlocal partition
        clusted_graph=dict()
        nodes=np.argsort(partition[:,1])
        for x in range(len(nodes)):
            clusted_graph.update({nodes[x]:partition[:,0][x]})
        H = nx.relabel_nodes(G, clusted_graph)
        return H

    G=nx.from_numpy_matrix(get_adj(edge_list))
    H=get_clustered_graph(G)
    A_bar=nx.to_numpy_matrix(H)
    return A_bar

if __name__ == "__main__":

    ########### Answer qn 1-4 for facebook data #################################################
    # Import facebook_combined.txt
    # nodes_connectivity_list is a nx2 numpy array, where every row
    # is a edge connecting i<->j (entry in the first column is node i,
    # entry in the second column is node j)
    # Each row represents a unique edge. Hence, any repetitions in data must be cleaned away.

    nodes_connectivity_list_fb = import_facebook_data("./data/facebook_combined.txt")

    # This is for question no. 1
    # fielder_vec    : n-length numpy array. (n being number of nodes in the network)
    # adj_mat        : nxn adjacency matrix of the graph
    # graph_partition: graph_partitition is a nx2 numpy array where the first column consists of all
    #                  nodes in the network and the second column lists their community id (starting from 0)
    #                  Follow the convention that the community id is equal to the lowest nodeID in that community.
    fielder_vec_fb, adj_mat_fb, graph_partition_fb = spectralDecomp_OneIter(nodes_connectivity_list_fb)

    # This is for question no. 2. Use the function
    # written for question no.1 iteratetively within this function.
    # graph_partition is a nx2 numpy array, as before. It now contains all the community id's that you have
    # identified as part of question 2. The naming convention for the community id is as before.

    graph_partition_fb = spectralDecomposition(nodes_connectivity_list_fb)

    # This is for question no. 3
    # Create the sorted adjacency matrix of the entire graph. You will need the identified communities from
    # question 3 (in the form of the nx2 numpy array graph_partition) and the nodes_connectivity_list. The
    # adjacency matrix is to be sorted in an increasing order of communitites.

    clustered_adj_mat_fb = createSortedAdjMat(graph_partition_fb, nodes_connectivity_list_fb)

    # This is for question no. 4
    # run one iteration of louvain algorithm and return the resulting graph_partition. The description of
    # graph_partition vector is as before.

    graph_partition_louvain_fb = louvain_one_iter(nodes_connectivity_list_fb)
    # print(graph_partition_louvain_fb)

    ############ Answer qn 1-4 for bitcoin data #################################################
    # Import soc-sign-bitcoinotc.csv

    nodes_connectivity_list_btc = import_bitcoin_data("./data/soc-sign-bitcoinotc.csv")
    # nodes_connectivity_list_btc = import_bitcoin_data("./data/graph.csv")

    # Question 1
    # fielder_vec_btc, adj_mat_btc, graph_partition_btc = spectralDecomp_OneIter(nodes_connectivity_list_btc)

    # Question 2
    graph_partition_btc = spectralDecomposition(nodes_connectivity_list_btc)

    # Question 3
    clustered_adj_mat_btc = createSortedAdjMat(graph_partition_btc, nodes_connectivity_list_btc)

    # Question 4
    graph_partition_louvain_btc = louvain_one_iter(nodes_connectivity_list_btc)


#!/usr/bin/env python
""" Pipeline for .tex documents.
    The documents get converted from draft mode to
    compile ready documents and then compiled
    using the LaTeX engine

Created: 07.10.2021
@author: Max Weise
"""

import os
import shutil
import logging
import datetime
import argparse
import subprocess

# === DEFINITION OF CONSTANTS ===
WORKING_PATH = './'
FILE_PREFIX = '[piped]_'    # Use this to identify modified files
FILE_PREFIX_SPLIT = '[piped]'
DEPLOY_FOLER = 'DEPLOY'

LOGGING_LEVELS = {
    'DEBUG'     : logging.DEBUG,
    'INFO'      : logging.INFO,
    'WARNING'   : logging.WARNING,
    'ERROR'     : logging.ERROR,
    'CRITICAL'  : logging.CRITICAL,
}

def copy_file(__file_name: str) -> str:
    """ This function copies the wip-tex file to compile it without
        modifying the original file.
    """
    new_name = FILE_PREFIX + __file_name
    logging.info(f'Copying the file {__file_name}.tex -> {new_name}.tex')
    shutil.copy(__file_name + '.tex', new_name + '.tex')

    return new_name

def remove_draft_option(__file_name: str) -> None:  # sourcery skip: extract-method
    """ This function removes the draft option from a tex file."""

    try:
        lines_of_file = []
        logging.info(f'Now reading {__file_name}.tex')
        with open(__file_name + '.tex', 'r') as f:
            lines_of_file = f.readlines()

        parts_of_line = lines_of_file[0].split(',')
        parts_of_line.pop(parts_of_line.index(' draft'))

        lines_of_file[0] = ','.join(parts_of_line)

        logging.info(f'Now writing {__file_name}.tex')
        with open(__file_name + '.tex', 'w') as f:
            f.writelines(lines_of_file)
    except Exception as e:
        logging.error(e)

def compile_tex_file(__file_name: str, verbose: bool = False) -> None:
    """ Call LaTeX.exe and compile the tex document to a PDF file."""

    arg_list = ['pdflatex', __file_name + '.tex']

    if not verbose:
        arg_list.insert(1, '-quiet')

    try:
        logging.info(f'Calling <{arg_list}>')
        subprocess.check_call(arg_list)
    except Exception as e:
        logging.error(e)

def _get_date_prefix() -> str:
    """ Return a string representation of the current date in the format yyyy_mm_dd_HH_MM"""
    year = datetime.date.today().year
    month = datetime.date.today().month
    day = datetime.date.today().day
    hour = datetime.datetime.now().hour
    minute = datetime.datetime.now().minute

    return f'{year}_{month}_{day}_{hour}_{minute}_'

def _move_file_to_folder(__file_name: str) -> None:
    """ Move the compiled file to a specific folder"""
    deploy_path = os.path.join(WORKING_PATH, DEPLOY_FOLER)
    try:
        os.mkdir(deploy_path)
    except FileExistsError as e:
        logging.info(e)
        logging.warning(f'The directory {DEPLOY_FOLER} does allready exist.')
    except Exception as e:
        logging.error(e)

    today = _get_date_prefix()
    file_name = __file_name + '.pdf'

    # Remove the working prefix from the file
    string_list = __file_name.split('_')
    string_list.pop(string_list.index(FILE_PREFIX_SPLIT))
    new_name = '_'.join(string_list) + '.pdf'

    try:
        logging.info('Moving file to deploy directory')
        os.rename(os.path.join(WORKING_PATH, file_name), os.path.join(deploy_path, today + '_' + new_name))
    except Exception as e:
        logging.error(e)

def _clean_working_dir() -> None:
    """ Remove all auxiliary files which have been generated in the process"""
    logging.info('Cleaning up working directory')
    for f in os.listdir('.'):
        if FILE_PREFIX in f:
            os.remove(f)

def clean_up(__file_name: str = None) -> None:
    """ This function cleans the working directory"""
    _move_file_to_folder(__file_name)
    _clean_working_dir()

def _create_parser() -> argparse.ArgumentParser:
    """ Returns an argumentParser object which contains all valid CLI arguments."""
    # == Setup for system objects ==
    parser = argparse.ArgumentParser()      # Parse CLI Arguments

    # == Add Arguments for the parser to recognize ==
    # Positional Arguments
    parser.add_argument(
        'filename',
        help='Name of file which should be processed in the pipeline'
    )

    # Options
    parser.add_argument(
        '-v',
        help='Turn on the console output for LaTeX.exe',
        action='store_true'
    )

    parser.add_argument(
        '-log',
        help='Select the loglevel of this pipeline. Default: ERROR',
        choices=list(LOGGING_LEVELS.keys()),
        default=LOGGING_LEVELS['ERROR']
    )

    return parser

def main() -> None:
    """ Process of Pipeline"""
    parser = _create_parser()

    try:
        args = parser.parse_args()

        logging.basicConfig(format='[%(levelname)s] - %(message)s', level=LOGGING_LEVELS[args.log])

        verbose = bool(args.v)

        new_file_name = copy_file(args.filename)

        remove_draft_option(new_file_name)
        compile_tex_file(new_file_name, verbose=verbose)
        clean_up(new_file_name)
    except Exception as e:
        print(e)

if __name__ == '__main__':
    main()


import timeit
class Node:
    def __init__(self, value, children=None):
        # a leaf node has an empty list of children
        if children is None:
            children = []
        self.value = value
        self.children = children

root = Node(1, [
    Node(2, [
        Node(3, [
            Node(4),
            Node(5)
        ]),
        Node(6)
    ]),
    Node(7, [
        Node(8, [
            Node(9)
        ])
    ]),
    Node(10)
])

def iterative_dfs(root_node, target):
    # create a stack of nodes to visit
    nodes_to_visit = [root_node]

    while nodes_to_visit:
        # visit the next node
        node = nodes_to_visit.pop()

        if node.value == target:
            # found it!
            return node

        # make sure we visit the child nodes
        for child in node.children:
            nodes_to_visit.append(child)

    # no more nodes to visit, target wasn't found
    return None

start_time = timeit.default_timer()
#print(iterative_dfs(root,8))
iterative_dfs(root,8)
elapsed = timeit.default_timer() - start_time
print("Iterative depth:")
print(elapsed)


def fib(n) :
    if n == 1 or n == 2 :
        cnt[0] += 1
        return 1
    else :
        return fib(n-1) + fib(n - 2)

def fibonacci(n) :
    f = []
    f.append(1)
    f.append(1)
    for i in range(2, n) :
        f.append(f[i - 1] + f[i - 2])
        cnt[1] += 1
    return f[n-1]

n = int(input())
cnt = [0, 0]

fib(n)
fibonacci(n)
print(cnt[0], cnt[1])


from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.http import FileResponse
import wave
import struct

HEADER_SIZE = 44

@api_view(['POST'])
def volume(request):
        try:
            # value for the volume change
            factor = float(request.data["factor"])
            audio_file = request.data["audio_file"]

            try:
                input_wav = wave.open(audio_file, 'rb')
            except:
                return Response({"error": "Could not open audio file"})

            output_file = 'output.wav'
            output_wav = wave.open(output_file, 'wb')

            # get the params of the input WAV file
            params = input_wav.getparams()

            # write the header to the output file
            output_wav.setparams(params)
            for _ in range(HEADER_SIZE):
                output_wav.writeframes(input_wav.readframes(1))

            # read samples from input file, adjust volume, and write to the output file
            while True:
                frame = input_wav.readframes(1)
                if not frame:
                    break

                sample = struct.unpack("<i", frame)
                adjusted_sample = int(sample[0] * factor)
                output_wav.writeframes(struct.pack("<i", adjusted_sample))

            # close files
            input_wav.close()
            output_wav.close()

            # return the actual file
            with open(output_file, 'rb') as file:
                 response = FileResponse(file, as_attachment=True, filename='output.wav')

            return response
            # return Response({"message": "Volume changed", "output_audio_file": output_file})

        except Exception as error:
             return Response({"error": str(error)})


import os
import platform
import re
import shutil
import subprocess
import pytest

@pytest.mark.parametrize("tool", [
    "acyclic",
    "bcomps",
    "ccomps",
    "circo",
    "cluster",
    "diffimg",
    "dijkstra",
    "dot",
    "dot2gxl",
    "dot_builtins",
    "dotty",
    "edgepaint",
    "fdp",
    "gc",
    "gml2gv",
    "graphml2gv",
    "gv2gml",
    "gv2gxl",
    "gvcolor",
    "gvedit",
    "gvgen",
    "gvmap",
    "gvmap.sh",
    "gvpack",
    "gvpr",
    "gxl2dot",
    "gxl2gv",
    "lefty",
    "lneato",
    "mingle",
    "mm2gv",
    "neato",
    "nop",
    "osage",
    "patchwork",
    "prune",
    "sccmap",
    "sfdp",
    "smyrna",
    "tred",
    "twopi",
    "unflatten",
    "vimdot",
])
def test_tools(tool):

  if shutil.which(tool) is None:
    pytest.skip(f"{tool} not available")

  # FIXME: Remove skip when
  # https://gitlab.com/graphviz/graphviz/-/issues/1829 is fixed
  if tool == "smyrna" and os.getenv("build_system") == "msbuild":
    pytest.skip("smyrna fails to start because of missing DLLs in Windows MSBuild builds (#1829)")

  # FIXME: Remove skip when
  # https://gitlab.com/graphviz/graphviz/-/issues/1838 is fixed
  if tool == "gvpack" and platform.system() != "Windows":
    if os.getenv("build_system") == "cmake":
      pytest.skip("gvpack does not find libgvplugin_neato_layout.so.6"
                  "when built with CMake (#1838)")

  # Ensure that X fails to open display
  environ_copy = os.environ.copy()
  environ_copy.pop("DISPLAY", None)

  # Test usage
  p = subprocess.Popen([tool, "-?"], env=environ_copy, stdin=subprocess.DEVNULL,
                       stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                       universal_newlines=True)
  output, _ = p.communicate()
  ret = p.returncode

  # FIXME: https://gitlab.com/graphviz/graphviz/-/issues/1934
  # cope with flaky failures, while also failing if this flakiness has been
  # fixed
  if tool == "lneato" and os.getenv("build_system") == "msbuild":
    has_pass = ret == 0
    has_fail = ret != 0
    for _ in range(100):
      if has_pass and has_fail:
        break
      p = subprocess.Popen([tool, "-?"], env=environ_copy,
                           stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT, universal_newlines=True)
      out, _ = p.communicate()
      if p.returncode == 0:
        has_pass = True
        ret = p.returncode
        output = out
      else:
        has_fail = True
    assert has_pass, "could not find passing execution"
    assert has_fail, "could not find failing execution (#1934 fixed?)"

  assert ret == 0, f"`{tool} -?` failed. Output was: {output}"

  assert re.match("usage", output, flags=re.IGNORECASE) is not None, \
    f"{tool} -? did not show usage. Output was: {output}"

  # Test unsupported option
  returncode = subprocess.call(
      [tool, "-$"],
      env=environ_copy,
  )

  assert returncode != 0, f"{tool} accepted unsupported option -$"