import subprocess, json, time, threading
from http.server import HTTPServer, BaseHTTPRequestHandler
import pyModeS as pms

aircraft, cpr = {}, {}

HTML = """<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ADS-B Радар</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@10/ol.css">
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { background: #1a1a2e; color: #e0e0e0; font-family: 'Courier New', monospace; height: 100vh; overflow: hidden; }
  #map { width: 100vw; height: 100vh; }
  #header {
    position: absolute; top: 0; left: 0; right: 0; z-index: 1000;
    background: rgba(26,26,46,0.92); border-bottom: 1px solid #2a2a5a;
    padding: 8px 16px; display: flex; align-items: center; gap: 16px;
  }
  #header h1 { font-size: 16px; color: #7eb8f7; letter-spacing: 2px; }
  #count { font-size: 13px; color: #aaa; }
  #popup {
    position: absolute; z-index: 2000;
    background: rgba(16,16,36,0.97); border: 1px solid #3a3a7a;
    border-radius: 8px; padding: 12px 16px; min-width: 200px;
    box-shadow: 0 4px 24px rgba(0,0,0,0.6);
    display: none; pointer-events: none;
  }
  #popup .icao { font-size: 18px; font-weight: bold; color: #7eb8f7; margin-bottom: 6px; }
  #popup .row { font-size: 13px; color: #ccc; margin: 2px 0; }
  #popup .label { color: #888; margin-right: 6px; }
  .ol-attribution { display: none !important; }
</style>
</head>
<body>
<div id="header">
  <h1>✈ ADS-B РАДАР</h1>
  <span id="count">Самолётов: 0</span>
</div>
<div id="map"></div>
<div id="popup">
  <div class="icao" id="p-icao"></div>
  <div class="row"><span class="label">Позывной:</span><span id="p-flight">—</span></div>
  <div class="row"><span class="label">Высота:</span><span id="p-alt">—</span></div>
  <div class="row"><span class="label">Скорость:</span><span id="p-speed">—</span></div>
  <div class="row"><span class="label">Курс:</span><span id="p-track">—</span></div>
  <div class="row"><span class="label">Пакетов:</span><span id="p-msgs">—</span></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/ol@10/dist/ol.js"></script>
<script>
const map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({
      source: new ol.source.XYZ({
        url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'
      })
    })
  ],
  view: new ol.View({ center: ol.proj.fromLonLat([37.62, 55.75]), zoom: 8 }),
  controls: []
});

const vectorSource = new ol.source.Vector();
map.addLayer(new ol.layer.Vector({ source: vectorSource }));

const features = {};
let selectedIcao = null;
let nowTs = Date.now() / 1000;
const popup = document.getElementById('popup');

function freshColor(seen) {
  const age = nowTs - (seen || 0);
  if (age < 15) return '#00ff88';
  if (age < 45) return '#ffcc00';
  return '#ff4444';
}

function makeStyle(track, selected, seen) {
  const color = selected ? '#ffd700' : freshColor(seen);
  return new ol.style.Style({ text: new ol.style.Text({
    text: '✈', font: (selected ? 'bold 26px' : '22px') + ' sans-serif',
    rotation: (track || 0) * Math.PI / 180,
    fill: new ol.style.Fill({ color }),
    stroke: new ol.style.Stroke({ color: '#1a1a2e', width: 2 })
  })});
}

function showPopup(ac, pixel) {
  selectedIcao = ac.icao;
  document.getElementById('p-icao').textContent = ac.icao.toUpperCase();
  document.getElementById('p-flight').textContent = ac.flight || '—';
  document.getElementById('p-alt').textContent = ac.altitude != null ? ac.altitude + ' ft' : '—';
  document.getElementById('p-speed').textContent = ac.speed != null ? ac.speed + ' кт' : '—';
  document.getElementById('p-track').textContent = ac.track != null ? ac.track + '°' : '—';
  document.getElementById('p-msgs').textContent = ac.msgs || '—';
  let [x, y] = pixel;
  if (x + 220 > map.getSize()[0]) x -= 230; else x += 20;
  popup.style.left = x + 'px'; popup.style.top = (y - 10) + 'px';
  popup.style.display = 'block';
  if (features[ac.icao]) features[ac.icao].setStyle(makeStyle(ac.track, true, ac.seen));
}

function hidePopup() {
  if (selectedIcao && features[selectedIcao]) {
    const d = features[selectedIcao]._data;
    features[selectedIcao].setStyle(makeStyle(d?.track, false, d?.seen));
  }
  selectedIcao = null;
  popup.style.display = 'none';
}

map.on('click', e => {
  const hit = map.forEachFeatureAtPixel(e.pixel, f => f, { hitTolerance: 12 });
  hit?._data ? showPopup(hit._data, e.pixel) : hidePopup();
});
map.on('pointermove', e => {
  map.getTargetElement().style.cursor =
    map.hasFeatureAtPixel(e.pixel, { hitTolerance: 12 }) ? 'pointer' : '';
});

async function refresh() {
  try {
    const data = await (await fetch('/aircraft.json')).json();
    nowTs = data.now;
    const active = new Set();
    for (const ac of data.aircraft) {
      if (ac.lat == null || ac.lon == null) continue;
      active.add(ac.icao);
      const coord = ol.proj.fromLonLat([ac.lon, ac.lat]);
      const sel = ac.icao === selectedIcao;
      if (features[ac.icao]) {
        features[ac.icao].getGeometry().setCoordinates(coord);
        features[ac.icao].setStyle(makeStyle(ac.track, sel, ac.seen));
        features[ac.icao]._data = ac;
      } else {
        const f = new ol.Feature({ geometry: new ol.geom.Point(coord) });
        f.setStyle(makeStyle(ac.track, false, ac.seen)); f._data = ac;
        vectorSource.addFeature(f); features[ac.icao] = f;
      }
    }
    for (const icao of Object.keys(features)) {
      if (!active.has(icao)) { vectorSource.removeFeature(features[icao]); delete features[icao]; }
    }
    document.getElementById('count').textContent = 'Самолётов: ' + active.size;
  } catch(e) {}
}
refresh(); setInterval(refresh, 3000);
</script>
</body>
</html>"""


