"""
"th3rd" - a demo for instanssi 2023
code: nisual
music: plive

font "connection" by darrell flood
thanks to modern gl / glsl tutorial by Coder Space in youtube

warning: this presentation includes flashing lights
"""

# Explicit imports needed for pyinstaller to succesfully include all the dependencies within single .exe

import moderngl_window.context.pyglet
import glcontext
import moderngl_window.loaders.program
import moderngl_window.loaders.program.single
import moderngl_window.loaders.program.separate

import pyglet

import multiprocessing
import math
import random

import glcontext
import sys
import os
import time as hightime
import numpy as np
import soundfile as sf
from pydub import AudioSegment
import ffmpeg

import moderngl_window as mglw

# Comment out lines "os.chdir(sys._MEIPASS)" and "use_path = 'moderngl_window\\scene\\programs\\'" when
# running from development environment
# Comment out line "use_path = 'programs" when creating single executable.

os.chdir(sys._MEIPASS)
use_path = 'moderngl_window\\scene\\programs\\'
#use_path = 'programs'

# Initialize pyglet media player and load in music and font

player = pyglet.media.Player()
source = pyglet.media.load('moderngl_window/scene/programs/m.mp3')
pyglet.font.add_file("moderngl_window/scene/programs/font.ttf")
font = pyglet.font.load("Connection", 32)

# Declare class to create mgwl window instance and provide configuration

