# -*- coding: utf-8 -*-
"""
Experimentul 5 — Comunicatorul nelocal fara coincidente propus in DIRD
(DIA-08-1003-016, sectiunile VI-X, Fig. 7, 10 si 11).

Versiunea "slit-imaging" a experimentului Ghost Interference: fantele imagine S2
sunt cuplate la doua fibre optice de 10 km care duc la un COMUTATOR optic:
  pozitia "1" -> fibrele merg la doua detectoare separate -> masura WHICH-WAY
                 -> camera ar vedea difractie de o fanta (fara franje);
  pozitia "0" -> fibrele merg intr-un combinator C -> masura de UNDA
                 -> camera ar vedea franje de doua fante.
Emitatorul trimite biti comutand; receptorul citeste histograma camerei CCD.

Doua modele:
  * "QM standard" — combinatorul are DOUA porturi de iesire ((|A>+|B>)/sqrt2 si
    (|A>-|B>)/sqrt2). Sumand peste ambele (sau peste fotonii pierduti), camera
    vede mereu (|Psi_A|^2+|Psi_B|^2)/2 -> niciun semnal (no-signaling).
  * "Ipoteza DIRD" — toata lumina iese coerent pe portul detectat, deci pozitia
    "0" ar produce franje vizibile fara coincidente -> semnal supraluminal
    si, cu fibre de intarziere (Fig. 10), chiar RETROCAUZAL.

Panoul de timp reproduce calculul din sectiunea VIII: fibra de 10 km (n=1.5)
intarzie "trimiterea" cu 50 us; daca receptia unui bit dureaza mai putin,
bitul este citit INAINTE de a fi trimis.
"""
import sys
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

from phys_comun import amplitudini_fante, esantioneaza, contrast_franje, perioada_franje

XMAX = 12.0
NX = 2001
NBIN = 97
MAXBITI = 8
PRAG = 0.15
RATA = 10e6        # perechi/s (sursa KTP polata periodic, sectiunea VIII)
FIBRA_KM = 10.0
DELAY_US = FIBRA_KM * 1.5 / 3e5 * 1e6 * 1000 / 1000  # 50 us


