# -*- coding: utf-8 -*-
"""
Experimentul 7 — Masa optica din "qflab-virtual-lab_-new-experiment (5).json"
(Quantum Flytrap Virtual Lab): sursa GHZ cu 3 fotoni, polarizor pe bratul lui
Alice, retea de beam-splittere / PBS si detectoare pe bratele lui Bob.

Simulare cuantica locala exacta a starii GHZ:
    |GHZ> = (|HHH> + |VVV>) / sqrt(2)
 - fotonul 1 (Alice) trece prin polarizorul rotit la unghiul theta
   (absorption = 1 in JSON: fotonul respins este absorbit);
 - fotonii 2 si 3 (Bob) sunt masurati in baza diagonala (+/-) de reteaua
   PBS + beam-splitter cu detectoarele din JSON.

Rezultatul cheie (de ce nu exista comunicare supraluminala):
 - ratele de detectie ale lui Bob, FARA gating pe Alice, sunt 25%/25%/25%/25%
   INDIFERENT de theta -> rotirea polarizorului nu transmite nimic;
 - corelatiile IN COINCIDENTA cu trecerea fotonului prin polarizorul lui Alice
   variaza cu sin(2*theta) — dar pentru a le vedea e nevoie de canalul clasic
   (firele "wire" detector -> goal din fisierul JSON = gating-ul de coincidenta).
"""
import sys
import json
import os
import numpy as np
import tkinter as tk
from tkinter import ttk
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

CALE_JSON = r"C:\Users\XyZ\Desktop\FLP\qflab-virtual-lab_-new-experiment (5).json"
REZ = ("++", "+-", "-+", "--")


def probabilitati(theta, alice_a_trecut):
    """P(s2,s3) in baza diagonala, conditionat de rezultatul polarizorului.

    Daca fotonul 1 trece prin polarizorul la theta, starea 2-3 colapseaza in
    cos(t)|HH> + sin(t)|VV>; daca e absorbit, in sin(t)|HH> - cos(t)|VV>.
    In baza X: P(++)=P(--)=(1 +/- sin 2t)/4, P(+-)=P(-+)=(1 -/+ sin 2t)/4.
    """
    s = np.sin(2 * theta)
    if not alice_a_trecut:
        s = -s
    return np.array([(1 + s) / 4, (1 - s) / 4, (1 - s) / 4, (1 + s) / 4])


