# Filename: A08_multiplayer_demo_server.py
# Written by: James D. Miller

import sys, os
import pygame

# PyGame Constants
from pygame.locals import *
from pygame.color import THECOLORS

from PodSixNet.Server import Server
from PodSixNet.Channel import Channel

import socket

#=======================================================================
# Classes       
#=======================================================================

class ClientChannel(Channel):
    def __init__(self, *args, **kwargs):
        Channel.__init__(self, *args, **kwargs)
    
    def Network_CN(self, data):
        global CND
        
        # This function runs when "CN" data is recieved from a client. I believe this
        # runs once for each data "Send" by each client.
        
        # The naming of this function must correspond with the value of the 'action' key
        # sent in the data dictionary from the client. All the clients are using
        # an action key value of "CN", so this function must end with "CN".
        
        # "data" is a dictionary of state info sent by each client. 
        # The "ID" value, in the dictionary, identifies who (which client) 
        # this dictionary is coming from.
        clientname = 'C' + str(data['ID'])
        
        # Update the client state in the CN_data dictionary based on the incoming data.
        CND.CN_data[clientname]['state'] = data
        
        # Keep track of client activity.
        CND.CN_data[clientname]['sendCount'] += 1
        
        # If the left mouse button was down, append the mouse location tuple to the FIFO list.
        if data['mouseB1'] == 'D':
            CND.CN_data[clientname]['historyXY'].append(data['mouseXY'])
            # Displace the oldest part of the FIFO; pop it off.
            if len(CND.CN_data[clientname]['historyXY']) > 200:
                CND.CN_data[clientname]['historyXY'].pop(0)
        
        # You should be reluctant to print here. Printing can slow the server frame rate. It's
        # tempting to try and observe this data flow through printing, but...
        
    def Close(self):
        print "deleting player"
     
     
class GameServer(Server):
    channelClass = ClientChannel
    
    def __init__(self, *args, **kwargs):
        Server.__init__(self, *args, **kwargs)
        self.client_count = 0
        
    # This runs when each client connects.
    def Connected(self, channel, addr):
        print 'new connection (channel, addr):', channel, addr
        
        self.client_count += 1
        if (self.client_count <= 10):
            channel.Send({"action": "hello", "P_ID":self.client_count})
        else:
            channel.Send({"action": "hello", "P_ID":0})

class ClientData:
    def __init__(self):
        # CN_data is a dictionary of dictionaries.
        self.loopsSinceLastQuietCheck = 0
        
        self.CN_data = {}
        for m in range(1,11):
            # Set the player's Cm key value to a dictionary.
            self.CN_data['C' + str(m)]  =  {'state':None, 'sendCount':0, 'previousSendCount':0, 'active':False, 'historyXY':[]}
    
    def checkForQuietClients(self):
        self.loopsSinceLastQuietCheck += 1
        if self.loopsSinceLastQuietCheck > 20:
            self.loopsSinceLastQuietCheck = 0
            for clientname in CND.CN_data:
                # Check for the no change case (client is quiet).
                countChange = CND.CN_data[clientname]['sendCount'] - CND.CN_data[clientname]['previousSendCount']
                if countChange == 0:
                    CND.CN_data[clientname]['active'] = False
                else:
                    CND.CN_data[clientname]['active'] = True
                # Update the previous value for use in the next comparison.
                CND.CN_data[clientname]['previousSendCount'] = CND.CN_data[clientname]['sendCount']


#=======================================================================
# Functions.        
#=======================================================================
        
def checkforLocalUserInput():    
    # Get all the events since the last call to get().
    for event in pygame.event.get():
        if (event.type == pygame.QUIT): 
            sys.exit()
        elif (event.type == pygame.KEYDOWN):
            if (event.key == K_ESCAPE):
                sys.exit()
            elif (event.key==K_c):
                server_display.fill(THECOLORS["blue"])  
                
def draw_cursor_histories( CN_data):
    for player in CN_data:
        if CND.CN_data[player]['active']:
            draw_cursor_history( CN_data[player]['historyXY'], player_colors[player])
                
