import cv2
import numpy as np
import time
import subprocess
import serial
from tkinter import *
from tkinter import font #to set button police

#-------------------------
#IMAGE PROCESSING CONSTANT
#-------------------------
GREEN_LOW_THRESHOLD = 60 #240 #s'arranger pour avoir image sombre
MAX_PIP_AREA=200 
MIN_DICE_AREA=2000
MIN_PIP_AREA =50
#CROP : [0:720,100:870]=[y:y+h,x:x+L]
HEIGHT_START=0
HEIGHT_END=720
WIDTH_START=100
WIDTH_END=870
#-------------
#GAME CONSTANT
#-------------
NBR_TOKENS=6
#-------------
#COMMUNICATION
#-------------
CAMERA_COM=0
ARDUINO_COM="/dev/ttyACM0"
ser=serial.Serial(ARDUINO_COM,9600)
#---------
#INTERFACE
#---------

def get_position(player):
    #get players position from Arduino serial com
    ser.write('g')
    player[1]=ser.read()
    print("position recue"+player[1])
    
def set_position(position,sense):
    #place the board in front of the player
    ser.write('s'+sense+position)
    while ser.read()!="right position":
        pass
    
def wait_dice():
    while number_of_dice()!=(0,0):
        print("wait for 0 dice")
    while number_of_dice() !=(3,0): #3 dice and none of them is erroneous
        print("wait for 3 dice")

def update_interface(strvar,content):
    strvar.set(content)
    root.update()
 
def game_charge(players):
    charge=Toplevel()
    charge['bg']='grey'
    #charge.attributes('-fullscreen',1)
    ### Creat static graphic content
    btnExit=Button(charge,bg="blue",text="Exit",font=police,width=15,height=5,command=charge.destroy).grid(row=10+len(players),column=1)
    lblUnderlined=Label(charge,bg="grey",text="------------------",font=police).grid(row=2,column=3)
    lblPlayersResult=Label(charge,bg="grey",text="Results",font=police).grid(row=3,column=4)
    lblTokenRemTitle=Label(charge,bg="grey",text="Tokens remaining",font=police).grid(row=7+len(players),column=3)
    lblToken=Label(charge,bg="grey",text="Tokens",font=police).grid(row=3,column=5)
    
    ### Create dynamic graphic content
    scoreList=list()
    tokenList=list()
    lblNameList=list()
    lblScoreList=list()
    lblTokenList=list()
    for i in range (0,len(players)):
        scoreList.append(StringVar())
        tokenList.append(StringVar())
        lblNameList.append(Label(charge,bg="grey",text=players[i][0],font=police).grid(row=4+i,column=3))
        lblScoreList.append(Label(charge,bg="grey",textvariable=scoreList[i],font=police).grid(row=4+i,column=4))
        lblTokenList.append(Label(charge,bg="grey",textvariable=tokenList[i],font=police).grid(row=4+i,column=5))
    nameTurn=StringVar()
    lblNameTurn=Label(charge,bg="grey",textvariable=nameTurn,font=police).grid(row=1,column=3)
    tokenRemNbr=StringVar()
    lblTokenRemNbr=Label(charge,bg="grey",textvariable=tokenRemNbr,font=police).grid(row=7+len(players),column=4)   

    root.update()
   
    ### Algo 
    remTokens=NBR_TOKENS
    update_interface(tokenRemNbr,remTokens)
    index=0
    counter=0
    
    while remTokens != 0 :
        set_position(players[index][1],'h')
        update_interface(nameTurn,players[index][0]+"'s Turn")
        wait_dice()
        players[index][2]=score(raw_result())
        update_interface(scoreList[index],players[index][2])
        counter=counter+1
        if counter!=len(players):
            index=index+1
            if index+1>len(players):
                index=0
        else:
            indexLosers=det_losers(players,list(range(0,len(players),1)))
            #in case of Losers equality --> go on until one remaining loser beginning with the last loser
            indexBis=len(indexLosers)-1
            counterBis=0
            while len(indexLosers)!=1:
                set_position(players[indexLosers[indexBis]][1],'a')
                update_interface(nameTurn,players[indexLosers[indexBis]][0]+"'s Turn")
                wait_dice()
                players[indexLosers[indexBis]][2]=score(raw_result())
                counterBis=counterBis+1
                if counterBis!=len(indexLosers):
                    indexBis=indexBis-1
                    if indexBis<0:
                        indexBis=len(indexLosers)-1
                else:      
                    indexLosers=det_losers(players,indexLosers)
                    counterBis=0
                
            #give tokens to looser    
            indexWinners=det_winners(players,list(range(0,len(players),1)))
            players[indexLosers[0]][3]=players[indexLosers[0]][3]+tokens(players[indexWinners[0]][2])
            remTokens=remTokens-tokens(players[indexWinners[0]][2])
            if remTokens<0:
                players[indexLosers[0]][3]=players[indexLosers[0]][3]+remTokens
                remTokens=0              
            update_interface(tokenList[indexLosers[0]],players[indexLosers[0]][3])            
            update_interface(tokenRemNbr,remTokens)
            #the looser gonna start the next turn
            index=indexLosers[0]
            counter=0
            reset_score(players)
    indexRemPlayers=rem_players(players)#players remaining for the discharge part
    if len(indexRemPlayers)==1:
        end_game(players[indexRemPlayers[0]])
    else:
        charge.destroy()
        game_discharge(indexRemPlayers,players)

