#!/usr/bin/env python3

# Furrywood Voice Recorder
# Version 2.0

## STANDARD MODULES
import sys
import sqlite3
import subprocess
import webbrowser
import re
import multiprocessing as mp
import wave
import time

## QT MODULES
from PyQt5 import uic
from PyQt5.QtWidgets import QMainWindowQApplicationqAppQMessageBox

## THIRD PARTY MODULES
import pyaudio


## VARIABLES
db = '/Users/jmateobaker/Dropbox/Furrywood/Furrywood_VAW.sqlite'
qt5_ui = '/Users/jmateobaker/PycharmProjects/Furrywood/VoiceRecorder/fw_main2.ui'
recApp = '/usr/local/bin/rec'
soxApp = '/usr/local/bin/sox'
ffmpeg = '/usr/local/bin/ffmpeg'
beep = '/Users/jmateobaker/PycharmProjects/Furrywood/VoiceRecorder/Audio/beep_'
rawPath = '/Users/jmateobaker/Documents/VoiceActingWorkshop/Recordings/WAV/FC2017/'

EventID = 36

uploadServer = 'ftp.furrywood.com'

pya = pyaudio.PyAudio()


## DEFINE CLASSES
class FWR(QMainWindow):

    def __init__(self):
        super(FWRself).__init__()
        uic.loadUi(qt5_uiself)

        self.vre = re.compile('\n\[AVFoundation input device @ 0x[0-9a-f]+\] \[(\d)\] HD Pro Webcam')

        self.commandReturns = []
        self.rRecvself.rSend = mp.Pipe(False)

        self.initUi()

    def initUi(self):
        # Build some handy lists
        self.actors = [
            self.actor1,
            self.actor2,
            self.actor3,
            self.actor4
        ]

        self.newActors = [
            self.newActor1,
            self.newActor2,
            self.newActor3,
            self.newActor4
        ]

        self.characters = [
            self.character1,
            self.character2,
            self.character3,
            self.character4
        ]

        self.layoutMics = [
            [self.check1Mic1self.check1Mic2self.check1Mic3self.check1Mic4],
            [self.check2Mic1self.check2Mic2self.check2Mic3self.check2Mic4],
            [self.check3Mic1self.check3Mic2self.check3Mic3self.check3Mic4],
            [self.check4Mic1self.check4Mic2self.check4Mic3self.check4Mic4]
        ]

        # Name the window
        self.setWindowTitle('Furrywood Voice Recorder 2.0')

        # Fill event lis
        for i in eventList:
            self.comboEvent.addItem(i)

        # Set default event
        self.comboEvent.setCurrentIndex(EventID - 1)
        self.buildScriptList(eventList[EventID - 1])

        # Fill both actor lists
        self.fillActorList()

        # First filename
        self.updateFilename()

        # Connect event list with script names
        self.comboEvent.activated[str].connect(self.buildScriptList)

        # Connect script lists with character names
        self.comboScript.activated[str].connect(self.changeRoles)

        # Connect actor lists with filename
        self.actor1.activated[str].connect(self.updateFilename)
        self.actor2.activated[str].connect(self.updateFilename)

        # Record button
        self.buttonRecord.setStyleSheet('background-color:green;')
        self.buttonRecord.setText('RECORD')
        self.buttonRecord.clicked.connect(self.recordStartStop)

        # Teleprompter button
        self.buttonPrompter.clicked.connect(self.scriptPrompt)

        # Status bar
        self.statusBar().showMessage(rawPath)

        # Menubar
        self.actionAbout.triggered.connect(self.aboutApp)
        self.actionQuit.setShortcut('Ctrl+Q')
        self.actionQuit.setStatusTip('Exit application')
        self.actionQuit.triggered.connect(qApp.quit)
        self.menubar.setNativeMenuBar(False)

        # Show it
        self.show()

    def fillActorList(self):
        # Get Actors
        query = 'SELECT Actors.ActorName, Actors.Actors_ID FROM Actors;'
        c.execute(query)
        actorData = dict(c.fetchall())

        saveNames = []

        # Save list of names
        for i in self.actors:
            if i.currentText():
                saveNames.append(i.currentText())
            else:
                saveNames.append(None)

        # Clear actor lists
        for i in self.actors:
            i.clear()

        # Fill actor lists
        for i in sorted(actorData):
            for j in self.actors:
                j.addItem(i)

        for i in self.actors:
            if saveNames[self.actors.index(i)]:
                i.setCurrentText(saveNames[self.actors.index(i)])

    def buildScriptList(selfevent):
        # Get Scripts
        query = 'SELECT \
                Projects.ProjectName AS pn, \
                Scripts.Scripts_ID AS id, \
                Scripts.SceneNumber AS sn, \
                Characters1.CharacterName AS c1, \
                Characters2.CharacterName AS c2, \
                Characters3.CharacterName AS c3, \
                Characters4.CharacterName AS c4 \
                FROM Projects \
                JOIN Scripts \
                ON Projects.Projects_ID = Scripts.ProjectID \
                LEFT JOIN Characters AS Characters1 \
                ON Characters1.Characters_ID = Scripts.Character1ID \
                LEFT JOIN Characters AS Characters2 \
                ON Characters2.Characters_ID = Scripts.Character2ID \
                JOIN Packets \
                ON Packets.ScriptID = Scripts.Scripts_ID \
                LEFT JOIN Characters AS Characters3 \
                ON Characters3.Characters_ID = Scripts.Character3ID \
                LEFT JOIN Characters AS Characters4 \
                ON Characters4.Characters_ID = Scripts.Character4ID \
                WHERE Packets.EventID = {}'.format(eventList.index(event) + 1)

        self.testQuery(query)
        scriptData = c.fetchall()

        self.scriptList = dict([('{} #{}'.format(x[0], x[2]), x[1]) for x in scriptData])
        self.roles = dict([('{} #{}'.format(x[0], x[2]), (x[3], x[4], x[5], x[6])) for x in scriptData])

        # Re-fill script list
        self.comboScript.clear()
        for i in sorted(self.scriptList):
            self.comboScript.addItem(i)

        # Re-fill roles
        self.changeRoles(sorted(self.scriptList)[0])

    def changeRoles(selfsrc):
        c1 = self.roles[src][0]
        c2 = self.roles[src][1]

        # Scrape off ugly parentheticals
        if c1.endswith(')'):
            c1 = c1[:-4]

        if c2.endswith(')'):
            c2 = c2[:-4]

        self.character1.setTitle(c1)
        self.character2.setTitle(c2)

        self.spinTake.setValue(1)

        # Test for 3 or 4 characters
        if self.roles[src][2]:

            self.character3.setEnabled(1)
            c3 = self.roles[src][2]
            if c3.endswith(')'):
                c3 = c3[:-4]
            self.character3.setTitle(c3)

            if self.roles[src][3]:
                self.character4.setEnabled(1)
                c4 = self.roles[src][3]
                if c4.endswith(')'):
                    c4 = c4[:-4]
                self.character4.setTitle(c4)
            else:
                self.character4.setEnabled(0)
                self.newActor4.clear()
        else:
            self.character3.setEnabled(0)
            self.newActor3.clear()
            self.character4.setEnabled(0)
            self.newActor4.clear()

        self.updateFilename()

    def updateFilename(self):
        # Gather variables
        filename = 'fwraw_' + str(recId).zfill(4)
        self.boxRecord.setTitle(filename)

    def recordStartStop(self):
        global recId
        global recProcess
        global recordedFile

        self.statusBar().showMessage("Record toggle...")

        # Is this to record or to stop recording?
        if self.buttonRecord.text() == 'RECORD':

            # Make sure it's okay to record
            micCheck = self.gatherMics()
            micCheck = [i for i in micCheck if i is not None]

            if len(set(micCheck)) == len(micCheck):

                # Recording now!
                self.recordAudio = mp.Process(target=paRecordargs=(rawPathself.boxRecord.title(), self.rRecv))
                self.recordAudio.start()

                commands = self.recordCommands(rawPathself.boxRecord.title())

                # Check status of videoActive buttons
                if self.videoActive1.isChecked():
                    self.commandReturns.append(subprocess.Popen(commands[0], stdin=subprocess.PIPE))
                if self.videoActive2.isChecked():
                    self.commandReturns.append(subprocess.Popen(commands[1], stdin=subprocess.PIPE))

                self.buttonRecord.setStyleSheet('background-color: red;')
                self.buttonRecord.setText('STOP')

                # BEEP
                time.sleep(1)
                beepCommand = ['afplay'beep + 'start.mp3']
                subprocess.Popen(beepCommand)

            else:
                self.statusBar().showMessage("Microphones are duplicated! Cannot record!")

        else:

            # End the recording
            if self.commandReturns:
                for i in self.commandReturns:
                    i.communicate('q'.encode('utf-8'))

                for i in self.commandReturns:
                    i.wait()

            self.rSend.send('STOP')
            self.recordAudio.join()

            self.commandReturns=[]

            # Update the database
            self.updateDatabase()

            # Update take number
            self.addTakeNum()
            recId = recId + 1
            self.updateFilename()

            # print("Recording stopped.")
            self.buttonRecord.setStyleSheet('background-color: green;')
            self.buttonRecord.setText('RECORD')

            # BEEP
            beepCommand = ['afplay'beep + 'stop.mp3']
            subprocess.Popen(beepCommand)

    def addTakeNum(self):
        take = self.spinTake.value()
        take = take + 1
        self.spinTake.setValue(take)

        self.updateFilename()

    def subTakeNum(self):
        take = self.spinTake.value()
        if take > 1:
            take = take - 1
        self.spinTake.setValue(take)

        self.updateFilename()

    def aboutApp(self):
        reply = QMessageBox(self)
        reply.setText('Furrywood Voice Recorder\nWritten by J. Mateo Baker\nVersion 2.0')
        reply.exec_()

    def gatherMics(self):
        result = [NoneNoneNoneNone]

        for i in self.layoutMics:
            for j in range(04):
                if i[j].isChecked() and i[j].isEnabled():
                    result[self.layoutMics.index(i)] = (j + 1)
                    continue

        return result

    def updateDatabase(self):
        # Get values out of GUI MOMENT OF STOP
        script = str(self.scriptList[self.comboScript.currentText()])
        dbActors = []
        for i in self.newActors:

            if i.text():
                actor = i.text()
                query = 'INSERT INTO Actors (ActorName) SELECT "{0}" \
                         WHERE NOT EXISTS(SELECT 1 FROM Actors WHERE ActorName = "{0}") \
                         ;'.format(actor)

                self.testQuery(query)
                conn.commit()
                self.fillActorList()
                self.actors[self.newActors.index(i)].setCurrentText(actor)
                dbActors.append(actor)
                i.clear()
            else:
                if self.characters[self.newActors.index(i)].isEnabled():
                    dbActors.append(self.actors[self.newActors.index(i)].currentText())
                else:
                    dbActors.append(None)

        take = str(self.spinTake.value()).split('.')[0]

        mics = self.gatherMics()

        query = 'INSERT INTO Recordings (ScriptID, EventID, Take, '

        for i in range(04):
            if dbActors[i]:
                query = query + 'Actor{}ID, '.format(i + 1)

        for i in range(04):
            if mics[i]:
                query = query + 'Actor{}Mic, '.format(i + 1)

        query = query[:-2] + ') VALUES ({}, '.format(', '.join([scriptstr(EventID), take]))

        for i in range(04):
            if dbActors[i]:
                query = query + '(SELECT Actors_ID FROM Actors WHERE ActorName = "{}"), '.format(dbActors[i])

        for i in range(04):
            if mics[i]:
                query = query + '{}, '.format(mics[i])

        query = query[:-2] + ');'

        self.testQuery(query)
        conn.commit()

    def testQuery(selfquery):
        c.execute(query)

    def scriptPrompt(self):
        actors = [
            self.actor1.currentText(),
            self.actor2.currentText(),
            self.actor3.currentText(),
            self.actor4.currentText()
        ]

        if self.newActor1.text():
            actors[0] = self.newActor1.text()

        if self.newActor2.text():
            actors[1] = self.newActor2.text()

        if self.newActor3.text():
            actors[2] = self.newActor3.text()

        if self.newActor4.text():
            actors[3] = self.newActor4.text()

        script = self.scriptList[self.comboScript.currentText()]

        query = 'SELECT mci, mli, mrk \
                 FROM tprompt \
                 WHERE msi={} \
                 ORDER BY mqi;'.format(script)

        htmlpath = '/Users/jmateobaker/PycharmProjects/Furrywood/VoiceRecorder'
        template = '{}/scrtemp.html'.format(htmlpath)
        htmlfile = '{}/htmlsample.html'.format(htmlpath)

        with open(template'r'as x:
            htmlhead = x.readlines()

        c.execute(query)
        data = c.fetchall()

        query = 'SELECT Actors FROM ActorCount WHERE Scripts_ID={};'.format(script)
        c.execute(query)
        actorCount = c.fetchone()[0]

        mics = self.gatherMics()

        with open(htmlfile'w'as t:

            # Print names at top
            at = [
                '<header>\n',
                '<table>\n',
                '<tr>\n'
            ]

            for ac in range(0actorCount):
                if mics[ac]:
                    at.append('<td class="c{0}">\n{1}\n</td>\n'.format(mics[ac], actors[ac]))
                else:
                    at.append('<td class="sfx">\n{}\n</td>\n'.format(actors[ac]))

            at += ['</tr>\n''</table>\n''</header>\n']

            # Build script below
            dt = ['<scr><table style="width:100%;text-align:left">\n']
            for n in data:

                # Fix parenthetical names
                if n[0][-1] == ')':
                    lchar = n[0][:-4].upper()
                else:
                    lchar = n[0].upper()

                if n[2] == 1 and mics[0]:
                    dt.append('<tr class="c{}">\n'.format(mics[0]))
                elif n[2] == 2 and mics[1]:
                    dt.append('<tr class="c{}">\n'.format(mics[1]))
                elif n[2] == 3 and mics[2]:
                    dt.append('<tr class="c{}">\n'.format(mics[2]))
                elif n[2] == 4 and mics[3]:
                    dt.append('<tr class="c{}">\n'.format(mics[3]))
                else:
                    dt.append('<tr class="sfx">\n')
                dt.append('<td class="charName">{}</td>\n'.format(lchar))
                dt.append('<td class="line">{}</td>\n'.format(n[1]))
                dt.append('</tr>\n')
                dt.append('</scr>\n')

            pos = htmlhead.index('_SCRIPT_\n')
            htmlhead[pos:pos] = at + dt
            htmlhead.remove('_SCRIPT_\n')

            for i in htmlhead:
                t.write(i)

        b = webbrowser.get('safari')
        b.open('file:{}'.format(htmlfile))

    def recordCommands(selfpathfile):
        # Discover the hardware first
        dc = [
            ffmpeg,
            '-f',
            'avfoundation',
            '-list_devices',
            '1',
            '-i',
            '-'
        ]

        b = subprocess.Popen(dcstderr=subprocess.PIPEstdout=subprocess.PIPE)

        output = b.communicate()[1].decode('utf-8')
        vlist = re.findall(self.vreoutput)

        # Strange pairing
        fixlist = ((vlist[0], vlist[3]), (vlist[1], vlist[2]))

        commands = []

        # Now Videos

        for i in fixlist:
            commands.append([
                ffmpeg,
                '-y',
                '-loglevel',
                '0',
                #'-threads',
                #'8',
                #'-thread_queue_size',
                #'128',
                '-f',
                'avfoundation',
                '-s',
                '1280x720',
                '-framerate',
                '30',
                '-pix_fmt',
                'uyvy422',
                '-video_device_index',
                i[0],
                '-audio_device_index',
                i[1],
                '-i',
                'vid1:aud1',
                '-c:v',
                'libx264',
                '-preset',
                'ultrafast',
                '-pix_fmt',
                'yuv420p',
                '-c:a',
                'copy',
                '{}{}_{}.mov'.format(pathfilefixlist.index(i))
            ])

        return commands


## FUNCTIONS
def paRecord(pathfilepipe):
    # Set recording variables
    paChunk = 1024
    paFormat = pyaudio.paInt24
    paChannels = 4
    paRate = 48000
    audDevice = 2

    # Locate Scarlett
    for i in range(0pya.get_device_count()):
        ad = pya.get_device_info_by_index(i)

        if ad['name'] == 'Scarlett 18i8 USB':
            audDevice = ad['index']

    # Begin Recording
    paStream = pya.open(format=paFormat,
                        channels=paChannels,
                        input_device_index=audDevice,
                        rate=paRate,
                        input=True,
                        frames_per_buffer=paChunk)

    frames=[]

    while True:
        data = paStream.read(paChunk)
        frames.append(data)

        # Check if stop message has arrived
        if pipe.poll():
            pipe.recv()
            break

    paStream.stop_stream()
    paStream.close()

    wf = wave.open('{}{}.wav'.format(pathfile), 'wb')
    wf.setnchannels(paChannels)
    wf.setsampwidth(pya.get_sample_size(paFormat))
    wf.setframerate(paRate)
    wf.writeframes(b''.join(frames))
    wf.close()


### LAUNCH PROGRAM
if __name__ == '__main__':
    mp.set_start_method('spawn')

    conn = sqlite3.connect(db)
    c = conn.cursor()

    # Determine last recording id
    query = "SELECT MAX(Recordings_ID) FROM Recordings;"
    c.execute(query)

    recId = c.fetchone()[0]
    if recId:
        recId = recId + 1
    else:
        recId = 1

    query = 'SELECT Events.Events_ID as id, \
                Conventions.ConventionName as cn, \
                Appearances.Year as yr, \
                Panels.PanelCODE as pc, \
                Events.EventNumber as en \
                FROM Appearances \
                JOIN Events \
                ON Appearances.Appearances_ID = Events.AppearancesID \
                JOIN Conventions \
                ON Conventions.Conventions_ID = Appearances.ConventionsID \
                JOIN Panels \
                ON Panels.Panels_ID = Events.PanelID;'

    c.execute(query)
    eventData = c.fetchall()

    eventList = ['{} - {}, {}'.format(x[1], x[2], x[3], x[4]) for x in eventData]

    app = QApplication(sys.argv)
    window = FWR()
    sys.exit(app.exec_())