class AppGHZ:
    def __init__(self, root):
        self.root = root
        root.title("Exp. 7 — Quantum Flytrap: sursa GHZ + polarizor (qflab JSON)")
        self.rng = np.random.default_rng()

        ctl = ttk.Frame(root, padding=8)
        ctl.pack(side=tk.LEFT, fill=tk.Y)
        ttk.Label(ctl, text="|GHZ> = (|HHH>+|VVV>)/sqrt(2)",
                  font=("Segoe UI", 11, "bold")).pack(pady=(0, 6))

        self.desen = tk.Canvas(ctl, width=280, height=220, bg="#10131c",
                               highlightthickness=1, highlightbackground="#aaa")
        self.desen.pack(pady=4)
        self.deseneaza_masa()

        ttk.Label(ctl, text="Unghiul polarizorului lui Alice theta [grade]:").pack(
            anchor="w", pady=(8, 0))
        self.v_theta = tk.DoubleVar(value=67.5)   # rotation=3 in JSON -> 3*22.5 grade
        ttk.Scale(ctl, from_=0, to=90, variable=self.v_theta,
                  command=lambda e: self.actualizeaza_teorie()).pack(fill=tk.X)
        self.lbl_t = ttk.Label(ctl, text="", font=("Consolas", 10))
        self.lbl_t.pack(anchor="w")

        ttk.Label(ctl, text="Fotoni pe rulare:").pack(anchor="w", pady=(8, 0))
        self.v_n = tk.IntVar(value=2000)
        ttk.Scale(ctl, from_=200, to=20000, variable=self.v_n).pack(fill=tk.X)

        ttk.Button(ctl, text="Ruleaza fotonii", command=self.ruleaza).pack(
            fill=tk.X, pady=8)
        self.lbl = ttk.Label(ctl, text="", font=("Consolas", 9), justify="left")
        self.lbl.pack(anchor="w")

        ttk.Label(ctl, wraplength=260, justify="left", foreground="#555", text=(
            "Stanga: ratele lui Bob FARA informatia lui Alice — plate (25%) "
            "indiferent de theta: polarizorul nu poate trimite semnale.\n"
            "Dreapta: aceleasi detectoare, IN COINCIDENTA cu trecerea fotonului "
            "prin polarizor (firele detector->goal din JSON) — corelatii "
            "dependente de theta, vizibile doar cu canal clasic.")).pack(anchor="w")

        self.fig = Figure(figsize=(8.5, 6), dpi=100)
        self.axL = self.fig.add_subplot(121)
        self.axR = self.fig.add_subplot(122)
        self.fig.subplots_adjust(wspace=0.35, bottom=0.12)
        self.canvas = FigureCanvasTkAgg(self.fig, master=root)
        self.canvas.get_tk_widget().pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

        self.actualizeaza_teorie()
        if "--test" in sys.argv:
            self.ruleaza()
            root.after(2500, root.destroy)

    # ------------------------------------------------------------------
    def deseneaza_masa(self):
        """Deseneaza schema mesei optice din fisierul JSON (daca exista)."""
        c = self.desen
        c.create_text(8, 10, anchor="w", fill="#9ecbff", font=("Segoe UI", 8),
                      text="schema din qflab JSON (13 x 10)")
        piese = []
        if os.path.exists(CALE_JSON):
            try:
                with open(CALE_JSON, encoding="utf-8") as f:
                    date = json.load(f)
                piese = [(p["coord"]["x"], p["coord"]["y"], p["states"][0]["type"])
                         for p in date["grid"]["pieces"]]
            except Exception:
                pass
        if not piese:
            piese = [(0, 6, "Polarizer"), (0, 9, "Mirror"), (1, 8, "Mirror"),
                     (1, 9, "GHZWSource"), (7, 5, "Mirror"), (7, 8, "Mirror"),
                     (8, 1, "Goal"), (8, 7, "Detector"),
                     (9, 5, "PolarizingBeamSplitter"), (9, 7, "BeamSplitter"),
                     (9, 8, "Detector"), (11, 1, "Goal"), (11, 4, "Detector"),
                     (11, 5, "BeamSplitter"), (11, 7, "PolarizingBeamSplitter"),
                     (11, 9, "Mirror")]
        cul = {"GHZWSource": "#ff5555", "Polarizer": "#ffd400", "Mirror": "#cccccc",
               "BeamSplitter": "#66ccff", "PolarizingBeamSplitter": "#3388ff",
               "Detector": "#55ff88", "Goal": "#aa88ff"}
        sc, ox, oy = 20, 12, 20
        for (x, y, tip) in piese:
            X, Y = ox + x * sc, oy + y * sc
            c.create_rectangle(X + 2, Y + 2, X + sc - 2, Y + sc - 2,
                               fill=cul.get(tip, "#999"), outline="")
            c.create_text(X + sc / 2, Y + sc / 2, text=tip[0],
                          font=("Segoe UI", 7, "bold"))
        c.create_text(8, 212, anchor="w", fill="#888", font=("Segoe UI", 7),
                      text="G=sursa GHZ  P=polarizor  M=oglinda  B/P=splittere  D=detector")

    # ------------------------------------------------------------------
    def actualizeaza_teorie(self, counts_n=None, counts_c=None):
        th = np.radians(self.v_theta.get())
        self.lbl_t.config(text=f"theta = {self.v_theta.get():5.1f} grade   "
                               f"sin(2*theta) = {np.sin(2 * th):+.2f}")
        p_cond = probabilitati(th, True)
        p_neg = np.full(4, 0.25)   # marginala lui Bob: mereu plata

        for ax, teorie, cnt, titlu, culoare in (
                (self.axL, p_neg, counts_n,
                 "Bob singur (fara gating)\n— nu depinde de theta", "#888888"),
                (self.axR, p_cond, counts_c,
                 "Bob in coincidenta cu\npolarizorul lui Alice", "#1f77b4")):
            ax.clear()
            xs = np.arange(4)
            if cnt is not None and cnt.sum() > 0:
                ax.bar(xs, cnt / cnt.sum(), color=culoare, label="simulare MC")
            ax.plot(xs, teorie, "r_", markersize=26, markeredgewidth=3,
                    label="teorie")
            ax.set_xticks(xs, REZ)
            ax.set_ylim(0, 0.7)
            ax.set_title(titlu, fontsize=10)
            ax.legend(fontsize=8)
            ax.set_xlabel("detectoarele lui Bob (baza +/-)")
        self.canvas.draw_idle()

    def ruleaza(self):
        th = np.radians(self.v_theta.get())
        n = int(self.v_n.get())
        trecut = self.rng.random(n) < 0.5     # P(pass) = 1/2 pentru GHZ
        counts_n = np.zeros(4)
        counts_c = np.zeros(4)
        for grup, gate in ((trecut, True), (~trecut, False)):
            m = int(grup.sum())
            if m == 0:
                continue
            rez = self.rng.choice(4, size=m, p=probabilitati(th, gate))
            cnt = np.bincount(rez, minlength=4)
            counts_n += cnt
            if gate:
                counts_c += cnt
        self.actualizeaza_teorie(counts_n, counts_c)
        self.lbl.config(text=(f"fotoni: {n}\n"
                              f"Alice a trecut: {int(trecut.sum())} "
                              f"({trecut.mean() * 100:.1f}%)\n"
                              f"singles Bob:  {counts_n / counts_n.sum()}\n"
                              f"coincidente:  {counts_c / max(counts_c.sum(), 1)}"))


if __name__ == "__main__":
    root = tk.Tk()
    AppGHZ(root)
    root.mainloop()
