#!/usr/bin/env python3
import sys
import queue
import time
import socket
import threading
import math
import select


class site(object):
	def __init__(self, site_num, setup_file, command_file):
		super(site, self).__init__()

		self.site_num = int(site_num) #ex: 1, 2, 3,...
		self.site_id = 0			#ex: 5001, 5002,...
		self.setup_file = setup_file
		self.command_file = command_file

		self.incoming_channels = list()
		self.outgoing_channels = list()
		self.queue_data = list()

		self.snapshots_dict = {}

		self.marker_count = 0
		self.global_snap_count = 0
		self.my_snap_count = 0
		self.total_sites = 0
		self.sites_done = 0 # if this becomes total_sites-1 then I can be

		


		self.port_id_and_number = {}

		self.local_bank = 10

		self.incoming_channels.append(0) #this way we can start index at 1
		self.outgoing_channels.append(0) #this way we can start index at 1
		self.queue_data.append(0)





	def open_connections(self):
		self.total_sites = int(self.setup_file[0])
		TCP_IP = str(self.setup_file[self.site_num].split(" ")[0].rstrip())

		
		for i in range(1, self.total_sites+1):
			port_id = int(self.setup_file[i].split(" ")[1].rstrip()) #gets all the listed port ids
			self.port_id_and_number[port_id] = i
			self.port_id_and_number[i] = port_id #can use dict to search for portid
			self.incoming_channels.append(None)
			self.outgoing_channels.append(None)
			q = queue.Queue()
			self.queue_data.append(q)

		TCP_PORT_SELF = self.port_id_and_number[self.site_num]
		self.site_id = TCP_PORT_SELF


		receiving_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		receiving_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		receiving_socket.bind((TCP_IP, TCP_PORT_SELF))
		receiving_socket.listen(1)


		


		#SOCKET WORK, CONNECT ALL SOCKETS
		for i in range(self.total_sites+1, len(self.setup_file)):
			links = self.setup_file[i].rstrip().split(" ")

			site_id_from = int(links[0]) 					#ex: 1, 2..
			site_id_to = int(links[1]) 						#ex: 1, 2..

			from_port = self.port_id_and_number[site_id_from] 	#ex: 5001, 5002,...
			to_port = self.port_id_and_number[site_id_to] 		#ex: 5001, 5002,...


			if(from_port == self.site_id):
				time.sleep(5)
				dest_addr = (TCP_IP, to_port)

				s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
				s.connect(dest_addr)
				self.outgoing_channels[site_id_to] = s

			elif(to_port == self.site_id):
				time.sleep(5)
				stream, addr = receiving_socket.accept()
				self.incoming_channels[site_id_from] = stream




	def read_commands(self):

		for i in self.command_file:
			if(i.find("send") != -1): #IF SEND
				i = i.strip("send ")
				site_id_to = int(i[0])	#ex: 1, 2,...

				amount = int(i[2]) 		#money amount sending
				self.send_money(amount, site_id_to)
				self.receive()

			elif(i.find("sleep") != -1): #IF SLEEP
				time.sleep(int(i.strip("sleep ")))
				self.receive()

			elif(i.find("snapshot") != -1): #IF SNAPSHOT
				self.my_snap_count +=1
				self.global_snap_count += 1
				site_num = self.port_id_and_number[self.site_id]
				snap_name = str(site_num) + "." + str(self.my_snap_count)
				new_snapshot = snapshot(self.total_sites, site_num, snap_name, self.local_bank, self.incoming_channels)
				
				self.snapshots_dict[ new_snapshot.get_snap_name() ] = new_snapshot
				self.send_markers( new_snapshot.get_snap_name() )
				self.receive()

		



	def send_money(self, amount, to_id):
		self.local_bank -= amount
		s = self.outgoing_channels[to_id]
		s.sendall((str(amount) + "%").encode())


	def receive(self):
		for i in range(1, len(self.incoming_channels)):
			s = self.incoming_channels[i]

			if s != None:
				s.settimeout(15)
				try:
					data = s.recv(1024).decode()
					if data: 

						current_data = data.split("%")

						for j in current_data:
							if (j!=''):
								self.queue_data[i].put(j)
						

						while(self.queue_data[i].empty() == False):
							current = self.queue_data[i].get()
							if(str(current).find("marker") != -1): #if marker
								self.marker_count += 1
								marker_snap_name = current.split(" ")[-1] 

								if ( not self.snapshots_dict.get( marker_snap_name ) ):
									# received new snap name
									site_num = self.port_id_and_number[self.site_id]
									new_snapshot = snapshot(self.total_sites, site_num, marker_snap_name, self.local_bank, self.incoming_channels)
									new_snapshot.close_channel(i)
									self.snapshots_dict[ marker_snap_name ] = new_snapshot
									self.send_markers( marker_snap_name )
								else:
									#snap already exists, cloe channels 
									self.snapshots_dict[ marker_snap_name ].close_channel(i)


							elif(current != ''):
								# loop through all incomplete snapshots and add it to all of them????
								# self.snapshots_list[self.global_snap_count-1].add_to_channel(i, int(current))
								for k in self.snapshots_dict:
									if ( not self.snapshots_dict[k].is_complete() ):
										self.snapshots_dict[k].add_to_channel(i, int(current))
								self.local_bank += int(current)

						


				except socket.timeout:
					continue
		return


	def send_markers(self, snap_name):
		for i in range(1, len(self.outgoing_channels)):
			s = self.outgoing_channels[i]
			if s != None:
				s.sendall(("marker from: " + str(snap_name) +"%").encode()) # keep this send message the same





	def close_connections(self):
		for i in range(1, len(self.outgoing_channels)):
			s = self.outgoing_channels[i]
			if s != None:
				s.close()
			s2 = self.incoming_channels[i]
			if s2 != None:
				s2.close()

	def output(self):
		for i in self.snapshots_dict:
			self.snapshots_dict[i].print_snapshot()

