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 requests
import re
import os
import time

def return_vaild_name(title):
    result = re.sub(r'[\\/:*?"<>|\r\n]+', '_', title)
    return result

def request_dom(url):
    res = requests.get(url, proxies={'https': 'http://127.0.0.1:7890','http': 'http://127.0.0.1:7890'})
    if res.status_code == 200:
        return res.text

def parse_dom_to_page_num(text):
    res = re.search(r'Pages:.*?(\d+)', text, re.S)
    if res:
        return res.group(1)

def parse_dom_to_galleries_num(text):
    res = re.search(r'<meta itemprop="image".*?galleries/(\d+)/cover.jpg', text, re.S)
    if res:
        return res.group(1)

def parse_dom_to_name(text):
    res = re.search(
        r'(.*?)(.*?)(.*?)', text,
        re.S)
    if res:
        return ''.join(res.groups())

def get_download_urls(galleries_num, page_num):
    urls = ['https://i.nhentai.net/galleries/{}/{}.jpg'.format(galleries_num, page) for page in range(1, int(page_num) + 1)]
    return urls

def download_img( galleries_num, page_num, file_name):
    file_name = return_vaild_name(file_name)

    file_url = f'd:\tmp\comic\{file_name}'

    if not os.path.exists(file_url):
        os.mkdir(file_url)

    print(f'======开始下载{file_url}')
    for page in range(1, int(page_num) + 1):
        url = 'https://i.nhentai.net/galleries/{}/{}.jpg'.format(galleries_num, page)
        res = requests.get(url, proxies={'https': 'http://127.0.0.1:7890','http': 'http://127.0.0.1:7890'})
        if res.status_code == 200:
            data = res.content

            img_url = '{}/{}.jpg'.format(file_url, page)
            with open(img_url, 'wb') as f:
                if f.write(data):
                    print(r'已存储到:{}/{}.jpg,一共{}页'.format(file_name, page,page_num))
                else:
                    print('--------存储失败:', file_name)
        time.sleep(1)

def main(url):
    text = request_dom(url)
    page_num = parse_dom_to_page_num(text)
    galleries_num = parse_dom_to_galleries_num(text)
    name = parse_dom_to_name(text)
    print('---start---',galleries_num, page_num, name)
    download_img(galleries_num, page_num, name)
    print('---end---')

if __name__ == '__main__':
    commic_ids = [2162994]
    for commic_id in commic_ids:
        url = f'https://nhentai.net/g/{commic_id}/'
        main(url)


import argparse
import os
import sys
sys.path.append("src/python/")

import numpy as np
import pandas as pd
import anndata as ann
import scanpy as sc

from utils import diffexp, dge_top_n

