{% extends 'base.html' %}
{% load static %}
{% block title %}TLS 流量态势 - 天璇{% endblock %}
{% block content %}
<div class="card">
    <h2>TLS 流量态势可视化</h2>
    <p style="color:#666;">三维态势感知显示源和目标之间的 TLS 连接，提供全局流量态势可视化</p>
    <p style="font-size:0.8rem;color:#999;">
        <span id="arcCount">0</span> 条流量 | 
        颜色: <span style="color:#4361ee;">TLSv1.3</span> 
        <span style="color:#f72585;">TLSv1.2</span> 
        <span style="color:#7209b7;">TLSv1.1/1.0</span>
        <span style="color:#4cc9f0;">其他</span>
        <span style="color:#888;margin-left:0.5rem;">| 支持 hex 格式 (03 03 → TLSv1.2)</span>
    </p>
</div>

{% if all_runs %}
<div class="card" style="margin-bottom:1rem;">
    <h3>选择数据运行</h3>
    <form method="get" action="{% url 'analysis:globe' %}" id="runSelectForm">
        <div style="display:flex;flex-wrap:wrap;gap:0.5rem;align-items:center;">
            {% for r in all_runs %}
            <label style="font-size:0.85rem;cursor:pointer;">
                <input type="checkbox" name="runs" value="{{ r.display_id }}"
                    {% if r.display_id in selected_ids %}checked{% endif %}>
                Run #{{ r.display_id }}
            </label>
            {% endfor %}
            <button type="submit" class="btn btn-primary" style="margin-left:auto;">更新地球</button>
        </div>
    </form>
</div>
{% endif %}

<div class="card" style="margin-bottom:1rem;padding:0.8rem;">
    <div style="display:flex;gap:1.5rem;align-items:center;flex-wrap:wrap;">
        <label style="font-size:0.85rem;cursor:pointer;">
            <input type="checkbox" id="toggleGrid" checked onchange="gridGroup.visible = this.checked;">
            经纬网格
        </label>
        <label style="font-size:0.85rem;cursor:pointer;">
            <input type="checkbox" id="toggleBorders" checked onchange="borderGroup.visible = this.checked;">
            国境线
        </label>
        <span style="font-size:0.85rem;color:#666;">鼠标拖拽旋转 · 滚轮缩放</span>
    </div>
</div>

<div class="card" style="padding:0;overflow:hidden;background:radial-gradient(ellipse at center, #0a0a2e 0%, #000 100%);">
    <div id="globeContainer" style="width:100%;height:70vh;min-height:400px;cursor:grab;"></div>
</div>

<script src="{% static 'tianxuan/three.min.js' %}"></script>
<script src="{% static 'tianxuan/world_borders.js' %}"></script>
<script>
var flowData = {{ geo_flows|safe }};
var container = document.getElementById('globeContainer');
var H = container.clientHeight || window.innerHeight * 0.8;
var W = container.clientWidth;

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, W/H, 0.1, 1000);
camera.position.set(0, 0.5, 12);

var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(W, H);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setClearColor(0x000000, 0);
container.appendChild(renderer.domElement);

var texLoader = new THREE.TextureLoader();
var earthMap = texLoader.load("{% static 'tianxuan/earth_atmos_2048.jpg' %}");

var earthGeo = new THREE.SphereGeometry(5, 96, 96);
var earthMat = new THREE.MeshPhongMaterial({ map: earthMap, specular: new THREE.Color(0x333333), shininess: 5 });
var earth = new THREE.Mesh(earthGeo, earthMat);
scene.add(earth);

var glowGeo = new THREE.SphereGeometry(5.08, 64, 64);
var glowMat = new THREE.MeshPhongMaterial({ color: 0x4361ee, transparent: true, opacity: 0.12, side: THREE.BackSide });
scene.add(new THREE.Mesh(glowGeo, glowMat));

