"""============================================================================
   Ex2: PCA - sklearn --> mở rộng thêm phân tích phương sai để xác định k
      a) Đọc tập tin dữ liệu Student_12f.xls vào dataframe.
      b) Áp dụng phương pháp PCA để giảm xuống k chiều (2 < k < 12).
         Giải thích nguyên nhân hay cơ sở về số chiều được giảm.
      c) Giảm chiều xuống còn k = 2 và trực quan hóa dữ liệu. Nhận xét kết quả.
============================================================================"""
import matplotlib.pyplot as plt
import numpy             as np
import pandas            as pd
import seaborn           as sns

from sklearn.decomposition import PCA

print('============================================================')
print('*** a) Đọc tập tin dữ liệu Student_12f.xls vào dataframe ***')
print('============================================================')

# Default folder: 'C:\Users\AnTe'
# Muốn biết [default folder] thì thực hiện [File > Open]
LDS3folder = 'D:/NAT/RnD/Edu/01.CD - DH/T3H.LDS3.Maths and Stats for Data Science'
folder     = LDS3folder + '/Materials/Projects/Data/Bai 3/'
data       = pd.read_excel(folder + 'Student_12f.xls')
print(data.head(), '\n')

print('=============================================================')
print('*** b) Áp dụng PCA để giảm xuống còn k chiều (2 < k < 12) ***')
print('=============================================================')
#   https://stackoverflow.com/questions/32857029/python-scikit-learn-pca-explained-variance-ratio-cutoff
#   - The pca.explained_variance_ratio_ returns a vector of the variance explained by each dimension.
#   - The pca.explained_variance_ratio_[i] gives the variance explained solely by the i+1st dimension.
#   - The pca.explained_variance_ratio_.cumsum() will return a vector x
#     such that x[i] returns the cumulative variance explained by the first i+1 dimensions.

#   (1) PCA().components_: Chuyển vị của ma trận vectơ riêng EigenVectors.T
#   (2) PCA().explained_variance_: Các giá trị riêng
#   (3) PCA().explained_variance_ratio_: Tỷ lệ phương sai so với dữ liệu gốc
#   (4) Hàm numpy.cumsum()

#-------------------------------------------------------------
# CÁCH 1: Chọn k dựa trên đồ thị biểu diễn phương sai tích lũy
#-------------------------------------------------------------
print('CÁCH 1: Chọn k dựa trên đồ thị biểu diễn phương sai tích lũy')
pca = PCA().fit(data)

# Vẽ đồ thị biểu diễn % phương sai tích lũy theo số features
plt.figure()
plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('Số lượng features')
plt.ylabel('Variance (%)')
plt.title('Đồ thị biểu diễn % phương sai tích lũy theo số features')
plt.show()

# Nhận xét:
#   - #f =  0: giữ lại    0%  phương sai so với dữ liệu gốc
#   - #f =  3: giữ lại ~ 85%  phương sai so với dữ liệu gốc
#   - #f >  3: giữ lại > 90%  phương sai so với dữ liệu gốc
#   - #f = 12: giữ lại   100% phương sai so với dữ liệu gốc
#

print('Phân tích chi tiết theo k:')
for k in range(1, 13):
    pca = PCA(k)
    pca.fit(data)

    var = pca.explained_variance_ratio_.sum() * 100
    print('   * k = %2d' %k, '--> phương sai tích lũy %.0f%%' %var)

    # Test - BEGIN
    print('- Chuyển vị của ma trận vectơ riêng P_T', pca.components_.shape, ': \n',
                                                     pca.components_, '\n')
    print('- Ma trận trị riêng LAMBDA', pca.explained_variance_.shape, ': \n',
                                        pca.explained_variance_, '\n')
    # Test - END

#---------------------------------------------------------------
# CÁCH 2: Chọn k dựa trên ngưỡng phương sai (tích lũy) mong muốn
#---------------------------------------------------------------
print('\nCÁCH 2: Chọn k dựa trên ngưỡng phương sai (tích lũy) mong muốn')
# Giả sử muốn giữ lại 90%
threshold = .9
percent   = threshold * 100
pca       = PCA(threshold)

pca.fit(data)
k   = pca.n_components_
var = sum(pca.explained_variance_ratio_) * 100
print('   Muốn phương sai tích lũy >= %.0f%%' %percent, 'thì k >= %d' %k,
      ' (k = %d' %k, '--> %.0f%%)' %var)

print('\nPhân tích chi tiết theo ngưỡng phương sai:')
A = np.array([.5, .6, .7, .8, .9, .95, .99])
for x in A:
    percent   = x * 100
    pca       = PCA(x)

    pca.fit(data)
    k   = pca.n_components_
    var = sum(pca.explained_variance_ratio_) * 100
    print('   Muốn phương sai tích lũy >= %.0f%%' %percent, 'thì k >= %2d' %k,
          ' (k = %2d' %k, '--> %.0f%%)' %var)

print('========================================================')
print('*** c) Giảm chiều còn k = 2 và trực quan hóa dữ liệu ***')
print('========================================================')
k   = 2
pca = PCA(k)
pca.fit(data)

# transform data
B = pca.transform(data)
print('- Ma trận B_T', B.shape)

principalDf = pd.DataFrame(data = B, columns = ['Component 1', 'Component 2'])
print(principalDf.head(), '\n')

plt.figure(figsize = (8, 6))
sns.jointplot(x = 'Component 1', y = 'Component 2', data = principalDf)
plt.show()

B3. PCA/Ex2 - PCA voi sklearn (Phan tich phuong sai).py


from sklearn.model_selection import StratifiedShuffleSplit

def cross_validation(clf, features, labels):
    cv = StratifiedShuffleSplit(random_state=106)
    true_negatives = 0
    true_positives = 0
    false_negatives = 0
    false_positives = 0

    for train_idx, test_idx in cv.split(features, labels):
        features_train = []
        features_test = []
        labels_train = []
        labels_test = []

        for i in train_idx:
            features_train.append(features.iloc[i])
            labels_train.append(labels[i])

        for j in test_idx:
            features_test.append(features.iloc[j])
            labels_test.append(labels[j])

    clf.fit(features_train, labels_train)
    pred = clf.predict(features_test)

    for prediction, truth in zip(pred, labels_test):
        if prediction == 0 and truth == 0:
            true_negatives += 1
        elif prediction == 1 and truth == 1:
            true_positives += 1
        elif prediction == 0 and truth == 1:
            false_negatives += 1
        elif prediction == 1 and truth == 0:
            false_positives += 1
        else:
            print('Found a predicted label not equals to 0 or 1')

    total_predictions = \
        true_negatives + true_positives + false_positives + false_negatives

    accuracy = (true_positives+true_negatives) / total_predictions
    precision = true_positives / (true_positives+false_positives)
    recall = true_positives / (true_positives+false_negatives)
    f1 = 2 * (precision*recall) / (precision+recall)

    print('accuracy: {}\nprecision: {} \nrecall: {}\nf1: {}'.format
          (accuracy, precision, recall, f1))


import pygame
from itertools import product
class Board:
    def __init__(self, w, scr):
        self.world=w
        self.screen=scr

    def paint(self):
        org=self.world.get_organism()
        width=self.world.get_n()
        height=self.world.get_m()
        sizex = 500 / width
        sizey = 500 / height
        color=(250,250,250)
        sx=500 % sizex
        sy=500 % sizey
        sx=int(sx)
        sy=int(sy)
        for obj in org:
            x=obj.get_x()
            y=obj.get_y()
            c=obj.color()
            for xx, yy in product(range(500 - sx), range(500 - sy)):
                if x == (xx / sizex) and y == (yy / sizey):
                    pygame.draw.rect(self.screen, c, pygame.Rect(xx, yy, sizex, sizey))


from pyspark.sql.functions import col
from pyspark.sql.types import (
    DoubleType,
    IntegerType,
    LongType,
    StringType,
    StructField,
    TimestampType,
)

from . import AbstractSchema

class AnswersSchema(AbstractSchema):
    def get_columnid(self):
        return ["activityId", "userId"]

    def get_orderby(self):
        return [col("scoreOfAccuracy").desc(), col("timestamp").desc()]

    def get_raw_schema(self):
        schema = [
            StructField("activityId", IntegerType(), True),
            StructField("userId", IntegerType(), True),
            StructField("scoreOfAccuracy", DoubleType(), True),
            StructField("answerDatetime", TimestampType(), True),
            StructField("approved", StringType(), True),
            StructField("timestamp", TimestampType(), True),
        ]
        return schema

    def get_column_names(self):
        column_names = [
            "activityId",
            "userId",
            "scoreOfAccuracy",
            "answerDatetime",
            "approved",
        ]
        return column_names

    def get_schema(self):
        schema = [
            StructField("activityId", IntegerType(), True),
            StructField("userId", IntegerType(), True),
            StructField("scoreOfAccuracy", DoubleType(), True),
            StructField("answerDatetime", LongType(), True),
            StructField("approved", StringType(), True),
        ]
        return schema


# load the dataset
'''
data = open('dataset.csv').read()
labels=[]
texts= []
for i, line in enumerate(data.split("\n")):
    print line
    content = line.split()
    print content[0]
    labels.append(content[0])
    texts.append(content[1:])

# create a dataframe using texts and lables
traindataframe = pandas.DataFrame()
traindataframe['content']=content
traindataframe['label']=labels

import numpy as np
csv = np.genfromtxt ('dataset.csv', delimiter=",")
second = csv[:,0]
third = csv[:,1]
print second[0]
'''
import pandas
import csv
import numpy
from sklearn import model_selection , preprocessing , svm , metrics , naive_bayes, linear_model
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer

def training(classifier_used,training_feature,label,valid_feature):

    classifier_used.fit(training_feature,label)

    predictions=classifier_used.predict(valid_feature)
    #print(valid_y)
    r= metrics.accuracy_score(predictions,yvld)
    return r

content=[]
label=[]

readfile=csv.reader(open("dataset.csv", "rt"), delimiter=",")
x=list(readfile)
res=numpy.array(x).astype("str")
print (res.size)
print (type(res))
print (len(res))
for i in range (1,len(res)):
    content.append(res[i][0])
    label.append(res[i][1])