def game_discharge(indexRemPlayers,players):#indexplayersDis includes indexes of players who gonna play the discharge ; players ALSO includes players who gonna NOT play the discharge part

    ### Creat new list with discharge players
    playersDis=[[] for i in range(len(indexRemPlayers))]
    for i in range(0,len(indexRemPlayers),1):
        playersDis[i]=players[indexRemPlayers[i]]
        
    discharge=Toplevel()
    #discharge.attributes('-fullscreen',1)
    discharge['bg']='grey'
    #btnBacktoMenu=Button(discharge,bg="blue",text="Back",font=police,width=15,height=5,command=discharge.destroy).pack(side=BOTTOM)

    ### Creat static graphic content
    btnExit=Button(discharge,bg="blue",text="Exit",font=police,width=15,height=5,command=discharge.destroy).grid(row=10+len(playersDis),column=1)
    lblUnderlined=Label(discharge,bg="grey",text="------------------",font=police).grid(row=2,column=3)
    lblPlayersResult=Label(discharge,bg="grey",text="Results",font=police).grid(row=3,column=4)
    lblToken=Label(discharge,bg="grey",text="Tokens",font=police).grid(row=3,column=5)
    lblMaxRollTitle=Label(discharge,bg="grey",text="# Max of rolls = ",font=police).grid(row=7,column=1)
    ### Create dynamic graphic content
    scoreList=list()
    tokenList=list()
    nameList=list() #no more static (players exit the game one by one so the list changes)
    lblNameList=list()
    lblScoreList=list()
    lblTokenList=list()
    for i in range (0,len(playersDis)):
        scoreList.append(StringVar())
        tokenList.append(StringVar())
        nameList.append(StringVar())
        lblNameList.append(Label(discharge,bg="grey",textvariable=nameList[i],font=police).grid(row=4+i,column=3))
        lblScoreList.append(Label(discharge,bg="grey",textvariable=scoreList[i],font=police).grid(row=4+i,column=4))
        lblTokenList.append(Label(discharge,bg="grey",textvariable=tokenList[i],font=police).grid(row=4+i,column=5))
    nameTurn=StringVar()
    lblNameTurn=Label(discharge,bg="grey",textvariable=nameTurn,font=police).grid(row=1,column=3)
    rollMaxNbr=StringVar()
    lblRollMaxNbr=Label(discharge,bg="grey",textvariable=rollMaxNbr,font=police).grid(row=8,column=1)

    root.update()
    for i in range(len(playersDis)):
        update_interface(nameList[i],playersDis[i][0])
        update_interface(tokenList[i],playersDis[i][3])
        
    ### Algo
    index=0 #begin at the first discharge player
    while len(playersDis)>1:
        nbrRolls=3
        update_interface(rollMaxNbr,nbrRolls)
        counter=0        
        while counter!=len(rem_players(playersDis)):
            set_position(playersDis[index][1],'h')
            update_interface(nameTurn,playersDis[index][0]+"'s Turn")
            wait_dice()
            playersDis[index][2]=score(raw_result())
            update_interface(scoreList[index],playersDis[index][2])
            counter=counter+1
            for j in range(1,nbrRolls,1):
                p = subprocess.Popen('btn_next.py',stdout=subprocess.PIPE,shell=True)
                res,err= p.communicate()
                discharge.focus_force()
                if res==b'n\r\n':
                    break
                else:
                    wait_dice()
                    playersDis[index][2]=score(raw_result())
                    update_interface(scoreList[index],playersDis[index][2])
            #first player of the turn decides the number max of rolls
            if counter==1:
                nbrRolls=j+1
                update_interface(rollMaxNbr,nbrRolls-1)
            index=index+1    
            if index+1>len(playersDis):
                index=0
        indexLosers=det_losers(playersDis,indexRemPlayers)
        #in case of Losers equality --> go on until one remaining looser
        indexBis=len(indexLosers)-1
        counterBis=0
        while len(indexLosers)!=1:
            set_position(playersDis[indexBis][1],'a')
            wait_dice()
            playersDis[indexLosers[indexBis]][2]=score(raw_result())
            update_interface(scoreList[indexBis],playersDis[indexBis][2])
            counterBis=counterBis+1
            if counterBis!=len(indexLosers):
                indexBis=indexBis-1
                if indexBis<0:
                    indexBis=len(indexLosers)-1
            else:      
                indexLosers=det_losers(playersDis,indexLosers)
        #save the winners result before determining how many winners
        indexWinners=det_winners(playersDis,indexRemPlayers)
        nbrTokens=tokens(playersDis[indexWinners[0]][2])
        #in case of winners equality --> go on until one remaining winner
        indexTer=len(indexWinners)-1
        counterTer=0
        while len(indexWinners)!=1:
            set_position(playersDis[indexTer][1],'a')
            wait_dice()
            playersDis[indexWinners[indexTer]][2]=score(raw_result())
            update_interface(scoreList[indexTer],playersDis[indexTer][2])
            counterTer=counterTer+1
            if counterTer!=len(indexWinners):
                indexTer=indexTer-1
                if indexTer<0:
                    indexTer=len(indexWinners)-1
            else:
                indexWinners=det_winners(playersDis,indexWinners)
                
        #Give tokens to looser and get tokens from winner 
        playersDis[indexLosers[0]][3]=playersDis[indexLosers[0]][3]+nbrTokens
        playersDis[indexWinners[0]][3]=playersDis[indexWinners[0]][3]-nbrTokens
        if playersDis[indexWinners[0]][3]<0:
            playersDis[indexLosers[0]][3]=playersDis[indexLosers[0]][3]+playersDis[indexWinners[0]][3]
            playersDis[indexWinners[0]][3]=0
        #Remove players who don't have token anymore
        indexToRemove=remove_players(playersDis,indexRemPlayers)
        reset_score(playersDis)
        for i in range(0,len(playersDis)):
            update_interface(nameList[i],playersDis[i][0])
            update_interface(scoreList[i],playersDis[i][2])
            update_interface(tokenList[i],playersDis[i][3])
    end_game(playersDis)