def main(h5ad_loc, save_loc, dataset_name, rep):
    # Load h5ad file
    adata = sc.read_h5ad(h5ad_loc)

    # Extract summary statistics from h5ad file
    num_batches_ds = adata.uns["downsampling_stats"]["num_batches"]
    num_celltypes_ds = adata.uns["downsampling_stats"]["num_celltypes_downsampled"]
    prop_ds = adata.uns["downsampling_stats"]["proportion_downsampled"]

    # Subset adatas based on method for integration and store lognorm counts in raw
    # attribute for diffexp testing
    methods = ["harmony", "scvi", "scanorama", "seurat", "liger", "bbknn"]
    method_adatas = []
    for method in methods:
        adata_copy = adata.copy()
        adata_subset = adata_copy[adata_copy.obs["integration_method"] == method]
        adata_subset.X = adata_subset.layers["raw"] # Unlogged, unnorm counts
        sc.pp.normalize_total(
            adata_subset,
            target_sum = 1e4
        )
        sc.pp.log1p(adata_subset)
        adata_subset.raw = adata_subset # Freeze for DGE test
        method_adatas.append(adata_subset)

    # Extract list of all DGEs for all leiden clusters in each method
    method_dge_dfs = []
    for adata_method_subset in method_adatas:
        adata_method_subset = diffexp(
            adata_method_subset,
            groupby = "leiden",
            use_raw = True,
            method = "wilcoxon"
        )
        dge_results = dge_top_n(
            adata_method_subset,
            n = len(adata.var),
            obs_group = "leiden"
        )
        method_dge_dfs.append(dge_results)

    # For each method, compute the ranking metrics for all genes in the dataset
    all_genes = np.sort(adata.var.index.values)
    all_genes_tiled = np.tile(all_genes, (len(methods)))
    methods_repeat = np.repeat(methods, len(all_genes))
    gene_max_imp_per_method = []
    gene_min_imp_per_method = []
    for method_dge_df in method_dge_dfs:
        method_clusters = np.unique(method_dge_df["Cluster"].__array__())
        cluster_ranks = []
        for cluster in method_clusters:
            cluster_sub = method_dge_df[method_dge_df["Cluster"] == cluster]
            gene_ranks_sorted = np.argsort(cluster_sub.iloc[:, 1].__array__())
            cluster_ranks.append(gene_ranks_sorted)
        cluster_ranks_stack = np.stack(cluster_ranks, axis = 0)
        # Min for max because lowest number for ranking corresponds to highest importance
        gene_max_imp_per_method.append(
            np.min(cluster_ranks_stack, axis = 0)
        )
        gene_min_imp_per_method.append(
            np.max(cluster_ranks_stack, axis = 0)
        )

    # Concatenate max and min ranks for each method
    gene_max_imp_per_method_concat = np.concatenate(gene_max_imp_per_method)
    gene_min_imp_per_method_concat = np.concatenate(gene_min_imp_per_method)

    # Create summary df of all genes and their ranking metrics
    dge_ranking_summary_df = pd.DataFrame({
        "Dataset": dataset_name,
        "Number of batches downsampled": num_batches_ds,
        "Number of celltypes downsampled": num_celltypes_ds,
        "Proportion downsampled": prop_ds,
        "Replicate": rep,
        "Method": methods_repeat,
        "Gene": all_genes_tiled,
        "Max rank": gene_max_imp_per_method_concat,
        "Min rank": gene_min_imp_per_method_concat
    })
    dge_ranking_summary_df.to_csv(save_loc, sep = "\t", index = False)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description = "Input and output files for dge ranking summary"
    )
    parser.add_argument(
        "--infile",
        type = str,
        help = "Path of integrated h5ad file"
    )
    parser.add_argument(
        "--outfile",
        type = str,
        help = "Filepath for saving dge ranking statistics of integrated h5ad file"
    )
    parser.add_argument(
        "--dataset",
        type = str,
        help = "Name of dataset"
    )
    parser.add_argument(
        "--rep",
        type = int,
        help = "Repetition number"
    )
    args = parser.parse_args()
    main(
        h5ad_loc = args.infile,
        save_loc = args.outfile,
        dataset_name = args.dataset,
        rep = args.rep
    )


from collections import defaultdict
import os
import re

DIRECTORY = 'леммы'

def get_lemmas(text):
    return [token.split(':')[0] for token in text.split()]

def get_inverted_index():
    term_documents_dict = defaultdict(list)
    for root, dirs, files in os.walk(DIRECTORY):
        print(files)
        for file in files:
            if file.lower().endswith('.txt') and file.lower().startswith('lemmas'):
                idx = re.search('\d+', file).group(0)
                with open(os.path.join(root, file), encoding='utf-8') as f:
                    lemmas = get_lemmas(f.read())
                for lemma in lemmas:
                    term_documents_dict[lemma].append(idx)
    return term_documents_dict

if __name__ == '__main__':
    inverted_index = get_inverted_index()
    with open('inverted_index.txt', 'w', encoding='utf-8') as f:
        for term, docs in inverted_index.items():
            f.write(f"{term} {' '.join(map(str, docs))}\n")
    inverted_index_info = [{'count': len(docs), 'inverted_array': docs, 'word': term} for term, docs in inverted_index.items()]
    with open('inverted_index_2.txt', 'w', encoding='utf-8') as f:
        for term_info in inverted_index_info:
            f.write(str(term_info) + '\n')


#!/usr/bin/env python3

"""
Perform a survival analysis on the Magma time-to-bug results produced by the
exp2json.py script.

Results are output to CSV.

Author: Adrian Herrera
"""

from argparse import ArgumentParser, Namespace
from collections import defaultdict
from itertools import product
from math import sqrt
from pathlib import Path
from typing import List, Tuple
import json
import warnings

from lifelines import KaplanMeierFitter
from lifelines.utils import restricted_mean_survival_time as rmst
import pandas as pd

METRICS = ('reached', 'triggered')
ddr = lambda: defaultdict(ddr)

def parse_args() -> Namespace:
    """Parse command-line arguments."""
    parser = ArgumentParser(description='Magma survival analysis')
    parser.add_argument('-n', '--num-trials', type=int, required=True,
                        help='Number of trials')
    parser.add_argument('-t', '--trial-length', type=int, required=True,
                        help='Length of an individual trial (in seconds)')
    parser.add_argument('-r', '--raw-data', action='store_true',
                        help='Store the raw run data')
    parser.add_argument('json', type=Path,
                        help='Magma-generated JSON file (containing bug data)')
    return parser.parse_args()

