"""
the logic and drawing of the tubes

Balls are moved around inside here, so might as well be drawn in here, too.
"""

from OpenGL.GL import *
from OpenGL.GLU import *
import pygame, pygame.image
from pygame.locals import *
import random
import math

import texMgr
import mouse
import crate
import sound
import options
import voiceover

import fsm_mgr as fsm
import draw_util

import state_pause_menu
import state_level_intro

ballTextures={}
cb_ballTextures={}
tubeStraightFrontTex=-1
tubeStraightBackTex=-1
tubeTwistedFrontTex=-1
tubeTwistedBackTex=-1

cursorTwistTex=-1
cursorNoTex=-1

crateTex=-1

tubeMap=[[0, 1, 1, 1, 1, 1, 1],
         [0, 2, 1, 0, 1, 2, 1],
         [0, 1, 1, 1, 1, 1, 1],
         [0, 1, 1, 2, 1, 1, 1]]


SIZE=100.0

BALLSIZE=50.0
BALLFUDGE=12.0

CURSORSIZE=45.0

gSpeeds={'easy':(30.0, 25.0),
         'medium':(50.0, 35.0),
         'hard':(70.0, 50.0),
         'insane':(120.0, 70.0)}


gBoard=None
gMousePos=(0,0)

gGridMousePos=(0,0)

class Ball:
    def __init__(self, color, column=0, y=0):
        self.column = column
        self.y=y
        self.x=float(self.column)
        self.color=color

    def __str__(self):
        return "(B: %.2f %.2f)"%(self.x, self.y)

    def __repr__(self):
        return self.__str__()        

    def draw(self):
        if options.g_color_assist:
            glBindTexture(GL_TEXTURE_2D, cb_ballTextures[self.color])
        else:
            glBindTexture(GL_TEXTURE_2D, ballTextures[self.color])
        
        left=self.x-BALLSIZE/2+BALLFUDGE
        right=left+BALLSIZE-2*BALLFUDGE
        bottom=self.y-BALLSIZE/2+BALLFUDGE
        top=bottom+BALLSIZE-2*BALLFUDGE

        glBegin(GL_QUADS)
        glTexCoord2f(0.0, 1.0); glVertex2f(  left, top)
        glTexCoord2f(1.0, 1.0); glVertex2f( right, top)
        glTexCoord2f(1.0, 0.0); glVertex2f( right, bottom)
        glTexCoord2f(0.0, 0.0); glVertex2f(  left, bottom)
        glEnd()


class StraightTube:
    def __init__(self, xLoc, yLoc, board):
        self.xLoc=xLoc
        self.yLoc=yLoc
        self.board=board
        self.balls=[]

        self.width=1
        self.height=1

        self.bottom=self.yLoc*board.scale+board.bufferY
        self.top=self.bottom+board.scale
        self.left=self.xLoc*board.scale+board.bufferX
        self.right=self.left+board.scale
        self.centerX=(self.left+self.right)/2.0

        self.type=1
        

    def __str__(self):
        return "(ST: %2d %2d)"%(self.xLoc, self.yLoc)

    def __repr__(self):
        return self.__str__()

    def update(self, dt):
        exitingBalls=[]

        for b in self.balls:
            newY=b.y-self.board.straightSpeed*dt
            if newY<self.bottom:
                newY=self.bottom
                exitingBalls.append(b)
                
            b.y=newY
            b.x=self.centerX

        if exitingBalls:
            nextPiece=self.board.getPiece(self.xLoc, self.yLoc-1)
            for b in exitingBalls:
                if not nextPiece:
                    self.board.exitBall(b)
                else:
                    nextPiece.enterBall(b)
            self.balls=[b for b in self.balls if b not in exitingBalls]

    def enterBall(self, b):
        self.balls.append(b)

    def draw(self, layer):
        if layer==-1:
            glBindTexture(GL_TEXTURE_2D, tubeStraightBackTex)
            glBegin(GL_QUADS)
            glTexCoord2f(0.0, 1.0); glVertex2f(  self.left,   self.top)
            glTexCoord2f(1.0, 1.0); glVertex2f(  self.right,  self.top)
            glTexCoord2f(1.0, 0.0); glVertex2f(  self.right,  self.bottom)
            glTexCoord2f(0.0, 0.0); glVertex2f(  self.left,   self.bottom)
            glEnd()
        elif layer==0:
            for b in self.balls:
                b.draw()
        elif layer==1:
            glBindTexture(GL_TEXTURE_2D, tubeStraightFrontTex)
            glBegin(GL_QUADS)
            glTexCoord2f(0.0, 1.0); glVertex2f(  self.left,   self.top)
            glTexCoord2f(1.0, 1.0); glVertex2f(  self.right,  self.top)
            glTexCoord2f(1.0, 0.0); glVertex2f(  self.right,  self.bottom)
            glTexCoord2f(0.0, 0.0); glVertex2f(  self.left,   self.bottom)
            glEnd()

    def extractBalls(self, x, y):
        return self.balls[:]

        