def draw_cursor_history( cursor_history, color):
    for mouse_xy in cursor_history:
        pygame.draw.circle(server_display, color,   mouse_xy, 10, 0)
    
def draw_server_cursors( CN_data):
    for player in CN_data:
        if CND.CN_data[player]['active']:
            draw_fancy_server_cursor(CN_data[player]['state']['mouseXY'], player_colors[player])

def draw_fancy_server_cursor( cursor_position, color):
    draw_server_cursor( cursor_position, color, 0)
    draw_server_cursor( cursor_position, THECOLORS["black"], 1)

def draw_server_cursor( cursor_position, color, edge_px):
    cursor_outline_vertices = []
    cursor_outline_vertices.append(  cursor_position )
    cursor_outline_vertices.append( (cursor_position[0] + 10,  cursor_position[1] + 10) )
    cursor_outline_vertices.append( (cursor_position[0] +  0,  cursor_position[1] + 14) )
    
    pygame.draw.polygon(server_display, color, cursor_outline_vertices, edge_px)

def render_all_player_keys( CN_data):
    for player in CN_data:
        if CND.CN_data[player]['active']:
            render_keys( CN_data[player]['state'])  
            
def render_keys( user_state):
    y_offset = 10 + (user_state['ID'] - 1) * 37
    
    # The W row...
    pygame.draw.rect(server_display, THECOLORS["black"], pygame.Rect(20, y_offset    , 10, 20))
    txt_string = user_state['w']
    txt_surface = fnt.render(txt_string, 1, THECOLORS["yellow"])
    server_display.blit(txt_surface, [20, y_offset])       
    
    # The ASD row...
    pygame.draw.rect(server_display, THECOLORS["black"], pygame.Rect(10, y_offset + 20, 30, 20))
    txt_string = user_state['a'] + user_state['s'] + user_state['d']
    txt_surface = fnt.render(txt_string, 1, THECOLORS["yellow"])
    server_display.blit(txt_surface, [10, y_offset + 20])   

    
#=======================================================================
# Main Program statements   
#=======================================================================
        
pygame.init()

server_display = pygame.display.set_mode((600,400))
pygame.display.set_caption("Server: Rendering Window.") 

# Instantiate clock to help control the framerate.
server_clock = pygame.time.Clock()
framerate_limit = 100

server_display.fill(THECOLORS["yellow"])

# Font object for rendering text onto display surface.
fnt = pygame.font.SysFont("Arial", 14)

# Setup network server.
local_ip = socket.gethostbyname(socket.gethostname())
local_port = 4330
print "Server IP address and port:", local_ip, local_port
drawBoard_server = GameServer(localaddr=(local_ip, local_port))

# Initialize the client states and mouse histories.
CND = ClientData()

# Colors
player_colors = {'C1': THECOLORS["green"],'C2': THECOLORS["tan"],'C3': THECOLORS["black"],'C4': THECOLORS["blue"],'C5': THECOLORS["pink"],
                 'C6': THECOLORS["red"],'C7': THECOLORS["coral"],'C8': THECOLORS["orangered1"],'C9': THECOLORS["grey80"],'C10': THECOLORS["rosybrown3"]}

# Set a limit for the quietness demerits.
qCount_limit = 100
                 
while True:
    
    server_display.fill(THECOLORS["yellow"])
    
    # Background for text
    pygame.draw.rect(server_display, THECOLORS["black"], pygame.Rect(550, 9, 60, 20))
    # Text
    fps_string = "%.0f" % server_clock.get_fps()
    txt_surface = fnt.render(fps_string, True, THECOLORS["white"])
    server_display.blit(txt_surface, [550, 10])   
    
    drawBoard_server.Pump()
    
    dt_s = float(server_clock.tick(framerate_limit) * 1e-3)
    
    CND.checkForQuietClients()
    
    checkforLocalUserInput()
    
    draw_cursor_histories( CND.CN_data)
    
    draw_server_cursors( CND.CN_data)
    
    render_all_player_keys( CND.CN_data)
    
    pygame.display.flip()