def get_time_to_bug(data: dict, num_trials: int) -> dict:
    """Get time-to-bug data from Magma JSON dictionary."""
    for fuzzer, f_data in data.items():
        for target, t_data in f_data.items():
            for program, p_data in t_data.items():
                bugs = ddr()
                for run, r_data in p_data.items():
                    for metric, m_data in r_data.items():
                        for bug, time in m_data.items():
                            if metric not in bugs[bug]:
                                bugs[bug][metric] = [None] * num_trials
                            bugs[bug][metric][int(run)] = time
                for bug, b_data in bugs.items():
                    yield dict(
                        target=target,
                        program=program,
                        fuzzer=fuzzer,
                        bug=bug,
                        **b_data,
                    )

def calc_survival(data: List[int], trial_len: int) -> Tuple[float, float]:
    """Do the survival analysis."""
    df = pd.DataFrame(data)
    T = df.fillna(trial_len)
    E = df.notnull()

    kmf = KaplanMeierFitter()
    kmf.fit(T, E)

    # Compute the restricted mean survival time and 95% confidence interval
    surv_time_mean, surv_time_var = rmst(kmf, t=trial_len, return_variance=True)
    surv_time_var = abs(surv_time_var)
    surv_time_ci = 1.96 * (sqrt(surv_time_var) /
                           sqrt(len(kmf.survival_function_)))

    return surv_time_mean, surv_time_ci

def main():
    """The main function."""
    args = parse_args()
    num_trials = args.num_trials

    # Ignore warnings
    warnings.simplefilter('ignore')

    surv_data = dict(target=[],
                         program=[],
                         bug=[],
                         fuzzer=[],
                         survival_time_reached=[],
                         survival_ci_reached=[],
                         survival_time_triggered=[],
                         survival_ci_triggered=[])

    # Create fields for storing the raw times (if required)
    if args.raw_data:
        for run in range(0, num_trials):
            surv_data[f'reached_{run}'] = []
            surv_data[f'triggered_{run}'] = []

    # Read Magma JSON data
    with args.json.open() as inf:
        json_data = json.load(inf).get('results', {})

    # Do survival analysis on the time-to-bug results
    for ttb in get_time_to_bug(json_data, num_trials):
        # Save table data
        surv_data['target'].append(ttb['target'])
        surv_data['program'].append(ttb['program'])
        surv_data['bug'].append(ttb['bug'])
        surv_data['fuzzer'].append(ttb['fuzzer'])

        # Save raw times (if required)
        if args.raw_data:
            for metric, run in product(METRICS, range(0, num_trials)):
                time = ttb.get(metric, [None] * num_trials)[run]
                surv_data[f'{metric}_{run}'].append(time)

        # Do survival analysis
        for metric in METRICS:
            if metric not in ttb:
                surv_time_mean = None
                surv_time_ci = None
            else:
                surv_time_mean, surv_time_ci = calc_survival(ttb[metric],
                                                             args.trial_length)

            surv_data[f'survival_time_{metric}'].append(surv_time_mean)
            surv_data[f'survival_ci_{metric}'].append(surv_time_ci)

    # Write to CSV
    order = ['bug', 'program', 'target', 'fuzzer']
    print(pd.DataFrame.from_dict(surv_data).sort_values(by=order).to_csv(index=False))

if __name__ == '__main__':
    main()


# -*- coding: utf-8 -*-
"""
Created on Mon Feb 17 11:48:56 2020

@author: mgutierrez
"""
import datetime as dt

from app import Config
from app.tables_test import Position

def calculate_performance_user(user, positions, dti_positions):
    """ Calculate KPIs user. """
    # get now
    now = dt.datetime.now()
    # get current day
    today = now.strftime("%Y%m%d")
    # get current week
    thisweek = now.strftime("%V")
    # get this month positions
    thismonth = now.strftime("%m")
    # get this year positions
    thisyear = now.strftime("%Y")
    # init rois
    dayly_roi = 0.0
    weekly_roi = 0.0
    monthly_roi = 0.0
    yearly_roi = 0.0
    total_roi = 0.0
    # init returns
    dayly_return = 0.0
    weekly_return = 0.0
    monthly_return = 0.0
    yearly_return = 0.0
    total_return = 0.0
    # loop over positions
    for i in range(len(dti_positions)):
        if positions[i].groiist==0.0 and positions[i].roiist==0.0:
            roi_i = positions[i].roisoll
        else:
            roi_i = positions[i].roiist
        lots_i = user.positionsplits[i].userlots
        if dti_positions[i].strftime("%Y%m%d")==today:
            dayly_roi += roi_i
            dayly_return += lots_i*Config.LOT*roi_i/100
        if dti_positions[i].strftime("%V")==thisweek:
            weekly_roi += roi_i
            weekly_return += lots_i*Config.LOT*roi_i/100
        if dti_positions[i].strftime("%m")==thismonth:
            monthly_roi += roi_i
            monthly_return += lots_i*Config.LOT*roi_i/100
        if dti_positions[i].strftime("%Y")==thisyear:
            yearly_roi += roi_i
            yearly_return += lots_i*Config.LOT*roi_i/100
        total_roi += roi_i
        total_return += lots_i*Config.LOT*roi_i/100
    # build performance dictionary
    performance = {"dayly_roi":dayly_roi,
                   "weekly_roi":weekly_roi,
                   "monthly_roi":monthly_roi,
                   "yearly_roi":yearly_roi,
                   "total_roi":total_roi,

                   "dayly_return":dayly_return,
                   "weekly_return":weekly_return,
                   "monthly_return":monthly_return,
                   "yearly_return":yearly_return,
                   "total_return":total_return}

    return performance

