import curses
import json
import os
import string
import sys
import threading
import time
import websocket
from websocket import WebSocketException as WSException

from maps import Map, ChunkMap
from chunks import ChunkDiff, jsonify_diffs, dejsonify_diffs
from utils import Position
from clientchunkpool import ClientChunkPool

class Client():
	def __init__(self, address, logfile=None):
		self.stopping = False
		
		self.map_ = None
		self.chunkmap = None
		self.chunkmap_active = False
		
		self.address = f"ws://{address}/"
		self._drawevent = threading.Event()
		self.pool = ClientChunkPool(self)
		
		self.logfile = logfile
		self.log_messages = []
	
	def launch(self, stdscr):
		# connect to server
		try:
			self._ws = websocket.create_connection(
				self.address,
				enable_multithread=True
			)
		except:
			sys.stderr.write(f"Could not connect to server: {self.address!r}\n")
			return
		
		# create map etc.
		sizey, sizex = stdscr.getmaxyx()
		self.map_ = Map(sizex, sizey, self.pool, self)
		self.chunkmap = ChunkMap(self.map_)
		
		# start connection thread
		self.connectionthread = threading.Thread(
			target=self.connection_thread,
			name="connectionthread"
		)
		self.connectionthread.start()
		
		# start input thread
		self.inputthread = threading.Thread(
			target=self.input_thread,
			name="inputthread",
			args=(stdscr,),
			daemon=True
		)
		self.inputthread.start()
		
		# update screen until stopped
		while not self.stopping:
			self._drawevent.wait()
			self._drawevent.clear()
			
			with self.map_ as m:
				m.draw()
				
				if self.chunkmap_active:
					self.chunkmap.draw()
					curses.curs_set(False)
				else:
					curses.curs_set(True)
			
			#m.update_cursor()
			#m.noutrefresh()
			
			curses.doupdate()
		
		if self.logfile:
			self.save_log(self.logfile)
	
	def redraw(self):
		self._drawevent.set()
	
	def log(self, message):
		if self.logfile:
			self.log_messages.append(message)
	
	def save_log(self, filename):
		with open(filename, "w") as f:
			f.write(f"[[[ {int(time.time())} ]]]\n")
			for msg in self.log_messages:
				f.write(msg + "\n")
	
	def input_thread(self, scr):
		while True:
			i = scr.get_wch()
			
			if   i == "\x1b": self.stop()
			elif i == 266: # F2
				self.chunkmap_active = not self.chunkmap_active
				self.redraw()
			elif i == 267: # F3
				self.map_.alternating_colors = not self.map_.alternating_colors
				self.redraw()
			elif i == 268:
				self.stdscr_visible = not self.stdscr_visible
				if self.stdscr_visible: self.stdscr.redrawwin()
				else: self.map_.redraw()
				self.redraw()
			elif i == 269: # F5
				self.map_.redraw()
			# scrolling the map (10 vertical, 20 horizontal)
			elif i == 569: self.map_.scroll(0, -10) # ctrl + up
			elif i == 528: self.map_.scroll(0, 10)  # ctrl + down
			elif i == 548: self.map_.scroll(-20, 0) # ctrl + left
			elif i == 563: self.map_.scroll(20, 0)  # ctrl + right
			
			# break here if chunkmap is shown: Don't allow for cursor movement or input
			elif self.chunkmap_active: pass
		
			# quick cursor movement (5 vertical, 10 horizontal)
			elif i == 337: self.map_.move_cursor(0, -5)  # shift + up
			elif i == 336: self.map_.move_cursor(0, 5)   # shift + down
			elif i == 393: self.map_.move_cursor(-10, 0) # shift + left
			elif i == 402: self.map_.move_cursor(10, 0)  # shift + right
			# normal cursor movement
			elif i == 259: self.map_.move_cursor(0, -1) # up
			elif i == 258: self.map_.move_cursor(0, 1)  # down
			elif i == 260: self.map_.move_cursor(-1, 0) # left
			elif i == 261: self.map_.move_cursor(1, 0)  # right
			# edit world
			elif i == "\x7f": self.map_.delete()
			elif i == "\n":   self.map_.newline()
			#elif i in string.digits + string.ascii_letters + string.punctuation + " ":
				#self.map_.write(i)
			elif isinstance(i, str) and len(i) == 1 and ord(i) > 31 and (i not in string.whitespace or i == " "):
				self.map_.write(i)
			
			self.log(f"K: {i!r}")
	
	def connection_thread(self):
		while True:
			try:
				j = self._ws.recv()
				if j:
					self.handle_json(json.loads(j))
			except (WSException, ConnectionResetError, OSError):
				#self.stop()
				return
	
	def handle_json(self, message):
		if message["type"] == "apply-changes":
			diffs = dejsonify_diffs(message["data"])
			self.map_.commit_diffs(diffs)
	
	def stop(self):
		self.stopping = True
		self._ws.close()
		self.redraw()

	def request_chunks(self, coords):
		message = {"type": "request-chunks", "data": coords}
		self._ws.send(json.dumps(message))
	
	def unload_chunks(self, coords):
		message = {"type": "unload-chunks", "data": coords}
		self._ws.send(json.dumps(message))
	
	def send_changes(self, diffs):
		diffs = jsonify_diffs(diffs)
		message = {"type": "save-changes", "data": diffs}
		self._ws.send(json.dumps(message))

def main(argv):
	if len(argv) == 2:
		client = Client(argv[1])
	elif len(argv) == 3:
		client = Client(argv[1], argv[2])
	else:
		print("Usage:")
		print(f"  {argv[0]} address [logfile]")
		return
	
	os.environ.setdefault('ESCDELAY', '25') # only a 25 millisecond delay
	
	curses.wrapper(client.launch)

if __name__ == "__main__":
	main(sys.argv)

@@ -2400,175 +2400,8 @@
 w()%0A
-%09%09%09elif i == 268:%0A%09%09%09%09self.stdscr_visible = not self.stdscr_visible%0A%09%09%09%09if self.stdscr_visible: self.stdscr.redrawwin()%0A%09%09%09%09else: self.map_.redraw()%0A%09%09%09%09self.redraw()%0A
 %09%09%09e

Remove accidentally-left-in code for debugging