def end_game(loser):
    endGame=Toplevel()
    #endGame.attributes('-fullscreen',1)
    endGame['bg']='grey'
    
    lblTitle=Label(endGame,bg="grey",text="And the loser is ... ",font=police).pack(padx=10,pady=30)
    lblLoser=Label(endGame,bg="grey",text=loser[0],font=police).pack(padx=10,pady=30)
    btnBacktoMenu=Button(endGame,bg="blue",text="Back",font=police,width=15,height=5,command=endGame.destroy).pack(side=BOTTOM)
    
def menu_select_position(page,entries,players):
    select_position=Toplevel()
    #select_position.attributes('-fullscreen',1)
    select_position['bg']='grey'
    btnBack=Button(select_position,bg="blue",text="Back",font=police,width=15,height=5,command=select_position.destroy).pack(side=BOTTOM)

    lblTitle=Label(select_position,bg="grey",text="Place the board in front of the corresponding player and confirm position",font=police).pack(padx=10,pady=30)
    lblPlayer=[[] for i in range(len(players))]
    btnPosition=[[] for i in range(len(players))]
    for i in range(0,len(players),1):
        players[i]=[entries[i].get(),0,0,0,0]
        lblPlayer[i]=Label(select_position,bg="grey",text=players[i][0],font=police).pack(padx=10,pady=30)
        btnPosition[i]=Button(select_position,bg="blue",text="Confirm",font=police,width=20,height=4,command= lambda *args:get_position(players[i])).pack(anchor=CENTER) 
    btnChargePart=Button(select_position,bg="blue",text="START",font=police,width=20,height=8,command= lambda *args: game_charge(players)).pack(anchor=CENTER)
    