var gridMat = new THREE.LineBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.12 });
var gridGroup = new THREE.Group();
for (var lat = -80; lat <= 80; lat += 10) {
    var phi = (90 - lat) * Math.PI / 180; var pts = [];
    for (var i = 0; i <= 64; i++) {
        var theta = i / 64 * Math.PI * 2;
        pts.push(new THREE.Vector3(5.02 * Math.sin(phi) * Math.cos(theta), 5.02 * Math.cos(phi), 5.02 * Math.sin(phi) * Math.sin(theta)));
    }
    gridGroup.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), gridMat));
}
for (var lon = 0; lon < 360; lon += 10) {
    var pts = [];
    for (var i = 0; i <= 32; i++) {
        var phi = i / 32 * Math.PI; var theta = lon * Math.PI / 180;
        pts.push(new THREE.Vector3(5.02 * Math.sin(phi) * Math.cos(theta), 5.02 * Math.cos(phi), 5.02 * Math.sin(phi) * Math.sin(theta)));
    }
    gridGroup.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), gridMat));
}
scene.add(gridGroup);

// ── World borders (loaded from static file) ──
var borderGroup = new THREE.Group();
var borderMat = new THREE.LineBasicMaterial({ color: 0x88dd88, transparent: true, opacity: 0.4, depthTest: true, depthWrite: false });
var borderData = [];  // { line, center }
function renderBorders(borders) {
    if (!borders) return;
    for (var c = 0; c < borders.length; c++) {
        var pts = [];
        for (var p = 0; p < borders[c].length; p++) {
            var lat = borders[c][p][0], lon = borders[c][p][1];
            var phi = (90 - lat) * Math.PI / 180;
            var theta = (lon + 180) * Math.PI / 180;
            pts.push(new THREE.Vector3(5.01 * Math.sin(phi) * (-Math.cos(theta)), 5.01 * Math.cos(phi), 5.01 * Math.sin(phi) * Math.sin(theta)));
        }
        if (pts.length > 2) {
            var line = new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), borderMat);
            borderGroup.add(line);
            // Compute center of polygon (average of all points)
            var cx = 0, cy = 0, cz = 0;
            for (var p = 0; p < pts.length; p++) { cx += pts[p].x; cy += pts[p].y; cz += pts[p].z; }
            borderData.push({ line: line, center: new THREE.Vector3(cx / pts.length, cy / pts.length, cz / pts.length) });
        }
    }
}
if (typeof worldBorders !== 'undefined') renderBorders(worldBorders);
scene.add(borderGroup);

var starsGeo = new THREE.BufferGeometry();
var starPos = new Float32Array(5000 * 3);
for (var i = 0; i < 5000 * 3; i++) starPos[i] = (Math.random() - 0.5) * 400;
starsGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
scene.add(new THREE.Points(starsGeo, new THREE.PointsMaterial({ color: 0xffffff, size: 0.2, transparent: true, opacity: 0.7 })));

scene.add(new THREE.AmbientLight(0x222244));
var dl = new THREE.DirectionalLight(0xffffff, 1.2); dl.position.set(10, 15, 10); scene.add(dl);
scene.add(new THREE.DirectionalLight(0x8888ff, 0.3));

function ll2pos(lat, lon, r) {
    var phi = (90 - lat) * Math.PI / 180;
    var theta = (lon + 180) * Math.PI / 180;
    r = r || 5.5;  // well above earth surface (5.0) for depth testing
    return new THREE.Vector3(-r * Math.sin(phi) * Math.cos(theta), r * Math.cos(phi), r * Math.sin(phi) * Math.sin(theta));
}

var arcColors = { 'TLSv1.3': 0x4361ee, 'TLSv1.2': 0xf72585, 'TLSv1.1': 0x7209b7, 'TLSv1.0': 0x7209b7 };
var flowGroup = new THREE.Group();
var flowDataList = [];

flowData.slice(0, 120).forEach(function(f) {
    var src = ll2pos(f.slat, f.slon, 5.06);
    var dst = ll2pos(f.dlat, f.dlon, 5.06);
    if (src.distanceTo(dst) < 1.0) return;
    var c = arcColors[f.tls] || 0x4cc9f0;

    // Line from src to dst
    var lineGeo = new THREE.BufferGeometry().setFromPoints([src, dst]);
    var line = new THREE.Line(lineGeo, new THREE.LineBasicMaterial({
        color: c, transparent: true, opacity: 0.18, depthTest: true, depthWrite: false
    }));
    flowGroup.add(line);

    // Single dot
    var dot = new THREE.Mesh(
        new THREE.SphereGeometry(0.05, 6, 6),
        new THREE.MeshBasicMaterial({ color: c, transparent: true, opacity: 0.85, depthTest: true, depthWrite: false })
    );
    dot.position.copy(src);
    flowGroup.add(dot);

    flowDataList.push({
        src: src.clone(), dst: dst.clone(), dot: dot,
        time: f.time != null ? f.time : Math.random()
    });
});

