import numpy as np
import init

init_volume = 14  # Initial volume for each Track



class audioloop:
    def __init__(self):
        self.initialized = 0
        self.length_factor = 1
        self.length = 0
        self.maxpeak = 0
        self.readp = 0
        self.writep = 0
        self.is_recording = False
        self.is_playing = False
        self.is_waiting_rec = False
        self.is_waiting_play = False
        self.is_waiting_mute = False
        self.undo_mode = 0
        self.is_solo = False
        self.is_sequence = False
        self.volume = init_volume
        self.preceding_buffer = np.zeros([CHUNK], dtype=np.int16)
        self.main_audio = np.zeros([MAXLENGTH, CHUNK], dtype=np.int16)  # self.main_audio contain main audio data in arrays of CHUNKs.
        self.dub_audio = np.zeros([MAXLENGTH, CHUNK], dtype=np.int16)

    # increment_pointers() increments pointers and, when restarting while recording
    def increment_pointers(self):
        if self.readp == self.length - 1:
            #if self.is_sequence:
            #    sequence_counter += 1
            self.readp = 0
            print(' '*60, end='\r')
        else:
            self.readp += 1
        progress = (loops[0].readp / (loops[0].length + 1))*50
        self.writep = (self.writep + 1) % self.length
        print('#'*int(progress), end='\r')

    # read_buffer() reads and returns a buffer of audio from the loop
    def read_buffer(self):
        # Turns On 0,1s the Red Led of PLAYBUTTON to mark the starting of Master Loop
        global rec_file, sequence_counter
        if setup_donerecording and loops[0].readp == 0:
            init.UNDOLEDR.value = 1
            init.UNDOLEDG.value = 0
            if set_recording_file:
                rec_file = True
            else:
                rec_file = False

        # If a Track is_waiting_rec, put it to Rec when reaches the end of length of track 0 (not initialized) or at end of selected track (if initialized)
        if self.is_waiting_rec:
            if ((self.initialized >= 1 and self.writep == self.length - 1) or
                (self.initialized == 0 and loops[0].writep == loops[0].length - 1)):
                self.is_recording = True
                self.is_waiting_rec = False
                print('---= Start Recording Track ', selected_loop, '\n')

        # If a Track is not initialized, exit and returns silence
        if self.initialized == 0:
            return(silence)

        # If the Track is initialized:
        # Control of UnMute
        if self.is_waiting_play and loops[0].writep == loops[0].length - 1:
            self.is_waiting_play = False
            self.is_playing = True

        # Control of Mute
        if self.is_waiting_mute and loops[0].writep == loops[0].length - 1:
            self.is_waiting_mute = False
            self.is_playing = False

        # If a Track is Muted or waiting_play, increment_pointers and exit with silence
        if not self.is_playing or self.is_waiting_play:
            self.increment_pointers()
            return(silence)

        # If not any of the cases, increment_pointers and return the buffer addressed by readp
        tmp = self.readp
        self.increment_pointers()

        if self.undo_mode == 1:
            return(self.main_audio[tmp, :])  # If Undo was pressed, plays only main_audio
        elif self.undo_mode == 2:
            return(self.dub_audio[tmp, :])  # If Undo was pressed, plays only dub_audio
        elif self.undo_mode == 0:
            return(self.main_audio[tmp, :]*(init_volume/max_volume)**1.4142 +
                   self.dub_audio[tmp, :]*(init_volume/max_volume)**1.4142)  # If Undo was not pressed, plays sum of main and dub audio

    # write_buffer() appends a new buffer on main_audio if not initialized or on dub_audio if initialized
    def write_buffers(self, data):
        global LENGTH
        self.maxpeak = max(np.max(np.abs(data))/max_amplitude*100, self.maxpeak)
        if self.initialized >= 1:
            if self.writep < self.length - 1:
                if self.undo_mode == 0:  # If Undo was not pressed, writes on main_audio the sum of main and dub audio
                    if self.initialized == 1:
                        self.main_audio[self.writep, :] = self.dub_audio[self.writep, :]
                    else:
                        scaled_volume = (init_volume / max_volume) ** 2
                        self.main_audio[self.writep, :] = (self.dub_audio[self.writep, :]*scaled_volume +
                                                           self.main_audio[self.writep, :]*scaled_volume)
                    self.dub_audio[self.writep, :] = np.copy(data)  # Add to dub_audio the buffer entering through Jack
                elif self.undo_mode == 1:
                    self.dub_audio[self.writep, :] = np.copy(data)  # Add to dub_audio the buffer entering through Jack
                elif self.undo_mode == 2:
                    self.main_audio[self.writep, :] = np.copy(data)  # Add to main_audio the buffer entering through Jack
            elif self.writep == self.length - 1:
                self.is_recording = False
                self.initialize()
                self.is_waiting_rec = False
                self.is_playing = True
                self.undo_mode = 0
        else:
            if self.length >= (MAXLENGTH - 1):
                self.length = 0
                print('Loop Full')
                return
            self.dub_audio[self.length, :] = np.copy(data)  # Add to main_audio the buffer entering through Jack
            self.length += 1  # Increase the length of the loop
            if not setup_donerecording:
                LENGTH += 1

    #set_recording() either starts or stops recording
    #   if uninitialized and recording, stop recording (appending) and initialize
    #   if initialized and not recording, set as "waiting to record"
    def set_recording(self):
        global setup_is_recording, setup_donerecording
        print('---= set_recording Called for Track ', selected_loop, ' =-', '\n')
        #already_recording = False

        #if chosen track is currently recording, flag it
        if self.is_recording:  # turn off recording
            self.initialize()
            self.is_playing = True
            self.is_recording = False
            self.is_waiting_rec = False
            print('-------------= Stop Rec =---', '\n')
            if selected_loop == 0 and not setup_donerecording:
                setup_is_recording = False
                setup_donerecording = True
                print('-------------= Master Track Recorded =---', '\n')
            debug()
            return
        else:
            #unless flagged, schedule recording. If chosen track was recording, then stop recording
            #if not already_recording:
            if self.is_waiting_rec and setup_donerecording:
                self.is_waiting_rec = False
                return
            if selected_loop == 0 and not setup_donerecording:
                self.is_recording = True
                setup_is_recording = True
            else:
                self.is_waiting_rec = True
            debug()

    #initialize() raises self.length to closest integer multiple of LENGTH and initializes read and write pointers
    def initialize(self): #It initializes when recording of loop stops. It de-initializes after Clearing.
        if self.initialized == 0:
            self.writep = self.length - 1
            self.length_factor = (int((self.length - OVERSHOOT) / LENGTH) + 1)
            self.length = self.length_factor * LENGTH
            self.readp = (self.writep + LATENCY) % self.length  #audio should be written ahead of where it is being read from, to compensate for input+output init.LATENCY
        self.initialized += 1
        print('     length ' + str(self.length),' /  last buffer recorded ' + str(self.writep),'\n')
        print('-------------= Initialized = ', str(self.initialized), '\n')

        # Apply Fades to dub_audio recently recorded
        np.multiply(self.dub_audio[0], fade_in, out = self.dub_audio[0], casting = 'unsafe')  # Fade In to the first buffer
        np.multiply(self.dub_audio[self.length-1], fade_out, out=self.dub_audio[self.length-1], casting='unsafe')  # Fade Out to the last buffer
        debug()

    def toggle_mute(self):
        print('-=Toggle Mute=-','\n')
        if self.initialized >= 1:
            if self.is_playing:
                if not self.is_waiting_mute:
                    self.is_waiting_mute = True
                else:
                    self.is_waiting_mute = False
                print('-------------= Mute =---', '\n')

            else:
                if not self.is_waiting_play:
                    self.is_waiting_play = True
                else:
                    self.is_waiting_play = False
                self.is_solo = False
                print('-------------= UnMute =---', '\n')
            debug()

    def toggle_solo(self):
        print('-=Toggle Solo=-','\n')
        if not self.is_solo and self.initialized >= 1:
            for i in range(number_of_tracks):
                if i != selected_loop and loops[i].initialized >= 1 and not loops[i].is_solo and loops[i].is_playing:
                    loops[i].is_waiting_mute = True
            self.is_solo = True
            print('-------------= Solo =---', '\n')
        else:
            for i in range (number_of_tracks):
                if i != selected_loop and loops[i].initialized >= 1:
                    loops[i].is_waiting_play = True
                    loops[i].is_solo = False
            self.is_solo = False
            print('-------------= UnSolo =---', '\n')
        debug()

    def toggle_sequence(self):  # Still not implemented
        print('-=Toggle Sequence=-','\n')
        if self.initialized >= 1 and sequence_length == 0:
            if not self.is_sequence:
                for i in range(selected_loop + 1, number_of_tracks):
                    if loops[i].initialized >= 1:
                        sequence_length += 1
                    else:
                        print('sequence_length = ', sequence_length, '\n')
                        break
                #for i in range(selected_loop + 1, sequence_length):
                self.is_sequence = True
                print('-------------= Sequence =---', '\n')
            else:
                self.is_sequence = False
                sequence_length = 0
                print('-------------= UnSequence =---', '\n')
            debug()

    def undo(self):
        global LENGTH
        if self.is_recording:
            if self.initialized == 0:
                self.clear_track()
                if selected_loop == 0:
                    LENGTH = 0
                return
        if self.is_playing:
            if self.undo_mode <= 1:
                self.undo_mode += 1
            else:
                self.undo_mode = 0

        print('-=Undo=-', '\n')
        debug()

    # Clears all the Tracks of the looper
    def clear(self):
        global setup_donerecording, setup_is_recording, LENGTH
        if selected_loop == 0:
            for loop in loops:
                loop.__init__()
            setup_donerecording = False
            setup_is_recording = False
            LENGTH = 0
            print('-=Cleared ALL=-','\n')
        else:
            self.clear_track()
        debug()

    # Clears the track so that a new loop of the same or a different length can be recorded on the track
    def clear_track(self):
        self.__init__()
        print('-=Clear Track=-', '\n')
