"""
evony_monster_scout.py
----------------------
Ejemplo educativo de scouting de monstruos para Evony: The King's Return.

Enfoque: Captura de pantalla + reconocimiento de plantillas (template matching).
No modifica el cliente, no inyecta codigo, no intercepta paquetes.
Es el metodo de menor riesgo respecto a los TOS del juego.

Funcionamiento:
1. Abre Evony en un emulador (BlueStacks, LDPlayer, MuMu...) y ve al mapa del reino.
2. Ajusta GAME_REGION para que cubra la zona del mapa visible.
3. Coloca recortes .png de los iconos de monstruo en ./templates/
   (uno por tipo de monstruo: oso.png, bestia.png, dragon.png, etc.)
4. Ejecuta el script. Cada SCAN_INTERVAL segundos buscara coincidencias
   y las loggeara en monster_log.csv.

Dependencias:
    pip install mss opencv-python numpy pillow pytesseract
Y Tesseract OCR instalado en el sistema (en Mac: `brew install tesseract`).

NOTA: El script NO mueve el raton ni hace clicks ni interactua con el juego.
Solo lee la pantalla y guarda un log. Si quisieras auto-rally, ese es otro
nivel de automatizacion y aumenta el riesgo (entra en zona gris/prohibida).
"""

import csv
import time
from datetime import datetime
from pathlib import Path

import cv2
import mss
import numpy as np

try:
    import pytesseract
    OCR_AVAILABLE = True
except ImportError:
    OCR_AVAILABLE = False


# ====== CONFIGURACION ======

# Region de pantalla a capturar (ajustar a tu ventana de emulador).
# top, left = esquina superior izquierda en pixeles
# width, height = tamano de la captura
GAME_REGION = {"top": 100, "left": 100, "width": 1280, "height": 720}

# Carpeta con plantillas PNG (un PNG por tipo de monstruo)
TEMPLATES_DIR = Path(__file__).parent / "templates"

# Umbral de similitud (0.0 a 1.0). Mas alto = mas estricto.
# 0.78-0.85 suele funcionar bien con plantillas limpias.
MATCH_THRESHOLD = 0.80

# Segundos entre escaneos
SCAN_INTERVAL = 30

# Archivo CSV donde se guardan los hallazgos
LOG_FILE = Path(__file__).parent / "monster_log.csv"


# ====== FUNCIONES ======

def capture_screen() -> np.ndarray:
    """Captura la region configurada y devuelve un array BGR de OpenCV."""
    with mss.mss() as sct:
        shot = sct.grab(GAME_REGION)
        img = np.array(shot)  # llega en BGRA
        return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)


def load_templates() -> dict:
    """Carga todas las plantillas .png del directorio."""
    templates = {}
    if not TEMPLATES_DIR.exists():
        TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)
        print(f"[!] Cree {TEMPLATES_DIR}. Anade recortes .png de monstruos ahi.")
        return templates
    for path in sorted(TEMPLATES_DIR.glob("*.png")):
        img = cv2.imread(str(path), cv2.IMREAD_COLOR)
        if img is not None:
            templates[path.stem] = img
    return templates


def find_matches(screen: np.ndarray, template: np.ndarray,
                 threshold: float = MATCH_THRESHOLD) -> list:
    """
    Devuelve lista de (x, y, score) donde la plantilla coincide en la captura.
    Dedupe matches que esten muy cerca entre si.
    """
    result = cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED)
    ys, xs = np.where(result >= threshold)
    h, w = template.shape[:2]

    matches = []
    seen = []
    # Ordenar por score descendente
    order = np.argsort(-result[ys, xs])
    for i in order:
        x, y = int(xs[i]), int(ys[i])
        if any(abs(x - sx) < w // 2 and abs(y - sy) < h // 2 for sx, sy in seen):
            continue
        seen.append((x, y))
        matches.append((x, y, float(result[y, x])))
    return matches


def read_level_near(screen: np.ndarray, x: int, y: int,
                    w: int, h: int) -> str | None:
    """
    Intenta leer el nivel del monstruo con OCR justo debajo del icono.
    Ajusta el ROI segun donde aparezca el numero en tu UI.
    """
    if not OCR_AVAILABLE:
        return None
    roi = screen[y + h: y + h + h, x: x + w]
    if roi.size == 0:
        return None
    gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
    _, thr = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY)
    text = pytesseract.image_to_string(
        thr,
        config="--psm 7 -c tessedit_char_whitelist=0123456789Llv"
    ).strip()
    return text or None


def log_finding(monster: str, x: int, y: int, score: float,
                level: str | None = None) -> None:
    """Anade una fila al CSV de hallazgos."""
    new_file = not LOG_FILE.exists()
    with LOG_FILE.open("a", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        if new_file:
            w.writerow(["timestamp", "monster", "x_px", "y_px", "score", "level"])
        w.writerow([
            datetime.now().isoformat(timespec="seconds"),
            monster, x, y, f"{score:.3f}", level or ""
        ])


def main() -> None:
    templates = load_templates()
    if not templates:
        print("[!] No hay plantillas en ./templates. Saliendo.")
        return

    print(f"[+] {len(templates)} plantillas cargadas: {list(templates)}")
    print(f"[+] Region de captura: {GAME_REGION}")
    print(f"[+] Escaneando cada {SCAN_INTERVAL}s. Ctrl+C para detener.")
    print(f"[+] Log: {LOG_FILE}")

    try:
        while True:
            screen = capture_screen()
            ts = datetime.now().strftime("%H:%M:%S")
            total = 0
            for name, tpl in templates.items():
                h, w = tpl.shape[:2]
                for x, y, score in find_matches(screen, tpl):
                    level = read_level_near(screen, x, y, w, h)
                    print(f"[{ts}] {name:>15} @ ({x:4d},{y:4d}) "
                          f"score={score:.2f} lvl={level or '-'}")
                    log_finding(name, x, y, score, level)
                    total += 1
            if total == 0:
                print(f"[{ts}] sin hallazgos en este scan")
            time.sleep(SCAN_INTERVAL)
    except KeyboardInterrupt:
        print("\n[+] Detenido por el usuario.")


if __name__ == "__main__":
    main()