class TwistedTube:
    def __init__(self, xLoc, yLoc, board):
        self.xLoc=xLoc
        self.yLoc=yLoc
        self.board=board
        self.swballs=[]
        self.seballs=[]

        self.width=2
        self.height=2

        self.bottom=self.yLoc*board.scale+board.bufferY
        self.top=self.bottom+board.scale*self.height
        self.left=self.xLoc*board.scale+board.bufferX
        self.right=self.left+board.scale*self.width
        self.centerX=(self.left+self.right)/2.0
        self.centerY=(self.top+self.bottom)/2.0

        self.entry1=(self.left+self.centerX)/2.0
        self.entry2=(self.right+self.centerX)/2.0

        self.qWidth=self.entry1-self.left

        self.type=2

    def __str__(self):
        return "(TT: %2d %2d)"%(self.xLoc, self.yLoc)

    def __repr__(self):
        return self.__str__()

    def updateSW(self,dt):
        exitingBalls=[]
        
        for b in self.swballs:
            newY=b.y-self.board.twistedSpeed*dt

            fracY=(newY-self.bottom)/(self.top-self.bottom)

            newX=self.centerX-math.cos(fracY*math.pi)*self.qWidth
            
            if newY<self.bottom:
                newY=self.bottom
                exitingBalls.append(b)
                
            b.y=newY
            b.x=newX

        if exitingBalls:
            nextPiece=self.board.getPiece(self.xLoc, self.yLoc-1)
            for b in exitingBalls:
                if not nextPiece:
                    self.board.exitBall(b)
                else:
                    nextPiece.enterBall(b)
            self.swballs=[b for b in self.swballs if b not in exitingBalls]


    def updateSE(self,dt):
        exitingBalls=[]
        
        for b in self.seballs:
            newY=b.y-self.board.twistedSpeed*dt

            fracY=(newY-self.bottom)/(self.top-self.bottom)

            newX=self.centerX+math.cos(fracY*math.pi)*self.qWidth
            
            if newY<self.bottom:
                newY=self.bottom
                exitingBalls.append(b)
                
            b.y=newY
            b.x=newX

        if exitingBalls:
            nextPiece=self.board.getPiece(self.xLoc+1, self.yLoc-1)
            for b in exitingBalls:
                if not nextPiece:
                    self.board.exitBall(b)
                else:
                    nextPiece.enterBall(b)
            self.seballs=[b for b in self.seballs if b not in exitingBalls]

    def update(self, dt):
        self.updateSE(dt)
        self.updateSW(dt)

    def enterBall(self, b):
        if b.x<self.centerX:
            if b.y>self.centerY:
                self.seballs.append(b)
            else:
                self.swballs.append(b)                
        else:
            if b.y>self.centerY:
                self.swballs.append(b)
            else:
                self.seballs.append(b)                

    def draw(self, layer):
        if layer==-1:
            glBindTexture(GL_TEXTURE_2D, tubeTwistedBackTex)
            glBegin(GL_QUADS)
            glTexCoord2f(0.0, 1.0); glVertex2f(  self.left,   self.top)
            glTexCoord2f(1.0, 1.0); glVertex2f(  self.right,  self.top)
            glTexCoord2f(1.0, 0.0); glVertex2f(  self.right,  self.bottom)
            glTexCoord2f(0.0, 0.0); glVertex2f(  self.left,   self.bottom)
            glEnd()
        elif layer==0:
            for b in self.swballs:
                b.draw()
            for b in self.seballs:
                b.draw()                
        elif layer==1:
            glBindTexture(GL_TEXTURE_2D, tubeTwistedFrontTex)
            glBegin(GL_QUADS)
            glTexCoord2f(0.0, 1.0); glVertex2f(  self.left,   self.top)
            glTexCoord2f(1.0, 1.0); glVertex2f(  self.right,  self.top)
            glTexCoord2f(1.0, 0.0); glVertex2f(  self.right,  self.bottom)
            glTexCoord2f(0.0, 0.0); glVertex2f(  self.left,   self.bottom)
            glEnd()
        
    def extractBalls(self, x, y):
        if (x,y)==(0,0):
            return [b for b in self.swballs if b.x<self.centerX]
        elif (x,y)==(1,0):
            return [b for b in self.seballs if b.x>=self.centerX]
        elif (x,y)==(0,1):
            return [b for b in self.seballs if b.x<self.centerX]
        elif (x,y)==(1,1):
            return [b for b in self.swballs if b.x>=self.centerX]
        assert 0
                
                       

            
            
    
        

