#-------------------------------------#
#               VSyntha               #
#   Created by Daniel Lazorcak 2023   #
#=====================================#

#   Import PyQt5 - GUI

from PyQt5 import QtCore as qc
from PyQt5 import QtGui as qg
from PyQt5 import QtWidgets as qtw
from PyQt5.QtCore import pyqtSignal, QThread, pyqtSlot, Qt

#   Imoport OpenCV - image processing

import cv2
import numpy as np
from pyo import *

#   Import z .py súborov

from m_1 import *  # Import funkcií a tried pre metódu 1

#   Trieda typu QThread pre prehrávanie videa a audia
#   Zamedzenie zamrznutia programu pri používaní


class MethodThread(QThread):
    # vytvorenie pyqt sginálu
    change_pixmap = pyqtSignal(np.ndarray)
    # Nastavenie inicializačnej funkcie triedy -- vstupné argumenty sú
    #   fname - cesta k videosúboru
    #   pbar - objekt Progress-bar z GUI
    #   masterfader - objekt slideru slúžiaci ako fader
    #   tonality_checkbox - objekt checkbox pre kontrolu tonality
    #   intensity_dial - objekt ovládajúci intenzitu filtrácie
    #   mod_checkbox - objekt checkbox pre kontrolu tonálnej modluácie

    def __init__(self, fname, pbar, masterfader, tonality_checkbox, intensity_dial, mod_checkbox):
        super().__init__()
        #   Nastavenie premennej run flag - ovládanie behu prehrávania - po inicializácií sa automaticky začne prehrávať
        self._run_flag = True
        self.file = fname
        self.bar = pbar
        self.mf = masterfader
        self.tonality = tonality_checkbox
        self.intensity_d = intensity_dial
        self.modcheck = mod_checkbox
        self.logarr =[]
    #   Funkcia run() triedy ovláda spustenie prehrávania videa a spustenie zvukovej syntézy
    def run(self):
        # načítanie video súbora do premennej capture, následný výpočet času medzi snímkami
        # pomocou FPS do premennej fps_timing
        capture = cv2.VideoCapture(self.file)
        length = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
        fps_timing = 1/capture.get(cv2.CAP_PROP_FPS)
        # inicializácia PYO servera a MIDI vstupov
        s = Server()
        s.setMidiInputDevice(99)
        s.boot()

        # nastavenie počtu bodov pre histogram
        bins = 255

        # Nastavenia MIDI
        transpo = Bendin(brange=2, scale=1)
        hfdamp = Midictl(ctlnumber=1, minscale=500, maxscale=10000, init=5000)
        lfofreq = Midictl(ctlnumber=2, minscale=0.1, maxscale=8, init=0.2)
        # inicializácia triedy syntetizátora
        a1 = SynthPlayer()
        # Pridanie reverbu na výstup
        rev = STRev(a1.sig(), inpos=[0.1, 0.9],
                    revtime=2, cutoff=4000, bal=0.15).out()
        # spustenie servera
        s.start()
        #self.logfile = open(self.file+"log.txt","a")
        # Cyklus while - pokiaľ je premenná runflag pravdivá a video sa má prehrávať, tak
        # každú periódu medzi snímkami prebehne vykreslenie videa a výpočet zvukového signálu
        while self._run_flag:
            ret, img = capture.read()
            # ak boli dáta získané, tak sa pokračuje vo výpočte, ak nie, runflag je nastavený na false
            if ret:
                t1_start = time.time() 
                # zmena zobrazeného frameu v prehrávači
                self.change_pixmap.emit(img)
                # zistenie nastavení masterfadera, tonality, intenzity a modulácie
                mf_value = self.mf.value()
                tonal = self.tonality.isChecked()
                intensity = self.intensity_d.value()
                mod = self.modcheck.isChecked()
                # volanie funkcie na výpočet zvukového signálu
                M1comp(img, a1, bins, mf_value, tonal, intensity, mod)
                # výpočet aktuálnej pozície prehrávania videa, zobrazenie na progressbare
                current_frame = capture.get(cv2.CAP_PROP_POS_FRAMES)
                if length != 0:
                    self.bar.setValue(current_frame/length*100)
                    t1_stop = time.time() 
                    dt = t1_stop-t1_start
                    if (fps_timing-dt) > 0:
                        time.sleep(fps_timing-dt)
                    else:
                        pass
            else:
                self._run_flag = False
        # zastavenie PYO servera po ukončení cyklu
        s.stop()

    # Funkcia release() na spustenie zastaveného prehrávania
    def release(self):
        self._run_flag = True
        self.start()

    # Funkcia stop() na zastavenie prehrávania
    def stop(self):
        self._run_flag = False
       # self.logfile.writelines(self.logarr)
        self.wait()