def decode():
    proc = subprocess.Popen(["rtl_adsb.exe"], stdout=subprocess.PIPE, text=True)
    for line in proc.stdout:
        line = line.strip()
        if not (line.startswith('*') and line.endswith(';')):
            continue
        msg = line[1:-1]
        if len(msg) not in (14, 28):
            continue
        try:
            if pms.df(msg) != 17:
                continue
            icao = pms.icao(msg)
            tc = pms.typecode(msg)
            if icao not in aircraft:
                aircraft[icao] = {'icao': icao}
            aircraft[icao]['seen'] = time.time()
            aircraft[icao]['msgs'] = aircraft[icao].get('msgs', 0) + 1
            if 1 <= tc <= 4:
                cs = pms.adsb.callsign(msg).strip()
                if cs: aircraft[icao]['flight'] = cs
            elif 9 <= tc <= 18:
                alt = pms.adsb.altitude(msg)
                if alt: aircraft[icao]['altitude'] = alt
                oe = pms.adsb.oe_flag(msg)
                cpr.setdefault(icao, {})[oe] = (msg, time.time())
                try:
                    pos = pms.adsb.position_with_ref(msg, 55.75, 37.62)
                    if pos: aircraft[icao]['lat'], aircraft[icao]['lon'] = pos
                except: pass
                if 0 in cpr[icao] and 1 in cpr[icao]:
                    m0,t0 = cpr[icao][0]; m1,t1 = cpr[icao][1]
                    if abs(t0-t1) < 10:
                        try:
                            pos = pms.adsb.position(m0, m1, t0, t1)
                            if pos: aircraft[icao]['lat'], aircraft[icao]['lon'] = pos
                        except: pass
            elif tc == 19:
                v = pms.adsb.velocity(msg)
                if v: aircraft[icao]['speed'], aircraft[icao]['track'] = v[0], v[1]
        except: pass


class H(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/aircraft.json':
            now = time.time()
            active = [v for v in aircraft.values() if now - v.get('seen',0) < 120]
            body = json.dumps({'now': now, 'aircraft': active}).encode()
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.send_header('Access-Control-Allow-Origin', '*')
            self.end_headers()
            self.wfile.write(body)
        elif self.path == '/':
            body = HTML.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'text/html; charset=utf-8')
            self.end_headers()
            self.wfile.write(body)
        else:
            self.send_response(404)
            self.end_headers()
    def log_message(self, *a): pass


threading.Thread(target=decode, daemon=True).start()
print("Запущен!")
print("  Карта:  http://localhost:8080/")
print("  API:    http://localhost:8080/aircraft.json")
HTTPServer(('0.0.0.0', 8080), H).serve_forever()