'''
print len(text)
print len(label)
print type(text)
#print text.size
print label[0]
print label[10]
print label[1500]
'''
train_dataframe = pandas.DataFrame()
train_dataframe['content']=content
train_dataframe['label']=label

model=model_selection.train_test_split(train_dataframe['content'], train_dataframe['label'],test_size=0.33)
xtrn=model[0]
xvld=model[1]
ytrn=model[2]
yvld=model[3]
#print (xtrn)
#print ("\n")
#print ("\n")
#print (xvld)
#print ("\n")
#print ("\n")
#print (ytrn)
#print ("\n")
#print ("\n")
#print (yvld)
#print ("\n")
#print ("\n")
#print  (len(xtrn))
#print (len(xvld))
#print (len(ytrn))
#print (len(yvld))

e=preprocessing.LabelEncoder()
ytrn=e.fit_transform(ytrn)
yvld=e.fit_transform(yvld)

tfidf_vector = TfidfVectorizer(analyzer='word', token_pattern=r'\w{1,}')
tfidf_vector.fit(train_dataframe['content'])
xtrn_tfidf=tfidf_vector.transform(xtrn)
xvalid_tfidf=tfidf_vector.transform(xvld)

#print (tfidf_vector)
#print (xtrn_tfidf)

tfidf_vector_ngram = TfidfVectorizer(analyzer='word', token_pattern=r'\w{1,}',ngram_range=(2,3))
tfidf_vector_ngram.fit(train_dataframe['content'])
xtrn_tfidf_ngram =  tfidf_vector_ngram.transform(xtrn)
xvld_tfidf_ngram =  tfidf_vector_ngram.transform(xvld)

acc1=training(naive_bayes.MultinomialNB(),xtrn_tfidf,ytrn,xvalid_tfidf)#training Naive Bayes on tfidf features and get accuracy
print ("Naive Bayes+tfidf :",acc1)

#acc2=training(linear_model.LogisticRegression(),xtrn_tfidf,ytrn,xvalid_tfidf)#training on logistic regression with tfidf features
#print ("Logistic Regression+tfidf :",acc2)

acc3=training(svm.SVC(), xtrn_tfidf_ngram,ytrn,xvld_tfidf_ngram)#training SVM on ngram tfidf features and get accuracy
print ("svm+ngram tfidf :",acc3)

# SVM on Ngram Level TF IDF Vectors
accuracy = training(svm.LinearSVC(),xtrn_tfidf_ngram, ytrn, xvld_tfidf_ngram)
print ("SVM, N-Gram Vectors: ", accuracy)

#accuracy = training(svm.LinearSVC(), xtrn_count, ytrn, xvalid_count)
#print ("SVM, N-Gram Vectors: ", accuracy)


#!/usr/local/bin/python
""" Capture pictures from webcamera to create stop motion video

Examples

Output to 'out' directory, capture every 60 seconds:
python camcapture.py

Output to 'mydir', capture every 30 seconds
python camcapture.py -o mydir -r 30

"""
import argparse
import cv2
import time
import datetime
import os
import sys
import time

def capture_frame(output_folder):
    vidcap = cv2.VideoCapture()
    vidcap.open(0)
    time.sleep(3)
    retval, image = vidcap.retrieve()
    vidcap.release()
    file_name = os.path.join(output_folder, get_date_string() + ".jpeg")
    cv2.imwrite(file_name, image)
    print(file_name)

def get_date_string():
    return datetime.datetime.now().strftime("%Y%m%d_%H%M%S")

def parse_args(argv):
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("--out-dir", "-o", default="out", help="output directory for images (default out)")
    parser.add_argument("--capture-rate", "-r", type=int, help="capture every X seconds (default 60)", default=60)
    return parser.parse_args(argv)

def main():
    args = parse_args(sys.argv[1:])
    out_dir = args.out_dir
    if not os.path.exists(out_dir):
        os.makedirs(out_dir)
    while True:
        capture_frame(out_dir)
        time.sleep(args.capture_rate)

if __name__ == '__main__':
    main()


# Shows different formula for time decay for use in a game
# The first uses a linear decay which results in a fast decay
# The Improved version uses a formula giving a decay that slows down over time
# This is based on the formula f(x) = x / x + h
# To run first install plotly using:
# pip3 install plotly

import plotly
from plotly import tools
import plotly.graph_objs as go

num_plots = 50
start_value = 10

x_axis= list(range(1,num_plots))

y_axis_linear = list()
for i in range (1,num_plots):
    y_axis_linear.append(start_value + 0.9 - (0.2*i))

linear = go.Scatter(
    x = x_axis,
    y = y_axis_linear,
    name = "Linear"
)

y_axis_new = list()
for i in range (1,num_plots):
    y_axis_new.append(start_value + 1.5 - (start_value * (i/ (i + 10))))

# Function is x / x + h
# function that tends towards, but never reaches 1
# h determines how quickly reach the half way point
# multiple it by the maximum we want to decrement our time by
new = go.Scatter(
    x = x_axis,
    y = y_axis_new,
    name = "Improved"
)

data = [linear, new]

plotly.offline.plot({
    "data": data,
    "layout": go.Layout(title="Different timers")
    }, auto_open=True)


from collections import defaultdict

import tator

if __name__ == '__main__':
    parser = tator.get_parser()
    args = parser.parse_args()
    api = tator.get_api(host=args.host, token=args.token)
    projects = api.get_project_list()
    total_size = 0
    total_duration = 0
    size_by_org = defaultdict(int)
    duration_by_org = defaultdict(int)
    for project in projects:
        stats = api.get_media_stats(project.id)
        print(f"Project {project.id} ({project.name}): {stats.total_size / 1e12:0.2f}TB, {stats.duration / 3600:0.2f} hours")
        size_by_org[project.organization] += stats.total_size
        duration_by_org[project.organization] += stats.duration
        total_size += stats.total_size
        total_duration += stats.duration
    print(f"-----------------------------------------------------------")
    for org in size_by_org.keys():
        print(f"Organization {org}: {size_by_org[org] / 1e12:0.2f}TB, {duration_by_org[org] / 3600:0.2f} hours")
    print(f"-----------------------------------------------------------")
    print(f"Total size: {total_size / 1e12:0.2f} TB")
    print(f"Total duration: {total_duration / 3600:0.2f} hours")


#!/usr/bin/python2
# This script parses the Naughty and Nice List (CSV).
# For each person on the list, query the NPPD
# infractions database and gather an infractions list.

# Written for the SANS Holiday Hack Challenge 2017
# by Jason Testart
# December 2017

import csv
import urllib2
import json

# Function: Given a name, return 'hits' from the NPPD database
def getInfractions(name):
    jsonQueryString = 'http://nppd.northpolechristmastown.com/infractions?query=name=%s&json=1'
    thisQuery = jsonQueryString % name.replace(' ','+')
    req = urllib2.Request(thisQuery)
    return urllib2.urlopen(req)

##
## MAIN
##
theListFileName = 'Naughty and Nice List.csv'
masterInfractionsList = []
with open(theListFileName, 'rb') as theListFile:
    theList = csv.reader(theListFile)
    for row in theList:
        infractionsList = ''
        infractionsList = json.load(getInfractions(row[0]))['infractions']
        if infractionsList:
            masterInfractionsList.extend(infractionsList)

outFileName = 'all_infractions.json'
outFile = open(outFileName, 'wb')
outFile.write(json.dumps(masterInfractionsList))
outFile.close


import urllib.request as req
from bs4 import BeautifulSoup
import openpyxl #엑셀에 넣기 위해
import os
from openpyxl.drawing.image import Image #앨범을 넣기 위해 부러와
#엑셀파일 존재하는지 확인 위해 OS 블러옴

if not os.path.exists("./멜론음원차트_오늘.xlsx"):
    book = openpyxl.Workbook()
    book.save("./멜론음원차트_오늘.xlsx")

headers = req.Request("https://www.melon.com/chart/index.htm", headers={"user-Agent": "Mozilla/5.0"})
code = req.urlopen(headers)
soup = BeautifulSoup(code, "lxml")
title = soup.select("div.ellipsis.rank01 a") #속성값에 띄어쓰기 있으면 점으로
name = soup.select("div.ellipsis.rank02 > span.checkEllipsis")
album = soup.select("div.ellipsis.rank03 a")
image = soup.select("a.image_typeAll img")

book = openpyxl.load_workbook("./멜론음원차트_오늘.xlsx") #먼저 불러와야해
# sheet = book["Sheet1"]
sheet = book.active #자동으로 열리는 그 시트를 사용하겠다
sheet.column_dimensions["A"].width = 15
sheet.column_dimensions["B"].width = 50
sheet.column_dimensions["C"].width = 30
sheet.column_dimensions["D"].width = 35

row_num = 1
#이미지 저정할 폴더 만들기
if not os.path.exists("./멜론이미지"):
    os.mkdir("./멜론이미지")

for i in range(len(title)):
    req.urlretrieve(image[i].attrs["src"], "./멜론이미지/{}.png".format(row_num))
    print(title[i].string, name[i].text, album[i].string, image[i].attrs["src"])
    img_for_ex = Image("./멜론이미지/{}.png".format(row_num)) #이미지 함수가 엑셀에 넣을 수 있게 변환을 해죠
    sheet.row_dimensions[row_num].height = 95
    sheet.add_image(img_for_ex,"A{}".format(row_num)) #이미지 넣는 방법
    sheet.cell(row=row_num, column=2).value = title[i].string
    sheet.cell(row=row_num, column=3).value = name[i].text
    sheet.cell(row=row_num, column=4).value = album[i].string
    book.save("./멜론음원차트_오늘.xlsx")
    row_num += 1


""" Various helper methods """

from django.core.signing import Signer, BadSignature
from django.conf import settings

import os
import base64
import binascii

def get_size(path):
    """
    Returns the size of a directory using Python 3.5 PEP 471 os.scandir()
    MUCH faster than os.walk as scandir returnings file attributes along
    with the filename, saving many system calls (2-3x) per attribute
    """
    total = 0
    for entry in os.scandir(path):
        if entry.is_dir(follow_symlinks=False):
            total += get_size(entry.path)
        else:
            total += entry.stat(follow_symlinks=False).st_size
    return total

