#!/usr/bin/env python3
"""Small browser controller for a headless Android emulator.

The controller intentionally uses only ADB primitives:

- `adb exec-out screencap -p` streams the current display.
- `adb shell input ...` sends tap, swipe, key, and text events.

This is slower than native emulator WebRTC, but it works in a headless shell and
does not require an emulator window or a physical Android device.
"""

import argparse
import json
import subprocess
import urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


INDEX = """<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Yorvana Billing Emulator</title>
  <style>
    :root { color-scheme: dark; }
    body { margin: 0; font: 14px system-ui, sans-serif; background: #111; color: #eee; }
    main { display: grid; grid-template-columns: minmax(300px, 430px) minmax(280px, 1fr); min-height: 100vh; }
    #screen { width: 100%; max-height: 100vh; object-fit: contain; background: #000; cursor: crosshair; }
    aside { padding: 16px; display: grid; align-content: start; gap: 12px; }
    button, input { font: inherit; padding: 10px 12px; border-radius: 6px; border: 1px solid #555; }
    button { background: #276ef1; color: white; cursor: pointer; }
    button.secondary { background: #24272d; }
    input { background: #202124; color: #eee; min-width: 0; }
    .row { display: flex; gap: 8px; flex-wrap: wrap; }
    .row button { flex: 1 1 auto; }
    #status { min-height: 20px; color: #b8c1d6; }
    @media (max-width: 760px) { main { grid-template-columns: 1fr; } aside { order: -1; } }
  </style>
</head>
<body>
<main>
  <img id="screen" alt="Android screen">
  <aside>
    <div class="row">
      <button onclick="key(3)">Home</button>
      <button onclick="key(4)">Back</button>
      <button onclick="key(187)">Overview</button>
      <button onclick="refresh()" class="secondary">Refresh</button>
    </div>
    <div class="row">
      <button onclick="launchPlay()">Play Store</button>
      <button onclick="swipe(540,1600,540,500,450)" class="secondary">Swipe Up</button>
      <button onclick="swipe(540,500,540,1600,450)" class="secondary">Swipe Down</button>
    </div>
    <input id="text" placeholder="Text for adb input text">
    <div class="row">
      <button onclick="typeText()">Type</button>
      <button onclick="key(66)" class="secondary">Enter</button>
      <button onclick="key(67)" class="secondary">Delete</button>
    </div>
    <div id="status"></div>
  </aside>
</main>
<script>
const img = document.getElementById('screen');
const statusEl = document.getElementById('status');
let naturalW = 1080;
let naturalH = 1920;

function status(text) { statusEl.textContent = text; }

async function post(path, body = {}) {
  status('Working...');
  const res = await fetch(path, { method: 'POST', body: JSON.stringify(body) });
  if (!res.ok) status(await res.text());
  else status('OK');
  setTimeout(refresh, 250);
}

function refresh() {
  img.src = '/screen.png?ts=' + Date.now();
}

img.onload = () => {
  naturalW = img.naturalWidth;
  naturalH = img.naturalHeight;
};

img.onclick = (ev) => {
  const rect = img.getBoundingClientRect();
  const x = Math.round((ev.clientX - rect.left) * naturalW / rect.width);
  const y = Math.round((ev.clientY - rect.top) * naturalH / rect.height);
  post('/tap', { x, y });
};

function key(code) { post('/key', { code }); }
function swipe(x1, y1, x2, y2, ms) { post('/swipe', { x1, y1, x2, y2, ms }); }
function launchPlay() { post('/launch-play'); }
function typeText() { post('/text', { text: document.getElementById('text').value }); }

refresh();
setInterval(refresh, 2000);
</script>
</body>
</html>
"""


def adb(serial, *args, stdin=None):
    return subprocess.run(
        ["adb", "-s", serial, *args],
        input=stdin,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=True,
    ).stdout


def encode_input_text(raw):
    return raw.replace("\\", "\\\\").replace(" ", "%s")


class Handler(BaseHTTPRequestHandler):
    serial = "emulator-5554"

    def do_HEAD(self):
        if urllib.parse.urlparse(self.path).path == "/screen.png":
            self.send_response(200)
            self.send_header("content-type", "image/png")
            self.send_header("cache-control", "no-store")
            self.end_headers()
            return
        self.send_response(404)
        self.end_headers()

    def do_GET(self):
        path = urllib.parse.urlparse(self.path).path
        if path == "/":
            self.send(200, "text/html; charset=utf-8", INDEX.encode())
            return
        if path == "/screen.png":
            try:
                self.send(200, "image/png", adb(self.serial, "exec-out", "screencap", "-p"))
            except subprocess.CalledProcessError as exc:
                self.send(500, "text/plain", exc.stderr)
            return
        self.send(404, "text/plain", b"not found")

    def do_POST(self):
        length = int(self.headers.get("content-length", "0"))
        body = json.loads(self.rfile.read(length) or b"{}")
        path = urllib.parse.urlparse(self.path).path
        try:
            if path == "/tap":
                adb(self.serial, "shell", "input", "tap", str(body["x"]), str(body["y"]))
            elif path == "/swipe":
                adb(
                    self.serial,
                    "shell",
                    "input",
                    "swipe",
                    str(body["x1"]),
                    str(body["y1"]),
                    str(body["x2"]),
                    str(body["y2"]),
                    str(body.get("ms", 300)),
                )
            elif path == "/key":
                adb(self.serial, "shell", "input", "keyevent", str(body["code"]))
            elif path == "/launch-play":
                adb(self.serial, "shell", "monkey", "-p", "com.android.vending", "1")
            elif path == "/text":
                adb(self.serial, "shell", "input", "text", encode_input_text(str(body.get("text", ""))))
            else:
                self.send(404, "text/plain", b"not found")
                return
            self.send(204, "text/plain", b"")
        except (KeyError, subprocess.CalledProcessError) as exc:
            message = getattr(exc, "stderr", b"bad request") or str(exc).encode()
            self.send(500, "text/plain", message)

    def log_message(self, fmt, *args):
        return

    def send(self, code, content_type, body):
        self.send_response(code)
        self.send_header("content-type", content_type)
        self.send_header("cache-control", "no-store")
        self.send_header("content-length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


def main():
    parser = argparse.ArgumentParser(description="Serve a browser controller for a headless Android emulator.")
    parser.add_argument("--serial", default="emulator-5554", help="ADB serial to control.")
    parser.add_argument("--host", default="127.0.0.1", help="Host/IP to bind.")
    parser.add_argument("--port", type=int, default=8090, help="HTTP port to bind.")
    args = parser.parse_args()

    Handler.serial = args.serial
    print(f"Serving {args.serial} at http://{args.host}:{args.port}/", flush=True)
    ThreadingHTTPServer((args.host, args.port), Handler).serve_forever()


if __name__ == "__main__":
    main()
