#!/usr/bin/python

# FRA Challenge 2019

import json, socket
import tty, termios, sys, select, time
import socket, md5
from Crypto.Cipher import ARC4 # apt-get install python-pycryptopp
import random, string

COLOR_BLACK=0
COLOR_RED=1
COLOR_GREEN=2
COLOR_YELLOW=3
COLOR_BLUE=4
COLOR_PURPLE=5
COLOR_CYAN=6
COLOR_WHITE=7

ANSI_CLEAR='\033[2J'
ANSI_RESET='\033[0m'
ANSI_COLORS={
	COLOR_BLACK:'\033[30m',
	COLOR_RED:'\033[31m',
	COLOR_GREEN:'\033[32m',
	COLOR_YELLOW:'\033[33m',
	COLOR_BLUE:'\033[34m',
	COLOR_PURPLE:'\033[35m',
	COLOR_CYAN:'\033[36m',
	COLOR_WHITE:'\033[37m',
}

ANSI_BGCOLORS={
	COLOR_BLACK:'\033[40m',
	COLOR_RED:'\033[41m',
	COLOR_GREEN:'\033[42m',
	COLOR_YELLOW:'\033[43m',
	COLOR_BLUE:'\033[44m',
	COLOR_PURPLE:'\033[45m',
	COLOR_CYAN:'\033[46m',
	COLOR_WHITE:'\033[47m',}

class ClientModel:
	def __init__(self, s):
		self.sock=s

		self.MAX_TILE_HEIGHT=16/2
		self.MAX_HEIGHT=60
		self.tiles={}
		self.objects={}
		self.player_y=0

		try:
			f=open('crypto.txt', 'r')
			self.xor_key=f.readline()
			self.longxor_key=f.readline().strip()
			self.rc4_key=f.readline().strip()
			f.close()
		except:
			self.xor_key=raw_input('Enter xor key: ')
			self.longxor_key=raw_input('Enter long xor key: ')

			# generate rc4 key
			self.rc4_key=''.join(random.sample(string.uppercase, 7))
			print "Keys are stored in crypto.txt"
			f=open('crypto.txt', 'w')
			self.xor_key=self.xor_key[0]
			print >>f, self.xor_key
			print >>f, self.longxor_key
			print >>f, self.rc4_key
			f.close()


	def get_player_y(self):
		return self.player_y


	def get_tile(self, number):
		try:
			return self.tiles[number]
		except:			
			# Tile missing! Return default
			# "pixels", fcolor, bcolor
			s=list(str(number))
			if len(s) % 2 ==0: # tiles need to be uneven in size
				s+='!'
			random.seed(number)
			colors=range(COLOR_RED, COLOR_WHITE+1)
			c, c2=random.sample(colors, 2)
			return {'pixels':[s,], 'colors':[[c for i in range(len(s))],], 'bgcolors':[[c2 for i in range(len(s))],]}


	def get_objects(self):
		return self.objects

	def poll_data(self):
		"""Check for new data"""
		data=[]

		if hasattr(self.sock, 'recv'):
			try:
				while 1: 
					data+=[self.sock.recv(8192)]
			except socket.timeout:
				pass
		else: # if self.sock is a filehandle, then do this instead...  good for debug
			data=[self.sock.read()]

		

		if len(data):
			for d in ''.join(data).split('\n'):
				try:

					if not len(d):
						continue
					j=json.loads(d)


					if j['encoding']=='longxor':
						decoded=''.join([chr(ord(j['data'][i]) ^ ord(self.longxor_key[i % len(self.longxor_key)])) for i in range(len(j['data']))])
					elif j['encoding']=='xor':
						decoded=''.join([chr(ord(i) ^ ord(self.xor_key[0])) for i in j['data']])
					elif j['encoding']=='rc4':
						C=ARC4.new(self.rc4_key)
						decoded=C.decrypt(j['data'].decode('base64'))
					else:
						decoded=j['data']
		

					# Received data must be decoded correctly
					if j['md5']==md5.md5(decoded).hexdigest():
						data=json.loads(decoded)

						if j['function']=='object':
							self.objects.setdefault(data['z'], [])
							self.objects[data['z']]+=[data]
						elif j['function']=='tile':
							self.tiles[data['number']]=data
						elif j['function']=='set_player_y':
							self.player_y=data
						elif j['function']=='object_update':

							for i,layer in self.objects.iteritems():
								for n in layer:
									if n['id']==data['id']:
										n['x']=data['x']
										n['y']=data['y']



				except Exception, e:
					print "ERROR", e, len(data), len(d)					
	
	def send_packet(self, com, arg):
		buf={"command": com, "arg": arg}
		self.sock.sendall(json.dumps(buf)+'\n')