def validate_b64folder(folder64):
    """
    Validates the given base64 folder path exists and the filename is valid
    Returns folder name if so
    """
    # Unpack base64 signed folder
    try:
        signed_folder = base64.b64decode(folder64)
    except binascii.Error:
        return False

    # Verify signature
    signer = Signer()
    try:
        folder = signer.unsign(signed_folder)
    except BadSignature:
        return False

    # Validate folder exists on filesystem
    base_dir = settings.TRANSMISSION_DOWNLOAD_DIR
    if os.path.isdir("{}{}".format(base_dir, folder)):
        return folder
    else:
        return False


from dataclasses import dataclass, field
from typing import List

from entity import MovingEntity
from entity.ship_components import (
    AvailableComponents,
    ShipComponent,
    container,
    defense,
)
from processor import logging

@dataclass
class Ship(MovingEntity):
    """尚未初始化数值和组件的飞船

    初始化属性和默认值已给出，可以根据需要在创建时动态设置
    """

    # 可用部件，随子类确定
    available_components: AvailableComponents = field(default_factory=dict, init=False)

    # tag，信息类型的属性
    tag: dict = field(default_factory=dict)

    # 船体值
    structure: defense.Structure = field(default_factory=defense.Structure)

    # 组件列表
    components: List[ShipComponent] = field(default_factory=list)

    def __post_init__(self):
        """通过继承这个方法来绑定动态逻辑的处理器Processor单例

        1. 记得调用super().__post_init__()
        2. 使用self.add_processor(p: Processor)来添加处理器，
           实例创建过程中会自动将实例index注册到对应的处理器单例中
        """
        super().__post_init__()
        return

@dataclass
class TestShip(Ship):
    """测试用的飞船模板

    实际过程中只需要通过某种工厂组件动态创建Ship实例即可
    """

    tag = {"name": "TestShip"}
    structure = defense.Structure(total=10, left=5)
    available_components: AvailableComponents = field(
        default_factory=lambda: {
            (defense.Armor, 1),
            (container.Battery, 1),
            (container.NuclearFuelTank, 1),
        }
    )
    components: List[ShipComponent] = field(
        default_factory=lambda: [
            defense.Armor(total=200, left=200),
        ]
    )

    def __post_init__(self):
        super().__post_init__()
        self.add_processor(logging.Logger())
        return


#!/usr/bin/env python3

import toml
import sys

def err(msg, *args):
    print('error:', msg % args, file = sys.stderr)

def enforce_key_not_in_configs(dict, key, type, action):
    if key in dict:
        err('%s %s should not %s in configs.toml',
            type, key, action)
        sys.exit(1)

def main():
    secrets = toml.load('secrets.toml')

    configs = toml.load('configs.toml')

    for setting in ['github', 'web']:
        enforce_key_not_in_configs(configs, setting,
                                   'setting', 'exist')
        configs[setting] = secrets[setting]

    for name in configs['repo']:
        d = configs['repo'][name]
        # set the default names
        if 'owner' not in d:
            d['owner'] = 'contain-rs'
        if 'name' not in d:
            d['name'] = name
        if 'reviewers' not in d:
            d['reviewers'] = []
        d['reviewers'] += configs['reviewers']
        # set the per-repo secrets.
        for (service, settings) in secrets['per-repo'].items():
            enforce_key_not_in_configs(d, service,
                                       'service',
                                       'be registered for repository %s' % name)
            d[service] = settings

    print(toml.dumps(configs))

if __name__ == '__main__':
    main()


import numpy as np
import warnings
import os
import statsmodels.formula.api as smf
import pandas as pd
import functools
import re
warnings.filterwarnings('ignore')

class stepwise:

    def __init__(self,step,fit_intercept):
        self.step = step
        self.fit_intercept = fit_intercept

    def reduce_concat(self,x, sep=""):
        return functools.reduce(lambda x, y: str(x) + sep + str(y), x)

    def fit(self,data,null_formula,full_formula,response):

        """Linear model designed by forward selection.
        Parameters:
        -----------
        data : pandas DataFrame with all possible predictors and response
        response: string, name of response column in data
        Returns:
        --------
        model: an "optimal" fitted statsmodels linear model
               with an intercept
               selected by forward selection
               evaluated by aic
        """

        null_temp        = re.split('~',null_formula)
        null_predic_com  = null_temp[1].split('+')
        null_predic      = null_predic_com[1:len(null_predic_com)]
        full_temp        = re.split('~',full_formula)
        full_predic_com  = full_temp[1].split('+')
        full_predic      = full_predic_com[1:len(full_predic_com)]
        indices          = [i for i,id in enumerate(full_predic) if id not in null_predic]
        domain           = [full_predic[i] for i in indices]
        start            = set(null_predic)
        remaining        = set(domain)
        selected         = null_predic
        current_score, best_new_score = float('inf'), float('inf')
        score_selected   = []
        variable_added   = []

        while (remaining and current_score == best_new_score and self.step >0):
            scores_with_candidates = []
            for candidate in remaining:
                formula = "{} ~ {}".format(response,' + '.join(selected + [candidate]))
                if self.fit_intercept == 0:
                    formula = formula + "-1"
                score = smf.ols(formula, data).fit().aic
                scores_with_candidates.append((score, candidate))
            scores_with_candidates.sort()
            best_new_score, best_candidate = scores_with_candidates.pop(0)
            if current_score > best_new_score:
                remaining.remove(best_candidate)
                selected.append(best_candidate)
                score_selected.append(best_new_score)
                variable_added.append(best_candidate)
                current_score = best_new_score
            self.step=self.step-1
        formula = "{} ~ {}".format(response,' + '.join(selected))
        if self.fit_intercept == 0:
            formula = formula + "-1"
        model = smf.ols(formula, data).fit()
        return model


from __future__ import absolute_import, division, print_function, unicode_literals
import requests
from requests.api import request
from oauthlib import oauth1
import json
import os
import re
try:
    # python2
    from urlparse import parse_qsl
except ImportError:
    # python3
    from urllib.parse import parse_qsl

class Fetcher(object):
    """
    Base class for Fetchers, which wrap and normalize the APIs of various HTTP
    libraries.

    (It's a slightly leaky abstraction designed to make testing easier.)
    """
    def fetch(self, client, method, url, data=None, headers=None, json=True):
        """Fetch the given request

        Returns
        -------
        content : str (python2) or bytes (python3)
        status_code : int
        """
        raise NotImplementedError()

class LoggingDelegator(object):
    """Wraps a fetcher and logs all requests."""
    def __init__(self, fetcher):
        self.fetcher = fetcher
        self.requests = []

    @property
    def last_request(self):
        return self.requests[-1] if self.requests else None

    def fetch(self, client, method, url, data=None, headers=None, json=True):
        self.requests.append((method, url, data, headers))
        return self.fetcher.fetch(client, method, url, data, headers, json)

class RequestsFetcher(Fetcher):
    """Fetches via HTTP from the Discogs API."""
    def fetch(self, client, method, url, data=None, headers=None, json=True):
        resp = requests.request(method, url, data=data, headers=headers)
        return resp.content, resp.status_code

class UserTokenRequestsFetcher(Fetcher):
    """Fetches via HTTP from the Discogs API using user_token authentication"""
    def __init__(self, user_token):
        self.user_token = user_token

    def fetch(self, client, method, url, data=None, headers=None, json=True):
        resp = requests.request(method, url, params={'token':self.user_token},
                                data=data, headers=headers)
        return resp.content, resp.status_code

class OAuth2Fetcher(Fetcher):
    """Fetches via HTTP + OAuth 1.0a from the Discogs API."""
    def __init__(self, consumer_key, consumer_secret, token=None, secret=None):
        self.client = oauth1.Client(consumer_key, client_secret=consumer_secret)
        self.store_token(token, secret)

    def store_token_from_qs(self, query_string):
        token_dict = dict(parse_qsl(query_string))
        token = token_dict[b'oauth_token'].decode('utf8')
        secret = token_dict[b'oauth_token_secret'].decode('utf8')
        self.store_token(token, secret)
        return token, secret

    def forget_token(self):
        self.store_token(None, None)

    def store_token(self, token, secret):
        self.client.resource_owner_key = token
        self.client.resource_owner_secret = secret

    def set_verifier(self, verifier):
        self.client.verifier = verifier

    def fetch(self, client, method, url, data=None, headers=None, json_format=True):
        body = json.dumps(data) if json_format and data else data
        uri, headers, body = self.client.sign(url, http_method=method,
                                              body=data, headers=headers)

        resp = request(method, uri, headers=headers, data=body)
        return resp.content, resp.status_code

class FilesystemFetcher(Fetcher):
    """Fetches from a directory of files."""
    default_response = json.dumps({'message': 'Resource not found.'}).encode('utf8'), 404
    path_with_params = re.compile('(?P<dir>(\w+/)+)(?P<query>\w+)\?(?P<params>.*)')

    def __init__(self, base_path):
        self.base_path = base_path

    def fetch(self, client, method, url, data=None, headers=None, json=True):
        url = url.replace(client._base_url, '')

        if json:
            base_name = ''.join((url[1:], '.json'))
        else:
            base_name = url[1:]

        path = os.path.join(self.base_path, base_name)

        # The exact path might not exist, but check for files with different
        # permutations of query parameters.
        if not os.path.exists(path):
            base_name = self.check_alternate_params(base_name, json)
            path = os.path.join(self.base_path, base_name)

        try:
            path = path.replace('?', '_')  # '?' is illegal in file names on Windows
            with open(path, 'r') as f:
                content = f.read().encode('utf8')  # return bytes not unicode
            return content, 200
        except:
            return self.default_response

    def check_alternate_params(self, base_name, json):
        """
        parse_qs() result is non-deterministic - a different file might be
        requested, making the tests fail randomly, depending on the order of parameters in the query.
        This fixes it by checking for matching file names with a different permutations of the parameters.
        """
        match = self.path_with_params.match(base_name)

        # No parameters in query - no match. Nothing to do.
        if not match:
            return base_name

        ext = '.json' if json else ''

        # The base name consists of one or more path elements (directories),
        # a query (discogs.com endpoint), query parameters, and possibly an extension like 'json'.
        # Extract these.
        base_dir = os.path.join(self.base_path, match.group('dir'))
        query = match.group('query')  # we'll need this to only check relevant filenames
        params_str = match.group('params')[:-len(ext)]  # strip extension if any
        params = set(params_str.split('&'))

        # List files that match the same query, possibly with different parameters
        filenames = [f for f in os.listdir(base_dir) if f.startswith(query)]
        for f in filenames:
            # Strip the query, the '?' sign (or its replacement) and the extension, if any
            params2_str = f[len(query) + 1:-len(ext)]
            params2 = set(params2_str.split('&'))
            if params == params2:
                return base_name.replace(params_str, params2_str)

        # No matching alternatives found - revert to original.
        return base_name