#   Trieda MainWindow - vykresľovanie grafického užívateľského prostredia GUI


class MainWindow(qtw.QWidget):

    #   Inicializácia triedy - nastavenie základných vlastností okna

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Vsyntha")
        self.setFixedSize(1000, 500)
        grid_layout = qtw.QGridLayout()
        self.setLayout(grid_layout)

        # Vytvorenie labelov pre vyberanie videa, nastavenie zarovnania, fontu
        # a následné vykreslenie na grid

        label_selectvideo = qtw.QLabel("Select a video file", self)
        label_selectvideo.setAlignment(qc.Qt.AlignCenter)
        label_selectvideo.setFont(qg.QFont("Helvetica", 20))
        grid_layout.addWidget(label_selectvideo, 1, 0, 1, 2)

        self.image_label = qtw.QLabel(self)
        # zmena veľkosti labelu pre prehrávanie videa
        self.image_label.resize(1000, 500)

        # Label pre vypísanie cesty k vybranému súboru
        label_filepath = qtw.QLabel("", self)
        grid_layout.addWidget(label_filepath, 3, 0)

        # Vytvorenie placeholder labelu pre video
        self.label_video_hold = qtw.QLabel("", self)
        # Vytvorenie pixmapu pre label následné priradenie, zarovnanie a vykreslenie na grid
        g = qg.QPixmap(16*30, 9*30)
        self.label_video_hold.setPixmap(g)
        self.label_video_hold.setAlignment(qc.Qt.AlignCenter)
        grid_layout.addWidget(self.label_video_hold, 4, 0, 4, 2)

        # Vytvorenie progressbaru, nastavenie vychádzajúcej hodnoty a vykreslenie na grid
        pbar = qtw.QProgressBar()
        pbar.setValue(0)
        # grid_layout.addWidget(pbar, 5, 0, 1, 2)

        # Vytvorenie faderu na ovládanie výstupnej hlasitosti, nastavenie vychádzajúcej
        # hodnoty a následne vykreslenie na grid
        masterfader = qtw.QSlider(Qt.Vertical)
        masterfader.setMinimum(10)
        masterfader.setMaximum(100)
        masterfader.setValue(80)
        grid_layout.addWidget(masterfader, 3, 5, 5, 1)

        # Vytvorenie kruhového potenciometru pre ovládanie intenzity ekvalizačných filtrov
        # nastavenie možných hodnôt a vykreslenie na grid
        intensity_dial = qtw.QDial(self)
        intensity_dial.setMinimum(0)
        intensity_dial.setMaximum(6)
        intensity_dial.setValue(1)
        grid_layout.addWidget(intensity_dial, 7, 4)

        # Label s textom pri potenciometry intezity, vykreslenie na grid
        intensity_label = qtw.QLabel("Filter Intensity", self)
        grid_layout.addWidget(intensity_label, 6, 4, 1, 1)

        # vytvorenie tlačidla pre prechádzanie súborov na výber videa, priradenie funkcie
        # button_pressed() po kliknutí, vykreslenie na grid
        button_browse = qtw.QPushButton("Browse", self,
                                        clicked=lambda: button_pressed())
        grid_layout.addWidget(button_browse, 2, 0, 1, 2)

        # Vytvorenie tlačidla pre možnosť použitia webkamery ako vstupného zariadenia, priradenie
        # funkcie button_pressed_webcam(), vykreslenie na grid
        use_video = qtw.QPushButton(
            "Use webcam", self, clicked=lambda: button_pressed_webcam())
        grid_layout.addWidget(use_video, 10, 0, 1, 2)

        # Funkcia button_pressed() je volaná po stlačení tlačidla "Browse", otvorí dialógove okno
        # pre vyhľadanie súbora s videom, následne cestu k súboru vykreslí do labelu filepath a
        # spustí thread pre prehrávanie videa a audia MethodThread
        def button_pressed():
            fname = qtw.QFileDialog.getOpenFileName(self, 'Open file',
                                                    '/Users/daniellazorcak/Documents/DP/App', "Video files (*.mp4 *.mov)")
            if fname != ('', ''):
                label_filepath.setText(fname[0])
                self.thread = MethodThread(
                    fname[0], pbar, masterfader, tonality_checkbox, intensity_dial, mod_checkbox)
                # Pripojenie prvku update_image k change_pixmap
                self.thread.change_pixmap.connect(self.update_image)
                self.thread.start()
        # Funkcia button_pressed_webcam() je volaná po stlačení tlačidla "Use webcam", spustí thread
        # MethodThread pre spustenie videa a audia a nastaví vstupný parameter file na 0 -> v OpenCV
        # to znamená, že bude volaný vstup z prvej zapojenej webkamery
        def button_pressed_webcam():
            webc = 0 
            self.thread = MethodThread(
                webc, pbar, masterfader, tonality_checkbox, intensity_dial, mod_checkbox)
            self.thread.change_pixmap.connect(self.update_image)
            self.thread.start()

        # Vytvorenie tlačidiel pre ovládanie prehrávania videa, napojenie na funkcie triedy
        # MethodThread stop() a release(), vykreslenie na grid
        button_play = qtw.QPushButton(
            "Play", self, clicked=lambda: self.thread.release())
        button_stop = qtw.QPushButton(
            "Stop", self, clicked=lambda: self.thread.stop())
        grid_layout.addWidget(button_play, 9, 0, 1, 1)
        grid_layout.addWidget(button_stop, 9, 1, 1, 1)

        # Vytvorenie labelu pre výber metódy, zarovanie, voľba fotnu, vykreslenie na grid
        label_method_selector = qtw.QLabel(
            "Select the parameters of synthesis", self)
        label_method_selector.setAlignment(qc.Qt.AlignCenter)
        label_method_selector.setFont(qg.QFont("Helvetica", 20))
        label_method_selector.setAlignment(qc.Qt.AlignCenter)
        grid_layout.addWidget(label_method_selector, 1, 3, 2, 2)

        # Vytvorenie checkboxu pre tonalitu/atonalitu, vykreslenie na grid
        tonality_checkbox = qtw.QCheckBox("Tonality", self)
        grid_layout.addWidget(tonality_checkbox, 4, 4)

        # Vytvorenie checkboxu pre moduláciu signálu, vykreslenie na grid
        mod_checkbox = qtw.QCheckBox("Modulation", self)
        grid_layout.addWidget(mod_checkbox, 5, 4)

        # Vykreslenie okna GUI
        self.show()

    # Dekorátor pyqtSlot pripája signál a mení ho z OpenCV na Qt objekt, následne ho vysiela do
    # placeholdra pre video
    @pyqtSlot(np.ndarray)
    def update_image(self, img):
        qtimg = self.convert_cv_qt(img)
        self.label_video_hold.setPixmap(qtimg)

    # Konverzia obrázka z OpenCV formátu na formát PyQt
    def convert_cv_qt(self, img):
        # Konverzia z BGR na RGB
        rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        # Výpočet dimenzií obrázka
        height, width, channel = rgb.shape
        bpl = channel * width
        # Konverzia na PyQt formát a vrátenie Pixmapu z obrázka
        convert_to_PyQt = qg.QImage(
            rgb.data, width, height, bpl, qg.QImage.Format_RGB888)
        p = convert_to_PyQt.scaled(30*16, 30*9, Qt.KeepAspectRatio)
        return qg.QPixmap.fromImage(p)

#   Volanie aplikácie, vykreslenie GUI


if __name__ == "__main__":
    app = qtw.QApplication([])
    # app.setStyleSheet("QMainWindow {background-image: url('bg.png'); background-repeat: no-repeat; background-position: center;} QPushButton { background-color: rgba(128, 128, 128, 0.5); color: white; font-weight: bold; border: 2px solid black; padding: 10px; }")

    mw = MainWindow()
    app.exec_()
