"""Video Details feature UI and event handlers"""
import os
from typing import Callable
import gradio as gr
from webui_utils.simple_config import SimpleConfig
from webui_utils.simple_icons import SimpleIcons
from webui_utils.simple_utils import seconds_to_hms, clean_dict, get_frac_str_as_float
from webui_utils.video_utils import get_video_details
from webui_utils.mtqdm import Mtqdm
from webui_tips import WebuiTips
from interpolate_engine import InterpolateEngine
from tabs.tab_base import TabBase

class VideoDetails(TabBase):
    """Encapsulates UI elements and events for the Video Details feature"""
    def __init__(self,
                    config : SimpleConfig,
                    engine : InterpolateEngine,
                    log_fn : Callable):
        TabBase.__init__(self, config, engine, log_fn)

    def render_tab(self):
        """Render tab into UI"""
        with gr.Tab("Video Details"):
            gr.HTML(SimpleIcons.BAR_CHART + "Show internal details for a media file",
                elem_id="tabheading")
            with gr.Row():
                input_file = gr.Text(max_lines=1, label="Media File Path",
                    placeholder="Path on this server to the media file to be inspected")
            with gr.Row():
                frame_rate = gr.Text(max_lines=1, label="Frame Rate")
                dimensions = gr.Text(max_lines=1, label="Dimensions")
                duration = gr.Text(max_lines=1, label="Duration")
                frame_count = gr.Text(max_lines=1, label="Frame Count")
                file_size = gr.Text(max_lines=1, label="File Size")
            count_frames = gr.Checkbox(value=False, label="Count Frames (Slower)",
                info="Scans file counting frames; required for media without frame count metadata")
            report_button = gr.Button("Get Details", variant="primary")
            output_text = gr.Textbox(label="Details", interactive=False)
            with gr.Accordion(SimpleIcons.TIPS_SYMBOL + " Guide", open=False):
                WebuiTips.video_details.render()
        report_button.click(self.create_report, inputs=[input_file, count_frames],
                    outputs=[output_text, frame_rate, duration, dimensions, frame_count, file_size])

    def create_report(self, input_path : str, count_frames : bool):
        """Create Report button handler"""
        if input_path:
            if os.path.exists(input_path):
                separator = ""
                report = []

                self.log(f"calling get_video_details for {input_path}")
                with Mtqdm().open_bar(total=1, desc="FFmpeg") as bar:
                    Mtqdm().message(bar, "FFmpeg in use ...")
                    data = get_video_details(input_path, count_frames=count_frames)
                    self.log("received details:")
                    self.log(str(data))
                    Mtqdm().update_bar(bar)

                error_data = data.get("error")
                if error_data:
                    report.append("ERROR: FFprobe returned an error processing the media file")
                    report.append(separator)
                    report.append("FFprobe command:")
                    report.append(error_data["ffprobe_cmd"])
                    report.append(separator)
                    report.append("Console output:")
                    report.append(error_data["console_output"])
                    return gr.update(value="\r\n".join(report)), None, None, None, None, None

                format = {}
                format_data = data["format"]
                format["filename"] = format_data.get("filename")
                format["start_time"] = seconds_to_hms(float(format_data.get("start_time", 0)))
                format["duration"] = seconds_to_hms(float(format_data.get("duration", 0)))
                file_size = f"{int(format_data.get('size', 0)):,d}"
                format["size"] = file_size
                format["bit_rate"] = f"{int(format_data.get('bit_rate', 0)):,d}"
                format["format_name"] = format_data.get("format_long_name")
                format = clean_dict(format)

                warning = "Unknown"
                video_summary = {}
                streams = []
                streams_data = data["streams"]
                for stream_data in streams_data:
                    index = stream_data.get("index")
                    codec_type = stream_data.get("codec_type")
                    stream = {}

                    avg_frame_rate = stream_data.get("avg_frame_rate")
                    avg_frame_rate = get_frac_str_as_float(avg_frame_rate)
                    r_frame_rate = stream_data.get("r_frame_rate")
                    r_frame_rate = get_frac_str_as_float(r_frame_rate)
                    frame_rate = avg_frame_rate or r_frame_rate
                    frame_rate = f"{frame_rate:0.2f}" if frame_rate else warning
                    stream["frame_rate"] = frame_rate

                    stream["start_time"] = seconds_to_hms(float(stream_data.get("start_time", 0)))
                    duration = seconds_to_hms(float(stream_data.get("duration", 0)))
                    stream["duration"] = duration
                    stream["width"] = stream_data.get("width")
                    stream["height"] = stream_data.get("height")

                    frame_count = stream_data.get("nb_read_frames") or stream_data.get("nb_frames")
                    frame_count = f"{int(frame_count):,d}" if frame_count else warning

                    stream["frame_count"] = frame_count
                    stream["bit_rate"] = f"{int(stream_data.get('bit_rate', 0)):,d}"
                    stream["codec_name"] = stream_data.get("codec_name")
                    stream["pix_fmt"] = stream_data.get("pix_fmt")
                    stream["sample_rate"] = stream_data.get("sample_rate")
                    stream["channels"] = stream_data.get("channels")
                    stream["channel_layout"] = stream_data.get("channel_layout")
                    stream["sample_aspect_ratio"] = stream_data.get("sample_aspect_ratio")
                    stream["display_aspect_ratio"] = stream_data.get("display_aspect_ratio")

                    # capture video details from the first found video stream
                    if not video_summary and codec_type == "video":
                        video_summary["frame_rate"] = frame_rate
                        video_summary["duration"] = duration[:duration.find(".")]
                        video_summary["dimensions"] = f"{stream['width']}x{stream['height']}"
                        video_summary["frame_count"] = frame_count
                        video_summary["file_size"] = file_size

                    stream = clean_dict(stream)
                    stream_name = f"#{index} {codec_type}"
                    streams.append({stream_name : stream})

                report.append("[Format]")
                for k, v in format.items():
                    report.append(f"{k}: {v}")
                report.append(separator)

                for stream in streams:
                    stream_name = list(stream.keys())[0]
                    stream_data = list(stream.values())[0]
                    report.append(f"[Stream {stream_name}]")
                    for k, v in stream_data.items():
                        report.append(f"{k}: {v}")
                    report.append(separator)
                report.append("More media file details availale in the Log Viewer")

                return gr.update(value="\r\n".join(report)),\
                                 video_summary.get("frame_rate"),\
                                 video_summary.get("duration"),\
                                 video_summary.get("dimensions"),\
                                 video_summary.get("frame_count"),\
                                 video_summary.get("file_size")
            else:
                return gr.update(value=f"media file {input_path} not found"), None, None, None,\
                                                                            None, None
        else:
            return gr.update(value="media file path must be supplied"), None, None, None, None, None


# 机器学习的画图
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
from sklearn.preprocessing import Imputer,LabelEncoder,OrdinalEncoder,CategoricalEncoder,LabelBinarizer,OneHotEncoder,StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_squared_error
from sklearn.externals import joblib
from sklearn.model_selection import GridSearchCV

df = pd.read_csv('txtdata/1959-2019.txt')

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.style.use('ggplot')

def make_train_text_by_rate(movie):
    # 第一步，分出训练集和测试集
    # 分层依据：评分
    # 先把10分制改成五分制，减少分层数量
    movie['rate_cat'] = np.ceil(movie['rate'] / 2)
    split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=58)
    for train_index, test_index in split.split(movie, movie["rate_cat"]):
        strat_train_set = movie.loc[train_index]
        strat_test_set = movie.loc[test_index]
    return strat_train_set, strat_test_set

def display_scores(scores):
    print('---------------------')
    print('scores:', scores)
    print('mean:', scores.mean())
    print('Standard Deviation:', scores.std())
    print('---------------------')

def score_the_module(model):
    # 验证模型
    scores = cross_val_score(model, movie_prepared, movie_labels,
                             scoring='neg_mean_squared_error', cv=10)
    rmse_scores = np.sqrt(-scores)
    display_scores(rmse_scores)

strat_train_set, strat_test_set = make_train_text_by_rate(df)

movie = strat_train_set.drop(['rate', 'rate_cat'], axis=1)
movie_labels = strat_train_set['rate'].copy()

num_attribs = ['rating_people', 'year', 'movietime']
cat_attribs_all = ['director', 'main_country', 'main_language',
                   'main_genre', 'second_genre',
                   'main_actor', 'second_actor']
cat_attribs_simple = ['main_country',
                        'main_genre', 'second_genre']

num_pipeline = Pipeline([
    ('imputer', Imputer(strategy='median')),
    ('std_scalar', StandardScaler()),
])

full_pipeline = ColumnTransformer([
        ("num", num_pipeline, num_attribs),
        ('cat_all', OrdinalEncoder(), cat_attribs_all),
        #('cat_simple', OrdinalEncoder(), cat_attribs_simple),
        #("cat_one_hot", OneHotEncoder(), cat_attribs_simple),
    ])

movie_prepared = full_pipeline.fit_transform(movie)
print(movie_prepared.shape)

def plot_picture(model,ax,title):
    # 准备画图数据
    train_real = strat_train_set['rate'].copy()

    test_x = strat_test_set.drop('rate', axis=1)
    test_real = strat_test_set['rate'].copy()
    test_x_prepared = full_pipeline.transform(test_x)
    test_predict = model.predict(test_x_prepared)

    #画散点图观察模型性能
    #plt.figure(figsize=(7, 7))
    train_predict = model.predict(movie_prepared)
    ax.set_xlabel('真实值')
    ax.set_ylabel('预测值')
    ax.scatter(train_real, train_predict, c='g', s=35, alpha=0.5, marker='o', label='训练集数据')
    ax.scatter(test_real, test_predict, c='b', s=35, alpha=0.5, marker='v', label='测试集数据')
    ax.set_title(title)
    ax.legend()
    #plt.show()

# 导入模型
# lin_reg = joblib.load('my_lin_reg.pkl')
# tree_reg = joblib.load('my_tree_reg.pkl')
# forest_reg = joblib.load('my_forest_reg.pkl')
# kmeans_reg = joblib.load('my_kmeans_reg.pkl')

#forest_final_reg = joblib.load('my_forest_final_reg.pkl')

forest_reg = RandomForestRegressor()
param_grid = [
    {"n_estimators": [3, 10, 30], 'max_features':[2, 4, 6]},
    {'bootstrap': [False], 'n_estimators':[3, 30], 'max_features':[2, 3, 4]},
]

grid_search = GridSearchCV(forest_reg, param_grid, cv=5, scoring='neg_mean_squared_error')
grid_search.fit(movie_prepared, movie_labels)

final_reg_ordi = grid_search.best_estimator_
score_the_module(final_reg_ordi)
joblib.dump(final_reg_ordi, 'my_forest_final_reg_odi.pkl')

#----------以下是检验模型
# movie_test = pd.read_csv('txtdata/movie_test.txt')
# x_test = movie_test.drop('rate', axis=1)
# y_test = movie_test['rate'].copy()
# x_test_prepared = full_pipeline.transform(x_test)
# y_predict = final_reg_ordi.predict(x_test_prepared)
# print(y_test.values)
# print(y_predict)
# score_the_module(forest_reg)
# score_the_module(final_reg_ordi)

# ---------------------------------------