class Board:
    def __init__(self,x,y):
        self.boardSizeX=0
        self.boardSizeY=0

        self.pieceMap={}
        self.resize(x,y)

        self.logicPieces=[]

        self.scale=50.0

        self.bufferX=25.0
        self.bufferY=100.0

        self.straightSpeed,self.twistedSpeed=gSpeeds['easy']

        self.ballsOnScreen=0
        self.maxBallsOnScreen=12

        self.timeNow=0.0
        self.timeLastLaunch=-100.0

        self.colorProbabilityTotal=50.0
        self.colorProbabilities={"red":10.0,
                                 "blue":10.0,
                                 "yellow":10.0,
                                 "green":10.0,
                                 "white":7.0,
                                 "black":3.0}

        self.boardDesc=None

        self.maxErrors=-1
        self.maxTime=-1

        self.currentErrors=0

    def setBoardDesc(self,bd):
        self.boardDesc=bd
        diff=bd.get('diff','easy')

        self.straightSpeed,self.twistedSpeed=gSpeeds[diff]

        self.straightSpeed=bd.get('str_speed', self.straightSpeed)
        self.twistedSpeed=bd.get('twist_speed', self.twistedSpeed)

        self.maxBallsOnScreen=bd.get('balls',3)


        for k in self.colorProbabilities.keys():
            self.colorProbabilities[k]=0.0

        self.colorProbabilityTotal=0.0
        for colorString in bd['ball_dist'].split("/"):
            colorName,freq=colorString.split(":")

            fFreq=float(freq)
            self.colorProbabilities[colorName]=fFreq
            self.colorProbabilityTotal+=fFreq

        if 'max_errors' in bd:
            self.maxErrors=bd['max_errors']

        if 'max_time' in bd:
            self.maxTime=bd['max_time']
        

    def addMiss(self):
        self.currentErrors+=1
        

    def timeBetweenLaunches(self):
        height=self.boardSizeY*self.scale
        timeToFall=height/float(self.straightSpeed)
        return timeToFall/self.maxBallsOnScreen

    def registerBallsOnscreen(self, num):
        self.ballsOnScreen+=num

    def resize(self, x, y):
        self.boardSizeX=x
        self.boardSizeY=y

        for i in range(x):
            for j in range(y):
                self.pieceMap[(x,y)]=0

    def update(self, dt):
        self.timeNow += dt
        
        for p in self.logicPieces:
            p.update(dt)

        if self.ballsOnScreen<self.maxBallsOnScreen:
            if self.timeNow>self.timeLastLaunch+self.timeBetweenLaunches():
                column=self.choose_random_column()
                row=self.boardSizeY-1
                if self.countBalls(column,row)==0:
                    color=self.pickNewBallColor()
                    b=Ball(color)
                    self.enterBall(b,column)

        if self.maxTime>0 and self.timeNow>self.maxTime:
            #timeOut
            sound.play('buzzer')
            voiceover.queue("long-bad")

            if pygame.mixer:
                pygame.mixer.music.fadeout(200)
            fsm.popState()
            outName="out-bad-"+gBoard.boardDesc['name']
            fsm.pushState(state_level_intro.StateIntro(),outName[:-2])

        if self.maxErrors>0 and self.currentErrors>= self.maxErrors:
            #errorOut
            sound.play('buzzer')
            voiceover.queue("long-bad")

            if pygame.mixer:
                pygame.mixer.music.fadeout(200)
            fsm.popState()
            outName="out-bad-"+gBoard.boardDesc['name']
            fsm.pushState(state_level_intro.StateIntro(),outName[:-2])

            

            


    def choose_random_column(self):
        tubeLocs=self.boardDesc['tubes']
        cols=[2*x for x in tubeLocs]+[2*x+1 for x in tubeLocs]
        return random.choice(cols)
    
    def countBalls(self, x, y):
        p=self.getPiece(x,y)
        if not p:
            return 0
        
        dx=x-p.xLoc
        dy=y-p.yLoc

        ballList=p.extractBalls(dx,dy)
        return len(ballList)

    def draw(self):
        for i in range(-1,2):
            for p in self.logicPieces:
                p.draw(i)

        if self.maxTime>0:
            starting_angle=2*math.pi
            ending_angle=0
            frac=self.timeNow/float(self.maxTime)

            angle=starting_angle+(ending_angle-starting_angle)*frac

            clock_size=64

            right=800
            top=600
            left=right-clock_size
            bottom=top-clock_size
            
            draw_util.draw_quad("clock_face", left, top, right, bottom)
            draw_util.draw_rot_quad("clock_hand", left, top, right, bottom, angle)

        if self.maxErrors>0:
            error_width=64.0
            error_height=min(error_width*self.maxErrors,300.0)
            error_width=error_height/self.maxErrors

            right=800
            bottom=0
            top=bottom+error_height
            left=right-error_width

            draw_util.draw_quad("mistake_back", left, top, right, bottom)

            for error_index in range(self.currentErrors):
                error_bottom=bottom+error_width*error_index
                error_top=error_bottom+error_width
                draw_util.draw_quad("cursor_no", left, error_top, right, error_bottom)

    def exitBall(self, b):
        crate.enterBall(self, b)
        self.registerBallsOnscreen(-1)

    def enterBall(self, b, x):
        b.x=(x+0.5)*self.scale+self.bufferX
        b.y=self.scale*self.boardSizeY+self.bufferY
        p=self.getPiece(x,self.boardSizeY-1)
        assert p
        p.enterBall(b)
        self.registerBallsOnscreen(1)
        self.timeLastLaunch=self.timeNow

    def pickNewBallColor(self):
        r=random.uniform(0.0,self.colorProbabilityTotal)

        probKeys=self.colorProbabilities.keys()
        probKeys.sort()
        
        for c in probKeys:
            num=self.colorProbabilities[c]
            if r<=num:
                return c
            r -= num
        assert 0
        
    def setPiece(self, x, y, pieceType):
        p=pieceFactory(pieceType, x, y, self)

        oldPieces=[]
        
        for i in range(x,x+p.width):
            for j in range(y,y+p.height):
                if (i,j) in self.pieceMap:
                    oldPiece=self.pieceMap[(i,j)]
                    if oldPiece not in oldPieces:
                        oldPieces.append(oldPiece)
                self.pieceMap[(i,j)]=p

        self.logicPieces=[mp for mp in self.logicPieces if mp not in oldPieces]

        self.logicPieces.append(p)

    def getPiece(self, x, y):

        return self.pieceMap.get((x,y), None)
    