def get_idx_same_datetime(datetimes, value, code):
    """ Get positions sharing same datetime value """
    idxs = [i for i in range(len(datetimes)) if datetimes[i].strftime(code)==value]
    return idxs

def get_positions_dti(positions):
    """ Get DTi of all positions """
    dts = []
    for position in positions:
        if type(position.dtiist)==str and len(position.dtiist)>0:
            dts.append(dt.datetime.strptime(position.dtiist,'%Y.%m.%d %H:%M:%S'))
        elif type(position.dtisoll)==str and len(position.dtisoll)>0:
            dts.append(dt.datetime.strptime(position.dtisoll,'%Y.%m.%d %H:%M:%S'))
        else:
            dts.append(dt.datetime.strptime('2018.01.01 23:59:59','%Y.%m.%d %H:%M:%S'))
    return dts

def get_positions_dto(positions):
    """ Get DTo of all positions """
    dts = []
    for position in positions:
        if type(position.dtoist)==str and len(position.dtoist)>0:
            dts.append(dt.datetime.strptime(position.dtoist,'%Y.%m.%d %H:%M:%S'))
        elif type(position.dtosoll)==str and len(position.dtosoll)>0:
            dts.append(dt.datetime.strptime(position.dtosoll,'%Y.%m.%d %H:%M:%S'))
        else:
            dts.append(dt.datetime.strptime('2018.01.01 23:59:59','%Y.%m.%d %H:%M:%S'))
    return dts

def get_positions_groi(positions):
    """ Get DTi of all positions """
    ret = []
    for position in positions:
        if position.groiist==0.0 and position.roiist==0.0:
            ret.append(position.groisoll)
        else:
            ret.append(position.groiist)
    return ret

def get_positions_roi(positions):
    """ Get DTi of all positions """
    ret = []
    for position in positions:
        if position.groiist==0.0 and position.roiist==0.0:
            ret.append(position.roisoll)
        else:
            ret.append(position.roiist)
    return ret

def get_positions_from_splits(user):
    """ Get positions from user splits """
    all_positions = [Position.query.filter_by(id=split.position_id).first() for split in user.positionsplits]
    return  all_positions

def filter_positions_date(positions, dtis, start_date=None, end_date=None):
    """ Filter positions with start and end dates """
    if start_date and end_date:
        idx_pos = []
        for i, dti in enumerate(dtis):
            if dti-start_date>=dt.timedelta(0) and end_date-dti>=dt.timedelta(0):
                idx_pos.append(i)
    else:
        idx_pos = [i for i, _ in enumerate(dtis)]
    return idx_pos

def sort_positions(dti_positions, reverse=True):
    """ Sort positions from older to newer """
    return [i[0] for i in sorted(enumerate(dti_positions), key=lambda x:x[1], reverse=reverse)]


import numpy as np
import cv2
from collections import deque

# Detects red color
# See below for creating modifiable values
#lower_red = np.array([161, 155, 84])
#upper_red = np.array([179, 255, 255])

# Empty function to make createTrackbar valid
def empty(x):
    pass

# Create a window to set the values
cv2.namedWindow("Tracker Color")
cv2.createTrackbar("Lower Hue", "Tracker Color", 0, 255, empty)
cv2.createTrackbar("Lower Sat.", "Tracker Color", 0, 255, empty)
cv2.createTrackbar("Lower Value", "Tracker Color", 0, 255, empty)
cv2.createTrackbar("Upper Hue", "Tracker Color", 255, 255, empty)
cv2.createTrackbar("Upper Sat.", "Tracker Color", 255, 255, empty)
cv2.createTrackbar("Upper Value", "Tracker Color", 255, 255, empty)

bpoints = [deque(maxlen=1024)]
gpoints = [deque(maxlen=1024)]
rpoints = [deque(maxlen=1024)]
ypoints = [deque(maxlen=1024)]
bindex, gindex, rindex, yindex = 0, 0, 0, 0