# fig, ax = plt.subplots(2,2,figsize=(10,10))
#
# plot_picture(lin_reg, ax[0][0], '线性回归模型')
# plot_picture(tree_reg, ax[0][1], '决策树模型')
# plot_picture(kmeans_reg, ax[1][0], 'K均值聚类模型')
# plot_picture(forest_reg, ax[1][1], '随机森林模型')

# fig, ax = plt.subplots(figsize=(5,5))
# plot_picture(forest_final_reg, ax, '随机森林模型（参数调整后）')
# plt.show()


import sqlite3
import sys
import webbrowser

import requests
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

import addcourse
import addinstructor
import addschool
import cloud
import settings
from ExtendedComboBox import ExtendedComboBox

with open('settings.txt', 'r') as f:
    r = f.readlines()
    down = True if r[0][:-1] == 'True' else False

if down:
    try:
        r = requests.get('http://google.com', timeout=1)
        cloud.download()
    except:
        QMessageBox.information(self, 'Informations', e)

conn = sqlite3.connect('data.sqlite')
curr = conn.cursor()
courseID = 0
schoolID = 0
instructorID = 0
tags = ''
schools, categories, instructors, all_courses, tags = [], [], [], [], []

class Main(QMainWindow):

    def __init__(self):
        global conn
        global curr
        super().__init__()
        self.setWindowTitle("Course Library")
        self.setGeometry(450, 150, 1365, 710)
        self.showMaximized()
        self.UI()
        self.show()

    def UI(self):
        self.toolBar()
        self.widgets()
        self.layouts()
        self.get_Defaults()
        self.filterWidget()
        self.tabWidget()
        self.displayCourses()
        self.displaySchools()
        self.displayInstructors()

    def closeEvent(self, event):
        with open('settings.txt', 'r') as f:
            r = f.readlines()
            uplo = True if r[-1] == 'True' else False
        if uplo:
            try:
                r = requests.get('http://google.com', timeout=1)
                QMessageBox.information(self, 'Updating', 'Please Wait until the database is successfully uploaded')
                cloud.upload()
                event.accept()
            except:
                QMessageBox.information(self, 'Error', 'Please Check your internet access to upload the database')
                event.accept()

    def toolBar(self):
        self.tb = self.addToolBar('Tool Bar')
        self.tb.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.addCourse = QAction(QIcon('icon/courses.png'), 'Add Course', self)
        self.tb.addAction(self.addCourse)
        self.addCourse.triggered.connect(self.funcAddCourse)
        self.tb.addSeparator()
        self.addSchool = QAction(QIcon('icon/online-course.png'), 'Add School', self)
        self.tb.addAction(self.addSchool)
        self.addSchool.triggered.connect(self.funcAddSchool)
        self.tb.addSeparator()
        self.addInstructor = QAction(QIcon('icon/online-learning.png'), 'Add Instructor', self)
        self.tb.addAction(self.addInstructor)
        self.addInstructor.triggered.connect(self.funcAddInstructor)
        self.tb.addSeparator()
        self.settings = QAction(QIcon('icon/settings.png'), 'Settings', self)
        self.tb.addAction(self.settings)
        self.settings.triggered.connect(self.funcSettings)
        self.tb.addSeparator()

    def tabWidget(self):
        self.tabs = QTabWidget()
        self.mainLowerLayout.addWidget(self.tabs)
        self.tabs.setLayout(self.mainLowerLayout)
        self.courses = QWidget()
        self.schools = QWidget()
        self.instructors = QWidget()
        self.tabs.addTab(self.courses, 'Courses')
        self.courses.setLayout(self.coursesLayout)
        self.tabs.addTab(self.schools, 'Schools')
        self.schools.setLayout(self.schoolsLayout)
        self.tabs.addTab(self.instructors, 'Instructors')
        self.instructors.setLayout(self.instructorsLayout)

    def filterWidget(self):
        self.schoolCombo = ExtendedComboBox()
        self.schoolCombo.addItem('Schools')
        self.schoolCombo.currentIndexChanged.connect(self.filter)
        self.categoryCombo = ExtendedComboBox()
        self.categoryCombo.addItem('Category')
        self.categoryCombo.currentIndexChanged.connect(self.filter)
        self.directoryCombo = ExtendedComboBox()
        self.directoryCombo.addItems(['Directory', '1', '2', '3', 'Online'])
        self.directoryCombo.currentIndexChanged.connect(self.filter)
        self.iscompletedCombo = ExtendedComboBox()
        self.iscompletedCombo.addItems(['State', 'Completed', 'Not Completed'])
        self.iscompletedCombo.currentIndexChanged.connect(self.filter)
        self.instructorBox = ExtendedComboBox()
        self.instructorBox.addItem('Instructors')
        self.instructorBox.currentIndexChanged.connect(self.filter)
        self.tagsBox = ExtendedComboBox()
        self.tagsBox.addItem('Tags')
        self.tagsBox.currentIndexChanged.connect(self.filter)
        self.searchBox = QLineEdit()
        self.searchBox.setPlaceholderText('Enter Course name')
        self.searchBox.setCompleter(self.get_completer(all_courses, self.searchBox))
        self.searchBox.textEdited.connect(self.searchCourse)
        self.searchBox.returnPressed.connect(self.searchCourse)
        self.searchButton = QPushButton('Search')

        self.mainUpperLayout.addWidget(self.schoolCombo, 20)
        self.mainUpperLayout.addWidget(self.categoryCombo, 20)
        self.mainUpperLayout.addWidget(self.directoryCombo, 20)
        self.mainUpperLayout.addWidget(self.iscompletedCombo, 20)
        self.mainUpperLayout.addWidget(self.instructorBox)
        self.mainUpperLayout.addWidget(self.tagsBox, 20)
        self.mainUpperLayout.addWidget(self.searchBox, 80)
        self.mainUpperLayout.addWidget(self.searchButton)
        self.searchButton.clicked.connect(self.searchCourse)

        self.schoolCombo.addItems(sorted(schools))
        self.categoryCombo.addItems(sorted(categories))
        self.instructorBox.addItems(sorted(instructors))
        self.tagsBox.addItems(sorted(tags))

    def layouts(self):
        self.mainLayout = QVBoxLayout()
        self.mainUpperLayout = QHBoxLayout()
        self.mainLowerLayout = QVBoxLayout()

        self.coursesLayout = QHBoxLayout()
        self.coursesLeftLayout = QHBoxLayout()
        self.coursesRightLayout = QVBoxLayout()
        self.coursesGroupBox = QGroupBox('Informations')
        self.coursesGroupBox.setFont(QFont("Times", 10, weight=QFont.Bold))

        self.schoolsLayout = QHBoxLayout()
        self.schoolsLeftLayout = QHBoxLayout()
        self.schoolsRightTopLayout = QHBoxLayout()
        self.schoolsRightBottomLayout = QHBoxLayout()
        self.schoolMainRightLayout = QVBoxLayout()
        self.schoolsGroupBox = QGroupBox('Search Schools')
        self.schoolsGroupBox2 = QGroupBox('School Informations')
        self.schoolsGroupBox.setFont(QFont("Times", 10, weight=QFont.Bold))
        self.schoolsGroupBox2.setFont(QFont("Times", 10, weight=QFont.Bold))

        self.instructorsLayout = QHBoxLayout()
        self.instructorsLeftLayout = QHBoxLayout()
        self.instructorsRightMainLayout = QVBoxLayout()
        self.instructorsRightTopLayout = QHBoxLayout()
        self.instructorsRightBottomLayout = QHBoxLayout()
        self.instructorsGroupBox = QGroupBox('Search Instructors')
        self.instructorsGroupBox.setFont(QFont("Times", 10, weight=QFont.Bold))
        self.instructorsGroupBox2 = QGroupBox('Instructor Informations')
        self.instructorsGroupBox2.setFont(QFont("Times", 10, weight=QFont.Bold))

        self.mainLayout.addLayout(self.mainUpperLayout)
        self.mainLayout.addLayout(self.mainLowerLayout)

        self.coursesGroupBox.setLayout(self.coursesRightLayout)
        self.coursesLayout.addLayout(self.coursesLeftLayout, 75)
        self.coursesLayout.addLayout(self.coursesRightLayout, 25)
        self.coursesLayout.addWidget(self.coursesGroupBox)
        self.coursesLeftLayout.addWidget(self.coursesTable)
        self.coursesRightLayout.addWidget(self.coursesInfo)

        self.schoolsLayout.addLayout(self.schoolsLeftLayout, 80)
        self.schoolsLayout.addLayout(self.schoolMainRightLayout, 20)
        self.schoolsLeftLayout.addWidget(self.schoolsTable)
        self.schoolsRightTopLayout.addWidget(self.schoolsSearchText)
        self.schoolsRightTopLayout.addWidget(self.schoolsSearchEntry)
        self.schoolsRightTopLayout.addWidget(self.schoolsSearchButton)
        self.schoolsGroupBox.setLayout(self.schoolsRightTopLayout)
        self.schoolsRightBottomLayout.addWidget(self.schoolsInfos)
        self.schoolsGroupBox2.setLayout(self.schoolsRightBottomLayout)
        self.schoolMainRightLayout.addWidget(self.schoolsGroupBox)
        self.schoolMainRightLayout.addWidget(self.schoolsGroupBox2)

        self.instructorsLayout.addLayout(self.instructorsLeftLayout, 80)
        self.instructorsLayout.addLayout(self.instructorsRightMainLayout, 20)
        self.instructorsLeftLayout.addWidget(self.instructorsTable)
        self.instructorsRightTopLayout.addWidget(self.instructorsSearchText)
        self.instructorsRightTopLayout.addWidget(self.instructorsSearchEntry)
        self.instructorsRightTopLayout.addWidget(self.instructorsSearchButton)
        self.instructorsGroupBox.setLayout(self.instructorsRightTopLayout)
        self.instructorsRightBottomLayout.addWidget(self.instructorsInfos)
        self.instructorsGroupBox2.setLayout(self.instructorsRightBottomLayout)
        self.instructorsRightMainLayout.addWidget(self.instructorsGroupBox)
        self.instructorsRightMainLayout.addWidget(self.instructorsGroupBox2)

        self.widget = QWidget()
        self.widget.setLayout(self.mainLayout)
        self.setCentralWidget(self.widget)

    def widgets(self):
        self.coursesTable = QTableWidget()
        self.coursesTable.setColumnCount(12)
        self.coursesTable.setColumnHidden(7, True)
        self.coursesTable.setColumnHidden(8, True)
        self.coursesTable.setColumnHidden(9, True)
        self.coursesTable.setColumnHidden(10, True)
        self.coursesTable.setColumnHidden(11, True)
        self.coursesTable.setHorizontalHeaderItem(0, QTableWidgetItem('Title'))
        self.coursesTable.setHorizontalHeaderItem(1, QTableWidgetItem('School'))
        self.coursesTable.setHorizontalHeaderItem(2, QTableWidgetItem('Instructor'))
        self.coursesTable.setHorizontalHeaderItem(3, QTableWidgetItem('Category'))
        self.coursesTable.setHorizontalHeaderItem(4, QTableWidgetItem('Duration'))
        self.coursesTable.setHorizontalHeaderItem(5, QTableWidgetItem('Directory'))
        self.coursesTable.setHorizontalHeaderItem(6, QTableWidgetItem('State'))
        self.coursesTable.setHorizontalHeaderItem(7, QTableWidgetItem('ID'))
        self.coursesTable.setHorizontalHeaderItem(8, QTableWidgetItem('SchoolID'))
        self.coursesTable.setHorizontalHeaderItem(9, QTableWidgetItem('InstructorID'))
        self.coursesTable.setHorizontalHeaderItem(10, QTableWidgetItem('Tags'))
        self.coursesTable.setHorizontalHeaderItem(11, QTableWidgetItem('Link'))
        self.coursesTable.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.coursesTable.horizontalHeader().setFont(QFont("Times", weight=QFont.Bold))
        self.coursesTable.resizeColumnsToContents()
        self.coursesTable.setSortingEnabled(True)
        self.coursesTable.horizontalHeader().sortIndicatorOrder()
        self.coursesTable.setSelectionMode(QAbstractItemView.SingleSelection)
        self.coursesTable.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.coursesTable.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.coursesTable.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.coursesTable.doubleClicked.connect(self.selectedCourse)
        self.coursesTable.clicked.connect(self.showDetails)

        self.coursesInfo = QTextEdit()
        self.coursesInfo.setHtml(self.getBasicInfo())
        self.coursesInfo.setReadOnly(True)

        self.schoolsTable = QTableWidget()
        self.schoolsTable.setColumnCount(4)
        self.schoolsTable.setColumnHidden(0, True)
        self.schoolsTable.setHorizontalHeaderItem(0, QTableWidgetItem('ID'))
        self.schoolsTable.setHorizontalHeaderItem(1, QTableWidgetItem('Name'))
        self.schoolsTable.setHorizontalHeaderItem(2, QTableWidgetItem('Website'))
        self.schoolsTable.setHorizontalHeaderItem(3, QTableWidgetItem('Courses Count'))
        self.schoolsTable.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.schoolsTable.horizontalHeader().setFont(QFont("Times", weight=QFont.Bold))
        self.schoolsTable.resizeColumnsToContents()
        self.schoolsTable.setSortingEnabled(True)
        self.schoolsTable.horizontalHeader().sortIndicatorOrder()
        self.schoolsTable.setSelectionMode(QAbstractItemView.SingleSelection)
        self.schoolsTable.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.schoolsTable.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.schoolsTable.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.schoolsTable.doubleClicked.connect(self.selectedSchool)
        self.schoolsTable.clicked.connect(self.showSchoolDetails)
        self.schoolsSearchText = QLabel("Search School")
        self.schoolsSearchEntry = QLineEdit()
        self.schoolsSearchEntry.setPlaceholderText('Enter School name')
        self.schoolsSearchEntry.setCompleter(self.get_completer(schools, self.schoolsSearchEntry))
        self.schoolsSearchEntry.textEdited.connect(self.searchSchool)
        self.schoolsSearchEntry.returnPressed.connect(self.searchSchool)
        self.schoolsSearchButton = QPushButton("Search")
        self.schoolsSearchButton.clicked.connect(self.searchSchool)
        self.schoolsInfos = QTextEdit()
        self.schoolsInfos.setText(self.getSchoolsInfo())

        self.instructorsTable = QTableWidget()
        self.instructorsTable.setColumnCount(4)
        self.instructorsTable.setColumnHidden(0, True)
        self.instructorsTable.setHorizontalHeaderItem(0, QTableWidgetItem('ID'))
        self.instructorsTable.setHorizontalHeaderItem(1, QTableWidgetItem('Name'))
        self.instructorsTable.setHorizontalHeaderItem(2, QTableWidgetItem('School'))
        self.instructorsTable.setHorizontalHeaderItem(3, QTableWidgetItem('Courses Count'))
        self.instructorsTable.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.instructorsTable.horizontalHeader().setFont(QFont("Times", weight=QFont.Bold))
        self.instructorsTable.resizeColumnsToContents()
        self.instructorsTable.setSortingEnabled(True)
        self.instructorsTable.horizontalHeader().sortIndicatorOrder()
        self.instructorsTable.setSelectionMode(QAbstractItemView.SingleSelection)
        self.instructorsTable.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.instructorsTable.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.instructorsTable.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.instructorsTable.doubleClicked.connect(self.selectedInstructor)
        self.instructorsTable.clicked.connect(self.showInstructorDetails)
        self.instructorsSearchText = QLabel("Search Instructor")
        self.instructorsSearchEntry = QLineEdit()
        self.instructorsSearchEntry.setPlaceholderText('Enter Instructor name')
        self.instructorsSearchEntry.setCompleter(self.get_completer(instructors, self.instructorsSearchEntry))
        self.instructorsSearchEntry.textEdited.connect(self.searchInstructor)
        self.instructorsSearchEntry.returnPressed.connect(self.searchInstructor)
        self.instructorsSearchButton = QPushButton("Search")
        self.instructorsSearchButton.clicked.connect(self.searchInstructor)
        self.instructorsInfos = QTextEdit()
        self.instructorsInfos.setText(self.getInstructorsInfo())

    def funcAddCourse(self):
        self.newCourse = addcourse.AddCourse(self)
        self.get_Defaults()

    def funcAddSchool(self):
        self.newSchool = addschool.AddSchool(self)
        self.get_Defaults()

    def funcSettings(self):
        self.newSettings = settings.Settings()
        self.get_Defaults()

    def funcRefresh(self):
        self.get_Defaults()
        self.schoolCombo.addItems(sorted(schools))
        self.categoryCombo.addItems(sorted(categories))
        self.instructorBox.addItems(sorted(instructors))
        self.tagsBox.addItems(sorted(tags))
        self.displayCourses()
        self.displaySchools()
        self.displayInstructors()

    def funcAddInstructor(self):
        self.newInstructor = addinstructor.AddInstructor(self)
        self.get_Defaults()

    def displayCourses(self):
        for i in reversed(range(self.coursesTable.rowCount())):
            self.coursesTable.removeRow(i)

        query = curr.execute(
            "SELECT Title,School.Name,Instructor.Name,Category,Duration,Directory,IsCompleted,course.ID,course.SchoolID,InstructorID,tags,course.link FROM course,instructor,school WHERE  course.InstructorID = instructor.ID and course.SchoolID = school.ID")

        oldSort = self.coursesTable.horizontalHeader().sortIndicatorSection()
        oldOrder = self.coursesTable.horizontalHeader().sortIndicatorOrder()
        self.coursesTable.setSortingEnabled(False)

        for row_data in query:
            row_number = self.coursesTable.rowCount()
            self.coursesTable.insertRow(row_number)
            for column_number, data in enumerate(row_data):
                item = QTableWidgetItem()
                item.setData(Qt.EditRole, data)
                if column_number != 0:
                    item.setTextAlignment(Qt.AlignCenter)
                self.coursesTable.setItem(row_number, column_number, item)
        self.coursesTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.coursesTable.sortItems(oldSort, oldOrder)
        self.coursesTable.setSortingEnabled(True)

    def displaySchools(self):
        oldSort = self.schoolsTable.horizontalHeader().sortIndicatorSection()
        oldOrder = self.schoolsTable.horizontalHeader().sortIndicatorOrder()
        self.schoolsTable.setSortingEnabled(False)

        for i in reversed(range(self.schoolsTable.rowCount())):
            self.schoolsTable.removeRow(i)

        query = curr.execute(
            "SELECT ID,Name,Website,CourseCount FROM school")

        for row_data in query:
            row_number = self.schoolsTable.rowCount()
            self.schoolsTable.insertRow(row_number)
            for column_number, data in enumerate(row_data):
                item = QTableWidgetItem()
                item.setData(Qt.EditRole, data)
                item.setTextAlignment(Qt.AlignCenter)
                self.schoolsTable.setItem(row_number, column_number, item)
        self.schoolsTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.schoolsTable.sortItems(oldSort, oldOrder)
        self.schoolsTable.setSortingEnabled(True)

    def displayInstructors(self):
        oldSort = self.instructorsTable.horizontalHeader().sortIndicatorSection()
        oldOrder = self.instructorsTable.horizontalHeader().sortIndicatorOrder()
        self.instructorsTable.setSortingEnabled(False)

        for i in reversed(range(self.instructorsTable.rowCount())):
            self.instructorsTable.removeRow(i)

        query = curr.execute(
            "SELECT Instructor.ID,Instructor.Name,School.Name,CoursesCount FROM school,Instructor WHERE SchoolID=School.ID")

        for row_data in query:
            row_number = self.instructorsTable.rowCount()
            self.instructorsTable.insertRow(row_number)
            for column_number, data in enumerate(row_data):
                item = QTableWidgetItem()
                item.setData(Qt.EditRole, data)
                item.setTextAlignment(Qt.AlignCenter)
                self.instructorsTable.setItem(row_number, column_number, item)
        self.instructorsTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.instructorsTable.sortItems(oldSort, oldOrder)
        self.instructorsTable.setSortingEnabled(True)

    def selectedCourse(self):
        global courseID
        listProduct = []

        for i in range(0, 12):
            try:
                listProduct.append(self.coursesTable.item(self.coursesTable.currentRow(), i).text())
            except Exception as e:
                print(e)
                QMessageBox.information(self, 'Informations', e)

        courseID = listProduct[7]
        try:
            self.display = DisplayCourse(self)
        except Exception as e:
            print(e)
            QMessageBox.information(self, 'Informations', e)
        self.display.show()

    def selectedSchool(self):
        global schoolID
        listSchools = []
        for i in range(0, 4):
            listSchools.append(self.schoolsTable.item(self.schoolsTable.currentRow(), i).text())
        schoolID = listSchools[0]
        try:
            self.displaySchool = DisplaySchool(self)
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)
        self.displaySchool.show()

    def selectedInstructor(self):
        global instructorID
        listInstructor = []
        for i in range(0, 4):
            listInstructor.append(self.instructorsTable.item(self.instructorsTable.currentRow(), i).text())
        instructorID = listInstructor[0]
        try:
            self.displayInstructor = DisplayInstructor(self)
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)
        self.displayInstructor.show()

    def get_Defaults(self):
        global schools
        global instructors
        global categories
        global all_courses
        global tags
        s = curr.execute('SELECT DISTINCT Name FROM School')
        schools = [i[0] for i in s]
        i = curr.execute('SELECT DISTINCT Name FROM Instructor')
        instructors = [j[0] for j in i]
        c = curr.execute('SELECT DISTINCT Category FROM Course')
        categories = [i[0] for i in c]
        a = curr.execute('SELECT DISTINCT Title FROM Course')
        all_courses = [i[0] for i in a]
        t = curr.execute('SELECT Tags FROM Course')
        tags = [i[0].split(',') for i in t]
        tags = {j for i in tags for j in i}
        tags.remove('')

    def filter(self):
        self.get_Defaults()
        try:
            school = self.schoolCombo.currentText().replace("'", "''")
            category = self.categoryCombo.currentText().replace("'", "''")
            instructor = self.instructorBox.currentText().replace("'", "''")
            state = self.iscompletedCombo.currentText().replace("'", "''")
            directory = self.directoryCombo.currentText().replace("'", "''")
            tag = self.tagsBox.currentText().replace("'", "''").split(',')
            all_schools, all_category, all_instructors, all_states, all_directory, all_tags = [], [], [], [], [], []

            if (school != 'Schools'):
                try:
                    q1 = curr.execute("select ID from school where name =?", (school,))
                    index = curr.fetchone()[0]
                    all_schools = curr.execute(
                        "SELECT Title,School.Name,Instructor.Name,Category,Duration,Directory,IsCompleted,course.ID,course.SchoolID,InstructorID,tags,course.link FROM course,instructor,school WHERE course.InstructorID = instructor.ID and course.SchoolID = school.ID and course.schoolID = ?",
                        (index,))
                    all_schools = curr.fetchall()
                except Exception as e:
                    QMessageBox.information(self, 'information', 'items does not exist')

            if (category != 'Category'):
                try:
                    all_category = curr.execute(
                        "SELECT Title,School.Name,Instructor.Name,Category,Duration,Directory,IsCompleted,course.ID,course.SchoolID,InstructorID,tags,course.link FROM course,instructor,school WHERE course.InstructorID = instructor.ID and course.SchoolID = school.ID and category = ?",
                        (category,))
                    all_category = curr.fetchall()
                except Exception as e:
                    QMessageBox.information(self, 'information', 'items does not exist')

            if (instructor != 'Instructors'):
                try:
                    q1 = curr.execute("select ID from Instructor where name =?", (instructor,))
                    index = curr.fetchone()[0]
                    all_instructors = curr.execute(
                        "SELECT Title,School.Name,Instructor.Name,Category,Duration,Directory,IsCompleted,course.ID,course.SchoolID,InstructorID,tags,course.link FROM course,instructor,school WHERE course.InstructorID = instructor.ID and course.SchoolID = school.ID and course.InstructorID = ?",
                        (index,))
                    all_instructors = curr.fetchall()
                except Exception as e:
                    QMessageBox.information(self, 'information', 'items does not exist')

            if (state != 'State'):
                try:
                    all_states = curr.execute(
                        "SELECT Title,School.Name,Instructor.Name,Category,Duration,Directory,IsCompleted,course.ID,course.SchoolID,InstructorID,tags,course.link FROM course,instructor,school WHERE course.InstructorID = instructor.ID and course.SchoolID = school.ID and IsCompleted=?",
                        (state,))
                    all_states = curr.fetchall()
                except Exception as e:
                    QMessageBox.information(self, 'Informations', e)

            if (directory != 'Directory'):
                try:
                    all_directory = curr.execute(
                        "SELECT Title,School.Name,Instructor.Name,Category,Duration,Directory,IsCompleted,course.ID,course.SchoolID,InstructorID,tags,course.link FROM course,instructor,school WHERE course.InstructorID = instructor.ID and course.SchoolID = school.ID and directory = ?",
                        (directory,))
                    all_directory = curr.fetchall()
                except Exception as e:
                    QMessageBox.information(self, 'information', 'items does not exist')
            all_tags = []
            if (tag != [] and tag[0] != 'Tags'):
                try:
                    for t in tag:
                        _ = curr.execute(
                            "SELECT Title,School.Name,Instructor.Name,Category,Duration,Directory,IsCompleted,course.ID,course.SchoolID,InstructorID,tags,course.link FROM course,instructor,school WHERE course.InstructorID = instructor.ID and course.SchoolID = school.ID and Tags Like '%'||?||'%'",
                            (t,))
                        all_tags.extend(curr.fetchall())
                except Exception as e:
                    QMessageBox.information(self, 'information', 'items does not exist')
            if any(
                    [all_schools, all_category, all_instructors, all_states, all_directory, all_tags]):
                all = set.intersection(
                    *(set(x) for x in [all_schools, all_category, all_instructors, all_states, all_directory, all_tags]
                      if
                      x))

                oldSort = self.coursesTable.horizontalHeader().sortIndicatorSection()
                oldOrder = self.coursesTable.horizontalHeader().sortIndicatorOrder()
                self.coursesTable.setSortingEnabled(False)

                for i in reversed(range(self.coursesTable.rowCount())):
                    self.coursesTable.removeRow(i)

                for row_data in all:
                    row_number = self.coursesTable.rowCount()
                    self.coursesTable.insertRow(row_number)
                    for column_number, data in enumerate(row_data):
                        item = QTableWidgetItem()
                        item.setData(Qt.EditRole, data)
                        if column_number != 0:
                            item.setTextAlignment(Qt.AlignCenter)
                        self.coursesTable.setItem(row_number, column_number, item)
                self.coursesTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
                self.coursesTable.sortItems(oldSort, oldOrder)
                self.coursesTable.setSortingEnabled(True)
            else:
                self.displayCourses()

        except Exception as e:
            QMessageBox.information(self, 'Informations', e)

    def get_completer(self, text, searchEntry):
        completer = QCompleter(text, searchEntry)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        completer.setCompletionMode(QCompleter.PopupCompletion)
        completer.setFilterMode(Qt.MatchContains)
        completer.setMaxVisibleItems(20)
        completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel)
        return completer

    def searchCourse(self):
        value = self.searchBox.text()
        if value != "":
            try:
                value = value.replace("'", "''")
                query = curr.execute(
                    f"SELECT Title,School.Name,Instructor.Name,Category,Duration,Directory,IsCompleted,course.ID,course.SchoolID,InstructorID,tags,course.link FROM course,instructor,school WHERE course.InstructorID = instructor.ID and course.SchoolID = school.ID and Title LIKE '%{value}%'")
            except Exception as e:
                QMessageBox.information(self, 'Informations', e)

            oldSort = self.coursesTable.horizontalHeader().sortIndicatorSection()
            oldOrder = self.coursesTable.horizontalHeader().sortIndicatorOrder()
            self.coursesTable.setSortingEnabled(False)

            for i in reversed(range(self.coursesTable.rowCount())):
                self.coursesTable.removeRow(i)

            for row_data in query:
                row_number = self.coursesTable.rowCount()
                self.coursesTable.insertRow(row_number)
                for column_number, data in enumerate(row_data):
                    item = QTableWidgetItem()
                    item.setData(Qt.EditRole, data)
                    item.setTextAlignment(Qt.AlignCenter)
                    self.coursesTable.setItem(row_number, column_number, item)
            self.coursesTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
            self.coursesTable.sortItems(oldSort, oldOrder)
            self.coursesTable.setSortingEnabled(True)
        else:
            self.displayCourses()

    def searchSchool(self):
        value = self.schoolsSearchEntry.text()
        if value:
            try:
                value = value.replace("'", "''")
                query = curr.execute(
                    f"SELECT ID,Name,Website,CourseCount FROM school WHERE Name LIKE '%{value}%'")
            except Exception as e:
                QMessageBox.information(self, 'Informations', e)

            oldSort = self.schoolsTable.horizontalHeader().sortIndicatorSection()
            oldOrder = self.schoolsTable.horizontalHeader().sortIndicatorOrder()
            self.schoolsTable.setSortingEnabled(False)

            for i in reversed(range(self.schoolsTable.rowCount())):
                self.schoolsTable.removeRow(i)

            for row_data in query:
                row_number = self.schoolsTable.rowCount()
                self.schoolsTable.insertRow(row_number)
                for column_number, data in enumerate(row_data):
                    item = QTableWidgetItem()
                    item.setData(Qt.EditRole, data)
                    item.setTextAlignment(Qt.AlignCenter)
                    self.schoolsTable.setItem(row_number, column_number, item)
            self.schoolsTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
            self.schoolsTable.sortItems(oldSort, oldOrder)
            self.schoolsTable.setSortingEnabled(True)
        else:
            self.displaySchools()

    def searchInstructor(self):
        value = self.instructorsSearchEntry.text()
        if value != "":
            try:
                value = value.replace("'", "''")
                query = curr.execute(
                    f"SELECT Instructor.ID,Instructor.Name,School.Name,CoursesCount FROM school,Instructor WHERE SchoolID=School.ID and Instructor.Name LIKE '%{value}%'")
            except Exception as e:
                QMessageBox.information(self, 'Informations', e)

            oldSort = self.instructorsTable.horizontalHeader().sortIndicatorSection()
            oldOrder = self.instructorsTable.horizontalHeader().sortIndicatorOrder()
            self.instructorsTable.setSortingEnabled(False)

            for i in reversed(range(self.instructorsTable.rowCount())):
                self.instructorsTable.removeRow(i)

            for row_data in query:
                row_number = self.instructorsTable.rowCount()
                self.instructorsTable.insertRow(row_number)
                for column_number, data in enumerate(row_data):
                    item = QTableWidgetItem()
                    item.setData(Qt.EditRole, data)
                    item.setTextAlignment(Qt.AlignCenter)
                    self.instructorsTable.setItem(row_number, column_number, item)
            self.instructorsTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
            self.instructorsTable.sortItems(oldSort, oldOrder)
            self.instructorsTable.setSortingEnabled(True)
        else:
            self.displayInstructors()

    def showDetails(self):
        listProduct = []

        for i in range(0, 12):  # Should be 10 not 9
            try:
                listProduct.append(self.coursesTable.item(self.coursesTable.currentRow(), i).text())
            except Exception as e:
                QMessageBox.information(self, 'Informations', e)
        dir = ""
        if listProduct[5] == '1':
            dir = 'First drive'
        elif listProduct[5] == '2':
            dir = 'Second drive'
        elif listProduct[5] == '3':
            dir = 'Third drive'
        else:
            dir = 'Online School'
        self.coursesInfo.setHtml(
            f"<br/><br/><br/>Course Title is:<font color=#0D7EDD> <br/> {listProduct[0]} <br/><br/> <font color=#000000> The course is from: <font color=#0D7EDD> <br/> {listProduct[1]} <br/><br/> <font color=#000000> The Instructor is: <font color=#0D7EDD> <br/> {listProduct[2]} <br/><br/> <font color=#000000> This Course Belongs to: <font color=#0D7EDD> <br/> {listProduct[3]} <br/><br/> <font color=#000000> This Course Is: <font color=#0D7EDD> <br/> {listProduct[6]} <br/> <font color=#000000> and it is located in the <font color=#0D7EDD> <br/> {dir} <br/><br/> <font color=#000000> The duration of the course is: <font color=#0D7EDD> <br/> {listProduct[4]} <br/><br/> <font color=#000000> Course Tags are: <font color=#0D7EDD> <br/> {listProduct[10]} <br/><br/> <font color=#000000> Course Link: <font color=#0D7EDD> <br/> {listProduct[11]}")

    def showSchoolDetails(self):
        listSchools = []
        for i in range(0, 4):
            listSchools.append(self.schoolsTable.item(self.schoolsTable.currentRow(), i).text())

        self.schoolsInfos.setHtml(
            f"<br/><br/><br/>School Name is:<font color=#0D7EDD> <br/> {listSchools[1]} <br/><br/> <font color=#000000> The link to the school is: <font color=#0D7EDD> <br/> {listSchools[2]} <br/><br/> <font color=#000000> And has <font color=#0D7EDD>{listSchools[3]} <font color=#000000> Courses.")

    def showInstructorDetails(self):
        listSchools = []
        for i in range(0, 4):
            listSchools.append(self.instructorsTable.item(self.instructorsTable.currentRow(), i).text())
        self.instructorsInfos.setHtml(
            f"<br/><br/><br/>Instructor Name is:<font color=#0D7EDD> <br/> {listSchools[1]} <br/><br/> <font color=#000000> The instructor teaches at: <font color=#0D7EDD> <br/> {listSchools[2]} <br/><br/> <font color=#000000> And has <font color=#0D7EDD>{listSchools[3]} <font color=#000000> Courses.")

    def getBasicInfo(self):
        def calculate_duration(prv, nex):
            h1, m1, s1 = map(int, prv.split(':'))
            h2, m2, s2 = map(int, nex.split(':'))

            h = h1 + h2
            m = m1 + m2
            s = s1 + s2
            if s >= 60:
                s %= 60
                m += 1
            if m >= 60:
                m %= 60
                h += 1
            h = '0' + str(h) if h <= 9 else h
            m = '0' + str(m) if m <= 9 else m
            s = '0' + str(s) if s <= 9 else s
            return f'{h}:{m}:{s}'

        courses_count = curr.execute('select count(*) from course')
        courses_count = curr.fetchone()[0]
        schools_count = curr.execute('select count(*) from school')
        schools_count = curr.fetchone()[0]
        instructors_count = curr.execute('select count(*) from instructor')
        instructors_count = curr.fetchone()[0]

        q = curr.execute('SELECT duration from course')
        duration = '00:00:00'
        for d in q:
            duration = calculate_duration(duration, d[0])
        return f"<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>You Have <font color=#0D7EDD> {courses_count} <font color=#000000> Course From <font color=#0D7EDD>  {schools_count} <font color=#000000> School and  <font color=#0D7EDD> {instructors_count} <font color=#000000>Instructor.<br/><br/><br/> The total duration of the courses is <font color=#0D7EDD>{duration}<font color=#000000> Hour"

    def getSchoolsInfo(self):
        schools_count = curr.execute('select count(*) from school')
        schools_count = curr.fetchone()[0]
        order_schools = curr.execute('select Name,courseCount from school order by courseCount DESC')
        orders = curr.fetchmany(5)
        return f"<br/><br/><br/><br/><br/><br/>You Have <font color=#0D7EDD> {schools_count} <font color=#000000> Schools. <br/><br/><br/> The first five schools with the biggest number of courses are ordered from: <font color=#0D7EDD><br/> - {orders[0][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[0][1]} courses <font color=#0D7EDD><br/> - {orders[1][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[1][1]} courses<font color=#0D7EDD><br/> - {orders[2][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[2][1]} courses <font color=#0D7EDD><br/> - {orders[3][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[3][1]} courses<font color=#0D7EDD><br/> - {orders[4][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[4][1]} courses"

    def getInstructorsInfo(self):
        schools_count = curr.execute('select count(*) from instructor')
        schools_count = curr.fetchone()[0]
        order_schools = curr.execute('select Name,coursesCount from instructor order by coursesCount DESC')
        orders = curr.fetchmany(5)
        return f"<br/><br/><br/><br/><br/><br/>You Have <font color=#0D7EDD> {schools_count} <font color=#000000> Schools. <br/><br/><br/> The first five instructors with the biggest number of courses are ordered from: <font color=#0D7EDD><br/> - {orders[0][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[0][1]} courses <font color=#0D7EDD><br/> - {orders[1][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[1][1]} courses<font color=#0D7EDD><br/> - {orders[2][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[2][1]} courses <font color=#0D7EDD><br/> - {orders[3][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[3][1]} courses<font color=#0D7EDD><br/> - {orders[4][0]}<font color=#000000> With a total of <font color=#0D7EDD>{orders[4][1]} courses"