def confirm(nbr_txt,page):
    nbr=int(nbr_txt)
    e=[[] for i in range(nbr)]
    if nbr!=0 :
        for i in range(0,nbr):
            lblName=Label(page,bg="grey",text="Enter player's name",font=police).pack(padx=10,pady=5)
            e[i]=Entry(page,font=police,width=20)
            e[i].pack(anchor=CENTER,pady=15)
    players=[[] for i in range(nbr)]
    btnConfirmName=Button(page,bg="blue",text="Confirm",font=police,width=20,height=8,command= lambda *args:menu_select_position(page,e,players)).pack(anchor=CENTER) 
     
def menu_select_player(): #set number of players
    select_player=Toplevel()
    #select_player.attributes('-fullscreen',1)
    select_player['bg']='grey'
    btnBack=Button(select_player,bg="blue",text="Back",font=police,width=15,height=5,command=select_player.destroy).pack(side=BOTTOM)
    lblSelect=Label(select_player,bg="grey",text="Enter the number of players",font=police).pack(padx=10,pady=30)
    nbr_player=0
    
    e=Entry(select_player,font=police,width=20,textvariable=nbr_player)
    e.pack(anchor=CENTER,pady=15)
    btnConfirmNbr=Button(select_player,bg="blue",text="Confirm",font=police,width=20,height=8,command= lambda *args:confirm(e.get(),select_player)).pack(anchor=CENTER)
    # ajouter state=disabled sur bouton

       
def menu_setting(): # settings page
    setting=Toplevel()
    #setting.attributes('-fullscreen',1)
    setting['bg']='grey'
    btnBack=Button(setting,bg="blue",text="Back",font=police,width=15,height=5,command=setting.destroy).pack(side=BOTTOM)

    
def menu_new(): #new game or settings
    new=Toplevel()
    #new.attributes('-fullscreen',1)
    new['bg']='grey'

    btnBack=Button(new,bg="blue",text="Back",font=police,width=15,height=5,command=new.destroy).pack(side=BOTTOM)
    btnNew=Button(new,bg="blue",text="New game",font=police,width=20,height=8,command=menu_select_player).pack(side=LEFT)
    btnSet=Button(new,bg="blue",text="Setting",font=police,width=20,height=8,command=menu_setting).pack(side=RIGHT)
    