def pieceFactory(pieceType, x, y, board):
    if pieceType==1:
        return StraightTube(x,y,board)
    elif pieceType==2:
        return TwistedTube(x,y,board)
    
    assert 0
    

def init():
    global tubeStraightFrontTex
    global tubeStraightBackTex
    global tubeTwistedFrontTex
    global tubeTwistedBackTex

    tubeStraightFrontTex = texMgr.loadTexture("data/pipe_straight_front.png")
    tubeStraightBackTex = texMgr.loadTexture("data/pipe_straight_back.png")
    tubeTwistedFrontTex = texMgr.loadTexture("data/pipe_twisted_front.png")
    tubeTwistedBackTex = texMgr.loadTexture("data/pipe_twisted_back.png")

    global cursorTwistTex
    global cursorNoTex
    cursorTwistTex = texMgr.loadTexture("data/cursor_twist.png", "cursor_twist")
    cursorNoTex = texMgr.loadTexture("data/cursor_no.png", "cursor_no")

    global crateTex
    crateTex = texMgr.loadTexture("data/crate.png")

    colorDict=['white',
               'black',
               'red',
               'blue',
               'green',
               'yellow']

    for i,c in enumerate(colorDict):
        ballTextures[c]=texMgr.loadTexture("data/%sBall.png"%c)
        cb_ballTextures[c]=texMgr.loadTexture("data/cb-%sBall.png"%c)
        
    global gBoard
    gBoard=Board(14,10)

    twists=[(3,4), (6,4), (9,4)]

    for x in range(14):
        for y in range(10):
            gBoard.setPiece(x,y, 1)

    for (x,y) in twists:
        gBoard.setPiece(x,y, 2)

    crate.init()
           

