import sounddevice as sd
from project import Project
from ui.widgets.play_head import PlayHead


class SongPlayer:
    def __init__(self, timeline, project: Project):
        self.audio = None
        self.stream = sd.OutputStream() # default stream
        self.project: Project = project
        self.playhead = 0
        self.paused = True
        
        self.timeline = timeline
        
    def play_callback(self, out, frames, time, status):
        if self.paused or self.audio is None:
            out.fill(0)
            return
        
        end = self.playhead + frames
        chunk = self.audio[self.playhead:end]
        
        if chunk.ndim == 1:
            chunk = chunk[:, None]
            
        if chunk.shape[1] != out.shape[1]:
            raise RuntimeError("Something very bad has happened :sob:, A channel mismatch happened.")
        
        if len(chunk) < frames:
            out[:len(chunk)] = chunk
            out[len(chunk):].fill(0)
            raise sd.CallbackStop()
        
        out[:] = chunk
        self.playhead = end
        
        
        
    async def update_visual_playhead(self):
        # get how many bars into the song we are
        num_bars = self.playhead / self.project.samples_per_bar
        
        # multiply that with the bar offset to update the ui to show where the playhead is in the song
        x_offset = num_bars * self.timeline.bar_offset * 4
        
        # now we can finally apply that offset :)
        self.timeline.query_one(PlayHead).offset = (
            x_offset,
            0
        )
        
    def seek(self, seconds: int):
        self.playhead = int(seconds * self.project.sample_rate)
        self.timeline.run_worker(self.update_visual_playhead())
        
    def pause(self):
        self.paused = True
        self.stream.close()
        
        play_btn = self.timeline.app.query_one("#play-button")
        play_btn.variant = "success"
        play_btn.label = "▶"
        
    def play_project(self, project: Project) -> bool:         
        # this just returns True if playing audio succeeded
        self.project = project
        self.audio = project.render()
        
        
        try:            
            if not self.stream.closed:
                self.stream.close()
            
            self.stream = sd.OutputStream(
                samplerate=project.sample_rate,
                channels=self.audio.shape[1] if self.audio.ndim > 1 else 1,
                callback=self.play_callback,
                blocksize=256
            )
            
            self.paused = False
            
            self.stream.start()
            
            
            play_btn = self.timeline.app.query_one("#play-button")
            play_btn.variant = "error"
            play_btn.label = "⏸"
            
            return True
        except sd.PortAudioError as e: # woopsies
            self.paused = True
            self.timeline.app.notify(f"Error: \"{e}\"\n\nSometimes errors can occur if the device you selected in your settings doesn't exist, or it's already in use.", title="Error while playing song", timeout=60, severity="error")
        
        return False