colors = [(255,0,0), (0,255,0), (0,0,255), (0,255,255)]

color_index = 0

# Create the window to paint on
paintWindow = np.zeros((471, 636, 3)) + 255
paintWindow = cv2.rectangle(paintWindow, (40,1), (140,65), (0,0,0), 2)
paintWindow = cv2.rectangle(paintWindow, (160,1), (255,65), colors[0], -1)
paintWindow = cv2.rectangle(paintWindow, (275,1), (370,65), colors[1], -1)
paintWindow = cv2.rectangle(paintWindow, (390,1), (485,65), colors[2], -1)
paintWindow = cv2.rectangle(paintWindow, (505,1), (600,65), colors[3], -1)

# These have set values on the paintWindow screen
# This is so it's easy to determine whether the tracker is in that certain area
cv2.putText(paintWindow, "CLEAR ALL", (49, 33),
    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,0), 2,
    cv2.LINE_AA)
cv2.putText(paintWindow, "BLUE", (185, 33),
    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 2,
    cv2.LINE_AA)
cv2.putText(paintWindow, "GREEN", (298, 33),
    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 2,
    cv2.LINE_AA)
cv2.putText(paintWindow, "RED", (420, 33),
    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 2,
    cv2.LINE_AA)
cv2.putText(paintWindow, "YELLOW", (520, 33),
    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (150,150,150), 2,
    cv2.LINE_AA)

cv2.namedWindow('Paint', cv2.WINDOW_AUTOSIZE)

# Capturing video
camera = cv2.VideoCapture(0)