class MemoryFetcher(Fetcher):
    """Fetches from a dict of URL -> (content, status_code)."""
    default_response = json.dumps({'message': 'Resource not found.'}).encode('utf8'), 404

    def __init__(self, responses):
        self.responses = responses

    def fetch(self, client, method, url, data=None, headers=None, json=True):
        return self.responses.get(url, self.default_response)


"""
YouTube: https://youtu.be/GdNA6AFk0Mg

Terminal display commands:
.header on
.mode columns
WHERE Clause Reference: https://www.tutorialspoint.com/sqlite/sqlite_where_clause.htm
"""
import sqlite3
from sqlite3 import OperationalError, IntegrityError
import os
import sys

class DB_Manager:

    conn = None

    def __init__(self, db_name, dict_output=False):
        self.db_name = db_name
        self.dict_output = dict_output

    def __enter__(self):
        self.conn = sqlite3.connect(self.db_name)
        # Query output for list of dictionaries
        if self.dict_output:
            self.conn.row_factory = sqlite3.Row
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.close()

    def create_table(self, sql_tables_file):
        """Creates table in DB from a script file"""
        with open(sql_tables_file, 'r') as f:
            try:
                self.conn.executescript(f.read())
            except OperationalError as e:
                print(e)
                pass

    def insert_row(self, table_name, insert_or_replace=False, **kwargs):
        """
        Inserts a row into a specified table.
        :param string table_name: name of table in DB
        :param dict **kwargs: dictionary key values of table column names and values
        """
        ior = '' if not insert_or_replace else 'OR REPLACE'
        sql = """INSERT {ior} INTO {tbl} {col} VALUES {val};
              """.format(tbl=table_name,
                         col=tuple(kwargs.keys()),
                         val=tuple(kwargs.values()),
                         ior=ior)
        try:
            self.conn.cursor().execute(sql)
            self.conn.commit()
        except IntegrityError:
            raise ValueError('ERROR: Unique Value already exists in DB.')

    def get_rows(self, table_name, param='1=1', fetch_last=False):
        """
        Returns rows from table
        :param string table_name: name of table in DB
        :param string param: conditional parameter to retreive row data
        :param bool fetch_last: returns last item added to table
        """
        param = param if type(param) is not list else \
            ' AND '.join([x for x in param])

        sql = """SELECT * FROM {} WHERE {};
              """.format(table_name, param)

        try:
            c = self.conn.cursor().execute(sql)
            # Dictionary output option
            if self.dict_output:
                query = [dict(row) for row in c.fetchall()]
            else:
                query = c.fetchall()
            # Last item in query
            if fetch_last:
                return query[-1]
            return query

        # When SQL Query is broken
        except OperationalError:
            raise (OperationalError("Cannot Execute SQL Query: %s" % sql))

    def update_row(self, table_name, col, new_val, where='1=1'):
        """Updating an existing row"""
        sql = """UPDATE {}
                 SET {col} = {nv}
                 WHERE {w};
              """.format(table_name, col=col, nv=new_val, w=where)
        self.conn.cursor().execute(sql)
        self.conn.commit()

if __name__ == '__main__':
    DB = '.{}lalala.sqlite'.format(os.sep)
    tables_script = '.{}tables.sql'.format(os.sep)
    with DB_Manager(DB, dict_output=True) as mngr:
        mngr.create_table(tables_script)
        v = {'first_name': 'hander',
             'last_name': 'taco',
             'email': 'hander@taco.com5',
             'phone': '123-456-8578'}
        mngr.insert_row(table_name='contacts', **v)
        print(mngr.get_rows('contacts', param=['first_name LIKE "hander"', 'contact_id > 3']))
        # print(mngr.get_rows('contacts', 'contact_id > 1', fetch_last=True))


#!/usr/bin/python3

"""
Module gérant l'interface graphique des fenêtres liées à la gestion des devoirs.
"""

## imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from autocompletion import *
from traitements.traitements import *
from dbManagement.dbManagement import BaseDeDonnées

## Classe pour la création des questions d'un devoir
class FenêtreQuestionsDevoir(object):
    """
    Classe gérant la construction d'une fenêtre popup pour la définition des questions /
    compétences associées à un devoir.

    Doit reçevoir un objet Devoir en paramètre.

    Ne gère pas l'attachement de plus de 10 compétences à chaque question.
    """
    maxCompétenceParQuestion = 10
    def __init__(self, devoir:Devoir, builder:Gtk.Builder, bdd:BaseDeDonnées, idDev:int):
        """
        Constructeur de la fenêtre.

        Utilise le patron disponible dans le .glade, donné par le Gtk.Builder, puis modifie
        les lignes en fonction des besoins du devoir considéré.
        """
        self.devoir_save = devoir
        self.fenêtre = builder.get_object("fenêtreQuestionsDevoir")
        self.vue = builder.get_object("fenêtreQuestionsDevoirAfficheur")
        self.vueModificateurs = builder.get_object("fenêtreQuestionsDevoirModificateurs")
        self.modèleModificateurs = builder.get_object("modèleModificateursDevoir")
        self.modèleCompétence = builder.get_object("modèleCompétence")
        self.saisieNoteMax = builder.get_object("fenêtreQuestionsDevoirSaisieNoteMax")
        self.saisieNvxComp = builder.get_object("fenêtreQuestionsDevoirSaisieNvxComp")
        self.fenêtre.set_title("Mise en place des questions du " + devoir.get_enTêteDevoir())
        builder.get_object("fenêtreQuestionsDevoirAppliquer").connect("clicked",self.sauvegarderCompétences)
        # Mise en place du modèle - doit être cohérent avec FenêtreQuestionsDevoir.maxCompétenceParQuestion
        self.modèle = Gtk.ListStore(str, \
                                    str,int,str,int,str,int,str,int,str,int,str,int,str,int,str,int,str,int,str,int)
        # Mise en place de la vue principale
        while self.vue.get_n_columns() > 0:
            self.vue.remove_column(self.vue.get_column(0))
        self.vue.set_model(self.modèle)
        rdNuméro = Gtk.CellRendererText()
        rdNuméro.set_property("editable",True)
        rdNuméro.connect("edited", self.celluleÉditée, (self.modèle,0,str))
        self.vue.append_column(Gtk.TreeViewColumn("N°",rdNuméro,text=0))
        for i in range(FenêtreQuestionsDevoir.maxCompétenceParQuestion):
            col = Gtk.TreeViewColumn("Compétence {}".format(i+1))
            completion = Gtk.EntryCompletion()
            rdComp = CellRendererAutoComplete(completion)
            rdCoef = Gtk.CellRendererText()
            completion.set_model(self.modèleCompétence)
            completion.pack_start(rdComp, True)
            completion.add_attribute(rdComp, "text", 0)
            completion.props.text_column = 0
            completion.set_match_func(match_anywhere, None)
            rdComp.set_property("editable",True)
            rdCoef.set_property("editable",True)
            rdComp.connect("edited", self.celluleÉditée, (self.modèle,2*i+1,str))
            rdCoef.connect("edited", self.celluleÉditée, (self.modèle,2*i+2,int))
            col.pack_start(rdComp,True)
            col.pack_start(rdCoef,False)
            col.add_attribute(rdComp, "text", 2*i+1)
            col.add_attribute(rdCoef, "text", 2*i+2)
            self.vue.append_column(col)
        self.vue.append_column(Gtk.TreeViewColumn("",Gtk.CellRendererText()))
        self.vue.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
        # Mise en place de la vue des modificateurs
        while self.vueModificateurs.get_n_columns() > 0:
            self.vueModificateurs.remove_column(self.vueModificateurs.get_column(0))
        typesModifs = Gtk.ListStore(str)
        [ typesModifs.append([typ]) for typ in devoir.get_listeCatégoriesModificateurs() ]
        col1, col2, col3 = Gtk.TreeViewColumn("Type de modificateur"), \
                           Gtk.TreeViewColumn("Nom"), Gtk.TreeViewColumn("Valeur")
        completion = Gtk.EntryCompletion()
        rdCatMod, rdNomMod, rdValMod = CellRendererAutoComplete(completion), \
                                       Gtk.CellRendererText(), Gtk.CellRendererText()
        completion.set_model(typesModifs)
        completion.pack_start(rdCatMod, True)
        completion.add_attribute(rdCatMod,"text",0)
        completion.props.text_column = 0
        completion.set_match_func(match_anywhere, None)
        rdCatMod.set_property("editable",True)
        rdNomMod.set_property("editable",True)
        rdValMod.set_property("editable",True)
        rdCatMod.connect("edited", self.celluleÉditée, (self.modèleModificateurs,0,str))
        rdNomMod.connect("edited", self.celluleÉditée, (self.modèleModificateurs,1,str))
        rdValMod.connect("edited", self.celluleÉditée, (self.modèleModificateurs,2,float))
        col1.pack_start(rdCatMod,True)
        col2.pack_start(rdNomMod,True)
        col3.pack_start(rdValMod,True)
        col1.add_attribute(rdCatMod, "text", 0)
        col2.add_attribute(rdNomMod, "text", 1)
        col3.add_attribute(rdValMod, "text", 2)
        col2.set_expand(True)
        col3.set_cell_data_func(rdValMod, \
                                lambda col, cell, model, iter, unused:
                                cell.set_property("text", "{:.2f}".format(model.get(iter, 2)[0])))
        self.vueModificateurs.append_column(col1)
        self.vueModificateurs.append_column(col2)
        self.vueModificateurs.append_column(col3)
        self.vue.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
        self.ajouterLigne(self.modèleModificateurs)
        # Initialisation des données
        self.bdd = bdd
        self.idDev = idDev
        self.màjModèle()
        self.gestionAffichage()
        # Lancement
        self.fenêtre.show_all()
        réponse = self.fenêtre.run()
        if réponse == Gtk.ResponseType.OK:
            self.sauvegarderCompétences(None)
        self.fenêtre.hide()

    def ajouterLigne(self,modèle) -> None:
        """
        Fonction interne pour ajouter une ligne vide au modèle, servant à créer une nouvelle question.
        """
        if modèle == self.modèle:
            nom = ""
            cp_coeff = ["",0]*FenêtreQuestionsDevoir.maxCompétenceParQuestion
            self.modèle.append([nom]+cp_coeff)
        elif modèle == self.modèleModificateurs:
            self.modèleModificateurs.append(["","",0])

    def màjModèle(self) -> None:
        """
        Fonction interne qui reset les modèles à partir des données du self.devoir_save
        """
        self.modèle.clear()
        for q in self.devoir_save.get_listeQuestionsModèle(FenêtreQuestionsDevoir.maxCompétenceParQuestion):
            self.modèle.append(q)
        self.ajouterLigne(self.modèle)
        self.modèleModificateurs.clear()
        for m in self.devoir_save.get_listeModificateursModèle():
            self.modèleModificateurs.append(m)
        self.ajouterLigne(self.modèleModificateurs)
        self.saisieNoteMax.set_text(str(self.devoir_save.get_noteMax()))
        self.saisieNvxComp.set_text(str(self.devoir_save.get_niveauxAcquisition()))

    def celluleÉditée(self, cellule, path, text, data):
        """
        Callback pour la mise à jour du modèle quand une case est éditée.
        """
        modèle,num,typ = data
        text = typ(text)
        modèle[path][num] = text
        if modèle[path][0] == "":
            modèle.remove(modèle.get_iter(path))
        if int(path) == len(modèle)-1:  # gestion de l'ajout de nouvelle ligne
            self.ajouterLigne(modèle)
        self.gestionAffichage()

    def gestionAffichage(self):
        """
        Fonction ayant pour rôle de définir les colonnes visibles ou non.
        """
        listeCompétences = [ [k for k in a[1:] if type(k) == str and k != ""] for a in self.modèle ]
        nbCols = max([ len(a) for a in listeCompétences ]) + 2
        for i in range(1,nbCols):
            self.vue.get_column(i).set_visible(True)
        for i in range(nbCols,FenêtreQuestionsDevoir.maxCompétenceParQuestion+1):
            self.vue.get_column(i).set_visible(False)

    def sauvegarderCompétences(self,dummy):
        """
        Callback utilisé pour mettre à jour l'objet Devoir sous-jacent à partir du modèle.
        """
        listeModèle = [ ligne[:] for ligne in self.modèle ]
        listeModèle.pop()  # on enlève la ligne vide
        listeCompétences = [ ligne[0] for ligne in self.modèleCompétence ]
        self.devoir_save.set_questionsDepuisModèle(listeModèle, listeCompétences)
        listeModèle = [ ligne[:] for ligne in self.modèleModificateurs ]
        listeModèle.pop()  # on enlève la ligne vide
        self.devoir_save.set_modificateursDepuisModèle(listeModèle)
        self.devoir_save.set_noteMax(self.saisieNoteMax.get_text())
        self.devoir_save.set_niveauxAcquisition(self.saisieNvxComp.get_text())
        # Gestion de la BDD
        self.bdd.sauverDevoir(self.devoir_save.get_évaluationBDD())
        self.màjModèle()

