"""Stellt den Zoom der Minimap auf die Stufe, die der Bot braucht.

Der Bot erkennt seine Position, indem er den Minimap-Ausschnitt auf der
Weltkarte wiederfindet. Das geht nur bei genau einer Zoomstufe: ein Pixel
der Minimap muss einem Feld im Spiel entsprechen. Tibia merkt sich den
Zoom pro Charakter, und er steht in keiner Konfigurationsdatei - er laesst
sich nur ueber die beiden Lupen-Knoepfe neben der Minimap aendern.

Vorgehen, damit es ohne Raten funktioniert:
1. Ganz herauszoomen. Damit ist der Startpunkt bekannt, egal wie der
   Charakter vorher stand.
2. Schrittweise hineinzoomen und nach jedem Schritt messen, wie gut sich
   der Ausschnitt auf der Weltkarte wiederfinden laesst.
3. Die Stufe mit dem besten Wert ansteuern.

Zwei Fallen, die hier bewusst vermieden werden:
- Der Knopfblock verschiebt sich, wenn sich die Seitenleiste aendert
  (gemessen: 19 Pixel). Darum wird seine Lage VOR JEDEM Klick neu
  bestimmt, nie einmal am Anfang.
- Klicks muessen ueber leftClick() laufen, nicht direkt ueber den
  Arduino-Befehl: leftClick() holt Tibia zuerst in den Vordergrund. Ohne
  Fokus landet der Klick im Nichts, und der Zoom aendert sich stumm nicht.
"""
import json
import sys
import time
from pathlib import Path

repo = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(repo))

import cv2

from src.repositories.radar import config
from src.repositories.radar.core import (
    getCoordinate, getFloorLevel, getRadarImage, getRadarToolsPosition,
)
from src.utils.core import getScreenshot
from src.utils.ino import ArduinoUnavailableError
from src.utils.mouse import leftClick

ERGEBNIS = repo / '.minimap-zoom.json'
# Der Knopfblock (radarTools.png) ist 20x60 Pixel und enthaelt drei
# Knoepfe zu je 20x20: oben herauszoomen, mitte hineinzoomen, unten Karte.
KNOPF_HOEHE = 20
RAUS_MITTE = KNOPF_HOEHE // 2                  # 10
REIN_MITTE = KNOPF_HOEHE + KNOPF_HOEHE // 2    # 30
# So oft, dass jede Zoomstufe sicher am Anschlag landet.
GANZ_RAUS = 8
STUFEN = 8


def _zustand():
    """Bild, Knopfblock, Trefferguete und gefundene Position.

    Als Erfolg zaehlt nicht ein absoluter Trefferwert, sondern ob der Bot
    seine Position tatsaechlich bestimmen kann - das ist der Zweck der
    ganzen Uebung. Der absolute Wert taugt dafuer nicht als Massstab: Auf
    der Minimap liegen das eigene Kreuz und die Punkte anderer Kreaturen,
    die es auf der Weltkarte nicht gibt. Selbst bei perfektem Zoom bleibt
    der Wert daher deutlich unter eins.
    """
    bild = getScreenshot()
    anker = getRadarToolsPosition(bild)
    if anker is None:
        return None, 0.0, None
    ausschnitt = getRadarImage(bild, anker)
    etage = getFloorLevel(bild)
    if etage is None:
        return anker, 0.0, None
    karte = config.floorsImgs[etage]
    if (ausschnitt.shape[0] > karte.shape[0]
            or ausschnitt.shape[1] > karte.shape[1]):
        return anker, 0.0, None
    treffer = cv2.matchTemplate(karte, ausschnitt, cv2.TM_CCOEFF_NORMED)
    wert = float(cv2.minMaxLoc(treffer)[1])
    try:
        position = getCoordinate(bild)
    except Exception:
        position = None
    return anker, wert, position


def _klick(versatz):
    """Auf einen der Lupen-Knoepfe klicken; Lage vorher neu bestimmen.

    Gibt False zurueck, wenn nicht geklickt werden konnte - etwa weil der
    Arduino belegt ist (der Bot laeuft bereits und haelt den seriellen
    Port). Der Startablauf soll daran nicht scheitern.
    """
    anker = getRadarToolsPosition(getScreenshot())
    if anker is None:
        return False
    try:
        leftClick((anker[0] + anker[2] // 2, anker[1] + versatz))
    except ArduinoUnavailableError:
        return False
    time.sleep(0.45)
    return True


def _bewertung(eintrag):
    """Position gefunden schlaegt jeden Trefferwert."""
    _, wert, position = eintrag
    return (position is not None, wert)


def main():
    anker, _, _ = _zustand()
    if anker is None:
        ERGEBNIS.write_text(json.dumps({
            'ok': False, 'grund': 'Minimap nicht gefunden',
        }), encoding='utf-8')
        print('mismatch')
        return 1

    # Schritt 1: bekannter Startpunkt.
    for _ in range(GANZ_RAUS):
        if not _klick(RAUS_MITTE):
            ERGEBNIS.write_text(json.dumps({
                'ok': False, 'grund': 'Eingabe nicht moeglich (Arduino belegt?)',
            }), encoding='utf-8')
            print('keine-eingabe')
            return 0

    # Schritt 2: hineinzoomen und dabei messen.
    _, wert, position = _zustand()
    verlauf = [(0, wert, position)]
    for stufe in range(1, STUFEN + 1):
        if not _klick(REIN_MITTE):
            break
        _, wert, position = _zustand()
        verlauf.append((stufe, wert, position))
        if position is not None:
            break

    beste = max(verlauf, key=_bewertung)
    beste_stufe = beste[0]

    # Schritt 3: die beste Stufe ansteuern. Wieder vom Anschlag aus, damit
    # die Schrittzahl eindeutig ist.
    if beste_stufe != verlauf[-1][0]:
        for _ in range(GANZ_RAUS):
            _klick(RAUS_MITTE)
        for _ in range(beste_stufe):
            _klick(REIN_MITTE)

    _, endwert, endposition = _zustand()
    ERGEBNIS.write_text(json.dumps({
        'ok': endposition is not None,
        'stufe': beste_stufe,
        'wert': round(endwert, 3),
        'position': endposition,
        'verlauf': [[s, round(w, 3), p is not None] for s, w, p in verlauf],
    }), encoding='utf-8')
    print('ok' if endposition is not None else f'schwach:{endwert:.3f}')
    return 0


if __name__ == '__main__':
    sys.exit(main())
