import socket
import os
import random
from threading import Thread
from datetime import datetime
from colorama import Fore, init, Back
from os import system
import ctypes
import time

# VERSION REPORTING
# REMEMBER TO CHANGE THIS

ver = "1.3"


def err(type, msg):
    print("We encountered an error. Here are the details:")
    print("Type:", type, "Message", msg)


s = socket.socket()
st = 0.02

def fileshare():
    print("This is an early feature, expect bugs.")

def sp(text):
    for char in text:
        print(Fore.LIGHTGREEN_EX + char, end="", flush=True)
        time.sleep(st)


# init colors
init()

# set the available colors
colors = [
    Fore.BLUE,
    Fore.CYAN,
    Fore.GREEN,
    Fore.LIGHTBLACK_EX,
    Fore.LIGHTBLUE_EX,
    Fore.LIGHTCYAN_EX,
    Fore.LIGHTGREEN_EX,
    Fore.LIGHTMAGENTA_EX,
    Fore.LIGHTRED_EX,
    Fore.LIGHTWHITE_EX,
    Fore.LIGHTYELLOW_EX,
    Fore.MAGENTA,
    Fore.RED,
    Fore.WHITE,
    Fore.YELLOW,
]
ctypes.windll.kernel32.SetConsoleTitleW("PyNet Version "+ver)
def privchat():
    print("Do you want to host or connect to a private server?")
    print(
        """
    (1) Host
    (2) Connect

    """
    )
    privchoice = input(">")
    if privchoice == "1":
        privcode = random.randint(1025, 65536)
        print(
            ">",
            privcode,
            "< This is your private server code. This changes every time you host, so keep that in mind",
        )
        tempprivip = socket.gethostname()
        privip = socket.gethostbyname(tempprivip)
        print(">", privip, "< This is your IP. It does not change every time you host.")
        print(
            "You will have to give both of these to the people who are connectiing to you."
        )
        print("Initializing server...")
        run(privcode)
    if privchoice == "2":
        print("Enter IP to connect to:")
        SERVER_HOST = input(">")
        print("Enter your private server code:")
        SERVER_PORT = input(">")
        SERVER_PORT = int(SERVER_PORT)
        separator_token = (
            "<SEP>"  
        )
        sp(f"[*] Connecting to {SERVER_HOST}:{SERVER_PORT}...")
        print("")
        s.connect((SERVER_HOST, SERVER_PORT))
        sp("[+] Connected.")
        print("")
        name = input("Enter your name: ")
        client_color = colors[random.randint(0,14)]
        sp("You can start chatting. Type q to return to the menu.")
        print("")

        def listen_for_messages():
            while True:
                message = s.recv(1024).decode()
                print("\n" + message)

        t = Thread(target=listen_for_messages)
        t.daemon = True
        t.start()
        while True:
            to_send = input(">")
            if to_send.lower() == "q":
                break
            date_now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            to_send = f"{client_color}[{date_now}] {name}{separator_token}{to_send}{Fore.RESET}"
            s.send(to_send.encode())
        choice(0)
def run(port):
    print("This is now your server window. Open the client again and choose connect on the private server menu.")
    ctypes.windll.kernel32.SetConsoleTitleW("PyNet Private Server")
    # server's IP address
    hostname = socket.gethostname()  
    SERVER_HOST = socket.gethostbyname(hostname)
    SERVER_PORT = port # port we want to use
    separator_token = "<SEP>" # we will use this to separate the client name & message
    print("Pynet private server, running on code", port)
    print("Running on IP", SERVER_HOST)
    # initialize list/set of all connected client's sockets
    client_sockets = set()
    # create a TCP socket
    s = socket.socket()
    # make the port as reusable port
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # bind the socket to the address we specified
    s.bind((SERVER_HOST, SERVER_PORT))
    # listen for upcoming connections
    s.listen(5)
    print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")

    def listen_for_client(cs):
        """
        This function keep listening for a message from `cs` socket
        Whenever a message is received, broadcast it to all other connected clients
        """
        while True:
            try:
                # keep listening for a message from `cs` socket
                msg = cs.recv(1024).decode()
                print(msg)
            except Exception as e:
                # client no longer connected
                # remove it from the set
                print(f"[!] Error: {e}")
                client_sockets.remove(cs)
            else:
                # if we received a message, replace the <SEP> 
                # token with ": " for nice printing
                msg = msg.replace(separator_token, ": ")
            # iterate over all connected sockets
            for client_socket in client_sockets:
                # and send the message
                client_socket.send(msg.encode())


    while True:
        # we keep listening for new connections all the time
        client_socket, client_address = s.accept()
        print(f"[+] {client_address} connected.")
        # add the new connected client to connected sockets
        client_sockets.add(client_socket)
        # start a new thread that listens for each client's messages
        t = Thread(target=listen_for_client, args=(client_socket,))
        # make the thread daemon so it ends whenever the main thread ends
        t.daemon = True
        # start the thread
        t.start()

    # close client sockets
    for cs in client_sockets:
        cs.close()
    # close server socket
    s.close()



def choice(nclr):
    if nclr != 1 :
        os.system("cls")
    print(
        """
Welcome to PyNet!

What would you like to do?
(1) Public chat [IN DEVELOPMENT]
(2) Join or Create a Private Chat            
(3) About

    """
    )
    boardchoice = input("Enter your choice>")
    if boardchoice == "1":
        print("Temporary, when a permanent host is found this will be populated.")
        choice(0)
    if boardchoice == "2":
        privchat()
    if boardchoice == "3":
        print("PyNet Version", ver)
        print("Developed by Xavier Hale")
        print("Version complied on 2/5/2025")
        choice(1)


choice(0)

s.close()