## Classe pour la saisie des évaluations lors d'un devoir
class FenêtreÉvaluationDevoir(object):
    """
    Classe gérant la construction d'une fenêtre popup pour l'évaluation d'un devoir
    par compétences.

    Doit reçevoir un objet Devoir en paramètre.
    """
    colÉtNo, colÉtNom, colÉtPrésence, colÉtÉvalué = 0,1,2,3
    colQuest, colComp, colCoef, colÉval = 0,1,2,3
    premièreOuverture = True
    handler_id_sélecteurÉtudiant = None
    def __init__(self, devoir:Devoir, builder:Gtk.Builder, bdd:BaseDeDonnées, idDev:int):
        """
        Constructeur de la fenêtre.

        Utilise le patron disponible dans le .glade, donné par le Gtk.Builder, puis modifie
        les lignes en fonction des besoins du devoir considéré.
        """
        # Récupération de la fenêtre et des objets
        self.fenêtre = builder.get_object("fenêtreÉvaluationDevoir")
        self.modèle = builder.get_object("fenêtreÉvaluationDevoirModèle")
        self.vue = builder.get_object("fenêtreÉvaluationDevoirTreeView")
        self.sélecteurÉtudiant = builder.get_object("fenêtreÉvaluationDevoirSélecteurÉtudiant")
        self.switchPrésence = builder.get_object("fenêtreÉvaluationDevoirSwitchPrésence")
        self.modèlePointsFixes = builder.get_object("fenêtreÉvaluationDevoirModèlePointsFixes")
        self.modèleModificateurs = builder.get_object("fenêtreÉvaluationDevoirModèleModificateurs")
        self.vuePointsFixes = {'label':builder.get_object("fenêtreÉvaluationDevoirLabelPointsFixes"),
                               'tv':builder.get_object("fenêtreÉvaluationDevoirTreeViewPointsFixes"),
                               'rd':builder.get_object("fenêtreÉvaluationDevoirRdPointsFixes")}
        self.vueModificateurs = {'label':builder.get_object("fenêtreÉvaluationDevoirLabelModifs"),
                                 'tv':builder.get_object("fenêtreÉvaluationDevoirTreeviewModifs"),
                                 'rd':builder.get_object("fenêtreÉvaluationDevoirRdModifs")}
        if FenêtreÉvaluationDevoir.premièreOuverture:
            renderer_étudiant = Gtk.CellRendererText()
            renderer_étudiant.set_property("foreground","grey")
            self.sélecteurÉtudiant.pack_start(renderer_étudiant, True)
            self.sélecteurÉtudiant.add_attribute(renderer_étudiant, "text", FenêtreÉvaluationDevoir.colÉtNom)
            self.sélecteurÉtudiant.add_attribute(renderer_étudiant,'foreground-set',\
                                                 FenêtreÉvaluationDevoir.colÉtÉvalué)
            rdr = builder.get_object("fenêtreÉvaluationDevoirTreeViewColonneNomRenderer")
            builder.get_object("fenêtreÉvaluationDevoirTreeViewColonneNom").add_attribute(rdr,'visible',4)
            col = builder.get_object("fenêtreÉvaluationDevoirColModifsVal")
            col.set_cell_data_func(builder.get_object("fenêtreÉvaluationDevoirColModifsValRd"), \
                                   lambda col, cell, model, iter, unused:
                                   cell.set_property("text", "{:.2f}".format(model.get(iter, 1)[0])))
            col = builder.get_object("fenêtreÉvaluationDevoirColPointsFixes")
            col.set_cell_data_func(builder.get_object("fenêtreÉvaluationDevoirRdPointsFixes"), \
                                   lambda col, cell, model, iter, unused:
                                   cell.set_property("text", "{:.2f}".format(model.get(iter, 2)[0])))
            col = builder.get_object("fenêtreÉvaluationDevoirColPointsFixes1")
            col.set_cell_data_func(builder.get_object("fenêtreÉvaluationDevoirRdPointsFixes1"), \
                                   lambda col, cell, model, iter, unused:
                                   cell.set_property("text", "{:.2f}".format(model.get(iter, 1)[0])))
            FenêtreÉvaluationDevoir.premièreOuverture = False
        # Préparation des attributs autres
        self.fenêtre.set_title("Évaluation du " + devoir.get_enTêteDevoir())
        self.devoir_save = devoir
        self.étudiantActif = 0
        self.bdd = bdd
        self.idDev = idDev
        # Sélection des étudiants
        if FenêtreÉvaluationDevoir.handler_id_sélecteurÉtudiant is not None:
            self.sélecteurÉtudiant.disconnect(FenêtreÉvaluationDevoir.handler_id_sélecteurÉtudiant)
        self.modèleÉtudiants = Gtk.ListStore(int,str,bool,bool)
        for ét in devoir.get_listeÉtudiantsModèle():
            self.modèleÉtudiants.append(ét)
        self.sélecteurÉtudiant.set_model(self.modèleÉtudiants)
        self.sélecteurÉtudiant.set_active(self.étudiantActif)
        self.changeÉtudiant(self.sélecteurÉtudiant,False)
        # Autres infos
        texte_nvx = "Niveaux d'acquisition : {}".format(devoir.get_niveauxAcquisition())
        builder.get_object("fenêtreÉvaluationDevoirLabelNvxComp").set_text(texte_nvx)
        builder.get_object("fenêtreÉvaluationDevoirTreeViewColonneÉval").\
            set_title("Éval (/{})".format(devoir.get_niveauxAcquisition()-1))
        # Connection des signaux
        builder.get_object("fenêtreÉvaluationDevoirÉditeurÉval").connect("edited", self.évaluationÉditée, \
                                                                         (self.modèle,int))
        FenêtreÉvaluationDevoir.handler_id_sélecteurÉtudiant = self.sélecteurÉtudiant.\
                                                               connect("changed", self.changeÉtudiant)
        builder.get_object("fenêtreÉvaluationDevoirBoutonAppliquer").connect("clicked", self.sauvegarderÉvaluation)
        builder.get_object("fenêtreÉvaluationDevoirÉditeurÉval").connect("edited", self.évaluationÉditée,
                                                                         (self.modèle,int))
        self.switchPrésence.connect("state_set", self.switchPrésenceÉtudiant)
        builder.get_object("fenêtreÉvaluationDevoirColModifsÉval").connect("edited",self.évaluationÉditée, \
                                                                           (self.modèleModificateurs,int))
        builder.get_object("fenêtreÉvaluationDevoirRdPointsFixes").connect("edited",self.évaluationÉditée, \
                                                                           (self.modèlePointsFixes,float))
        # Lancement
        self.fenêtre.show()
        réponse = self.fenêtre.run()
        if réponse == Gtk.ResponseType.OK:
            self.sauvegarderÉvaluation(None)
        self.fenêtre.hide()

    def évaluationÉditée(self, cellule, path, text, data):
        """
        Callback pour la mise à jour du modèle quand une case est éditée.
        """
        modèle,typ = data
        try:
            val = typ(text)
            if modèle == self.modèle and val < self.devoir_save.get_niveauxAcquisition() and val > -2:
                modèle[path][FenêtreÉvaluationDevoir.colÉval] = val
            elif modèle == self.modèlePointsFixes and val <= modèle[path][1]:
                modèle[path][2] = val
            elif modèle == self.modèleModificateurs:
                modèle[path][2] = val
        except ValueError:  # cas d'un non-entier proposé
            pass

    def switchPrésenceÉtudiant(self, switch:Gtk.Switch, data:object) -> None:
        """
        Callback pour la modification du statut présent ou non de l'étudiant.
        """
        self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtPrésence] = switch.get_active()

    def changeÉtudiant(self, sélecteur:Gtk.ComboBox, sauvegarder:bool=True) -> None:
        """
        Callback pour le changement d'étudiant.
        """
        if sauvegarder:
            self.sauvegarderÉvaluation()
        self.étudiantActif = self.sélecteurÉtudiant.get_active()
        self.modèle.clear()
        self.modèlePointsFixes.clear()
        self.modèleModificateurs.clear()
        évaluation = self.devoir_save.get_évaluationÉtudiantModèle(self.étudiantActif)
        self.switchPrésence.set_active(évaluation[1])
        self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtPrésence] = évaluation[1]
        for row in évaluation[0]:
            if row[0] == "points fixes":
                self.modèlePointsFixes.append(row[1:])
            elif row[0] == "pourcentage":
                self.modèleModificateurs.append(row[1:])
            else:
                self.modèle.append(row + [True])
                if len(self.modèle) > 1 and self.modèle[-1][0] == self.modèle[-2][0]:
                    self.modèle[-1][-1] = False
        if len(self.modèlePointsFixes) == 0:
            self.vuePointsFixes['label'].set_visible(False)
            self.vuePointsFixes['tv'].set_visible(False)
        else:
            self.vuePointsFixes['label'].set_visible(True)
            self.vuePointsFixes['tv'].set_visible(True)
        if len(self.modèleModificateurs) == 0:
            self.vueModificateurs['label'].set_visible(False)
            self.vueModificateurs['tv'].set_visible(False)
        else:
            self.vueModificateurs['label'].set_visible(True)
            self.vueModificateurs['tv'].set_visible(True)

    def sauvegarderÉvaluation(self, data:object=None):
        """
        Callback pour sauvegarder l'évaluation de l'étudiant.
        """
        liste = [ ["points fixes"]+row[:] for row in self.modèlePointsFixes ] + \
                [ ["pourcentage"] +row[:] for row in self.modèleModificateurs] + \
                [ row[:] for row in self.modèle ]
        présence = self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtPrésence]
        self.devoir_save.set_évaluationÉtudiantModèle(self.étudiantActif, liste, présence)
        uneÉval = (np.array([ row[FenêtreÉvaluationDevoir.colÉval] for row in self.modèle ]) > -1).any()
        présence = self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtPrésence]
        self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtÉvalué] = uneÉval or not présence
        self.bdd.sauverÉvaluationUnÉtudiant(self.devoir_save.get_évaluationBDDUnÉtudiant(self.étudiantActif))


