import socket
import threading
from datetime import datetime
import time

BROADCAST_IP = "192.168.0.255"

# Listening port Server Discovery
SERVER_BROADCAST_PORT = 5974

# Listening port Client Discovery
CLIENT_BROADCAST_PORT = 5973

# Local host information
MY_HOST = socket.gethostname()
MY_IP = socket.gethostbyname(MY_HOST)


class Server():
    def __init__(self):
        self.leader_IP = '' # fix the leader IP
        self.serverList = [] # list if servers and their addresses
        self.clients = []
        self.informServer = False
        self.isLeader = False  # New variable to track if the server is the leader
        

    def printwt(self, msg):
        current_date_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        print(f'[{current_date_time}] {msg}')

    
    def print_group_view(self):
        print("Group view is:", self.serverList)


        #This function enables the server to listen to the server broadcast port and reply the ip address
    def BroadcastListenAndReply(self):

        # if my IP is not in the server list add it
        if MY_IP not in self.serverList:
            self.serverList.append(MY_IP)

        # create socket bind to server address
        broadcast_listen_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        broadcast_listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        broadcast_listen_sock.bind((MY_IP, SERVER_BROADCAST_PORT))

        
        while True:
    
                data, address = broadcast_listen_sock.recvfrom(1024)

                if data:
                    newServer_address = data.decode()
                    self.printwt(f'New participant wants to connect: {newServer_address}')
                    self.isLeader = False

                    # if the decoded address is not in the server list add it and print the list

                    if newServer_address not in self.serverList:
                        self.serverList.append(newServer_address)

                    reply_message = MY_IP
                    broadcast_listen_sock.sendto(str.encode(reply_message), address)
 
                    self.printwt('Replied my IP to new participant')

        

                time.sleep(1)
                self.print_group_view()
                self.printwt(f'The current leader IP is: {self.leader_IP}')
            

        #this function enables the server to send a broadcast to the server group and receive the answers of existing members
    def BroadcastSendAndReceive(self):
    
        # create socket
        broadcast_group = (BROADCAST_IP, SERVER_BROADCAST_PORT)
        broadcast_send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        broadcast_send_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        broadcast_send_sock.settimeout(2)

        # ...

        message = MY_IP
        broadcast_send_sock.sendto(message.encode(), broadcast_group)
        self.printwt("Sent my IP to server group")

        # if my IP is not in the server list add it
        if MY_IP not in self.serverList:
            self.serverList.append(MY_IP)
        
        # listen for IPs from existing servers
        maxLoop = 5
        currentLoop = 0

        # Anzahl der eingehenden Antworten initialisieren
        num_responses = 0
        
        while currentLoop < maxLoop:
            print("Waiting for responses...")  # Debug-Ausgabe
            while True:     
                currentLoop += 1

                try:
                    # receive reply data from the other participants
                    reply, address = broadcast_send_sock.recvfrom(1024)

                    if reply:
                        reply_address = reply.decode()

                        # Debug-Ausgabe
                        print(f"Received response from: {reply_address}")

                        # if reply address is not in the server list, add it
                        if reply_address not in self.serverList:
                            self.serverList.append(reply_address)

                        # Erhöhe die Anzahl der eingehenden Antworten
                        num_responses += 1
                        print(f"Current server list: {self.serverList}")  # Debug-Ausgabe

                except socket.timeout:
                    break

        if num_responses == 1:     
            broadcast_send_sock.close()
            self.isLeader = True
            self.leader_IP = MY_IP # Hier wird die IP-Adresse des Leaders zugewiesen
            self.printwt(f'I am the only server in the system, so the leader IP is: {self.leader_IP}')
            time.sleep(1)


        self.print_group_view()
        

    # Listen to client broadcast (request) and reply with Server IP
    def ListenForClientAndReply(self):
        # Create a UDP socket
        listen_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # Set the socket to broadcast and enable reusing addresses
        listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # Bind socket to address and port
        listen_socket.bind((MY_IP, CLIENT_BROADCAST_PORT))

        # print("Listening to broadcast messages")

        # Receiving broadcast massage
        while True:
            data, addr = listen_socket.recvfrom(1024)

            if data:
                self.printwt(data.decode())

        # if Iam the leader, answer the client including my IP
                if MY_IP == self.leader_IP:
                    reply_message = MY_IP
                    listen_socket.sendto(str.encode(reply_message), addr)
                    self.printwt('Replied my IP to new client')

    def handle_client(self, client_socket, client_address):
        self.clients.append(client_socket)

        while True:
            try:
                data = client_socket.recv(1024)
                if not data:
                    break
                self.broadcast(data, client_socket)

            except:
                self.clients.remove(client_socket)
                break

    def broadcast(self, message, sender_socket):
        for client in self.clients:
            try:
                if client != sender_socket:
                    client.send(message)
            except:
                self.clients.remove(client)


# starting all simultaneously working procedures
if __name__== '__main__':
    server = Server()

    thread2 = threading.Thread(target = server.BroadcastListenAndReply)
    thread2.start()

    thread3 = threading.Thread(target = server.BroadcastSendAndReceive)
    thread3.start()

    thread1 = threading.Thread(target = server.ListenForClientAndReply)
    thread1.start()

    # Socket erstellen und binden
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind((MY_IP, 5555))
    server_socket.listen(5)

    while True:
        client_socket, client_address = server_socket.accept()

        client_thread = threading.Thread(target=server.handle_client, args=(client_socket, client_address))
        client_thread.start()