#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Bataille Navale - Client LAN
Se connecte au serveur, place les bateaux (manuel ou aléatoire), puis joue.
"""

import socket
import json
import argparse
import threading
import random
import os
import time

ROWS = 10
COLS = 10
SHIPS_DEF = {
    "Carrier": 5,
    "Battleship": 4,
    "Cruiser": 3,
    "Submarine": 3,
    "Destroyer": 2
}

def clear():
    os.system("cls" if os.name == "nt" else "clear")

def send_line(conn, data):
    conn.sendall((json.dumps(data) + "\n").encode())

def recv_loop(conn, handler):
    f = conn.makefile(mode="r", encoding="utf-8")
    try:
        while True:
            line = f.readline()
            if not line:
                break
            data = json.loads(line.strip())
            handler(data)
    except Exception as e:
        print("Connection closed.", e)
    finally:
        try:
            f.close()
        except:
            pass

class ClientGame:
    def __init__(self, host, port):
        self.sock = socket.create_connection((host, port))
        self.lock = threading.Lock()
        self.player_idx = None
        self.my_board = [["." for _ in range(COLS)] for _ in range(ROWS)]
        self.enemy_board = [["." for _ in range(COLS)] for _ in range(ROWS)]  # track hits/misses
        self.ships = {}  # name -> list of [r,c]
        self.turn = None
        self.running = True
        threading.Thread(target=recv_loop, args=(self.sock, self.on_message), daemon=True).start()

    def on_message(self, data):
        t = data.get("type")
        if t == "welcome":
            self.player_idx = data.get("player_idx")
            print(f"[INFO] {data.get('msg')}")
        elif t == "start_place":
            print("[INFO] Place tes bateaux.")
            self.place_phase()
        elif t == "placed_ok":
            print("[INFO] Placement reçu par le serveur. En attente de l'adversaire...")
        elif t == "start_game":
            print("[INFO] Partie démarrée !")
            # draw boards then wait for turn messages
            self.draw_boards()
        elif t == "turn":
            self.turn = data.get("current")
            if self.turn == self.player_idx:
                print("\nC'est ton tour !")
                self.player_turn()
            else:
                print("\nTour de l'adversaire, attends...")
        elif t == "result":
            res = data.get("res")
            r = data.get("r"); c = data.get("c")
            if res == "miss":
                print(f"Ton tir en ({r},{c}) a manqué.")
                self.enemy_board[r][c] = "o"
            elif res == "hit":
                print(f"Ton tir en ({r},{c}) a touché un navire !")
                self.enemy_board[r][c] = "X"
            elif res == "sunk":
                ship = data.get("ship")
                print(f"Tu as coulé le navire {ship} !")
                self.enemy_board[r][c] = "X"
            self.draw_boards()
        elif t == "update":
            shot = data.get("opponent_shot")
            r = shot.get("r"); c = shot.get("c"); res = shot.get("res")
            if res == "miss":
                print(f"L'adversaire a tiré en ({r},{c}) : manqué.")
                self.my_board[r][c] = "o"
            else:
                print(f"L'adversaire a tiré en ({r},{c}) : {res.upper()} !")
                self.my_board[r][c] = "X"
            self.draw_boards()
        elif t == "end":
            winner = data.get("winner")
            reason = data.get("reason")
            if winner == self.player_idx:
                print("\n🎉 Tu as gagné !", reason)
            else:
                print("\n😢 Tu as perdu.", reason)
            self.running = False
            print("Partie terminée. Ferme le client (Ctrl+C) ou attend la fermeture.")
        elif t == "info":
            print("[INFO]", data.get("msg"))
        elif t == "error":
            print("[ERROR]", data.get("msg"))
        else:
            print("[MSG]", data)

    def draw_boards(self):
        clear()
        print("Ton plateau:\t\t\tPlateau adversaire (tes tirs)")
        header = "  " + " ".join(str(i) for i in range(COLS))
        print(header + "\t\t" + header)
        for r in range(ROWS):
            left = str(r) + " " + " ".join(self.my_board[r])
            right = str(r) + " " + " ".join(self.enemy_board[r])
            print(left + "\t\t" + right)
        print()

    def place_phase(self):
        # ask player if manual or random
        while True:
            choice = input("Placement manuel (m) ou aléatoire (a) ? [m/a] ").strip().lower()
            if choice in ("m", "a"):
                break
        if choice == "a":
            self.ships = self.random_place()
            self.apply_ships_to_board()
            send_line(self.sock, {"type": "place", "ships": self.ships})
            print("Placement aléatoire envoyé.")
            return
        # manual placement
        print("Instructions: entre des positions sous la forme r,c (ex: 3,5).")
        print("Tu dois placer les navires suivants (nom : taille):")
        for name, size in SHIPS_DEF.items():
            print(f" - {name} : {size}")
        placed = {}
        for name, size in SHIPS_DEF.items():
            while True:
                print("\nTon plateau actuel:")
                self.draw_boards_single()
                print(f"Place {name} (taille {size}). Tu dois entrer {size} positions consécutives en ligne ou colonne.")
                s = input(f"Positions séparées par espace (ex: 2,3 2,4 2,5 ...): ").strip()
                try:
                    parts = s.split()
                    coords = []
                    for p in parts:
                        rr, cc = map(int, p.split(","))
                        coords.append([rr, cc])
                    # basic validation: count & contiguous
                    if len(coords) != size:
                        print("Mauvais nombre de positions.")
                        continue
                    # bounds
                    bad = False
                    for rr, cc in coords:
                        if not (0 <= rr < ROWS and 0 <= cc < COLS):
                            bad = True; break
                    if bad:
                        print("Position hors limites.")
                        continue
                    rows = [p[0] for p in coords]; cols = [p[1] for p in coords]
                    if not (len(set(rows)) == 1 or len(set(cols)) == 1):
                        print("Les coordonnées doivent être alignées sur une ligne ou une colonne.")
                        continue
                    if len(set(rows)) == 1:
                        sorted_cols = sorted(cols)
                        if sorted_cols != list(range(min(cols), max(cols)+1)):
                            print("Les colonnes doivent être consécutives.")
                            continue
                    else:
                        sorted_rows = sorted(rows)
                        if sorted_rows != list(range(min(rows), max(rows)+1)):
                            print("Les lignes doivent être consécutives.")
                            continue
                    # check overlap
                    overlap = False
                    for rr, cc in coords:
                        if self.my_board[rr][cc] != ".":
                            overlap = True; break
                    if overlap:
                        print("Chevauchement avec un navire déjà placé.")
                        continue
                    # ok
                    placed[name] = coords
                    for rr, cc in coords:
                        self.my_board[rr][cc] = name[0]  # mark with first letter
                    break
                except Exception as e:
                    print("Erreur de format:", e)
                    continue
        self.ships = placed
        send_line(self.sock, {"type": "place", "ships": self.ships})
        print("Placement envoyé au serveur. En attente de l'adversaire...")

    def draw_boards_single(self):
        header = "  " + " ".join(str(i) for i in range(COLS))
        print(header)
        for r in range(ROWS):
            left = str(r) + " " + " ".join(self.my_board[r])
            print(left)
        print()

    def random_place(self):
        board = [["." for _ in range(COLS)] for _ in range(ROWS)]
        ships = {}
        for name, size in SHIPS_DEF.items():
            placed = False
            attempts = 0
            while not placed and attempts < 200:
                attempts += 1
                dir = random.choice(["H", "V"])
                if dir == "H":
                    r = random.randrange(0, ROWS)
                    c = random.randrange(0, COLS - size + 1)
                    coords = [[r, c + i] for i in range(size)]
                else:
                    r = random.randrange(0, ROWS - size + 1)
                    c = random.randrange(0, COLS)
                    coords = [[r + i, c] for i in range(size)]
                # check overlap
                ok = True
                for rr, cc in coords:
                    if board[rr][cc] != ".":
                        ok = False; break
                if ok:
                    for rr, cc in coords:
                        board[rr][cc] = name[0]
                    ships[name] = coords
                    placed = True
            if not placed:
                raise RuntimeError("Impossible de placer tous les navires aléatoirement")
        self.my_board = board
        return ships

    def player_turn(self):
        # ask for coordinates to shoot
        self.draw_boards()
        while True:
            s = input("Tire en (r,c) ou 'q' pour abandonner: ").strip()
            if s.lower() == "q":
                send_line(self.sock, {"type": "forfeit"})
                print("Tu as abandonné.")
                self.running = False
                return
            try:
                r, c = map(int, s.split(","))
                if not (0 <= r < ROWS and 0 <= c < COLS):
                    print("Hors limites.")
                    continue
                # check local memory if already shot
                if self.enemy_board[r][c] != ".":
                    print("Tu as déjà tiré à cet endroit.")
                    continue
                send_line(self.sock, {"type": "shot", "r": r, "c": c})
                break
            except Exception as e:
                print("Format invalide. Ex: 3,4")
                continue

    def close(self):
        try:
            self.sock.close()
        except:
            pass

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=5000)
    args = parser.parse_args()

    print(f"Connexion au serveur {args.host}:{args.port} ...")
    try:
        game = ClientGame(args.host, args.port)
    except Exception as e:
        print("Impossible de se connecter:", e)
        raise SystemExit(1)

    try:
        while game.running:
            time.sleep(0.3)
    except KeyboardInterrupt:
        print("Fermeture client.")
    finally:
        game.close()