""" Defines the robot class and some actions the robot can perform and the main real-time control loop.
"""
import time
import threading
import logging
import math
import numpy as np
import sm_kinematics

log = logging.getLogger(__name__)

from enum import Enum

import config
# import joints
import pca9685_psd
import balance
import footpath

# FIXME
# import imu
# imu = imu.IMU()

# We always count and order the legs this way, left to right and front to rear
class LegId(Enum):
    LF = 0  # Left Front is first and number 0
    RF = 1
    LR = 2
    RR = 3  # Right Rear is last and number 3

# These are the things the robot can do.  So the robot can be in the
# "walk state" or the "neutral_stand state" or one of the others
class BehaviorState(Enum):
    INITIAL_POWERUP = 0     # Legs are in unknown position
    NEUTRAL_STAND   = 1     # Standing straight at medium height and weight equal on all feet
    WALK            = 2     # Actively walking, although velocity could be zero
    SIT             = 3     # Sit posture, with body tilted forward end upward

# These are sub-states. Each of the above states can have one of these sub-states.
class BehaviorSubState(Enum):
    STARTING     = 0       # Transition to current Behavior State
    CONTINUING   = 1
    TERMINATING  = 2       # Behavior is ending

init_time = time.time()
prior_state = BehaviorState.INITIAL_POWERUP
robot_substate = BehaviorSubState.STARTING

# PARAMETERS ##########################
neutral_body_height = 0.200
neutral_stance_width = 0.160
neutral_stance_length = 0.265

# Inter Task GLOBAL VARIABLES ####################
# These are used for inter-task communication.  The real-time control task
# reads them and the GUI or other controller task sets them.
# Always use the lock to access these.
gbl_run_control_loop = False

gbl_robot_state    = BehaviorState.INITIAL_POWERUP

# The next three variables are equivalent to the ROS "twist" message.
gbl_velocity_x = 0.0
gbl_velocity_y = 0.0
gbl_rot_rate_z = 0.0

gbl_stance_width   = neutral_stance_width
gbl_stance_length  = neutral_stance_length
gbl_body_center_up = neutral_body_height

gbl_body_center_left    = 0.0
gbl_body_center_forward = 0.0

gbl_body_angle_roll  = 0.0
gbl_body_angle_pitch = 0.0
gbl_body_angle_yaw   = 0.0

# Time to move from previous state to a new static pose such as "sit" or "stand"
# This should be set to something reasonable before changing the state to one of the static pose states
gbl_static_transition_time = 0.0

gbl_lock = threading.Lock()
## END GLOBALS #################

def limit_check_lp(Lp, check_id=''):
    """Perform a diagnostic error check on Lp array -- remove this in production code"""
    if abs(np.amax(Lp[:, :3])) > 0.300:
        # ERROR something is wrong, the legs are not over 300 mm long
        log.error('Lp array limit check failed')
        print(check_id, Lp)
    return

def neutral_stance_Lp() -> np.array:
    """Function to return a reasonable neutral stance on flat ground"""

    global gbl_stance_width
    global gbl_body_center_up
    global gbl_run_control_loop

    # Units for these are Meters.  Change the constants as opion about
    # what is best evolves.
    bias = -0.065

    if gbl_lock.acquire(blocking=True, timeout=0.5):
        stance_width  = gbl_stance_width
        stance_length = gbl_stance_length
        vertical      = gbl_body_center_up
        gbl_lock.release()
    else:
        log.warning('neutral_stance_Lp() failed to acquire lock')

    stance_halfwidth  = stance_width  / 2.0
    stance_halflength = stance_length / 2.0

    # Distances of each foot from body center
    #                   fore/aft               height      left/right
    Lp = np.array([[stance_halflength + bias, -vertical, stance_halfwidth, 1],  # LF
                   [stance_halflength + bias, -vertical, -stance_halfwidth, 1],  # RF
                   [-stance_halflength + bias, -vertical, stance_halfwidth, 1],  # LR
                   [-stance_halflength + bias, -vertical, -stance_halfwidth, 1]])  # RR

    limit_check_lp(Lp, check_id='get_neural_stance_Lp')
    return Lp

tick_start = 0.0
lp_neutral = neutral_stance_Lp()
lp = lp_neutral.copy()

velocity_x = 0.0
velocity_y = 0.0
rot_rate_z = 0.0
fp = footpath.Footpath()
body_center_up = 0.
transition_time = 0.