def drawTubes(bFront):
    for x in range(7):
        for y in range(4):
            tubeIcon=tubeMap[y][x]
            if tubeIcon==0:
                continue

            i=-1

            if (bFront):
                if tubeIcon==1:
                    i=tubeStraightFrontTex
                elif tubeIcon==2:
                    i=tubeTwistedFrontTex
            else:
                if tubeIcon==1:
                    i=tubeStraightBackTex
                elif tubeIcon==2:
                    i=tubeTwistedBackTex

            glBindTexture(GL_TEXTURE_2D, i)
                
            left=x*SIZE
            right=left+SIZE
            top=500-y*SIZE
            bottom=top-SIZE

            middleX=(left+right)/2
            middleY=(bottom+top)/2

            
            glBegin(GL_QUADS)

            if tubeIcon==2:            
                glTexCoord2f(0.0, 1.0); glVertex2f(  left, top)
                glTexCoord2f(1.0, 1.0); glVertex2f( right, top)
                glTexCoord2f(1.0, 0.0); glVertex2f( right, bottom)
                glTexCoord2f(0.0, 0.0); glVertex2f(  left, bottom)
            else:
                glTexCoord2f(0.0, 1.0); glVertex2f(  left, top)
                glTexCoord2f(1.0, 1.0); glVertex2f( middleX, top)
                glTexCoord2f(1.0, 0.0); glVertex2f( middleX, middleY)
                glTexCoord2f(0.0, 0.0); glVertex2f(  left, middleY)
                glTexCoord2f(0.0, 1.0); glVertex2f( middleX, top)
                glTexCoord2f(1.0, 1.0); glVertex2f( right, top)
                glTexCoord2f(1.0, 0.0); glVertex2f( right, middleY)
                glTexCoord2f(0.0, 0.0); glVertex2f( middleX, middleY)

                glTexCoord2f(0.0, 1.0); glVertex2f(  left, middleY)
                glTexCoord2f(1.0, 1.0); glVertex2f( middleX, middleY)
                glTexCoord2f(1.0, 0.0); glVertex2f( middleX, bottom)
                glTexCoord2f(0.0, 0.0); glVertex2f(  left, bottom)
                glTexCoord2f(0.0, 1.0); glVertex2f( middleX, middleY)
                glTexCoord2f(1.0, 1.0); glVertex2f( right, middleY)
                glTexCoord2f(1.0, 0.0); glVertex2f( right, bottom)
                glTexCoord2f(0.0, 0.0); glVertex2f( middleX, bottom)
                
            glEnd()