class ClientView:
	def __init__(self, model):	
		self.model=model

	def player_input(self, buf):

		if 'LEFT'==buf:
			self.model.send_packet("input", "LEFT")
		if 'RIGHT'==buf:
			self.model.send_packet("input", "RIGHT")

	def render(self):
		objects=self.model.get_objects()

		bitmap=[[' ' for i in range(60)] for j in range(60)]
		colormap=[[COLOR_WHITE for i in range(60)] for j in range(60)]
		bgcolormap=[[COLOR_BLACK for i in range(60)] for j in range(60)]

		# Draw all objects, layer for layer, in memory first
		for z, layer in objects.iteritems():

			for obj in layer:
				x=obj['x']
				y=obj['y']-self.model.player_y

				tile=M.get_tile(obj['tile'])

				width=len(tile['pixels'][0])
				height=len(tile['pixels'])


				for ty in range(-height/2+1, height/2+1):
					for tx in range(-width/2+1, width/2+1):


						try:
							if tile['pixels'][ty+height/2][tx+width/2]!='.' and tx+x>-1 and ty+y>-1:
								bitmap[y+ty][x+tx]=tile['pixels'][ty+height/2][tx+width/2]
								colormap[y+ty][x+tx]=tile['colors'][ty+height/2][tx+width/2]
								bgcolormap[y+ty][x+tx]=tile['bgcolors'][ty+height/2][tx+width/2]

						except IndexError, e:
							# draw outside map, probably ok
							pass
		
		current_color=-1
		current_bgcolor=-1

		# Prepare actual output to terminal
		buf=[ANSI_CLEAR,]
		for yy in range(60):
			buf+=['     ']
			if current_color!=-1:
				buf+=[ANSI_COLORS[current_color]+ANSI_BGCOLORS[current_bgcolor]]
			y=59-yy
			for x in range(60):
				color=colormap[y][x]
				if color!=current_color:
					current_color=color
					buf+=[ANSI_COLORS[color]]
				bgcolor=bgcolormap[y][x]
				if bgcolor!=current_bgcolor:
					current_bgcolor=bgcolor
					buf+=[ANSI_BGCOLORS[bgcolor]]

				buf+=[bitmap[y][x]]
			buf+=[ANSI_RESET+'\n']

		buf+=[ANSI_RESET]
		print ''.join(buf)



# For your sanity, and debug :) 
# follow stream in wireshark and... 
# cat <file with packets> | python client.py
if not sys.stdin.isatty(): 
	M=ClientModel(sys.stdin)
	V=ClientView(M)
	
	M.poll_data()
	V.render()
	
	sys.exit(0)
# -------------

HOST='localhost'
f=open('port', 'r')
PORT=int(f.readline().strip())
f.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.settimeout(0.0001)

M=ClientModel(s)
V=ClientView(M)

old_settings = termios.tcgetattr(sys.stdin)
try:
	tty.setcbreak(sys.stdin.fileno())

	while 1:
		# get updates from server
		M.poll_data()
		V.render()

		# nonblocking input	
		if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
			c=sys.stdin.read(1)
			
			if c=='\033':
				break # player pressed esc

			if c=='z': # left
				V.player_input('LEFT')
			if c=='x': # right
				V.player_input('RIGHT')
			
			sys.stdin.flush()	


		time.sleep(0.01)
		

finally:
	termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

s.close()