scene.add(flowGroup);
document.getElementById('arcCount').textContent = flowDataList.length;

function updatePulses(time) {
    var speed = 0.08;
    for (var a = 0; a < flowDataList.length; a++) {
        var d = flowDataList[a];
        if (!d.src || !d.dst) continue;
        var t = ((time * speed + d.time) % 1);
        d.dot.position.lerpVectors(d.src, d.dst, t);
    }
}

// ── Quaternion momentum rotation (camera-relative, no polar flip) ──
var clock = new THREE.Clock();
var vel = new THREE.Vector3();  // angular velocity as axis·angle vector
var MAX_ROT = 0.5;  // max radians per frame

function getCameraRight() {
    var dir = new THREE.Vector3();
    camera.getWorldDirection(dir);
    var right = new THREE.Vector3();
    right.crossVectors(dir, new THREE.Vector3(0, 1, 0)).normalize();
    return right;
}

function applyRotToAll(q) {
    earth.quaternion.premultiply(q);
    flowGroup.quaternion.premultiply(q);
    gridGroup.quaternion.premultiply(q);
    borderGroup.quaternion.premultiply(q);
}

function animate() {
    requestAnimationFrame(animate);
    var dt = Math.min(clock.getDelta(), 0.1);  // cap to avoid spiral of death

    // Apply angular velocity (scaled by zoom so inertia feels consistent)
    if (vel.length() > 0.0001) {
        var zoomScale = camera.position.z / 12.0;
        var angle = vel.length() * zoomScale;
        if (angle > MAX_ROT) angle = MAX_ROT;
        var q = new THREE.Quaternion().setFromAxisAngle(
            vel.clone().normalize(), angle
        );
        applyRotToAll(q);
    }

    // Frame-rate independent decay: 0.98^(dt*60)
    vel.multiplyScalar(Math.pow(0.98, dt * 60));
    if (vel.length() < 0.001) vel.set(0, 0, 0);

    updatePulses(Date.now() / 1000);
    renderer.render(scene, camera);
}
animate();

window.addEventListener('resize', function() {
    H = container.clientHeight || window.innerHeight * 0.8;
    var w = container.clientWidth; renderer.setSize(w, H); camera.aspect = w / H; camera.updateProjectionMatrix();
});

// ── Mouse interaction ──
var dragging = false, px = 0, py = 0;
var lastAng = new THREE.Vector3();
renderer.domElement.addEventListener('mousedown', function(e) {
    if (e.button === 1) { e.preventDefault(); camera.position.set(0, 0.5, 12); return; }
    dragging = true; px = e.clientX; py = e.clientY;
    vel.set(0, 0, 0);
    lastAng.set(0, 0, 0);
});
renderer.domElement.addEventListener('wheel', function(e) { e.preventDefault(); camera.position.z += e.deltaY * 0.01; if (camera.position.z < 6) camera.position.z = 6; if (camera.position.z > 30) camera.position.z = 30; }, { passive: false });
window.addEventListener('mousemove', function(e) {
    if (!dragging) return;
    var dx = e.clientX - px, dy = e.clientY - py;
    px = e.clientX; py = e.clientY;
    // Scale rotation by camera zoom: zoomed IN → less rotation per pixel, zoomed OUT → more
    var zoomScale = camera.position.z / 12.0;
    // Camera-relative rotation: dx → world Y axis, dy → camera right axis
    var right = getCameraRight();
    var ang = new THREE.Vector3();
    ang.addScaledVector(new THREE.Vector3(0, 1, 0), dx * 0.008 * zoomScale);
    ang.addScaledVector(right, dy * 0.005 * zoomScale);
    // Apply rotation directly during drag
    var dragAngle = ang.length();
    if (dragAngle > 0.0001) {
        var dragQ = new THREE.Quaternion().setFromAxisAngle(ang.clone().normalize(), dragAngle);
        applyRotToAll(dragQ);
    }
    lastAng.copy(ang);
});
window.addEventListener('mouseup', function() {
    if (dragging && lastAng.length() > 0.003) {
        vel.copy(lastAng);
    } else {
        vel.set(0, 0, 0);
    }
    dragging = false;
});
</script>
{% endblock %}