def drawCursor():
    twistType=identifyTwistLocation()
    if twistType>0:
        glBindTexture(GL_TEXTURE_2D, cursorTwistTex)
    else:
        glBindTexture(GL_TEXTURE_2D, cursorNoTex)
        

    tx,ty=gGridMousePos
    mx=tx*gBoard.scale+gBoard.bufferX
    my=ty*gBoard.scale+gBoard.bufferY
    
    left=mx-CURSORSIZE/2
    right=left+CURSORSIZE
    
    bottom=my-CURSORSIZE/2
    top=bottom+CURSORSIZE

    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 1.0); glVertex2f(  left, top)
    glTexCoord2f(1.0, 1.0); glVertex2f( right, top)
    glTexCoord2f(1.0, 0.0); glVertex2f( right, bottom)
    glTexCoord2f(0.0, 0.0); glVertex2f(  left, bottom)
    glEnd()

    

def draw():
    glColor3f(1.0, 1.0, 1.0)

    
    gBoard.draw()

    #drawTubes(False)

    #for b in allBalls:
    #    b.draw()

    #drawTubes(True)

    crate.drawCrates(gBoard)

    drawCursor()



MAX_BALLS = 48

gTimeNow=0.0
gBallCount=12

def update(dt):
    global gTimeNow

    gTimeNow+= dt
    
    gBoard.update(dt)
    global gMousePos

    mx,my=mouse.get_pos()

    gMousePos=mx,my

    global gGridMousePos

    gx=int((mx-gBoard.bufferX+gBoard.scale/2)/gBoard.scale)
    gy=int((my-gBoard.bufferY+gBoard.scale/2)/gBoard.scale)

    if gx<1:
        gx=1
    if gx>=gBoard.boardSizeX:
        gx=gBoard.boardSizeX-1

    if gy<=1:
        gy=1
    if gy>=gBoard.boardSizeY:
        gy=gBoard.boardSizeY-1
    
    gGridMousePos=(gx,gy)

    crate.update(dt)
                   

def handleMouseMove(x,y):
    global gMousePos
    gMousePos=(x,y)

            
def handleMouseButton(bDown, button):
    if not bDown:
        return

    twistType=identifyTwistLocation()

    gx,gy=gGridMousePos
    
    if twistType<0:
        # error!
        return

    elif twistType==1:
        # remove 4 straights, place 1 twisted
        ballList=(gBoard.getPiece(gx-1, gy-1).extractBalls(0,0)+
                  gBoard.getPiece(gx-1, gy  ).extractBalls(0,0)+
                  gBoard.getPiece(gx  , gy  ).extractBalls(0,0)+
                  gBoard.getPiece(gx  , gy-1).extractBalls(0,0))
        
        gBoard.setPiece(gx-1, gy-1, 2)
        for b in ballList:
            gBoard.getPiece(gx-1, gy-1).enterBall(b)
        sound.play("twist")
    elif twistType==2:
        # remove 1 twisted, place 4 straights

        p=gBoard.getPiece(gx-1, gy-1)
        ballList=[p.extractBalls(0,0),
                  p.extractBalls(0,1),
                  p.extractBalls(1,1),
                  p.extractBalls(1,0)]

        gBoard.setPiece(gx-1, gy-1, 1)
        for b in ballList[0]:
            gBoard.getPiece(gx-1, gy-1).enterBall(b)
        
        gBoard.setPiece(gx-1, gy  , 1)
        for b in ballList[1]:
            gBoard.getPiece(gx-1, gy).enterBall(b)
        
        gBoard.setPiece(gx  , gy  , 1)
        for b in ballList[2]:
            gBoard.getPiece(gx, gy).enterBall(b)
        
        gBoard.setPiece(gx  , gy-1, 1)
        for b in ballList[3]:
            gBoard.getPiece(gx, gy-1).enterBall(b)
        sound.play("twist")
        
        