class App(mglw.WindowConfig):
    window_size = 1920, 1080
    resource_dir = use_path
    fullscreen = True
    cursor = False
    start_time = hightime.time()
    aikalista = [] # initialize a list to store timestamps to be used for timeline
    aikalista.append((hightime.time()-start_time))
    loaded = "empty" # create flag variable for later use in shader status inspection
    text_x = 3500 # initialize starting variable for first text display
    text_string = "\"th3rd\" at instanssi23 by nisual plive greets asm_compocrew fairlight asd kewlers olddude linkkijkl_ry tbl taat jumalauta viznut fulcrum conspiracy ivory_labs moppi plk ... \"th3rd\""

    text_printed_flag = False # initialize flag for whether or not all the text has been displayed or not
    letter_enumerator = 0
    worditerator = 0

    audiofile = ('moderngl_window/scene/programs/m.mp3')


    # initialize variables for audio processing
    data, samplerate = sf.read(audiofile)
    n = len(data)
    Fs = samplerate
    ch1, ch2, = data.transpose()
    sound_axis = ch2

    """
    # FOR DEBUGGING PURPOSES COMMENT THIS OUT FOR RELEASE
    print(Fs)
    print(len(ch1))
    print(len(ch1) / Fs)
    """

    def __init__(self, **kwargs):
        # create screen-aligned quad and provide the first shader with self.prog

        super().__init__(**kwargs)

        # create screen aligned quad
        self.quad = mglw.geometry.quad_fs()

        # load shader program
        self.prog = self.load_program(vertex_shader="vs00.glsl",
                                      fragment_shader="fs00.glsl")
        self.set_uniform("resolution", self.window_size)

    def set_uniform(self, u_name, u_value):
        try:
            self.prog[u_name] = u_value
        except KeyError:
            #print(f"uniform: {u_name} - not used in shader")
            pass

    def render(self, time, frame_time):
        # Render function. Timeline lives here. Timing of each shader loading is done with if statements.
        # This may seem dirty, I will maybe figure out another way to do this next time
        self.ctx.clear()
        self.set_uniform("time", time)
        if (abs(self.ch1[int((hightime.time()-self.start_time) * self.Fs)])) > 0.25:
            self.set_uniform("soundexpresser", (abs(self.ch1[int((hightime.time()-self.start_time) * self.Fs)])))
        else:
            self.set_uniform("soundexpresser", 0.1)
        self.quad.render(self.prog)

        #print((abs(self.ch1[int(hightime.process_time() * self.Fs)])))


        self.letter_enumerator = 0
        word = self.text_string.split()[self.worditerator]

        if 184 < round((hightime.time()-self.start_time)) and self.text_printed_flag == False:

            for letter in word:
                self.letter_enumerator += 1
                label = pyglet.text.Label(letter,
                                          font_name="Connection",
                                          font_size=32,
                                          anchor_x = "center",
                                          x=self.text_x + (self.letter_enumerator * 70),
                                          y=((math.sin((hightime.time()-self.start_time)+self.letter_enumerator+6)+3)*15),
                                          color=(200, 25, 200, int((100+(math.sin((hightime.time()-self.start_time)+1)*50)))))
                if self.text_x > -70*len(word):
                    self.text_x -= ((int(6 / (len(word)-1))+1) * 0.7) * (0.003* abs(self.text_x*4 - 800)+1)
                else:
                    self.text_x = 2000
                    if self.worditerator < (len(self.text_string.split())-1):
                        self.worditerator += 1
                    else:
                        self.worditerator = 0
                        self.text_printed_flag = True

                label.draw()

        """
        # FOR DEBUGGING PURPOSES COMMENT THIS OUT FOR RELEASE
        timelabel = pyglet.text.Label("for_debug: {:07.2f} | shader: {:^6} | audio_amplitude {:02.5f}".format((hightime.time()-self.start_time), self.loaded, (abs(self.ch1[int((hightime.time()-self.start_time) * self.Fs)]))),
                                      font_size=32,
                                      anchor_x="left",
                                      x=20, y=20)
        timelabel.draw()
        """

        if 19.4 <= (hightime.time()-self.start_time) <= 21 and self.loaded != "01":
            self.prog = self.load_program(vertex_shader="vs01.glsl",
                                          fragment_shader="fs01.glsl")

            self.set_uniform("resolution", self.window_size)
            print("new shader loaded", (hightime.time()-self.start_time))
            self.loaded = "01"

        if 50.1 <= (hightime.time()-self.start_time) <= 51 and self.loaded != "02":
            self.prog = self.load_program(vertex_shader="vs02.glsl",
                                          fragment_shader="fs02.glsl")
            self.set_uniform("resolution", self.window_size)
            print("new shader loaded", (hightime.time()-self.start_time))
            self.loaded = "02"

        if 65.5 <= (hightime.time()-self.start_time) <= 67 and self.loaded != "03":
            self.prog = self.load_program(vertex_shader="vs03.glsl",
                                          fragment_shader="fs03.glsl")
            self.set_uniform("resolution", self.window_size)
            print("new shader loaded", (hightime.time()-self.start_time))
            self.loaded = "03"

        if 96.2 <= (hightime.time()-self.start_time) <= 97 and self.loaded != "01":
            self.prog = self.load_program(vertex_shader="vs01.glsl",
                                          fragment_shader="fs01.glsl")
            self.set_uniform("resolution", self.window_size)
            print("new shader loaded", (hightime.time()-self.start_time))
            self.loaded = "01"

        if 111.6 <= (hightime.time()-self.start_time) <= 112 and self.loaded != "04":
            self.prog = self.load_program(vertex_shader="vs00.glsl",
                                          fragment_shader="fs04.glsl")
            self.set_uniform("resolution", self.window_size)
            print("new shader loaded", (hightime.time()-self.start_time))
            self.loaded = "04"

        if 188 <= (hightime.time()-self.start_time) <= 185 and self.loaded != "01":
            self.prog = self.load_program(vertex_shader="vs01.glsl",
                                          fragment_shader="fs01.glsl")
            self.set_uniform("resolution", self.window_size)
            print("new shader loaded", (hightime.time()-self.start_time))
            self.loaded = "01"

        if 250 <= (hightime.time()-self.start_time) <= 251 and self.loaded != "00":
            self.prog = self.load_program(vertex_shader="vs00.glsl",
                                          fragment_shader="fs00.glsl")
            self.set_uniform("resolution", self.window_size)
            print("new shader loaded", (hightime.time()-self.start_time))
            self.loaded = "00"

        if (hightime.time()-self.start_time) - self.aikalista[-1] > 1:
            print("process_time", (hightime.time()-self.start_time))
            self.aikalista.append((hightime.time()-self.start_time))

        if (hightime.time()-self.start_time) > 270:
            exit()

def master():
    mglw.run_window_config(App)

def soundtrack():
    player.queue(source)
    player.play()


if __name__ == "__main__":
    multiprocessing.freeze_support()

    p1 = multiprocessing.Process(target=master)
    p2 = multiprocessing.Process(target=soundtrack())

    p1.start()
    p2.start()