class snapshot(object):
	def __init__(self, total_sites, my_channel_id, snap_name, my_snap_bank, incoming_channels ):
		super(snapshot, self).__init__()

		self.snap_name = str(snap_name)
		self.snap_complete = False
		self.total_sites = int(total_sites)
		self.my_channel_id = int(my_channel_id)
		self.my_snap_bank = my_snap_bank
		self.incoming_channels = incoming_channels

		self.closeds = list() # false if open, true if closed
		self.amounts = list() # amount recorded from each channel
		self.closeds.append(0)
		self.amounts.append(0)
		for i in range(1, total_sites+1):
			if(i == self.my_channel_id or incoming_channels[i] == None):
				self.closeds.append(True)
				self.amounts.append(None)
			else:
				self.closeds.append(False)
				self.amounts.append(0)


	def add_to_channel(self, channel_num, amount ):
		if self.closeds[ channel_num ] == False:
			self.amounts[ channel_num ] += amount

	def done(self):
		self.snap_complete = True

	def is_complete(self):
		return self.snap_complete

	def get_snap_name(self):
		return self.snap_name

	def close_channel(self, channel_id):
		self.closeds[channel_id] = True

	def print_snapshot(self):
		output = str(self.snap_name) + ": " + str(self.my_snap_bank)

		for i in range(1, len(self.amounts)):
			if(i != self.my_channel_id and self.amounts[i] != None):
				output += " " + str(self.amounts[i])

		print(output)




def main():
	args = sys.argv
	if (len(args)!=4):
		print("Usage: <port_id> <setup file> <command file> ")
		print(args)
		return

	site_num = args[1]
	setup_file= open(args[2], 'r')
	command_file = open(args[3], 'r')
	new_site = site(site_num, setup_file.readlines(), command_file.readlines())
	new_site.open_connections()
	time.sleep(5)
	new_site.read_commands()
	new_site.receive()
	time.sleep(10)
	new_site.receive()
	new_site.output()
	new_site.close_connections()

if __name__ == "__main__":
    main()