def getPieceType(piece):
    if not piece:
        return 0
    return piece.type

def identifyTwistLocation():
    gx,gy=gGridMousePos

    deltas=[(-1, -1),
            (-1,  0),
            ( 0,  0),
            ( 0, -1)]

    pieces=[gBoard.getPiece(gx+d[0],gy+d[1]) for d in deltas]

    types=[getPieceType(p) for p in pieces]

    if types==[1,1,1,1]:
        return 1

    if (types==[2,2,2,2] and
        id(pieces[0])==id(pieces[1]) and
        id(pieces[0])==id(pieces[2]) and
        id(pieces[0])==id(pieces[3])):
        return 2

    return -1

class StateGame(fsm.State):
    def __init__(self):
        fsm.State.__init__(self)

    def init(self, levelDesc):
        glClearColor(0.85, 0.85, 1.0, 1.0)
        global tubeStraightFrontTex
        global tubeStraightBackTex
        global tubeTwistedFrontTex
        global tubeTwistedBackTex

        tubeStraightFrontTex = texMgr.loadTexture("data/pipe_straight_front.png", "tube_s_f")
        tubeStraightBackTex = texMgr.loadTexture("data/pipe_straight_back.png",   "tube_s_b")
        tubeTwistedFrontTex = texMgr.loadTexture("data/pipe_twisted_front.png",   "tube_t_f")
        tubeTwistedBackTex = texMgr.loadTexture("data/pipe_twisted_back.png",     "tube_t_b")

        global cursorTwistTex
        global cursorNoTex
        cursorTwistTex = texMgr.loadTexture("data/cursor_twist.png", "cursor_twist")
        cursorNoTex = texMgr.loadTexture("data/cursor_no.png", "cursor_no")

        global crateTex
        crateTex = texMgr.loadTexture("data/crate.png", "crate")

        texMgr.loadTexture("data/clock_face.png", "clock_face")
        texMgr.loadTexture("data/clock_hand.png", "clock_hand")
        texMgr.loadTexture("data/mistake_back.png", "mistake_back")


        colorDict=['white',
                   'black',
                   'red',
                   'blue',
                   'green',
                   'yellow']

        for i,c in enumerate(colorDict):
            ballTextures[c]=texMgr.loadTexture("data/%sBall.png"%c, "ball_%s"%c)
            cb_ballTextures[c]=texMgr.loadTexture("data/cb-%sBall.png"%c, "cb-ball_%s"%c)


        global gBoard
        gBoard=Board(14,10)
        gBoard.setBoardDesc(levelDesc)

        
        twists=levelDesc.get('twists',[])
        
        for x in levelDesc['tubes']:
            for y in range(gBoard.boardSizeY):
                gBoard.setPiece(2*x,y, 1)
                gBoard.setPiece(2*x+1,y, 1)

        for (x,y) in twists:
            gBoard.setPiece(x,y, 2)

        crate.init(levelDesc)

    def activateApp(self, bNowActive):
        if not bNowActive:
            fsm.pushState(state_pause_menu.StatePauseMenu())

            return True
        return False

    def draw(self):
        glColor3f(1.0, 1.0, 1.0)


        gBoard.draw()
        crate.drawCrates(gBoard)

        drawCursor()


    def handleKey(self, key, unicode):
        if key==K_ESCAPE:
            fsm.pushState(state_pause_menu.StatePauseMenu())

        if key==K_s:
            options.g_sounds = not options.g_sounds
        if key==K_m:
            options.g_music = not options.g_music
        if key==K_y:
            options.g_invert_mouse = not options.g_invert_mouse
        if key==K_c:
            options.g_color_assist = not options.g_color_assist

        voiceKeys={K_1:1,
                   K_2:2,
                   K_3:3,
                   K_4:4,
                   K_5:5,
                   K_6:6,
                   K_7:7,
                   K_8:8,
                   K_9:9,
                   K_0:10}
                   
        if key in voiceKeys:
            voiceover.select_channel(voiceKeys[key])

            

    def handleMouseButton(self, bDown, buttonIndex):
        if not bDown:
            return False

        twistType=identifyTwistLocation()

        gx,gy=gGridMousePos
    
        if twistType<0:
            # error!
            return True

        elif twistType==1:
            # remove 4 straights, place 1 twisted
            ballList=(gBoard.getPiece(gx-1, gy-1).extractBalls(0,0)+
                      gBoard.getPiece(gx-1, gy  ).extractBalls(0,0)+
                      gBoard.getPiece(gx  , gy  ).extractBalls(0,0)+
                      gBoard.getPiece(gx  , gy-1).extractBalls(0,0))
        
            gBoard.setPiece(gx-1, gy-1, 2)
            for b in ballList:
                gBoard.getPiece(gx-1, gy-1).enterBall(b)
            sound.play("twist")
        elif twistType==2:
            # remove 1 twisted, place 4 straights

            p=gBoard.getPiece(gx-1, gy-1)
            ballList=[p.extractBalls(0,0),
                      p.extractBalls(0,1),
                      p.extractBalls(1,1),
                      p.extractBalls(1,0)]

            gBoard.setPiece(gx-1, gy-1, 1)
            for b in ballList[0]:
                gBoard.getPiece(gx-1, gy-1).enterBall(b)
        
            gBoard.setPiece(gx-1, gy  , 1)
            for b in ballList[1]:
                gBoard.getPiece(gx-1, gy).enterBall(b)
        
            gBoard.setPiece(gx  , gy  , 1)
            for b in ballList[2]:
                gBoard.getPiece(gx, gy).enterBall(b)
        
            gBoard.setPiece(gx  , gy-1, 1)
            for b in ballList[3]:
                gBoard.getPiece(gx, gy-1).enterBall(b)
            sound.play("twist")
        
        
        return True

    def update(self, dt):
        global gTimeNow
        gTimeNow+= dt

        if pygame.mixer:
            if options.g_music:
                # music
                if not pygame.mixer.music.get_busy():
                    songs=["data/music/woody-norm-2.ogg",
                           "data/music/woody-norm-2.ogg",
                           "data/music/woody-robot-2.ogg"]
    
                
                    pygame.mixer.music.load(random.choice(songs))
                    pygame.mixer.music.play()
            else:
                if pygame.mixer.music.get_busy():
                    pygame.mixer.music.fadeout(200)
        
    
        gBoard.update(dt)
        global gMousePos

        mx,my=mouse.get_pos()

        gMousePos=mx,my

        global gGridMousePos

        gx=int((mx-gBoard.bufferX+gBoard.scale/2)/gBoard.scale)
        gy=int((my-gBoard.bufferY+gBoard.scale/2)/gBoard.scale)

        if gx<1:
            gx=1
        if gx>=gBoard.boardSizeX:
            gx=gBoard.boardSizeX-1

        if gy<=1:
            gy=1
        if gy>=gBoard.boardSizeY:
            gy=gBoard.boardSizeY-1
    
        gGridMousePos=(gx,gy)

        if not crate.update(dt):
            pygame.mixer.music.fadeout(200)
            fsm.popState()

            outName="out-good-"+gBoard.boardDesc['name']
            fsm.pushState(state_level_intro.StateIntro(),outName[:-2])
            voiceover.queue("long-good",force=1)
            

        return True


    

        
        
    
    
