# -*- coding: utf-8 -*-
"""
こちらの PC から ATLUS 搭載PC のエージェントを叩く手元の道具。

  python3 atlas.py health
  python3 atlas.py windows [絞り込み]
  python3 atlas.py shot [--window ATLUS] [--scale 0.5]
  python3 atlas.py exec "コマンド"
  python3 atlas.py start "C:\\...\\AtlusNext.exe"
  python3 atlas.py push <手元のファイル> <向こうの置き場所>
  python3 atlas.py pull <向こうのファイル> <手元の置き場所>
  python3 atlas.py wait "C:\\atlas\\out\\*.xlsx" [待つ秒数]
  python3 atlas.py ls <向こうのディレクトリ>
  python3 atlas.py tree <ウィンドウ題名の一部> [深さ]
  python3 atlas.py ui '{"window":"ATLUS","action":"click","target":{"title":"開く"}}'

つなぎ先と合言葉は C:/Users/kyota/.config/secure-tokens/atlas-agent.json に置く。
  {"host": "192.168.1.51", "port": 8765, "token": "……"}
"""

import json
import os
import sys
import time
import urllib.parse
import urllib.request

sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")  # 失敗したときの文言も読めるようにする

CONF = os.environ.get("ATLAS_CONF",
                      r"C:/Users/kyota/.config/secure-tokens/atlas-agent.json")
SHOTDIR = os.environ.get(
    "ATLAS_SHOTDIR",
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "shots"))


def conf():
    if not os.path.exists(CONF):
        sys.exit("つなぎ先が未設定。%s に {\"host\":…, \"port\":…, \"token\":…} を置くこと" % CONF)
    with open(CONF, encoding="utf-8") as f:
        c = json.load(f)
    return "http://%s:%s" % (c["host"], c.get("port", 8765)), c["token"]


def call(path, query=None, body=None, raw=False, timeout=180):
    base, token = conf()
    url = base + path
    if query:
        url += "?" + urllib.parse.urlencode(query)
    data = None
    if body is not None:
        data = body if isinstance(body, bytes) else json.dumps(body).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers={"X-Token": token},
                                 method="POST" if data is not None else "GET")
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            blob = r.read()
            return (blob, dict(r.headers)) if raw else json.loads(blob.decode("utf-8"))
    except urllib.error.HTTPError as e:
        blob = e.read()
        try:
            return json.loads(blob.decode("utf-8"))
        except Exception:
            return {"ok": False, "error": "HTTP %d %s" % (e.code, blob[:200])}
    except urllib.error.URLError as e:
        sys.exit("つながらない: %s（エージェントが向こうで動いているか、"
                 "Windows ファイアウォールで塞がれていないか）" % e.reason)


def show(d):
    print(json.dumps(d, ensure_ascii=False, indent=1))


def main():
    if len(sys.argv) < 2:
        sys.exit(__doc__)
    cmd, a = sys.argv[1], sys.argv[2:]

    if cmd == "health":
        show(call("/health"))

    elif cmd == "windows":
        d = call("/windows", {"q": a[0]} if a else None)
        if not d.get("ok"):
            return show(d)
        print("見えているウィンドウ %d 個" % d["count"])
        for w in d["windows"]:
            r = w["rect"]
            print("  %-48s %-22s %dx%d  hwnd=%d"
                  % (w["title"][:48], w["class"][:22],
                     r[2] - r[0], r[3] - r[1], w["hwnd"]))

    elif cmd == "shot":
        q = {}
        for i, x in enumerate(a):
            if x == "--window":
                q["window"] = a[i + 1]
            if x == "--scale":
                q["scale"] = a[i + 1]
        blob, hdr = call("/shot", q, raw=True)
        if blob[:8] != b"\x89PNG\r\n\x1a\n":
            return show(json.loads(blob.decode("utf-8")))
        os.makedirs(SHOTDIR, exist_ok=True)
        p = os.path.join(SHOTDIR, time.strftime("%Y%m%d-%H%M%S") + ".png")
        with open(p, "wb") as f:
            f.write(blob)
        print("%s  (%s, %d bytes)" % (p, hdr.get("X-Image-Size"), len(blob)))

    elif cmd in ("exec", "start"):
        d = call("/" + cmd, body={"cmd": a[0], "timeout": float(a[1]) if len(a) > 1 else 120})
        if d.get("error"):          # 断られた理由を握りつぶさない
            return show(d)
        if cmd == "start":
            return show(d)
        print("rc=%s  %.1fs%s" % (d.get("rc"), d.get("elapsed", 0),
                                  "  ※時間切れ" if d.get("timed_out") else ""))
        if d.get("stdout"):
            print(d["stdout"].rstrip())
        if d.get("stderr"):
            print("--- stderr ---\n" + d["stderr"].rstrip())

    elif cmd == "push":
        with open(a[0], "rb") as f:
            blob = f.read()
        show(call("/upload", {"path": a[1]}, body=blob))

    elif cmd == "pull":
        blob, hdr = call("/download", {"path": a[0]}, raw=True)
        with open(a[1], "wb") as f:
            f.write(blob)
        # どの版から出た数字かを後から辿れるよう、出所を控えに残す
        print("%s  %d bytes" % (a[1], len(blob)))
        print("  sha256=%s" % hdr.get("X-Sha256"))
        print("  向こうでの作成=%s  取得元=%s" % (hdr.get("X-Mtime"), hdr.get("X-Source-Host")))

    elif cmd == "wait":
        q = {"glob" if ("*" in a[0] or "?" in a[0]) else "path": a[0]}
        if len(a) > 1:
            q["timeout"] = a[1]
        d = call("/wait", q, timeout=float(q.get("timeout", 300)) + 30)
        show(d)

    elif cmd == "ls":
        d = call("/ls", {"path": a[0] if a else "."})
        if not d.get("ok"):
            return show(d)
        print(d["path"])
        for e in d["entries"]:
            print("  %s %10s  %s  %s" % ("d" if e.get("dir") else "-",
                                         e.get("bytes", ""), e.get("mtime", ""), e["name"]))

    elif cmd == "tree":
        q = {"window": a[0]}
        if len(a) > 1:
            q["depth"] = a[1]
        blob, _ = call("/tree", q, raw=True)
        print(blob.decode("utf-8", "replace"))

    elif cmd == "ui":
        show(call("/ui", body=json.loads(a[0])))

    else:
        sys.exit(__doc__)


if __name__ == "__main__":
    main()