while True:
    (grabbed, frame) = camera.read()
    frame = cv2.flip(frame, 1)
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    if not grabbed:
        break

    l_h = cv2.getTrackbarPos("Lower Hue", "Tracker Color")
    l_s = cv2.getTrackbarPos("Lower Sat.", "Tracker Color")
    l_v = cv2.getTrackbarPos("Lower Value", "Tracker Color")
    u_h = cv2.getTrackbarPos("Upper Hue", "Tracker Color")
    u_s = cv2.getTrackbarPos("Upper Sat.", "Tracker Color")
    u_v = cv2.getTrackbarPos("Upper Value", "Tracker Color")

    lower_colors = np.array([l_h,l_s,l_v])
    upper_colors = np.array([u_h,u_s,u_v])

    frame = cv2.rectangle(frame, (40,1), (140,65), (122,122,122), -1)
    frame = cv2.rectangle(frame, (160,1), (255,65), colors[0], -1)
    frame = cv2.rectangle(frame, (275,1), (370,65), colors[1], -1)
    frame = cv2.rectangle(frame, (390,1), (485,65), colors[2], -1)
    frame = cv2.rectangle(frame, (505,1), (600,65), colors[3], -1)
    cv2.putText(frame, "CLEAR ALL", (49, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA)
    cv2.putText(frame, "BLUE", (185, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA)
    cv2.putText(frame, "GREEN", (298, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA)
    cv2.putText(frame, "RED", (420, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA)
    cv2.putText(frame, "YELLOW", (520, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (150,150,150), 2, cv2.LINE_AA)

    # Preprocess input
    kernel = np.ones((5,5), np.uint8)
    Mask = cv2.inRange(hsv, lower_colors, upper_colors)
    Mask = cv2.erode(Mask, kernel, iterations=2)
    Mask = cv2.morphologyEx(Mask, cv2.MORPH_OPEN, kernel)
    Mask = cv2.dilate(Mask, kernel, iterations=1)

    (cnts, _) = cv2.findContours(Mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    center = None

    if len(cnts) > 0:
        cnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
        ((x,y), radius) = cv2.minEnclosingCircle(cnt)
        cv2.circle(frame, (int(x), int(y)), int(radius), (0,255,255), 2)
        M = cv2.moments(cnt)
        center = (int(M['m10']/M['m00']), int(M['m01'] / M['m00']))

        if center[1] <= 65:
            if 40 <= center[0] <= 140:
                bpoints = [deque(maxlen=512)]
                gpoints = [deque(maxlen=512)]
                rpoints = [deque(maxlen=512)]
                ypoints = [deque(maxlen=512)]
                bindex, gindex, rindex, yindex = 0, 0, 0, 0
                paintWindow[67:,:,:] = 255
            elif 160 <= center[0] <= 255:
                color_index = 0
            elif 275 <= center[0] <= 370:
                color_index = 1
            elif 390 <= center[0] <= 485:
                color_index = 2
            elif 505 <= center[0] <= 600:
                color_index = 3
        else:
            if color_index == 0:
                bpoints[bindex].appendleft(center)
            elif color_index == 1:
                gpoints[gindex].appendleft(center)
            elif color_index == 2:
                rpoints[rindex].appendleft(center)
            elif color_index == 3:
                ypoints[yindex].appendleft(center)
    else:
        bpoints.append(deque(maxlen=512))
        bindex += 1
        gpoints.append(deque(maxlen=512))
        gindex += 1
        rpoints.append(deque(maxlen=512))
        rindex += 1
        ypoints.append(deque(maxlen=512))
        yindex += 1

    points = [bpoints, gpoints, rpoints, ypoints]
    for i in range(len(points)):
        for j in range(len(points[i])):
            for k in range(1, len(points[i][j])):
                if points[i][j][k-1] is None or points[i][j][k] is None:
                    continue
                cv2.line(frame, points[i][j][k-1], points[i][j][k], colors[i], 2)
                cv2.line(paintWindow, points[i][j][k-1], points[i][j][k], colors[i], 2)

    # Display output
    cv2.imshow("Web_cam_video", frame)
    cv2.imshow("Paint", paintWindow)
    cv2.imshow("Mask", Mask)

    if cv2.waitKey(1) & 0xFF ==ord("q"):
        break

camera.release()
cv2.destroyAllWindows()


import os
from abc import abstractmethod

from matplotlib import pyplot as plt
import numpy as np
import torch
from torch.utils.tensorboard import SummaryWriter
from tqdm import tqdm

from config import Configuration, Mode
from dataloaders.data_loader import get_data_loaders
from losses.loss import ArteryVeinLoss, ContrastiveLoss, SegmentationPointLoss
from utils.timer import Timer
from utils.visualization import visualize_features, visualize_closest_points

class GeneralTrainer:

    PIXEL_LIMIT = 10
    NUM_NEG_PAIRS = 128
    NUM_NEG_INDICES_FOR_LOSS = 10

    def __init__(self, cfg: Configuration):
        self._cfg = cfg
        self._writer = SummaryWriter()
        self._device = "cuda" if self._cfg.USE_CUDA else "cpu"
        self._model = self._cfg.MODEL(self._cfg).to(self._device)
        self._optimizer = torch.optim.SGD(
            self._model.parameters(), momentum=self._cfg.MOMENTUM, lr=self._cfg.INITIAL_LR,
            weight_decay=self._cfg.WEIGHT_DECAY, nesterov=True)
        self._lr_scheduler = torch.optim.lr_scheduler.ExponentialLR(
            optimizer=self._optimizer, gamma=self._cfg.LR_DECAY)
        self._timer = Timer()
        self._data_loaders = get_data_loaders(self._cfg.DATASET, cfg, modes=self._cfg.MODES)
        self._loss = ContrastiveLoss(cfg)
        self.load_checkpoint(os.path.join(self._cfg.CHECKPOINT_PATH, "skuska.pth"))

    def train(self):
        self._model = self._model.cuda() if self._cfg.USE_CUDA else self._model
        try:
            self._model.load_state_dict(torch.load("../skuska.pth", map_location="cuda")["model"])
            self._optimizer.load_state_dict(torch.load("../skuska.pth", map_location="cuda")["optimizer"])
        except FileNotFoundError:
            print("File not found!")
        print(self._model)
        for epoch in tqdm(range(self._cfg.EPOCHS)):
            self._timer.tic()
            self.train_single_epoch(epoch)
            self._lr_scheduler.step(epoch=epoch)
            self._timer.toc()

            if epoch % 10 == 0:
                self.save_checkpoint("skuska.pth")

    def train_single_epoch(self, epoch):
        self._model.train()
        assert Mode.TRAIN in self._data_loaders.keys()
        for idx, inputs in tqdm(enumerate(self._data_loaders[Mode.TRAIN])):
            idx_total = idx + len(self._data_loaders[Mode.TRAIN]) * epoch
            inputs = [inp.cuda() if self._cfg.USE_CUDA else inp for inp in inputs]
            self._optimizer.zero_grad()
            outputs = self.forward(inputs)
            loss = self.calculate_loss(outputs, inputs)
            # self._writer.add_scalar("Loss/Positive", positive_loss.item(), idx_total)
            # self._writer.add_scalar("Loss/Negative", negative_loss.item(), idx_total)
            loss.backward()
            print("Loss: {}".format(loss.item()))

            self._optimizer.step()
            self.visualize(inputs, outputs, loss, idx, idx_total, epoch)

    @abstractmethod
    def forward(self, *args, **kwargs):
        pass

    @abstractmethod
    def calculate_loss(self, *args, **kwargs):
        pass

    @abstractmethod
    def visualize(self, *args, **kwargs):
        pass

    def validate(self):
        pass

    def load_checkpoint(self, path):
        if os.path.exists(path):
            state_dict = torch.load(path)
            if "optimizer" in state_dict:
                self._optimizer.load_state_dict(state_dict["optimizer"])
            self._model.load_state_dict(state_dict["model"])
        self._model = self._model.cuda() if self._cfg.USE_CUDA else self._model

    def save_checkpoint(self, file_name):
        state_dict = {
            "model": self._model.state_dict(),
            "optimizer": self._optimizer.state_dict(),
            "learning_rate": self._lr_scheduler.state_dict()
        }
        if not os.path.exists(self._cfg.CHECKPOINT_PATH):
            os.makedirs(self._cfg.CHECKPOINT_PATH)
        torch.save(state_dict, os.path.join(self._cfg.CHECKPOINT_PATH, file_name))

class RegistrationTrainer(GeneralTrainer):

    def forward(self, inputs):
        output_features_1 = self._model(inputs[0])
        output_features_2 = self._model(inputs[1])
        return [output_features_1, output_features_2]

    def calculate_loss(self, outputs, inputs):
        return self._loss((outputs[0], outputs[1], inputs[2]))

    def visualize(self, inputs, outputs, loss, idx, idx_total, epoch=None):
        self._writer.add_scalar("Mean/feats1", outputs[0].detach().cpu().mean(), idx_total)
        self._writer.add_scalar("Mean/feats2", outputs[1].detach().cpu().mean(), idx_total)

        self._writer.add_scalar("Loss/Total", loss.item(), idx_total)
        if not (idx % self._cfg.TRAIN_VISUALIZATION_FREQUENCY):
            feats1_reduced, feats2_reduced = visualize_features([
                outputs[0].squeeze().permute([1, 2, 0]).detach().cpu().numpy(),
                outputs[1].squeeze().permute([1, 2, 0]).detach().cpu().numpy()])
            self._writer.add_image("Features1", torch.from_numpy(feats1_reduced).permute([2, 0, 1]), idx)
            self._writer.add_image("Features2", torch.from_numpy(feats2_reduced).permute([2, 0, 1]), idx)
            self._writer.add_image("Image1", inputs[0].squeeze(), idx)
            self._writer.add_image("Image2", inputs[1].squeeze(), idx)
            self._writer.add_figure("Matches", visualize_closest_points(
                image1=inputs[0].squeeze().permute([1, 2, 0]).detach().cpu().numpy(),
                image2=inputs[1].squeeze().permute([1, 2, 0]).detach().cpu().numpy(),
                features1=outputs[0].squeeze().permute([1, 2, 0]).detach().cpu().numpy(),
                features2=outputs[1].squeeze().permute([1, 2, 0]).detach().cpu().numpy(),
                number_of_correspondences=10), idx)

class VesselTrainer(GeneralTrainer):

    def forward(self, inputs):
        image, mask = inputs
        return self._model(image)

    def calculate_loss(self, outputs, inputs):
        return self._loss(outputs, inputs[1])

    def visualize(self, inputs, outputs, loss, idx, idx_total, *args):
        self._writer.add_scalar("Mean/feats1", outputs[0].detach().cpu().mean(), idx_total)

        self._writer.add_scalar("Loss/Total", loss.item(), idx_total)
        if not (idx % self._cfg.TRAIN_VISUALIZATION_FREQUENCY):
            feats = outputs[0].squeeze().permute([1, 2, 0]).detach().cpu().numpy()
            # TODO: refactor to a method taking an iterable!
            feats1_reduced = visualize_features([feats])
            self._writer.add_image("Features", torch.from_numpy(feats1_reduced[0]).permute([2, 0, 1]), idx)
            self._writer.add_image("Image1", inputs[0].squeeze(), idx)

class VeinArteryTrainer(GeneralTrainer):

    def __init__(self, config):
        super().__init__(config)
        self._loss = ArteryVeinLoss(config)

    def forward(self, inputs):
        image, mask = inputs
        return self._model(image)

    def calculate_loss(self, outputs, inputs):
        image, mask = inputs
        return self._loss(outputs, mask)

    def visualize(self, inputs, outputs, loss, idx, idx_total, epoch=0):
        self._writer.add_scalar("Mean/feats1", outputs[0].detach().cpu().mean(), idx_total)
        self._writer.add_scalar("Loss/Total", loss.item(), idx_total)
        mask = inputs[1].squeeze()
        mask_colored = torch.zeros(3, *mask.shape, dtype=torch.float)
        mask_colored[0, mask == 1] = 1.
        mask_colored[1, mask == 2] = 1.
        mask_colored[2, mask == 3] = 1.
        self._writer.add_image(f"Mask/{idx}", mask_colored, epoch)
        self._writer.add_image(f"Original_image/{idx}", inputs[0].squeeze(), epoch)

        feats = outputs.squeeze().permute([1, 2, 0]).detach().cpu().numpy()
        feats_pca = visualize_features([feats])[0]
        self._writer.add_image(f"Features/{idx}", torch.from_numpy(feats_pca).permute([2, 0, 1]), epoch)
        feats_pca_mask = visualize_features([feats[mask.cpu().numpy() > 0.5, :]])[0]
        output_mask = np.zeros_like(feats_pca)
        output_mask[mask.cpu().numpy() > 0.5, :] = np.squeeze(feats_pca_mask)
        self._writer.add_image(f"Features_masked/{idx}", torch.from_numpy(output_mask).permute([2, 0, 1]), epoch)

class SegmentationTrainer(VeinArteryTrainer):
    def __init__(self, config):
        super().__init__(config)
        self._seg_loss = torch.nn.CrossEntropyLoss(weight=torch.tensor([0.1, 1.]).to(self._device))
        self._loss = ArteryVeinLoss(self._cfg)
        self._seg_point_loss = SegmentationPointLoss(self._cfg)
        # self._loss = HardestContrastiveLoss(2)

    def calculate_loss(self, outputs, inputs):
        feats, segs = outputs
        image, mask = inputs
        label = (mask > 0).long()
        segmentation_loss = self._seg_loss(segs, label)
        feature_loss = self._loss(feats, mask)
        segmentation_point_loss = self._seg_point_loss(feats, label, segs)
        print(segmentation_point_loss)
        return segmentation_loss + feature_loss + segmentation_point_loss

    def visualize(self, inputs, outputs, loss, idx, idx_total, epoch=0):
        feats, segs = outputs
        super().visualize(inputs, feats, loss, idx, idx_total, epoch)
        if idx_total % 90 == 0:
            print(idx_total)
        output_mask = torch.argmax(segs.squeeze(), 0).unsqueeze(0)
        output_mask = torch.cat([output_mask, torch.zeros_like(output_mask), torch.zeros_like(output_mask)], 0)
        self._writer.add_image(f"Segmentation/{idx}", output_mask, epoch)
        fig = plt.figure(1, figsize=(20, 10), dpi=100)
        plt.subplot(1, 2, 1)
        plt.imshow(segs.squeeze()[0, :, :].cpu().detach().numpy())
        plt.subplot(1, 2, 2)
        plt.imshow(segs.squeeze()[1, :, :].cpu().detach().numpy())
        self._writer.add_figure(f"PredictionMaps/{idx}", fig, epoch)


import torch
import torch.nn as nn
import torch.nn.functional as F

class DoubleConv(nn.Module):
    """(convolution => [BN] => ReLU) * 2"""

    def __init__(self, in_channels, out_channels, mid_channels=None):
        super().__init__()
        if not mid_channels:
            mid_channels = out_channels
        self.double_conv = nn.Sequential(
            nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False),
            nn.InstanceNorm2d(mid_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False),
            nn.InstanceNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        return self.double_conv(x)

class Down(nn.Module):
    """Downscaling with maxpool then double conv"""

    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.maxpool_conv = nn.Sequential(
            nn.MaxPool2d(2),
            DoubleConv(in_channels, out_channels)
        )

    def forward(self, x):
        return self.maxpool_conv(x)

class Up(nn.Module):
    """Upscaling then double conv"""

    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
        self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)

    def forward(self, x1, x2=None):
        x1 = self.up(x1)
        if x2 is not None:
            diffY = x2.size()[2] - x1.size()[2]
            diffX = x2.size()[3] - x1.size()[3]
            x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2,
                            diffY // 2, diffY - diffY // 2])
            x = torch.cat([x2, x1], dim=1)
        else:
            x = x1
        return self.conv(x)

class OutConv(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(OutConv, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)
        self.act_fn = nn.Sigmoid()

    def forward(self, x):
        y = self.act_fn(self.conv(x))
        return y

class Upsampler(nn.Module):
    def __init__(self, input_dim, output_dim, network_capacity=32):
        super(Upsampler, self).__init__()

        dims = [network_capacity * s for s in [4, 8, 4, 2, 1]]

        self.inc = DoubleConv(input_dim, dims[0])
        self.down1 = Down(dims[0], dims[1])
        self.up1 = Up(dims[0] + dims[1], dims[2])
        self.up2 = Up(dims[2], dims[3])
        self.up3 = Up(dims[3], dims[4])
        self.outc = OutConv(dims[4], output_dim)

    def forward(self, x):
        x1 = self.inc(x) # res == 128
        x2 = self.down1(x1) # res == 64
        y = self.up1(x2, x1) # res == 128
        y = self.up2(y) # res == 256
        y = self.up3(y) # res == 512
        y = self.outc(y) # res == 512
        return y