#---------------------------------------
#IMAGE PROCESSING AND COUNTING FUNCTIONS
#---------------------------------------    
def number_of_dice():
    cam=cv2.VideoCapture(CAMERA_COM)
    cam.set(cv2.CAP_PROP_FRAME_WIDTH, 960)
    cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
    s, img = cam.read()
    if s:    # frame captured without any errors
        img=img[HEIGHT_START:HEIGHT_END,WIDTH_START:WIDTH_END]
        ### Split into BGR (RGB)
        blue = cv2.split(img)[0]
        green = cv2.split(img)[1]
        red = cv2.split(img)[2]
        ### Fetch the dice contours using green threshold and invert.
        diceblocks = cv2.threshold(green, GREEN_LOW_THRESHOLD, 255, 1)
        invdiceblocks = 255 - diceblocks[1]
        ### Find the contours
        (_,pyramids,hierarchy) = cv2.findContours(invdiceblocks.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

        ### Look at each dice face and determine number of pips
        nbr_dice=0
        dice_results = [0,0,0,0,0,0]
        wrongdice = 0

        for pyramid in pyramids:
            pips = 0
            if cv2.contourArea(pyramid)>MIN_DICE_AREA:
                # count the number of dice
                nbr_dice=nbr_dice+1
                rect = cv2.minAreaRect(pyramid)
                floatBox = cv2.boxPoints(rect)
                intBox = np.int0(floatBox)
                a,b,c,d = cv2.boundingRect(intBox)
                subimage = invdiceblocks[b:b+d,a:a+c]
                _,pip_contours, subhierarchy = cv2.findContours(subimage.copy(),cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
                for pip in pip_contours:
                        # count pips only if they are of a certain size
                        if cv2.contourArea(pip) >= MIN_PIP_AREA and cv2.contourArea(pip)<=MAX_PIP_AREA :
                                pips = pips + 1
                        
                # log erroneous dice
                if pips > 6 or pips == 0:
                    wrongdice = wrongdice + 1
                    print("pips issue")
                else:
                    dice_results[pips - 1] = dice_results[pips - 1] + 1
            else:
                pass
        print(wrongdice,nbr_dice)
        return(nbr_dice,wrongdice)
                    
    else:
        print("camera issue")
        return(-1)
                

def raw_result():
    cam=cv2.VideoCapture(CAMERA_COM)
    cam.set(cv2.CAP_PROP_FRAME_WIDTH, 960)
    cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
    s, img = cam.read()
    if s:    # frame captured without any errors
        img=img[HEIGHT_START:HEIGHT_END,WIDTH_START:WIDTH_END]
        ### Split into BGR (RGB)
        blue = cv2.split(img)[0]
        green = cv2.split(img)[1]
        red = cv2.split(img)[2]
        ### Fetch the dice contours using green threshold and invert.
        diceblocks = cv2.threshold(green, GREEN_LOW_THRESHOLD, 255, 1)
        invdiceblocks = 255 - diceblocks[1]
        ### Find the contours
        (_,pyramids,hierarchy) = cv2.findContours(invdiceblocks.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

        ### Look at each dice face and determine number of pips
        nbr_dice=0
        dice_results = [0,0,0,0,0,0]
        wrongdice = 0

        for pyramid in pyramids:
            pips = 0
            if cv2.contourArea(pyramid)>MIN_DICE_AREA:
                # count the number of dice
                nbr_dice=nbr_dice+1
                rect = cv2.minAreaRect(pyramid)
                floatBox = cv2.boxPoints(rect)
                intBox = np.int0(floatBox)
                a,b,c,d = cv2.boundingRect(intBox)
                subimage = invdiceblocks[b:b+d,a:a+c]
                _,pip_contours, subhierarchy = cv2.findContours(subimage.copy(),cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
                for pip in pip_contours:
                        # count pips only if they are of a certain size
                        if cv2.contourArea(pip) >= MIN_PIP_AREA and cv2.contourArea(pip)<=MAX_PIP_AREA :
                                pips = pips + 1
                        
                # log erroneous dice
                if pips > 6 or pips == 0:
                    wrongdice = wrongdice + 1
                    print("pips issue")
                else:
                    dice_results[pips - 1] = dice_results[pips - 1] + 1
            else:
                pass
        return(dice_results)
                    
    else:
        print("camera issue")
        return(-1)
    
def score(result):
    # mac detection
    if result[0]==3:
        return(680)
    elif result[0]==2:
        for i in range (1,6,1):
            if result[i]==1:
                return(675+(i-1))
            
    # 421 detection
    if result [0]==1 and result[1]==1 and result[3]==1:
        return (681)
    
    #straight detection
    for i in range (0,4,1):
        if result[i]==1 and result[i+1]==1 and result[i+2]==1:
            return(666+i)

    #three of a kind detection
    for i in range (1,6,1):
        if result[i]==3:
            return(669+i)

    #classic result return
    temp_dice_detected=0
    temp_score=0
    for i in range (5,-1,-1):
        if result[i]==2:
            if temp_dice_detected==0:
                temp_score=(i+1)*100+(i+1)*10
                result[i]=0
            elif temp_dice_detected==1:
                temp_score=temp_score+(i+1)*10+(i+1)*1
                return(temp_score)
            temp_dice_detected=temp_dice_detected+2
        elif result[i]==1:
            if temp_dice_detected==0:
                temp_score=(i+1)*100
            elif temp_dice_detected==1:
                temp_score=temp_score+(i+1)*10
            elif temp_dice_detected==2:
                temp_score=temp_score+(i+1)*1
                return(temp_score)
            temp_dice_detected=temp_dice_detected+1            

def tokens(score):
    if score==681: #421
        return 8
    if score==680: #mac 7
        return 7
    if score==679:
        return 6
    if score==678:
        return 5
    if score==677:
        return 4
    if score==676:
        return 3
    if score==675: #mac 2
        return 2
    if score<=674 and score>669: #x3
        return 3
    if score<=669 and score >=666: #straight
        return 2
    if score<666:
        return 1

def det_winners(players,potentialWinnersIndex):
    nbr=len(potentialWinnersIndex)
    winnersIndex=list()
    potentialWinners=[[]for i in range(nbr)]
    for i in range(0,nbr,1):
        potentialWinners[i]=players[potentialWinnersIndex[i]]

    maximum=max(c for a,b,c,d,e in potentialWinners)
    for i in range(0,nbr,1):
        if potentialWinners[i][2]==maximum:
            winnersIndex.append(i)
    return(winnersIndex)
    
def det_losers(players,potentialLosersIndex):
    #print("index pot: "+str(potentialLosersIndex))
    nbr=len(potentialLosersIndex)
    #print("nombre: "+str(nbr))
    losersIndex=list()
    potentialLosers=[[]for i in range(nbr)]
    for i in range(0,nbr,1):
        potentialLosers[i]=players[potentialLosersIndex[i]]
    #print("pot losers: "+str(potentialLosers))
    minimum=min(c for a,b,c,d,e in potentialLosers)
    #print(str(minimum))
    for i in range(0,nbr,1):
        if potentialLosers[i][2]==minimum:
            losersIndex.append(i)
    #print("index losers: "+str(losersIndex))
    return(losersIndex)

def rem_players(players):
    indexRemPlayers=list()
    for i in range(0,len(players),1):
        if players[i][3]!=0:
            indexRemPlayers.append(i)
    return (indexRemPlayers)

def remove_players(players,index):
    for i in range(0,len(players),1):
        if players[i][3]==0:
            indexToRemove=i
    del players[indexToRemove]
    del index[indexToRemove]           

def reset_score(players):
    for i in range(0,len(players),1):
        players[i][2]=0
        
#------------
#MAIN PROGRAM
#------------
root = Tk() #main page
police=font.Font(size=12 , weight='bold')
#root.attributes('-fullscreen',1)
root['bg']='grey'

lblSelect=Label(root,bg="grey",text="Select your game",font=police).pack(padx=10,pady=30)
btn421=Button(root,bg="blue",text="421",font=police,width=40,height=10,command=menu_new).pack()
btnQuit=Button(root,bg="blue",text="Exit",font=police,width=15,height=5,command=root.destroy).pack(side=BOTTOM)

root.mainloop()
            
        
           
           