def control_loop():
    """
    Realtime control loop for robot base controller.

    This function should be called as a task.  After some setup, a loop is
    entered that runs many times per second.  The loop will,
      1) read shared variables, after obtaining lock
      2) compute the desired location in (x,y,z), body relative for each foot
      3) perform inverse kinetics to find the joint angles for each leg
      4) update the servo motors to move the joints to the desired angle
    The loop continues to run until gbl_run_control_loop is set to False.

    Returns: none.

    """

    ### GLOBALS used for inter task communication
    global gbl_lock
    global gbl_run_control_loop

    global gbl_robot_state

    global gbl_body_center_up
    global gbl_body_center_left
    global gbl_body_center_forward

    global gbl_body_angle_roll
    global gbl_body_angle_pitch
    global gbl_body_angle_yaw

    global prior_state
    global robot_substate

    global velocity_x
    global velocity_y
    global rot_rate_z
    global tick_start
    global body_center_up

    global gbl_static_transition_time
    global transition_time

    ### Channel number on PCA9685 for each angle
    servo_channel = np.array(config.servo_channel_assignment,
                             dtype=np.uint8)
    # Class to abstract real and simulated servo hardware
    # servos = joints.ServoShim()
    s = pca9685_psd.Servo()

    # smk (Spot Micro Kinematic) object
    smk = sm_kinematics.Kinematic()

    # Heartbeat will blink a LED.
    heartbeat_time = 0.0

    loop_period = 1.0 / config.rt_loop_freq
    run_control_loop = True  # Set True in case acquire lock fails.
    while True:
        tick_start = time.time()

        # copy global parameters
        if gbl_lock.acquire(blocking=True, timeout=loop_period / 2.0):
            run_control_loop = gbl_run_control_loop

            robot_state = gbl_robot_state

            velocity_x = gbl_velocity_x
            velocity_y = gbl_velocity_y
            rot_rate_z = gbl_rot_rate_z

            body_center_up      = gbl_body_center_up
            body_center_left    = gbl_body_center_left
            body_center_forward = gbl_body_center_forward

            body_angle_roll  = gbl_body_angle_roll
            body_angle_pitch = gbl_body_angle_pitch
            body_angle_yaw   = gbl_body_angle_yaw

            transition_time = gbl_static_transition_time

            gbl_lock.release()
        else:
            log.warning('control_loop() failed to acquire lock {0}'.format(tick_start))
            print('control_loop() failed to acquire lock {0}'.format(tick_start)) #TODO

        # Check if this thread should continue running
        if not run_control_loop:
            log.info('control loop terminating at {0}'.format(tick_start))
            print('control loop terminating at {0}'.format(tick_start)) #TODO
            break

        # Heartbeat -- show the world this thread is alive
        if tick_start > heartbeat_time:
            #FIXME need to blink a real LED
            log.debug('heartbeat at {0}'.format(tick_start))
            print('heartbeat at {0}'.format(tick_start)) #TODO remove
            heartbeat_time = tick_start + config.rt_heartbeat_period

        # Did the robot state just change?  If so then the sub state has to be "STARTING"
        if robot_state != prior_state:
            robot_substate = BehaviorSubState.STARTING
            prior_state = robot_state

        if robot_state == BehaviorState.INITIAL_POWERUP:
            # In this state, there is nothing to do.
            continue

        elif robot_state == BehaviorState.NEUTRAL_STAND:
            control_stand()
        elif robot_state == BehaviorState.WALK:
            control_walk()
        elif robot_state == BehaviorState.SIT:
            control_sit()
        else:
            log.error('Invalid State, resetting')
            robot_state = BehaviorState.INITIAL_POWERUP

        #FIXME -- add in any body position and angle needed for things like balance
        # roll_correction, pitch_correction = balance.rotate_to_level(imu.get_acceleration())
        roll_correction, pitch_correction = 0.0, 0.0

        # Find the joint angles to move the foot to the required position
        body_angles = (body_angle_roll + roll_correction,
                       body_angle_yaw,
                       body_angle_pitch + pitch_correction)
        body_center = (body_center_forward,
                       body_center_up-neutral_body_height,
                       body_center_left)

        joint_angles = smk.calcIK(lp, body_angles, body_center)

        # Set the robots joints to the angles computed above.
        # Note that the function below will check if the motors are enabled or if
        # we are running in simulation only.  So maybe the angles only go to a log
        # file, or they could move the physical robot.
        """
        for leg_idx, leg in enumerate(joint_angles):
            for theta_idx, theta in enumerate(leg):
                servos.set_angle(servo_channel[leg_idx, theta_idx], theta)"""

        # Turn (4, 3) array into a (16,) array.
        # First make it a (4, 4) and then flatten it
        joint_angles_flattened = np.append(joint_angles, [[0.0], [0.0], [0.0], [0.0]], 1).reshape(-1)
        s.move_16_radian(joint_angles_flattened)

        tick_elapsed = time.time() - tick_start
        sleep_time = loop_period - tick_elapsed

        if sleep_time < 0.001:
            log.warning('loop overrun, elapsed = {0}'.format(tick_elapsed))
        else:
            assert 0.001 < sleep_time < loop_period, 'invalid sleep_time {0}'.format(sleep_time)
            time.sleep(sleep_time)

    # this task ends
    log.debug('control loop hits return')
    return

stand_start_time = 0.0
lp_zero = np.array([[0., 0., 0., 0.],
                    [0., 0., 0., 0.],
                    [0., 0., 0., 0.],
                    [0., 0., 0., 0.]])

# Table specifies the phase when each foot begins to move and when it finishes
# phase the fraction of the transition_time that has past since stand_start_time.
# FIXME This table needs to be computed based on distance each foot is to be moved.
#
#             Start  End
leg_phase = [(0.00, 0.20),  # leg 0 LF
             (0.50, 0.70),  # leg 1 RF
             (0.25, 0.45),  # leg 2 LR
             (0.75, 0.95)]  # leg 4 RR

def control_stand() -> None:

    global gbl_lock

    global robot_substate
    global stand_start_time
    global lp_zero
    global lp_neutral

    global transition_time
    global lp

    if robot_substate == BehaviorSubState.STARTING:

        # Substate is "starting" so we run the initialization code then set state to
        # "continueing"
        stand_start_time = time.time()

        lp_zero = lp.copy()   # save current feet location
        lp_neutral = neutral_stance_Lp()
        robot_substate = BehaviorSubState.CONTINUING

    if robot_substate == BehaviorSubState.CONTINUING:

        phase = (time.time() - stand_start_time) / transition_time
        if phase < 1.0:
            lp =  (phase * lp_zero) +  ((1.0 - phase) * lp_neutral)

        else:
            lp = lp_neutral.copy()

    else:
        log.error('Invalid Substate')
    return

step_start = 0.0
#                     LF     RF     LR     RR
leg_relative_phase = [0.00, -0.50, -0.50, -0.00]
static_step_air_fraction = 0.0
last_velocity = (0., 0., 0.)
step_period = 0.001 # 0.001 is a hack to avid potential for divide by zero error

def control_walk():

    global gbl_lock
    global robot_substate
    global step_start
    global tick_start
    global leg_relative_phase
    global static_step_air_fraction
    global body_center_up
    global last_velocity
    global step_period

    if robot_substate == BehaviorSubState.STARTING:
        step_start = tick_start
        robot_substate = BehaviorSubState.CONTINUING

    if robot_substate == BehaviorSubState.CONTINUING:

        # if the robot is not moving, there is nothing to do...
        if (abs(velocity_x) + abs(velocity_y)) < 0.002:
            return

        # Only call fp.set_velocity on change
        if last_velocity != (velocity_x, velocity_y, rot_rate_z):
            step_period = fp.set_velocity(velocity_x, velocity_y, rot_rate_z)
            last_velocity = (velocity_x, velocity_y, rot_rate_z)

        # get the current step phase, handle wrap-around.  Phase can never
        # be greater than 1.0
        assert step_period > 0.01  # verify a sane value
        step_phase = (tick_start - step_start) / step_period
        if step_phase > 1.0:
            (step_phase, _) = math.modf(step_phase)
            step_start = tick_start - (step_period * step_phase)

        assert 0.0 <= step_phase <= 1.0, \
            'invalid Step Phase {0}, {1}, {2}, {3}'. \
                format(step_phase, tick_start, step_start, step_period)

        num_feet_on_ground = 0
        for leg_id in LegId:
            leg_abs_phase = step_phase + leg_relative_phase[leg_id.value]

            if leg_abs_phase < 0:
                leg_abs_phase = 1.0 + leg_abs_phase
            assert 0.0 <= leg_abs_phase <= 1.0

            fx, fy, fz = fp.xyz(leg_abs_phase)
            x_neutral = lp_neutral[leg_id.value, 0]
            y_neutral = lp_neutral[leg_id.value, 2]
            lp[leg_id.value, :3] = [fx + x_neutral,
                                    fz - body_center_up,
                                    fy + y_neutral]
            if fz == 0.0:
                num_feet_on_ground += 1

        # Check if all four feet are on the ground, if so then we can
        # update any of the step parameters
        if num_feet_on_ground == 4:
            # Now is the time, with all four feet down
            # to adjust the step parameters.
            step_period = fp.set_velocity(velocity_x, velocity_y, rot_rate_z)

def control_sit():
    pass

class Robot:
    """
    This class provides basic controls for the robot base controller.

    The Robot class provides a set of functions that return immediately, that are
    use to control the robot's basic motion and behaviors such as walk, sit, or
    stand.  Creating a Robot object will start a task initialize the real-time
    control loop.  The Robot class then has methods to create movements and to
    get status.
    """

    def __init__(self):
        """Create a Robot object and a task to control it."""

        self.lock_timeout = 0.5
        self.__start_control_loop_thread()

        return

    def kill(self):
        """Quickly stops the realtime control task."""

        global gbl_lock
        global gbl_run_control_loop
        global gbl_robot_state

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_run_control_loop = False
            gbl_robot_state = BehaviorState.INITIAL_POWERUP
            gbl_lock.release()
        else:
            log.error('kill() failed to acquire lock')
        return

    def set_velocity(self, vel_x: float = 0.0, vel_y: float = 0.0, rot_z: float = 0.0):
        """Sets the robot's velocity and rate of turn over ground."""

        global gbl_lock
        global gbl_velocity_x
        global gbl_velocity_y
        global gbl_rot_rate_z

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_velocity_x = vel_x
            gbl_velocity_y = vel_y
            gbl_rot_rate_z = rot_z
            gbl_lock.release()
        else:
            log.error('set_velocity() failed to acquire lock')
        return

    def walk(self, vel_x: float = 0.0, vel_y: float = 0.0, rot_z: float = 0.0):
        """Cause robot to walk."""

        global gbl_lock
        global gbl_robot_state

        self.set_velocity(vel_x, vel_y, rot_z)

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_robot_state = BehaviorState.WALK
            gbl_lock.release()
        else:
            log.error('walk() failed to acquire lock')
        return

    def stand(self, transition: float = 1.0):
        """Cause robot to stand in a neutral position."""

        global gbl_lock
        global gbl_robot_state
        global gbl_static_transition_time

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_static_transition_time = transition
            gbl_robot_state    = BehaviorState.NEUTRAL_STAND
            gbl_lock.release()
        else:
            log.error('stand() failed to acquire lock')
        return

    def sit(self, transition_time: float = 0.0):
        """Cause robot to sit."""

        global gbl_lock
        global gbl_robot_state

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_robot_state    = BehaviorState.SIT
            gbl_lock.release()
        else:
            log.error('failed to acquire lock')
        return

    def set_body_angles(self, body_angle: (float, float, float) = (0.0, 0.0, 0.0)):
        """Set body rotations with respect to neutral position."""

        global gbl_lock

        global gbl_body_angle_roll
        global gbl_body_angle_pitch
        global gbl_body_angle_yaw

        limit = math.radians(21.0)  # limit for all angles

        roll = body_angle[0]
        assert -limit <= roll <= limit, f"roll = {roll}"

        pitch = body_angle[1]
        assert -limit <= pitch <= limit, f"pitch = {pitch}"

        yaw = body_angle[2]
        assert -limit <= yaw <= limit, f"yaw = {yaw}"

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):

            gbl_body_angle_roll = roll
            gbl_body_angle_pitch = pitch
            gbl_body_angle_yaw = yaw

            gbl_lock.release()
        else:
            log.error('failed to acquire lock')

    def set_body_center(self, body_point: (float, float, float)):
        """Set body translations with respect to neutral position."""

        global gbl_lock

        global gbl_body_center_up
        global gbl_body_center_left
        global gbl_body_center_forward

        forward = body_point[0]
        assert -0.100 <= forward <= 0.100

        left = body_point[1]
        assert -0.100 <= left <= 0.100

        up = body_point[2]
        assert 0.120 <= up <= 0.240

        log.debug('acquiring the lock...')
        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):

            gbl_body_center_forward = forward
            gbl_body_center_left = left
            gbl_body_center_up = up

            gbl_lock.release()
        else:
            log.error('failed to acquire lock')

    def set_stance_width(self, stance_width_delta: float):
        """Set the distance between the left and right feet

        Parms:
            stance_width_delta  Reduction from nominal stance in meters
        """

        global gbl_lock
        global gbl_stance_width

        assert -0.050 < stance_width_delta < 0.050

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_stance_width = neutral_stance_width + stance_width_delta
            gbl_lock.release()
        else:
            log.error('failed to acquire lock')
        return

    def set_stance_length(self, stance_length: float):
        """Set the distance between the front and rear feet."""

        global gbl_lock
        global gbl_stance_length

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_stance_length = stance_length
            gbl_lock.release()
        else:
            log.error('failed to acquire lock')
        return

    def __start_control_loop_thread(self):
        """Private method to setup and run the real time control loop."""
        global gbl_lock
        global gbl_run_control_loop
        global gbl_robot_state
        global gbl_robot_substate

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            loop_was_running = gbl_run_control_loop

            gbl_run_control_loop = True
            gbl_robot_state = BehaviorState.INITIAL_POWERUP
            gbl_robot_substate = BehaviorSubState.STARTING

            gbl_lock.release()
        else:
            log.error('failed to acquire lock')

        if loop_was_running:
            log.warning("control loop thread already active")
            print("control loop thread already active") #TODO remove
        else:

            self.control_loop_thread = threading.Thread(target=control_loop, args=(), daemon=True)
            log.debug("starting control_loop_thread")
            print("starting control_loop_thread") #TODO remove
            self.control_loop_thread.start()

        log.debug("control loop thread returning")
        return