class AppDIRD:
    def __init__(self, root):
        self.root = root
        root.title("Exp. 5 — Comunicatorul nelocal DIRD (Fig. 7/10/11)")
        self.rng = np.random.default_rng()
        self.x = np.linspace(-XMAX, XMAX, NX)
        self.bins = np.linspace(-XMAX, XMAX, NBIN + 1)
        self.centre = 0.5 * (self.bins[:-1] + self.bins[1:])
        self.psiA, self.psiB = amplitudini_fante(self.x)
        self.p_incoerent = (np.abs(self.psiA) ** 2 + np.abs(self.psiB) ** 2) / 2
        pc = np.abs(self.psiA + self.psiB) ** 2
        self.p_franje = pc / np.trapezoid(pc, self.x)
        self.per = perioada_franje()
        self.activ = False

        ctl = ttk.Frame(root, padding=8)
        ctl.pack(side=tk.LEFT, fill=tk.Y)
        ttk.Label(ctl, text="Comutator optic\nwhich-way / combinator",
                  font=("Segoe UI", 12, "bold")).pack(pady=(0, 6))

        self.desen = tk.Canvas(ctl, width=240, height=170, bg="white",
                               highlightthickness=1, highlightbackground="#aaa")
        self.desen.pack(pady=4)

        ttk.Label(ctl, text=f"Mesaj binar (max {MAXBITI} biti):").pack(anchor="w")
        self.v_msg = tk.StringVar(value="0110")
        ttk.Entry(ctl, textvariable=self.v_msg, width=14).pack(anchor="w", pady=2)

        ttk.Label(ctl, text="Fotoni pe bit:").pack(anchor="w", pady=(6, 0))
        self.v_n = tk.IntVar(value=3000)
        ttk.Scale(ctl, from_=100, to=20000, variable=self.v_n).pack(fill=tk.X)

        ttk.Label(ctl, text="Model fizic:").pack(anchor="w", pady=(6, 0))
        self.v_model = tk.StringVar(value="qm")
        ttk.Radiobutton(ctl, text="QM standard (corect)",
                        variable=self.v_model, value="qm").pack(anchor="w")
        ttk.Radiobutton(ctl, text="Ipoteza DIRD (semnalizare)",
                        variable=self.v_model, value="dird").pack(anchor="w")

        ttk.Button(ctl, text="Transmite", command=self.transmite).pack(fill=tk.X, pady=8)
        self.lbl_tx = ttk.Label(ctl, text="TX: -", font=("Consolas", 12))
        self.lbl_tx.pack(anchor="w")
        self.lbl_rx = ttk.Label(ctl, text="RX: -", font=("Consolas", 12))
        self.lbl_rx.pack(anchor="w")
        self.lbl_timp = ttk.Label(ctl, text="", font=("Consolas", 9), justify="left")
        self.lbl_timp.pack(anchor="w", pady=6)

        ttk.Label(ctl, wraplength=230, justify="left", foreground="#555", text=(
            "Fig. 10 din DIRD: fibre de 10 km intarzie evenimentul de 'trimitere' "
            "cu 50 µs. Daca cei N fotoni ai unui bit sosesc la camera in mai "
            "putin de 50 µs, in ipoteza DIRD bitul e citit INAINTE de a fi "
            "trimis (retrocauzalitate). In QM standard camera nu vede nimic, "
            "oricum ar sta comutatorul.")).pack(anchor="w")

        self.fig = Figure(figsize=(9, 6.5), dpi=100)
        self.canvas = FigureCanvasTkAgg(self.fig, master=root)
        self.canvas.get_tk_widget().pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

        self.deseneaza_schema("-")
        self.pregateste_axe([])
        if "--test" in sys.argv:
            self.transmite()
            root.after(3500, root.destroy)

    # ------------------------------------------------------------------
    def deseneaza_schema(self, bit):
        c = self.desen
        c.delete("all")
        c.create_rectangle(10, 70, 40, 100, fill="#cde", outline="black")
        c.create_text(25, 85, text="BBO", font=("Segoe UI", 7))
        c.create_line(40, 78, 110, 50, arrow=tk.LAST)   # brat idler -> S2
        c.create_line(40, 92, 110, 130, arrow=tk.LAST)  # brat semnal -> camera
        c.create_rectangle(110, 40, 118, 60, fill="black")
        c.create_text(114, 30, text="S2", font=("Segoe UI", 7))
        c.create_text(150, 38, text=f"fibre {FIBRA_KM:.0f} km", font=("Segoe UI", 7))
        # comutatorul
        culoare = "#bbb" if bit == "-" else ("#2ca02c" if bit == "0" else "#d62728")
        c.create_rectangle(180, 35, 230, 70, outline="black", fill=culoare)
        c.create_text(205, 45, text="comutator", font=("Segoe UI", 7))
        c.create_text(205, 58, text=("?" if bit == "-" else
                                     ("combinator C" if bit == "0" else "which-way D")),
                      font=("Segoe UI", 7, "bold"))
        c.create_rectangle(110, 120, 230, 145, fill="#eee", outline="black")
        c.create_text(170, 132, text="camera CCD (receptor)", font=("Segoe UI", 8))
        c.create_text(120, 160, text="emitator = comutatorul", font=("Segoe UI", 8),
                      anchor="w")

    def pregateste_axe(self, biti):
        self.fig.clear()
        n = max(1, len(biti))
        gs = self.fig.add_gridspec(2, n, hspace=0.5, wspace=0.35, height_ratios=[1, 1])
        self.ax_t = self.fig.add_subplot(gs[0, :])
        self.ax_t.plot(self.x, self.p_franje, "g", lw=1.3,
                       label="comutator '0' (combinator) — ipoteza DIRD")
        self.ax_t.plot(self.x, self.p_incoerent, "k", lw=1.5,
                       label="QM standard: camera, mereu")
        self.ax_t.legend(fontsize=8)
        self.ax_t.set_title("Modelele asteptate pe camera CCD")
        self.ax_bit = [self.fig.add_subplot(gs[1, i]) for i in range(n)]
        for a in self.ax_bit:
            a.set_xticks([]); a.set_yticks([])
        self.canvas.draw_idle()

    # ------------------------------------------------------------------
    def transmite(self):
        if self.activ:
            return
        msg = "".join(ch for ch in self.v_msg.get() if ch in "01")[:MAXBITI] or "0"
        self.v_msg.set(msg)
        self.biti, self.rx = msg, []
        self.pregateste_axe(msg)
        self.activ, self.idx = True, 0
        self.cnt = np.zeros(NBIN)
        self.ncur = 0
        self.lbl_tx.config(text=f"TX: {msg}")
        self.lbl_rx.config(text="RX: ")
        t_bit_us = int(self.v_n.get()) / RATA * 1e6
        avans = 50.0 - t_bit_us
        self.lbl_timp.config(text=(
            f"rata sursei:   {RATA / 1e6:.0f} MHz\n"
            f"durata bit:    {t_bit_us:.1f} us\n"
            f"intarziere fibra: 50.0 us\n"
            + (f"-> bit citit cu {avans:.1f} us INAINTE\n   de trimitere (retrocauzal!)"
               if avans > 0 else "-> bit citit dupa trimitere")))
        self.root.after(10, self.pas)

    def pas(self):
        if not self.activ:
            return
        bit = self.biti[self.idx]
        self.deseneaza_schema(bit)
        if self.v_model.get() == "dird" and bit == "0":
            pdf = self.p_franje
        else:
            pdf = self.p_incoerent
        nt = int(self.v_n.get())
        chunk = min(700, nt - self.ncur)
        x0, _ = esantioneaza(self.x, pdf, chunk, self.rng)
        self.cnt += np.histogram(x0, self.bins)[0]
        self.ncur += chunk

        a = self.ax_bit[self.idx]
        a.clear()
        a.fill_between(self.centre, self.cnt, step="mid", color="#1f77b4")
        a.set_title(f"TX='{bit}'", fontsize=9)
        a.set_xticks([]); a.set_yticks([])

        if self.ncur >= nt:
            cf = contrast_franje(self.cnt, self.per, self.centre)
            dec = "0" if cf > PRAG else "1"   # '0' = franje (combinator)
            self.rx.append(dec)
            a.set_xlabel(f"C={cf:.2f} -> RX='{dec}'",
                         color=("green" if dec == bit else "red"), fontsize=9)
            self.lbl_rx.config(text=f"RX: {''.join(self.rx)}")
            self.idx += 1
            self.cnt = np.zeros(NBIN)
            self.ncur = 0
            if self.idx >= len(self.biti):
                self.activ = False
                err = sum(x != y for x, y in zip(self.biti, self.rx))
                self.lbl_rx.config(text=f"RX: {''.join(self.rx)}  "
                                        f"({'OK' if err == 0 else f'{err} erori'})")
                self.deseneaza_schema("-")
        self.canvas.draw_idle()
        if self.activ:
            self.root.after(15, self.pas)


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