'''
Created on 23 juil. 2009

@author: Samuel Benveniste
'''
from math import floor, ceil
import pygame
import sys
import colorsys
import constants
from gradients import gradients
from logging.PickleableEvent import PickleableEvent


class PlayingScreen:
    '''
    The screen on which the game is played
    
        wiimotes: 
                The wiimotes used in this session
        window:
            The main display window
        screen:
            The main display surface
        clock:
            The clock used to animate the screen
        savedScreen:
            The background that is painted every time
        playerScreen:
            The buffer for painting everything before bliting
        width:
            The width of the window in pixels
        height:
            The height of the window in pixels
        extendScale :
            True if the scale is G to C instead of C to C
        cascade:
            True if crossing from note to note with a button pressed triggers a new note
        scaleSize:
            The size of the scale used
        cursorPositions:
            The positions of the cursors on the screen, in pixels
    '''
    
    
    
    def __init__(self, instrumentChoice=None, song = None, cascade=False, extendedScale=False, defaultInstrumentChannel = 16, defaultNote = 60):
        '''
        Constructor
        '''        
        self.blinkLength = 200
        self.minimalVelocity = 64
        self.shortScaleSize = 8
        self.longScaleSize = 11
        if not extendedScale:
            self.offset = self.longScaleSize - self.shortScaleSize
        else:
            self.offset = 0
        self.borderSize = 5
        self.savedHighlightedNote = 0
        
        self.wiimotes = instrumentChoice.wiimotes
        self.activeWiimotes = instrumentChoice.activeWiimotes
        self.window = instrumentChoice.window
        self.screen = instrumentChoice.screen
        self.blitOrigin = instrumentChoice.blitOrigin
        self.clock = instrumentChoice.clock
        self.width = instrumentChoice.width
        self.height = instrumentChoice.height
        self.cursorPositions = instrumentChoice.cursorPositions
        self.savedScreen = instrumentChoice.savedScreen
        self.playerScreen = instrumentChoice.playerScreen
        self.extendedScale = extendedScale
        self.cascade = cascade
        self.joys = instrumentChoice.joys
        self.portOffset = instrumentChoice.portOffset
        self.eventLog = instrumentChoice.eventLog
        self.cursorPositions = instrumentChoice.cursorPositions
        self.song = song
        self.songIterator = self.moveToNextNote()
        self.replay = instrumentChoice.replay
        
        self.defaultInstrumentChannel = defaultInstrumentChannel
        self.defaultNote = defaultNote
        
        self.done = False
        self.backToInstrumentChoice = False
        self.easyMode = False
        
        self.highlightedNote = self.songIterator.next()
        
        self.blinkOn = False
        self.savedBlinkOn = False
        ##Will prevent the song to move on if two consecutive notes are identical and the buttons have not been released in between the two
        ##i.e. it guarantees that there will be an attack between two identical consecutive notes
        self.highlightIsFree = True
        
        self.noteRects = []
        self.boundingRect = None
        self.notes = []
        self.buttonDown = []
        self.velocityLock = []
        
        self._blinkOffset = 0
        
        self.font = pygame.font.Font(None,50)
        self.firstWiimote = 0
        while not self.activeWiimotes[self.firstWiimote] :
            self.firstWiimote += 1
        self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.wiimotes[self.firstWiimote].instrument.notes[self.offset:]]
        
        self.drawBackground()
        self.initializeWiimotes()
        
        #The main loop
        while not self.done :
            
            #Clear the cursors from the screen
            if self.hasChanged():
                self.drawBackground()
            self.playerScreen.blit(self.savedScreen, (0, 0))
            
            # Limit frame speed to 50 FPS
            #
            timePassed = self.clock.tick(50)
            
            self._blinkOffset += timePassed
            if self._blinkOffset > self.blinkLength:
                self._blinkOffset -= self.blinkLength
                self.blinkOn = not self.blinkOn
                
            if self.replay:
                self.eventLog.update(timePassed)
                pickledEventsToPost = self.eventLog.getPickledEvents() 
                for pickledEvent in pickledEventsToPost:
                    pygame.event.post(pickledEvent.event)
            
            events = pygame.event.get()
            
            if not self.replay:
                pickledEvents = [PickleableEvent(event.type,event.dict) for event in events]
                if pickledEvents != [] :
                    self.eventLog.appendEventGroup(pickledEvents)
            
            for event in events:
                self.input(event)
                            
            for i in range(len(self.wiimotes)):
                if self.activeWiimotes[i]:
                    self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i])
                    if self.buttonDown[i] :
                        self.wiimotes[i].cursor.flash()
                    self.wiimotes[i].cursor.blit(self.playerScreen)
            
            self.screen.blit(self.playerScreen, (0,0))
            
            pygame.display.flip()
        
        for i in range(len(self.wiimotes)):
            if self.activeWiimotes[i]:
                self.wiimotes[i].stopNote(self.notes[i])            
            
    def drawBackground(self):
        self.savedScreen.fill((255,255,255))
        
        if self.extendedScale :
            self.scaleSize = self.longScaleSize
        else:
            self.scaleSize = self.shortScaleSize
        
        self.noteRects = [pygame.Rect(i * self.width / self.scaleSize+self.blitOrigin[0], self.blitOrigin[1], self.width / self.scaleSize + 1, self.height+1) for i in range(self.scaleSize)]
        #inflate last noteRect to cover the far right pixels
        self.noteRects[-1].width = self.noteRects[-1].width + 1
        
        #create bounding rect
        self.boundingRect = self.noteRects[0].unionall(self.noteRects)
        
        #fill the rectangles with a color gradient
        #We start with blue
        startingHue = 0.66666666666666663
        
        for rectNumber in range(self.scaleSize):
            colorRatio = float(rectNumber) / (self.scaleSize - 1)
            #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up
            hue = startingHue * (1 - colorRatio)
            if self.song != None:
                if rectNumber + self.offset == self.highlightedNote:
                    #The color of the bottom of the rectangle in hls coordinates
                    bottomColorHls = (hue, 0.6, 1)
                    #The color of the top of the rectangle in hls coordinates
                    topColorHls = (hue, 0.9, 1)
                else:
                    #The color of the bottom of the rectangle in hls coordinates
                    bottomColorHls = (hue, 0.2, 1)
                    #The color of the top of the rectangle in hls coordinates
                    topColorHls = (hue, 0.4, 1)
            else:
                #The color of the bottom of the rectangle in hls coordinates
                bottomColorHls = (hue, 0.6, 1)
                #The color of the top of the rectangle in hls coordinates
                topColorHls = (hue, 0.9, 1)
            
            #convert to rgb ranging from 0 to 255
            bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)]
            topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)]
            #add transparency
            bottomColorRgb.append(255)
            topColorRgb.append(255)
            #convert to tuple
            bottomColorRgb = tuple(bottomColorRgb)
            topColorRgb = tuple(topColorRgb)            
            
            self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber])
            
            textBlitPoint = (self.noteRects[rectNumber].left+(self.noteRects[rectNumber].width-self.renderedNoteNames[rectNumber].get_width())/2,
                             self.noteRects[rectNumber].bottom-self.renderedNoteNames[rectNumber].get_height())
            
            self.savedScreen.blit(self.renderedNoteNames[rectNumber], textBlitPoint)
            
            pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2)
            
            
            if self.song != None and self.blinkOn:
                borderSize = self.borderSize
                pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 0), self.noteRects[self.highlightedNote-self.offset].inflate(borderSize/2,borderSize/2), borderSize)
        
    def initializeWiimotes(self):
        for loop in self.wiimotes:
            if loop.port == None :
                loop.port = pygame.midi.Output(loop.portNumber)
            self.notes.append(0)
            self.buttonDown.append(False)
            self.velocityLock.append(False)
    
    def updateCursorPositionFromJoy(self, joyEvent):
        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()
        correctedJoyId = constants.joyNames.index(joyName)
        if correctedJoyId < len(self.cursorPositions):
            if joyEvent.axis == 0 :
                self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1])
            if joyEvent.axis == 1 :
                self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height()))
    
    def heightToVelocity(self, pos, controllerNumber):
        if self.song != None:
            if self.boundingRect.collidepoint(pos) and (self.highlightedNote == self.notes[controllerNumber] or self.velocityLock[controllerNumber]):
                velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)
            else :
                if self.easyMode:
                    velocity = None
                else:
                    velocity = self.minimalVelocity/3
        else:
            if self.boundingRect.collidepoint(pos):
                velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)
            else :
                velocity = self.minimalVelocity
        return(velocity)
    
    def widthToNote(self, pos):
        nn = 0
        try :
            while self.noteRects[nn].collidepoint(pos) == False:
                nn = nn + 1
            return(nn + self.offset)
        except(IndexError):
            return(None)
    
    def input(self, event): 
        
        if event.type == pygame.QUIT:
            for loop in self.wiimotes:
                del loop.port
            pygame.midi.quit()
            sys.exit(0) 
        
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                self.done = True
                
            if event.key == pygame.K_i:
                self.backToInstrumentChoice = True
                self.done = True 
        
        if event.type == pygame.JOYAXISMOTION:
            joyName = pygame.joystick.Joystick(event.joy).get_name()
            correctedJoyId = constants.joyNames.index(joyName)
            if self.activeWiimotes[correctedJoyId]:
                self.updateCursorPositionFromJoy(event)            
                wiimote = self.wiimotes[correctedJoyId]
                pos = self.cursorPositions[correctedJoyId]
    
                if self.buttonDown[correctedJoyId]:
                    if self.notes[correctedJoyId] != None:
                        velocity = self.heightToVelocity(pos, correctedJoyId)
                        CCHexCode = wiimote.getCCHexCode()
                        wiimote.port.write_short(CCHexCode, 07, velocity)
                    if self.cascade:
                        n = self.widthToNote(pos)
                        if n != self.notes[correctedJoyId]:
                            wiimote.stopNote(self.notes[correctedJoyId])
                            self.notes[correctedJoyId] = n
                            
                            if self.song != None :
                                if self.highlightedNote == self.notes[correctedJoyId]:
                                    self.highlightedNote = self.songIterator.next()
                                    self.velocityLock[correctedJoyId] = True
                                else:
                                    self.velocityLock[correctedJoyId] = False
                            
                            velocity = self.heightToVelocity(pos, correctedJoyId)
                            
                            wiimote.playNote(self.notes[correctedJoyId],velocity)
        
        if event.type == pygame.JOYBUTTONDOWN :
            
            joyName = pygame.joystick.Joystick(event.joy).get_name()
            correctedJoyId = constants.joyNames.index(joyName)
            if self.activeWiimotes[correctedJoyId]:
                wiimote = self.wiimotes[correctedJoyId]
                pos = self.cursorPositions[correctedJoyId]
    
                if not self.buttonDown[correctedJoyId]:
                    savedNote = self.notes[correctedJoyId]
                    self.notes[correctedJoyId] = self.widthToNote(pos)
                    
                    if self.song != None :
                        if self.highlightedNote == self.notes[correctedJoyId]:
                            self.highlightedNote = self.songIterator.next()
                            self.velocityLock[correctedJoyId] = True
                    
                    velocity = self.heightToVelocity(pos, correctedJoyId)
                    
                    if velocity != None :                            
                        if self.easyMode :
                            wiimote.stopNote(savedNote)
                        wiimote.playNote(self.notes[correctedJoyId],velocity)
                    self.buttonDown[correctedJoyId] = True
        
        if event.type == pygame.JOYBUTTONUP:
            joyName = pygame.joystick.Joystick(event.joy).get_name()
            correctedJoyId = constants.joyNames.index(joyName)
            if self.activeWiimotes[correctedJoyId]:
                self.buttonDown[correctedJoyId] = False
                wiimote = self.wiimotes[correctedJoyId]
                if not self.easyMode:
                    wiimote.stopNote(self.notes[correctedJoyId])
                    self.velocityLock[correctedJoyId] = False
            
        if event.type == pygame.MOUSEMOTION:
            
            self.updateCursorPositionFromMouse(event)

            correctedJoyId = 0
            while not self.activeWiimotes[correctedJoyId] :
                correctedJoyId += 1
            wiimote = self.wiimotes[correctedJoyId]
            pos = self.cursorPositions[correctedJoyId]

            if self.buttonDown[correctedJoyId]:
                if self.notes[correctedJoyId] != None:
                    velocity = self.heightToVelocity(pos, correctedJoyId)
                    CCHexCode = wiimote.getCCHexCode()
                    wiimote.port.write_short(CCHexCode, 07, velocity)
                if self.cascade:
                    n = self.widthToNote(pos)
                    if n != self.notes[correctedJoyId]:
                        wiimote.stopNote(self.notes[correctedJoyId])
                        self.notes[correctedJoyId] = n
                        
                        if self.song != None :
                            if self.highlightedNote == self.notes[correctedJoyId]:
                                self.highlightedNote = self.songIterator.next()
                                self.velocityLock[correctedJoyId] = True
                            else:
                                self.velocityLock[correctedJoyId] = False
                        
                        velocity = self.heightToVelocity(pos, correctedJoyId)
                            
                        wiimote.playNote(self.notes[correctedJoyId],velocity)                            
        
        if event.type == pygame.MOUSEBUTTONDOWN:
            
            if event.button == 1:
                correctedJoyId = 0
                while not self.activeWiimotes[correctedJoyId] :
                    correctedJoyId += 1
                wiimote = self.wiimotes[correctedJoyId]
                pos = self.cursorPositions[correctedJoyId]
    
                if not self.buttonDown[correctedJoyId]:
                    self.notes[correctedJoyId] = self.widthToNote(pos)
                    
                    if self.song != None :
                        if self.highlightedNote == self.notes[correctedJoyId]:
                            self.highlightedNote = self.songIterator.next()
                            self.velocityLock[correctedJoyId] = True
                    
                    velocity = self.heightToVelocity(pos, correctedJoyId)
                                
                    wiimote.playNote(self.notes[correctedJoyId],velocity)
                    self.buttonDown[correctedJoyId] = True
            
            if event.button == 2:
                
                self.done = True
        
        if event.type == pygame.MOUSEBUTTONUP:
            
            correctedJoyId = 0
            while not self.activeWiimotes[correctedJoyId] :
                correctedJoyId += 1
            wiimote = self.wiimotes[correctedJoyId]
            wiimote.stopNote(self.notes[correctedJoyId])
            self.buttonDown[correctedJoyId] = False
            self.velocityLock[correctedJoyId] = False
        
    def hasChanged(self):
        changed = False
        if self.song != None:
            if self.blinkOn != self.savedBlinkOn or self.highlightedNote != self.savedHighlightedNote:
                self.savedBlinkOn = self.blinkOn
                self.savedHighlightedNote = self.highlightedNote
                changed = True
        return(changed)
    
    def updateCursorPositionFromMouse(self, mouseEvent):
        correctedJoyId = 0
        while not self.activeWiimotes[correctedJoyId] :
            correctedJoyId += 1
        self.cursorPositions[correctedJoyId] = mouseEvent.pos
        
    def moveToNextNote(self):
        while True:
            if self.song == None:
                yield(None)
            else:
                for note in self.song:
                    yield note