def __walk_test(duration):
    """Debugging test, cause the robot to walk for a few seconds."""

    assert duration > 0.0
    assert duration <= 20.0

    r = Robot()
    r.walk(0.05, 0, 0)
    time.sleep(duration)
    r.set_velocity(0.0, 0.0, 0.0)
    r.stand()
    r.kill()
    return

if __name__ == "__main__":
    logging.basicConfig(filename='quad_controller.log',
                        filemode='w',
                        level=logging.DEBUG)
    __walk_test(10)


import torch.nn as nn
import model.octconv as oct
import model.venconv as ven
from model.median_pooling import median_pool_2d

class PrePocess(nn.Module):
    def __init__(self, config):
        super(PrePocess, self).__init__()
        # parameters
        low_channel_num = [config["in_channels"], 16, 32, config["lcn"]]
        high_channel_num = [config["in_channels"], 16, 32, config["hcn"]]
        self.pre_num = len(low_channel_num) - 1
        self.input_layers = 2

        # channel expansion
        self.downs = nn.AvgPool2d(kernel_size=(2, 2), stride=2)
        self.prelow = nn.ModuleList(
            ven.Conv_BN_ACT(low_channel_num[i], low_channel_num[i + 1],
                            config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.PReLU)
            for i in range(self.pre_num)
        )
        self.prehigh = nn.ModuleList(
            ven.Conv_BN_ACT(high_channel_num[i], high_channel_num[i + 1],
                            config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.PReLU)
            for i in range(self.pre_num)
        )

        # Pre process layers
        self.inLowLists = nn.ModuleList(
            ven.Conv_BN_ACT(low_channel_num[-1], low_channel_num[-1],
                            config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.PReLU)
            for i in range(self.input_layers))

        self.inHighLists = nn.ModuleList(
            ven.Conv_BN_ACT(high_channel_num[-1], high_channel_num[-1],
                            config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.PReLU)
            for i in range(self.input_layers))

    def forward(self, x):
        # Split input image
        x_l = self.downs(x)
        x_h = x
        # Channel expansion
        for i in range(self.pre_num):
            x_l = self.prelow[i](x_l)
            x_h = self.prehigh[i](x_h)

        # Pre process
        for i in range(self.input_layers):
            x_l = self.inLowLists[i](x_l)
            x_h = self.inHighLists[i](x_h)

        return x_h, x_l

class FeatureExtract(nn.Module):
    def __init__(self, config):
        super(FeatureExtract, self).__init__()
        # parameters
        self.hide_layers = config["hide_layers"]
        self.config = config

        # convolution layers
        self.octavecons = nn.ModuleList(oct.Conv_BN_ACT(config["hide_channels"], config["hide_channels"], config["kernel_size"], config["alpha"], config["alpha"], config["stride"],
                            config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.ReLU) for i in range(self.hide_layers))
        self.vencons = nn.ModuleList(ven.Conv_BN_ACT(config["hcn"], config["hcn"],
                            config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.PReLU) for i in range(self.hide_layers))
        # residual layers
        self.rescons = nn.ModuleList(oct.ResBlock(config["hide_channels"], config["hide_channels"], config["kernel_size"], config["alpha"],
                                    config["alpha"], config["stride"], config["padding"], config["dilation"], config["groups"]) for i in range(self.hide_layers))

    def forward(self, x):
        x_h, x_l = x
        for i in range(self.hide_layers):
            x_h, x_l = self.octavecons[i]((x_h, x_l))
            x_h = median_pool_2d(x_h, self.config["kernel_size"], self.config["stride"], self.config["padding"], self.config["dilation"])
            x_h = self.vencons[i](x_h)
            x_h, x_l = self.rescons[i]((x_h, x_l))

        return x_h, x_l

class HighFeature(nn.Module):
    def __init__(self, config):
        super(HighFeature, self).__init__()
        # parameters
        self.hide_layers = config["hide_layers"]

        # convolution layers
        self.octavecons = nn.ModuleList(oct.OctaveHighFeature_BN_ACT(config["hide_channels"], config["hide_channels"], config["kernel_size"], config["alpha"], config["stride"],
                            config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.ReLU) for i in range(self.hide_layers))
        # channel expansion
        self.lowExpansion = ven.Conv_BN_ACT(config["lcn"], config["hide_channels"],
                            config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"])
        self.highExpansion = ven.Conv_BN_ACT(config["hcn"], config["hide_channels"],
                            config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"])

    def forward(self, x):
        x_h, x_l = x
        x_l = self.lowExpansion(x_l)
        x_h = self.highExpansion(x_h)

        for i in range(self.hide_layers):
            x_h, x_l = self.octavecons[i]((x_h, x_l))

        return x_h, x_l

class Model(nn.Module):
    def __init__(self, config):
        super(Model, self).__init__()
        # parameters
        self.output_layers = 2
        lcn = int(config["alpha"] * config["hide_channels"])
        hcn = config["hide_channels"] - lcn
        config["lcn"] = lcn
        config["hcn"] = hcn
        self.config = config

        # Process
        self.preInput = PrePocess(config)
        self.featureE = FeatureExtract(config)
        self.highFeature = HighFeature(config)
        # for output
        self.proutl = ven.Conv_BN_ACT(config["hide_channels"], config["out_channels"],
                                      config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.ReLU
                                      )
        self.prouth = ven.Conv_BN_ACT(config["hide_channels"], config["hide_channels"],
                                      config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.ReLU
                                      )

        self.proutc = ven.Conv_BN_ACT(config["hide_channels"], config["out_channels"],
                                      config["kernel_size"], 1, config["padding"], config["dilation"], config["groups"], config["bias"], activation_layer=nn.ReLU
                                      )

    def forward(self, x):
        x = self.preInput(x)
        x = self.featureE(x)
        x_h, x_l = self.highFeature(x)

        x_h = self.prouth(x_h)

        output = self.proutc(x_h)

        x_l = self.proutl(x_l)

        return output, x_l


import speech_recognition as sr
import tkinter as tk
import algorithm

sentences = []
text_display = None

def update_text():
    global recognized_text
    text_display.config(state=tk.NORMAL)
    text_display.insert(tk.END, recognized_text + '\n')
    text_display.config(state=tk.DISABLED)
    text_display.see(tk.END)

def format_speech(list_of_sentences):
    format = []
    for sentence in list_of_sentences:
        sentence = sentence.split()

        # Append 'START' and 'END' to match formatting
        sentence = ['START'] + sentence + ['.'] + ['END']
        format.append(sentence)

    return format
def speech(training_data):
    global recognized_text, prev_text, sentences

    # Initialize the recognizer
    recognizer = sr.Recognizer()

    # Open the microphone and start listening for speech
    with sr.Microphone() as source:

        while True:
            try:
                audio = recognizer.listen(source, timeout=None)
                recognized_text = recognizer.recognize_google(audio)

                if recognized_text.lower() == "stop listening":
                    recognized_text = "Stopped listening."
                    update_text()
                    break  # Exit the loop when "stop" is detected

                elif recognized_text.lower() == "translate all":
                    recognized_text = str(algorithm.viterbi(training_data, format_speech(sentences)))

                elif recognized_text.lower() == "translate":
                    recognized_text = str(algorithm.viterbi(training_data, format_speech([prev_text])))

                elif recognized_text.lower() == "clear all":
                    recognized_text = "Cleared."
                    sentences.clear()
                else:
                    sentences.append(recognized_text)
                    prev_text = recognized_text

                update_text()

            except sr.WaitTimeoutError:
                pass

            except sr.UnknownValueError:
                recognized_text = "Could not understand audio."
                update_text()

def application(training_data):
    global text_display

    root = tk.Tk()
    root.title("Speech Recognition GUI")

    # Create a text widget to display recognized text
    text_display = tk.Text(root, wrap=tk.WORD, state=tk.DISABLED)
    text_display.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

    # Create a thread to listen for speech
    import threading
    speech_thread = threading.Thread(target=speech, args=(training_data,))
    speech_thread.start()

    root.mainloop()