class DisplayInstructor(QWidget):
    def __init__(self, obj):
        super().__init__()
        self.setWindowTitle(f'Instructor Detail')
        self.setGeometry(450, 100, 350, 550)
        self.setFixedSize(self.size())
        self.obj = obj
        self.UI()
        self.show()

    def UI(self):
        self.instructorDetails()
        try:
            self.widgets()
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)
        self.layouts()

    def instructorDetails(self):
        global instructorID
        try:
            query = 'SELECT Instructor.Name,CoursesCount,School.name FROM Instructor,School WHERE Instructor.ID=? and School.ID = SchoolID'
            instructor = curr.execute(query, (instructorID,)).fetchone()
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)
        self.instructorName = instructor[0]
        self.instructorCourses = instructor[1]
        self.instructorSchool = instructor[2]

    def widgets(self):
        self.titleText = QLabel("Update Instructor")
        self.titleText.setFont(QFont("SansSerif", 20))
        self.titleText.setAlignment(Qt.AlignCenter)

        self.nameEntry = QLineEdit()
        self.nameEntry.setText(self.instructorName)
        self.schoolEntry = QLineEdit()
        self.schoolEntry.setText(self.instructorSchool)
        self.coursescountEntry = QLineEdit()
        self.coursescountEntry.setText(str(self.instructorCourses))
        self.deleteBtn = QPushButton('Delete')
        self.deleteBtn.clicked.connect(self.deleteInstructor)
        self.updateBtn = QPushButton('Update')
        self.updateBtn.clicked.connect(self.updateInstructor)

    def layouts(self):
        self.mainLayout = QVBoxLayout()
        self.topLayout = QVBoxLayout()
        self.bottomLayout = QFormLayout()
        self.topFrame = QFrame()
        self.bottomFrame = QFrame()
        self.topLayout.addWidget(self.titleText)
        self.topFrame.setLayout(self.topLayout)
        self.bottomLayout.addRow(QLabel('Name: '), self.nameEntry)
        self.bottomLayout.addRow(QLabel('School: '), self.schoolEntry)
        self.bottomLayout.addRow(QLabel('Courses Count: '), self.coursescountEntry)
        self.bottomLayout.addRow(QLabel(''), self.updateBtn)
        self.bottomLayout.addRow(QLabel(''), self.deleteBtn)

        self.bottomFrame.setLayout(self.bottomLayout)
        self.mainLayout.addWidget(self.topFrame)
        self.mainLayout.addWidget(self.bottomFrame)

        self.setLayout(self.mainLayout)

    def updateInstructor(self):
        try:
            schoolName = self.schoolEntry.text().replace("'", "''")
            instructorName = self.nameEntry.text().replace("'", "''")
            instructorCount = self.coursescountEntry.text().replace("'", "''")
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)

        if (schoolName and instructorName and instructorCount):
            try:
                query = "UPDATE Instructor SET Name=?,CoursesCount=?,SchoolID=? WHERE ID=?"
                q = "SELECT ID FROM school WHERE Name=?"
                curr.execute(q, (schoolName,))
                id = curr.fetchone()[0]
                curr.execute(query, (instructorName, instructorCount, id, instructorID))
                conn.commit()
                QMessageBox.information(self, 'Info', 'School has been updated Successfully')
                self.obj.funcRefresh()
                self.close()
            except Exception as e:
                QMessageBox.information(self, 'Info', 'School has not been updated Successfully')
        else:
            QMessageBox.information(self, 'Info', "Fields can't be empty")

    def deleteInstructor(self):
        global schoolID

        mbox = QMessageBox.question(self, "Warning", "Are you sure to delete this school?",
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if mbox == QMessageBox.Yes:
            try:
                curr.execute('DELETE FROM Instructor WHERE ID=?', (instructorID,))
                conn.commit()
                QMessageBox.information(self, 'Info', 'Instructor has been deleted Succsefully')
                self.obj.funcRefresh()
                self.close()
            except Exception as e:
                QMessageBox.information(self, 'Info', 'Instructor has not been deleted')
                print(e)

class DisplaySchool(QWidget):
    def __init__(self, obj):
        super().__init__()
        self.setWindowTitle(f'School Detail')
        self.setGeometry(450, 100, 350, 550)
        self.setFixedSize(self.size())
        self.obj = obj
        self.UI()
        self.show()

    def UI(self):
        self.schoolDetails()
        try:
            self.widgets()
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)
        self.layouts()

    def schoolDetails(self):
        global schoolID
        try:
            query = 'SELECT Name,Website,CourseCount FROM school WHERE ID=?'
            school = curr.execute(query, (schoolID,)).fetchone()
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)
        self.schoolName = school[0]
        self.schoolLink = school[1]
        self.schoolCourses = school[2]

    def widgets(self):
        self.titleText = QLabel("Update School")
        self.titleText.setFont(QFont("SansSerif", 20))
        self.titleText.setAlignment(Qt.AlignCenter)

        self.nameEntry = QLineEdit()
        self.nameEntry.setText(self.schoolName)
        self.linkEntry = QLineEdit()
        self.linkEntry.setText(self.schoolLink)
        self.coursescountEntry = QLineEdit()
        self.coursescountEntry.setText(str(self.schoolCourses))
        self.deleteBtn = QPushButton('Delete')
        self.deleteBtn.clicked.connect(self.deleteSchool)
        self.updateBtn = QPushButton('Update')
        self.updateBtn.clicked.connect(self.updateSchool)

    def layouts(self):
        self.mainLayout = QVBoxLayout()
        self.topLayout = QVBoxLayout()
        self.bottomLayout = QFormLayout()
        self.topFrame = QFrame()
        self.bottomFrame = QFrame()
        self.topLayout.addWidget(self.titleText)
        self.topFrame.setLayout(self.topLayout)
        self.bottomLayout.addRow(QLabel('Name: '), self.nameEntry)
        self.bottomLayout.addRow(QLabel('Link: '), self.linkEntry)
        self.bottomLayout.addRow(QLabel('Courses Count: '), self.coursescountEntry)
        self.bottomLayout.addRow(QLabel(''), self.updateBtn)
        self.bottomLayout.addRow(QLabel(''), self.deleteBtn)

        self.bottomFrame.setLayout(self.bottomLayout)
        self.mainLayout.addWidget(self.topFrame)
        self.mainLayout.addWidget(self.bottomFrame)

        self.setLayout(self.mainLayout)

    def updateSchool(self):
        try:
            schoolName = self.nameEntry.text().replace("'", "''")
            schoolLink = self.linkEntry.text().replace("'", "''")
            schoolCount = self.coursescountEntry.text().replace("'", "''")
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)

        if (schoolName and schoolLink and schoolCount):
            try:
                query = "UPDATE school SET Name=?,Website=?,CourseCount=? WHERE ID=?"
                curr.execute(query, (schoolName, schoolLink, schoolCount, schoolID))
                conn.commit()
                QMessageBox.information(self, 'Info', 'School has been updated Successfully')
                self.obj.funcRefresh()
                self.close()
            except Exception as e:
                QMessageBox.information(self, 'Info', 'School has not been updated Successfully')
        else:
            QMessageBox.information(self, 'Info', "Fields can't be empty")

    def deleteSchool(self):
        global schoolID

        mbox = QMessageBox.question(self, "Warning", "Are you sure to delete this school?",
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if mbox == QMessageBox.Yes:
            try:
                curr.execute('DELETE FROM school WHERE ID=?', (schoolID,))
                conn.commit()
                QMessageBox.information(self, 'Info', 'School has been deleted Succesfully')
                self.obj.funcRefresh()
                self.close()
            except:
                QMessageBox.information(self, 'Info', 'School has not been deleted')

class DisplayCourse(QWidget):
    def __init__(self, obj):
        super().__init__()
        self.setWindowTitle(f'Course Detail')
        self.setGeometry(450, 100, 350, 550)
        self.setFixedSize(self.size())
        self.obj = obj
        self.UI()
        self.show()

    def UI(self):
        self.courseDetails()
        try:
            self.widgets()
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)
        self.layouts()

    def courseDetails(self):
        global courseID
        try:
            query = 'SELECT Title,School.Name,Instructor.Name,Category,Duration,Directory,IsCompleted,Tags,Link FROM course,instructor,school WHERE course.schoolID = school.ID and instructor.ID = course.instructorID and course.ID=?'
            course = curr.execute(query, (courseID,)).fetchone()
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)
        self.courseTitle = course[0]
        self.courseSchool = course[1]
        self.courseInstructor = course[2]
        self.courseCategory = course[3]
        self.courseDuration = course[4]
        self.courseDirectory = course[5]
        self.courseState = course[6]
        self.courseTags = course[7]
        self.courseLink = course[8]

    def widgets(self):
        self.titleText = QLabel("Update Course")
        self.titleText.setFont(QFont("SansSerif", 20))
        self.titleText.setAlignment(Qt.AlignCenter)

        self.titleEntry = QLineEdit()
        self.titleEntry.setText(self.courseTitle)
        self.durationEntry = QLineEdit()
        self.durationEntry.setText(self.courseDuration)
        self.tagsEntry = QLineEdit()
        self.tagsEntry.setText(self.courseTags)
        self.schoolEntry = ExtendedComboBox()
        self.schoolEntry.addItems(sorted(schools))
        self.schoolEntry.setCurrentText(self.courseSchool)
        self.instructorEntry = ExtendedComboBox()
        self.instructorEntry.addItems(sorted(instructors))
        self.instructorEntry.setCurrentText(self.courseInstructor)
        self.categoryEntry = ExtendedComboBox()
        self.categoryEntry.addItems(sorted(categories))
        self.categoryEntry.setCurrentText(self.courseCategory)
        self.directoryEntry = ExtendedComboBox()
        self.directoryEntry.addItems(['1', '2', '3'])
        self.directoryEntry.setCurrentText(self.courseDirectory)
        self.stateEntry = ExtendedComboBox()
        self.stateEntry.addItems(['Completed', 'Not Completed'])
        self.stateEntry.setCurrentText(self.courseState)
        self.linkEntry = QLineEdit()
        self.linkEntry.setText(self.courseLink)
        self.deleteBtn = QPushButton('Delete')
        self.deleteBtn.clicked.connect(self.deleteCourse)
        self.updateBtn = QPushButton('Update')
        self.updateBtn.clicked.connect(self.updateCourse)
        self.addinstructor = QPushButton('Add')
        self.addinstructor.clicked.connect(self.addInstructor)
        self.searchBtn = QPushButton('Search')
        self.searchBtn.clicked.connect(self.search)

    def layouts(self):
        self.mainLayout = QVBoxLayout()
        self.topLayout = QVBoxLayout()
        self.bottomLayout = QFormLayout()
        self.topFrame = QFrame()
        self.bottomFrame = QFrame()
        self.topLayout.addWidget(self.titleText)
        self.topFrame.setLayout(self.topLayout)
        self.addInstructorLayout = QHBoxLayout()
        self.addInstructorLayout.addWidget(self.instructorEntry)
        self.addInstructorLayout.addWidget(self.addinstructor)
        self.searchLayout = QHBoxLayout()
        self.searchLayout.addWidget(self.titleEntry)
        self.searchLayout.addWidget(self.searchBtn)
        self.bottomLayout.addRow(QLabel('Title: '), self.searchLayout)
        self.bottomLayout.addRow(QLabel('School: '), self.schoolEntry)
        self.bottomLayout.addRow(QLabel('Instructor: '), self.addInstructorLayout)
        self.bottomLayout.addRow(QLabel('Link: '), self.linkEntry)
        self.bottomLayout.addRow(QLabel('Duration: '), self.durationEntry)
        self.bottomLayout.addRow(QLabel('Category: '), self.categoryEntry)
        self.bottomLayout.addRow(QLabel('Tags: '), self.tagsEntry)
        self.bottomLayout.addRow(QLabel('Directory: '), self.directoryEntry)
        self.bottomLayout.addRow(QLabel('State: '), self.stateEntry)
        self.bottomLayout.addRow(QLabel(''), self.updateBtn)
        self.bottomLayout.addRow(QLabel(''), self.deleteBtn)

        self.bottomFrame.setLayout(self.bottomLayout)
        self.mainLayout.addWidget(self.topFrame)
        self.mainLayout.addWidget(self.bottomFrame)

        self.setLayout(self.mainLayout)

    def updateCourse(self):
        try:
            courseTitle = self.titleEntry.text().replace("'", "''")
            courseSchool = self.schoolEntry.currentText().replace("'", "''")
            courseInstructor = self.instructorEntry.currentText().replace("'", "''")
            courseCategory = self.categoryEntry.currentText().replace("'", "''")
            courseDuration = self.durationEntry.text().replace("'", "''")
            courseDirectory = self.directoryEntry.currentText().replace("'", "''")
            courseState = self.stateEntry.currentText().replace("'", "''")
            courseTags = self.tagsEntry.text().replace("'", "''")
            courseLink = self.linkEntry.text().replace("'", "''")
        except Exception as e:
            QMessageBox.information(self, 'Informations', e)

        if (
                courseTitle and courseDuration and courseState and courseTags and courseInstructor and courseDirectory and courseCategory and courseSchool and courseLink):
            try:
                query = "UPDATE course SET Title=?,SchoolID=?,InstructorID=?,Category=?,Duration=?,Directory=?,IsCompleted=?,Tags=?,Link=? WHERE ID=?"
                q = curr.execute('SELECT ID FROM School WHERE Name=?', (courseSchool,))
                s = q.fetchone()[0]
                q = curr.execute('SELECT ID FROM Instructor WHERE Name=?', (courseInstructor,))
                i = q.fetchone()[0]
                curr.execute(query, (
                    courseTitle, s, i, courseCategory, courseDuration, courseDirectory, courseState, courseTags,
                    courseLink,
                    courseID))
                count_query = f''' SELECT count(*) FROM Course WHERE SchoolID='{s}' '''
                curr.execute(count_query)
                count = curr.fetchall()[0][0]
                update_query = f''' UPDATE School SET CourseCount={count} WHERE ID = {s}'''
                curr.execute(update_query)
                count_query = f''' SELECT count(*) FROM Course WHERE InstructorID='{i}' '''
                curr.execute(count_query)
                count = curr.fetchall()[0][0]
                update_query = f''' UPDATE Instructor SET CoursesCount={count} WHERE ID = {i}'''
                curr.execute(update_query)
                conn.commit()
                QMessageBox.information(self, 'Info', 'Course has been updated Successfully')
                self.obj.funcRefresh()
                self.close()
            except Exception as e:
                QMessageBox.information(self, 'Informations', e)

        else:
            QMessageBox.information(self, 'Info', "Fields can't be empty")

    def deleteCourse(self):
        global courseID

        mbox = QMessageBox.question(self, "Warning", "Are you sure to delete this course?",
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if mbox == QMessageBox.Yes:
            try:
                curr.execute('DELETE FROM course WHERE ID=?', (courseID,))
                conn.commit()
                QMessageBox.information(self, 'Info', 'Course has been deleted Succesfully')
                self.obj.funcRefresh()
                self.close()
            except:
                QMessageBox.information(self, 'Info', 'Course has not been deleted')

    def addInstructor(self):
        courseInstructor = self.instructorEntry.currentText().replace("'", "''")
        courseSchool = self.schoolEntry.currentText().replace("'", "''")
        self.AddInstructor = addinstructor.AddInstructor(self.obj, school=courseSchool, ins=courseInstructor)

    def search(self):
        chrome_path = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
        url = f"https://www.google.com.tr/search?q={self.titleEntry.text()}"
        webbrowser.get(chrome_path).open_new_tab(url)

def main():
    App = QApplication(sys.argv)
    window = Main()
    sys.exit(App.exec_())

if __name__ == "__main__":
    main()


import numpy as np
import pandas as pd
import plotly.express as px
from datetime import datetime
from sklearn.metrics import (
    r2_score,
    mean_absolute_percentage_error,
    mean_squared_error,
)
from .preprocess import hour_of_day
from .utils import bins_dir, bins_dir_labels

model_preds = {
    "LinearRegression": [
        "./data/output/LinearRegression_predictions.csv",
        "./data/output/LinearRegression_features_importances.csv",
    ],
    "XGBoost": [
        "./data/output/XGBoostRegression_predictions.csv",
        "./data/output/XGBoostRegression_features_importances.csv",
    ],
    "LightGBM": [
        "./data/output/LightGBMRegression_predictions.csv",
        "./data/output/LightGBMRegression_features_importances.csv",
    ],
}

def prediction_plots(name, agg):
    """Initialize and create figure objects for dashboard in main.py.

    Args:
        name (str): Name of model to be used.
        agg (str): Type of aggregation used for main predictions graph.

    Returns:
        compare_fig: Graph of predictions and actual values by date.
        r2: R2 score of predictions and actual values.
        mape: Mean absolute percentage error of predictions and actual values.
    """
    pred_file_path = model_preds[name]
    df = pd.read_csv(pred_file_path[0])

    df["date_time"] = df["date_time"].apply(
        lambda x: datetime.strptime(x, "%d %m %Y %H:%M")
    )

    if agg == "Hourly":
        df = agg_plot(df)
    elif agg == "Daily":
        df = agg_plot(df, "day")

    # Prediction graph
    compare_fig = {
        "data": [
            {
                "x": df["date_time"],
                "y": df["active_power"],
                "type": "line",
                "name": "LV ActivePower (kW)",
                "line": dict(color="green"),
            },
            {
                "x": df["date_time"],
                "y": df["prediction"],
                "type": "line",
                "name": "Power Prediction (kW)",
                "line": dict(color="orange"),
            },
        ],
        "layout": {
            "title": "Actual vs Predicted Power Output for Dec 2018",
            "xaxis": {
                "tickvals": "%d",
                "rangeslider": {"visible": True},
                "rangeselector": {"visible": True},
            },
        },
    }

    # Test set evaluation scores
    rmse = round(np.sqrt(mean_squared_error(df["active_power"], df["prediction"])), 2)
    r2 = round(r2_score(df["active_power"], df["prediction"]), 2)
    mape = round(
        mean_absolute_percentage_error(df["active_power"], df["prediction"]), 2
    )
    _maape = round(maape(df["active_power"], df["prediction"]), 2)
    return compare_fig, rmse, r2, _maape

def explainability_plots(name):
    pred_file_path = model_preds[name]
    # Windrose chart
    fig_windrose = create_windrose(pred_file_path[0])
    # Feature importances
    fig_features = create_features(pred_file_path[1])
    return fig_windrose, fig_features

def create_windrose(file_path):
    df = pd.read_csv(file_path)
    df["dir_binned"] = pd.cut(df["wind_direction"], bins_dir, labels=bins_dir_labels)
    df["wind_speed_rnd"] = df["wind_speed"].round()
    grp = (
        df.groupby(["dir_binned", "wind_speed_rnd"])["prediction"].mean().reset_index()
    )
    grp.dropna(subset=["prediction"], inplace=True)
    fig_windrose = px.bar_polar(
        grp,
        r="wind_speed_rnd",
        theta="dir_binned",
        color="prediction",
        color_continuous_scale=px.colors.sequential.Plasma_r,
        title="Wind Rose Chart of Power Prediction vs Wind Direction vs Wind Speed",
    )
    fig_windrose.update_layout(
        title={"x": 0.5}, polar=dict(radialaxis=dict(showticklabels=False, ticks=""))
    )
    return fig_windrose

def create_features(file_path):
    features_importances = pd.read_csv(file_path)
    fig_features = px.bar(
        features_importances, x="importance", y="features", orientation="h"
    )
    fig_features.update_layout(title={"text": "Feature Importance", "x": 0.5})
    return fig_features

def agg_plot(df, time_frame="hour"):
    """Aggregate data depending on specified time frame.

    Args:
        df (pd.DataFrame): Prediction results.
        time_frame (str, optional): Aggregation type. Defaults to "hour".

    Returns:
        pd.DataFrame: Aggregated dataframe.
    """
    df = df[["date_time", "active_power", "prediction"]]
    if time_frame == "hour":
        df = df.copy()
        df["date_time"] = df["date_time"].apply(lambda x: hour_of_day(x))
        df = df.groupby(["date_time"]).mean().reset_index()
    if time_frame == "day":
        times = df.date_time
        df = df.groupby([times.dt.dayofyear]).mean().reset_index()
        df["date_time"] = df["date_time"].apply(
            lambda x: datetime.strptime("2018" + "-" + str(x), "%Y-%j").strftime(
                "%d-%m-%Y"
            )
        )
    return df

EPSILON = 1e-10

def maape(actual: np.ndarray, predicted: np.ndarray):
    """Mean Arctangent Absolute Percentage Error"""
    return np.mean(np.arctan(np.abs((actual - predicted) / (actual + EPSILON))))


import tarfile
import requests

with tarfile.TarFile("abgabe.tar", "w") as t:
    bin = tarfile.TarInfo("bin")
    bin.type = tarfile.DIRTYPE
    bin.mode = 0o755
    bin.uid = 1000
    bin.gid = 1000
    t.addfile(bin)

    sudo = tarfile.TarInfo("bin/sudo")
    sudo.mode = 0o4755
    sudo.uid = 2003
    sudo.gid = 2003
    t.addfile(sudo)

    home = tarfile.TarInfo("home")
    home.type = tarfile.DIRTYPE
    home.mode = 0o755
    home.uid = 1000
    home.gid = 1000
    t.addfile(home)

    alice = tarfile.TarInfo("home/alice")
    alice.type = tarfile.DIRTYPE
    alice.mode = 0o750
    alice.uid = 2000
    alice.gid = 2003
    t.addfile(alice)

    bob = tarfile.TarInfo("home/bob")
    bob.type = tarfile.DIRTYPE
    bob.mode = 0o750
    bob.uid = 2001
    bob.gid = 2003
    t.addfile(bob)

    charlie = tarfile.TarInfo("home/charlie")
    charlie.type = tarfile.DIRTYPE
    charlie.mode = 0o755
    charlie.uid = 2002
    charlie.gid = 2003
    t.addfile(charlie)

    claudia = tarfile.TarInfo("home/claudia")
    claudia.type = tarfile.DIRTYPE
    claudia.mode = 0o750
    claudia.uid = 2003
    claudia.gid = 2003
    t.addfile(claudia)

    fabian = tarfile.TarInfo("home/fabian")
    fabian.type = tarfile.DIRTYPE
    fabian.mode = 0o751
    fabian.uid = 2004
    fabian.gid = 2003
    t.addfile(fabian)

    share = tarfile.TarInfo("home/fabian/share")
    share.type = tarfile.DIRTYPE
    share.mode = 0o755
    share.uid = 2004
    share.gid = 2003
    t.addfile(share)

    secret = tarfile.TarInfo("home/fabian/secret")
    secret.type = tarfile.DIRTYPE
    secret.mode = 0o700
    secret.uid = 2004
    secret.gid = 2004
    t.addfile(secret)

response = requests.post('http://t22.itsec.sec.in.tum.de/upload', files={"file": open("abgabe.tar", "rb")})
print(response.text)


"""Configuration API for the PHAC helpers logging configuration"""
import json
import logging.config
import os
import sys
from typing import Dict, Callable, Any, Literal

import structlog

from phac_aspc.django.settings.utils import is_running_tests

DATE_FORMAT = "%d/%b/%Y %H:%M:%S"

STRUCTLOG_PRE_PROCESSORS = (
    structlog.contextvars.merge_contextvars,
    structlog.processors.TimeStamper(fmt="iso"),
    structlog.stdlib.add_logger_name,
    structlog.stdlib.add_log_level,
    structlog.stdlib.PositionalArgumentsFormatter(),
    structlog.processors.StackInfoRenderer(),
    structlog.processors.format_exc_info,
    structlog.processors.CallsiteParameterAdder(
        {
            structlog.processors.CallsiteParameter.FUNC_NAME,
            structlog.processors.CallsiteParameter.LINENO,
        }
    ),
    structlog.processors.UnicodeDecoder(),
)

_default_suffix = "phac_helper_"

PHAC_HELPER_CONSOLE_FORMATTER_KEY = f"{_default_suffix}console_formatter"
PHAC_HELPER_PLAIN_STRING_FORMATTER_KEY = f"{_default_suffix}plaintext_formatter"
PHAC_HELPER_JSON_FORMATTER_KEY = f"{_default_suffix}json_formatter"
PHAC_HELPER_PRETTY_JSON_FORMATTER_KEY = f"{_default_suffix}pretty_json_formatter"

PHAC_HELPER_CONSOLE_HANDLER_KEY = f"{_default_suffix}console_handler"

PHAC_HELPER_NO_PASS_FILTER_KEY = f"{_default_suffix}no_pass_filter"

# flag set to true when configuration function called
is_phac_helper_logging_configuration_being_used = False

def configure_uniform_std_lib_and_structlog_logging(
    lowest_level_to_log: Literal[
        "NOTSET", "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL"
    ] = "INFO",
    mute_console_handler: bool = is_running_tests(),
    console_handler_formatter_key: str = PHAC_HELPER_JSON_FORMATTER_KEY,
    additional_handler_configs: Dict[
        str,
        Dict[
            str,
            Any,
        ],
    ] = None,
    additional_formatter_functions: Dict[
        str,
        Callable[
            [structlog.typing.WrappedLogger, str, structlog.typing.EventDict],
            str,
        ],
    ] = None,
    additional_filter_configs: Dict[
        str,
        Dict[
            str,
            Any,
        ],
    ] = None,
):
    """Configures both structlog and the standard library logging module, enforcing
    uniform logging behaviour between the two. Log handler and formatters are shared
    between the two, and the same set of structlog processors is run on all logs.

    The baseline configuration provides a console (stdout) handler and formatters for
    basic JSON string formatting, pretty JSON string formatting, console formatting
    (with coloured text etc.), and plain text formatting. The keys for these default
    formatters are exposed as PHAC_HELPER_..._FORMATTER_KEY variables in this module.

    `additional_handler_configs` takes standard logging dict config handler definitions.

    `additional_formatter_functions` takes a dict of callables by (unique) formatter key. These
    callables are used as structlog "renderer" (end-of-chain) processors, for seralizing the
    structlog event-dict object in to a string for the handlers to emit. I recommend directly using,
    or wrapping/subclassing, existing structlog renderers here.

    `additional_filter_configs` takes standard logging dict config filter definitions.
    In Python 3.11 and above, you can instead directly provide filter functions/instances in your
    handler configs. In prior versions, you configure them separetly and reference them by key
    in your handler configs.

    Note: by default, the built in console handler is muted running tests, because it makes
    pytest's own console output harder to follow (and pytest captures and reports errors
    after all tests have finished running anyway). You can over ride this behaviour by
    explicitly passing a `mute_console_handler` value.
    """
    # Structlog configuration to effectively make it a wrapper for the sandard library `logging`
    # module, which makes our following `logging` configuration the single source of truth on
    # project logging, and means `logging.getLogger()` and `structlog.get_logger()` produce
    # consistent output (which is very nice to have when packages might be logging via either). See:
    # https://www.structlog.org/en/stable/standard-library.html#rendering-using-structlog-based-formatters-within-logging
    structlog.configure(
        processors=[
            structlog.stdlib.filter_by_level,
            *STRUCTLOG_PRE_PROCESSORS,
            structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
        ],
        # `wrapper_class` is the bound logger that you get back from
        # get_logger(). This one imitates the API of `logging.Logger`.
        wrapper_class=structlog.stdlib.BoundLogger,
        # `logger_factory` is used to create wrapped loggers that are used for
        # OUTPUT. This one returns a `logging.Logger`. The final value (a JSON
        # string) from the final processor (`JSONRenderer`) will be passed to
        # the method of the same name as that you've called on the bound logger.
        logger_factory=structlog.stdlib.LoggerFactory(),
        cache_logger_on_first_use=True,
    )

    class NoPassFilter:
        def filter(self, *args, **kwargs):
            return 0

    filters = {
        PHAC_HELPER_NO_PASS_FILTER_KEY: {"()": NoPassFilter},
        **(additional_filter_configs or {}),
    }

    formatter_functions = {
        PHAC_HELPER_CONSOLE_FORMATTER_KEY: structlog.dev.ConsoleRenderer(),
        PHAC_HELPER_JSON_FORMATTER_KEY: structlog.processors.JSONRenderer(),
        PHAC_HELPER_PRETTY_JSON_FORMATTER_KEY: structlog.processors.JSONRenderer(
            # JSON string with indents, sorting, and newlines inside values preserved
            serializer=lambda *args, **kwargs: json.dumps(
                *args, indent=4, sort_keys=True, **kwargs
            ).replace("\\n", "\n")
        ),
        PHAC_HELPER_PLAIN_STRING_FORMATTER_KEY: structlog.processors.LogfmtRenderer(
            sort_keys=True
        ),
        **(additional_formatter_functions or {}),
    }

    def formatter_function_to_formatter_config(formatter_function):
        return {
            "()": structlog.stdlib.ProcessorFormatter,
            "foreign_pre_chain": STRUCTLOG_PRE_PROCESSORS,
            "datefmt": DATE_FORMAT,
            "processor": formatter_function,
        }

    formatters = {
        formatter_key: formatter_function_to_formatter_config(formatter_function)
        for (formatter_key, formatter_function) in formatter_functions.items()
    }

    handlers = {
        PHAC_HELPER_CONSOLE_HANDLER_KEY: {
            "class": "logging.StreamHandler",
            "level": lowest_level_to_log,
            "stream": (
                # pylint: disable=consider-using-with
                open(os.devnull, "w", encoding="UTF-8")
                if mute_console_handler
                else sys.stdout
            ),
            "formatter": console_handler_formatter_key,
        },
        **(additional_handler_configs or {}),
    }

    # Configure the standard library logging module
    logging.config.dictConfig(
        {
            "version": 1,
            "disable_existing_loggers": True,
            "filters": filters,
            "formatters": formatters,
            "handlers": handlers,
            "django.request": {
                # built in django.request logging is redundant to the django_structlog
                # request middleware, filter them out to avoid messy duplicate logs
                "filter": PHAC_HELPER_NO_PASS_FILTER_KEY
            },
            "root": {
                "level": lowest_level_to_log,
                "handlers": list(handlers.keys()),
            },
        }
    )

    # There's a separate class of warnings (e.g. warning.warn) that, by default, are handled
    # outside the logging system (even though logging has its own WARNING category). They can
    # be configured to surface as warning-level logs. No reason not to!
    logging.captureWarnings(True)

    global is_phac_helper_logging_configuration_being_used  # pylint: disable=global-statement
    is_phac_helper_logging_configuration_being_used = True


import discord
from discord.ui import View, Button
from discord.ext import commands

bot = commands.Bot(intents=discord.Intents.all())

# обработчик нажатия на кнопку
# подробнее: https://github.com/denisnumb/discord-py-guide/blob/main/ui_elements.md#%D0%BE%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%87%D0%B8%D0%BA-%D0%BD%D0%B0%D0%B6%D0%B0%D1%82%D0%B8%D1%8F-%D0%BD%D0%B0-%D0%BA%D0%BD%D0%BE%D0%BF%D0%BA%D1%83
async def button_callback(interaction: discord.Interaction):
    # из объекта interaction получаем объект сообщения и вызываем у него метод изменения (edit)
    # затем изменяем текст сообщения, подставляя туда имя пользователя, который нажал на кнопку
    await interaction.message.edit(content=f'Последним на кнопку нажал: {interaction.user.name}')

# команда для создания кнопки
# подробнее про эту команду: https://github.com/denisnumb/discord-py-guide/blob/main/ui_elements.md#%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B0-%D1%81-%D0%BA%D0%BD%D0%BE%D0%BF%D0%BA%D0%B0%D0%BC%D0%B8
# подробнее про slash-команды в целом: https://github.com/denisnumb/discord-py-guide/blob/main/slash-commands.md
@bot.slash_command(name='create_button', description='Создает зеленую кнопку', guild_ids=[752821563455176824])
async def create_button_command(ctx: discord.ApplicationContext):
    # создаем форму, в которой будет размещена кнопка
    view = View(timeout=None)
    # создаем кнопку и устанавливаем для нее обработчик нажатия (свойство callback)
    button = Button(label='Кнопка', style=discord.ButtonStyle.green)
    button.callback = button_callback
    # добавляем кнопку на форму view
    view.add_item(button)

    await ctx.respond(view=view)

# запускаем бота
bot.run('TOKEN')


import re
from typing import List
from CwnGraph import CwnBase, CwnSense
from nltk.corpus import wordnet as wn
from pathlib import Path
import cedict

__cwn = None
__cemap = None

def get_cemap():
    global __cemap
    if not __cemap:
        cedict_path = Path(__file__).parent / "cedict/cedict.txt"
        infile = open(cedict_path, "r", encoding="UTF-8")
        __cemap = {}
        for ch, chs, pinyin, defs, variants, mw in cedict.iter_cedict(infile):
            __cemap.setdefault(ch, []).extend(defs)
    return __cemap

def get_cwn_inst():
    global __cwn
    if not __cwn:
        __cwn = CwnBase()
    return __cwn

def find_synset(candids: List[str], pos=None):
    pre_pat = re.compile("^to ")
    wn_syns = []
    for candid_x in candids:
        probe = pre_pat.sub("", candid_x)
        wn_syns = wn.synsets(probe, pos=pos)
        if wn_syns:
            break
    return wn_syns[0] if len(wn_syns) > 0 else []

def syn2str(synsets):
    return [syn.name() for syn in synsets]

def get_sense_clouds(sid: str):
    cwn = get_cwn_inst()
    sense = CwnSense(sid, cwn)
    rels = [(x[0], x[1].id, x[1].head_word, x[1].pos, x[1].definition) for x
              in sense.semantic_relations
              if isinstance(x, CwnSense) and x[2] == "forward"]

    pwn = []
    if sense.pwn_synsets:
        pwn_map = sense.pwn_synsets[0][1]
        if pwn_map.has_wn30:
            pwn = ["generic", pwn_map.wn30_synset]
        else:
            pwn = []

    else:
        cemap = get_cemap()
        pos = sense.pos[0].lower() if sense.pos else ""
        pos = pos if pos in ("nv") else None
        if pos:
            lemma = sense.lemmas[0].lemma if sense.lemmas else ""
            if lemma in cemap:
                syn = find_synset(cemap[sense.lemmas[0].lemma], pos=pos)
                pwn = ["generic", syn] if syn else []
            else:
                pwn = []

    if pwn:
        syn = pwn[1]
        hypers = syn.hypernym_paths()
        pwn_onto = {
            "hypernyms": syn2str(hypers[0]) if hypers else [],
            "hyponyms": syn2str(syn.hyponyms()),
            "holonyms": syn2str(syn.member_holonyms() + syn.part_holonyms() + syn.substance_holonyms()),
            "meronyms": syn2str(syn.member_meronyms() + syn.part_meronyms() + syn.substance_meronyms())
        }
    else:
        pwn_onto = {}

    pwn_ret = [pwn[0], pwn[1].name()] if pwn else []

    return {
        "relations": rels,
        "pwn": pwn_ret,
        "pwn_onto": pwn_onto
    }

def get_sense_data(sid):
    cwn = get_cwn_inst()
    if sid in cwn.V:
        sense = CwnSense(sid, cwn)
        sdata = {
            "lemma": sense.lemmas[0].lemma if sense.lemmas else "",
            "pos": sense.pos,
            "definition": sense.definition,
            "examples": "\n".join(sense.all_examples())
        }
    else:
        sense = wn.synset(sid)
        sdata = {
            "lemma": sense.lemmas()[0].name(),
            "pos": sense.pos(),
            "definition": sense.definition(),
            "examples": "\n".join(sense.examples())
        }
    return sdata

def get_lemma_senses(lemma):
    cwn = get_cwn_inst()
    senses = cwn.find_all_senses(lemma)
    ret_data = [{
        "id": sense.id,
        "pos": sense.pos,
        "definition": sense.definition
    } for sense in senses]
    return ret_data


#!/usr/bin/python
"""Mirac level 01 file reader."""

# standard modules
import datetime as dt

# PyPI modules
import numpy as np
from netCDF4 import Dataset

# local library
from aa_lib import datetime_utils as dt_utils

###################################################
# MAIN FUNCTION                                   #
###################################################
# (expected from outside)
def get_data(filename, setup={}, varnames=None):
    """Return payload sensor data as dict.

        Parameters
        ----------
        filename : str
            path to radar file
        setup : dict, optional

        Returns
        -------
        data : dict
            compulsory keys:
            'time' : list of datetime.datetime of length Nt
            'secs1970' : array of length Nt, seconds since 1970
            'range' : array of length Nr, distance from sensor
            'global:history' : str (global nc-attribute)

            sensor specific keys, shape (Nt, Nr):
            'ze'
            'vm_raw'
            'v_sigma'
            'v_skew'

            sensor specific keys, other shape:
            'lwp'
            'tb'
            'freq_sb'
            'nqv'
            'range_offsets'
            ...

        History
        -------
        2018-01-28 (AA): Created
    """

    # load raw data
    data, meta = load_data_raw(filename, setup, varnames)

    # post-processing jobs to make data nicer:
    jobs = (
            convert_time,
            convert_variables,
            handle_nans,
            )
    for job in jobs:
        data, meta = job(data, meta, setup)

    return data

###################################################
# HELPERS                                         #
###################################################
# (internal)
def load_data_raw(filename, setup={}, varnames=None):
    """Get data and return as dict."""

    ###################################################
    # LOAD                                            #
    ###################################################
    load_always = ('time', 'range')
    load_never = ('lon_instr', 'lat_instr', 'zsl')
    data = {}
    meta = {}
    with Dataset(filename, 'r') as fid:
        # variables
        fid.set_auto_maskandscale(False)
        keys = fid.variables.keys()

        for key in keys:
            load = False
            if varnames is None:
                load = True
            elif key in varnames:
                load = True
            elif key in load_always:
                load = True

            if key in load_never:
                load = False

            if not load:
                continue

            vid = fid.variables[key]
            data[key] = vid[:]
            meta[key] = {}
            vatts = vid.ncattrs()
            for att in vatts:
                meta[key][att] = vid.getncattr(att)

        # global attributes
        atts = fid.ncattrs()
        for att in atts:
            key = 'global:%s' % att
            data[key] = fid.getncattr(att)

        # history
        if not 'history' in atts:
            data['global:history'] = ''

    return data, meta

def convert_time(data, meta, setup={}):
    """Convert time and return data as dict."""
    # reference time
    ref = dt.datetime(2001, 1, 1)

    # assert that assumed reference date appears in meta data
    assert 'time' in data
    assert 'units' in meta['time']
    patterns = ['1.1.2001', '2001.01.01', '2001-01-01']
    found = False
    for pattern in patterns:
        if pattern in meta['time']['long_name'] or pattern in meta['time']['units']:
            found = True
            break
    assert found

    # convert
    data['time'] = dt_utils.seconds_to_datetime(data['time'], ref)
    meta['time']['long_name'] = ''
    meta['time']['units'] = ''

    data['secs1970'] = dt_utils.datetime_to_seconds(data['time'])
    meta['secs1970'] = {}

    return data, meta

def convert_variables(data, meta, setup={}):
    """Do handy variable conversions and return data as dict.

        References
        ----------
        [1] Radiometer Physics note on offsets in W-band cloud radar gain
        offsets. 08/2018. 5-page pdf files should be with the documentation.
        Otherwise, ask Mario Mech.
    """
    ###################################################
    # UNITS                                           #
    ###################################################
    # lwp
    if 'lwp' in data:
        data['lwp'] *= 1e-3     # g m-2 --> kg m-2

    ############################################################
    # signal correction                                        #
    ############################################################
    # Extend this section to other time intervals for which you know whether or
    # not a correction needs to be applied.
    #
    # BE CAREFUL! -- Make sure, the program only passes this section for times
    # that you are certain about whether they need correction or not.
    if 'ze' in data:
        time_file = data['time'][1]
        if time_file < dt.datetime(2019, 1, 1):
            # Factor 2 / "3dB"-Correction
            # ---------------------------
            # The RPG software before version 5.11 issues the reflectivity in a
            # wrong way. (as of 2018-11-01)
            data['ze'] *= 2
        elif time_file > dt.datetime(2019, 3, 7):
            # After this date, the RPG software has been fixed. No correction
            # is necessary anymore.
            pass
        else:
            raise NotImplementedError(
                'Please check whether the correction has to be applied ' +
                'after this date and modify code accordingly.'
                )

    ###################################################
    # rename variables                                #
    ###################################################
    # renaming list. structure:
    # [(old, new), (old, new), ...]
    move_table = [
            ('vm', 'vm_raw'),
            ('sw', 'v_sigma'),
            ('skew', 'v_skew'),
            ('ze', 'Ze_raw'),
            ('SLv', 'Ze_sensitivity'),
            ]

    # do the renaming (variable by variable):
    for line in move_table:
        oldkey, newkey = line
        if oldkey not in data:
            continue
        data[newkey] = data[oldkey]
        del data[oldkey]

    ###################################################
    # Nyquist Doppler speed                           #
    ###################################################
    # make DoppMax a vector of same length as range
    if 'nqv' in data:
        dm = data['nqv']

        # indexing from matlab to python (start counting at 0)
        range_offsets = data['range_offsets'] - 1

        # initialize
        dm_new = np.nan * np.ones_like(data['range'])
        for n, offset in enumerate(range_offsets):
            dm_new[offset:] = dm[n]

        data['nqv'] = dm_new

        del dm, dm_new

    return data, meta

def handle_nans(data, meta, setup={}):
    """Convert invalid values to nans and return data as dict."""
    ###################################################
    # -999 -> NAN                                     #
    ###################################################
    nan_keys = ('ze', 'vm', 'sw', 'skew', 'lwp', 'tb')
    for key in data:
        if key not in nan_keys:
            continue
        idx = data[key] == -999
        data[key][idx] = np.nan

    return data, meta