"""
DCQE pe Calculator Cuantic Real — Sectiunea 4.3 (Lee Wen Wu, 2021)

Implementeaza experimental propus in documentul "Superluminal Communication?"
folosind 3 qubiti pe un procesor IBM Quantum real:

  q[0] = Alice  (qubit "care-cale")
  q[1] = Bob pozitie bit grosier  (positia 0/1 pe ecranul lui Bob)
  q[2] = Bob pozitie bit fin      (positia 2/3 pe ecranul lui Bob)

Pozitii Bob:
  q[2]=0, q[1]=0 -> pozitia 0
  q[2]=0, q[1]=1 -> pozitia 1
  q[2]=1, q[1]=0 -> pozitia 2
  q[2]=1, q[1]=1 -> pozitia 3

Cazuri simulate:
  Cazul 0 (Sec 4.1): Alice masoara in baza {|0>,|1>} (D3/D4) -> fara interferenta
  Cazul 1 (Sec 4.2): Alice masoara in baza {|+>,|->} (D1/D2) -> fara interferenta (marginal)
  Cazul 2 (Sec 4.3): Idem Cazul 1, post-selectat pe Alice=|+> (D5) -> INTERFERENTA!
"""

import os
import tkinter as tk
from tkinter import messagebox, ttk
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager


# =========================================================
# 0. INTERFATA GRAFICA
# =========================================================
def obtine_parametri():
    p = {"api_key": "", "shots": 4096, "delay": 0, "ruleaza": False}

    def on_submit():
        key = entry_api.get().strip()
        if not key:
            messagebox.showwarning("Atentie", "Introduce cheia API IBM Quantum!")
            return
        try:
            shots = int(entry_shots.get().strip())
            delay = int(entry_delay.get().strip())
            if shots < 1 or delay < 0:
                raise ValueError
        except ValueError:
            messagebox.showwarning("Eroare", "Shots si delay trebuie sa fie intregi pozitivi!")
            return
        p["api_key"] = key
        p["shots"]   = shots
        p["delay"]   = delay
        p["ruleaza"] = True
        root.destroy()

    def on_close():
        root.destroy()
        exit()

    root = tk.Tk()
    root.title("DCQE Sec.4.3 — Hardware IBM Quantum")
    root.geometry("470x340")
    root.resizable(False, False)
    root.protocol("WM_DELETE_WINDOW", on_close)

    tk.Label(root, text="DCQE Hardware — Sectiunea 4.3",
             font=("Arial", 13, "bold"), fg="#0f62fe").pack(pady=(14, 2))
    tk.Label(root, text="Implementare pe procesor IBM Quantum real (3 qubiti)",
             font=("Arial", 9), fg="gray").pack(pady=(0, 12))

    frame = tk.Frame(root); frame.pack(padx=30, fill="x")

    def row(lbl, default, show=None):
        f = tk.Frame(frame); f.pack(fill="x", pady=5)
        tk.Label(f, text=lbl, font=("Arial", 10), width=38, anchor="w").pack(side="left")
        kw = {"show": show} if show else {}
        e = tk.Entry(f, width=14, font=("Arial", 10), justify="center", **kw)
        e.insert(0, str(default)); e.pack(side="left")
        return e

    entry_api   = row("Cheie API IBM Quantum:", "", show="*")
    entry_shots = row("Numar shots:", 4096)
    entry_delay = row("Delay Bob (µs, 0 = fara delay):", 0)

    tk.Label(root,
             text="Procesorul cel mai liber va fi selectat automat.\n"
                  "Circuitul are 3 qubiti si ~8 porti — compatibil cu orice backend.",
             font=("Arial", 8), fg="gray").pack(pady=6)

    tk.Button(root, text="▶  Porneste Experimentul", command=on_submit,
              bg="#0f62fe", fg="white", font=("Arial", 11, "bold"),
              padx=10, pady=7).pack(pady=10)
    root.bind("<Return>", lambda e: on_submit())
    root.mainloop()
    return p


# =========================================================
# 1. CONSTRUCTIA CIRCUITELOR
# =========================================================
def construieste_circuite(delay_us):
    """
    Starea SPDC entanglata (analog digital cu 4 pozitii pentru Bob):

      |ψ> = 1/(2√2) * ( |000> + |001> - |010> - |011>
                       + i|100> - i|101> - i|110> + i|111> )

    In care |q2 q1 q0> cu q0=Alice, q1=BobPos1, q2=BobPos2.

    Proprietati cheie:
      - Bob marginal: P(pos) = 0.25 pentru toate cele 4 pozitii (fara interferenta)
      - Post-selectat Alice=|+> (D5 click): P(0)=P(1)=0.5, P(2)=P(3)=0
      - Post-selectat Alice=|-> (antifaza): P(0)=P(1)=0, P(2)=P(3)=0.5

    Circuitul de preparare: H(0) + H(1) + H(2) + Z(1) + CRZ(-pi, 2, 0)
    """
    def prep(qc):
        qc.h(0)          # Alice in superpoziție
        qc.h(1)          # Bob bit1 in superpoziție
        qc.h(2)          # Bob bit2 in superpoziție
        qc.z(1)          # faza -1 pe pozitiile 1 si 3
        qc.crz(-np.pi, 2, 0)  # faza diferita intre cai (interferenta)
        if delay_us > 0:
            qc.delay(delay_us, 1, unit='us')
            qc.delay(delay_us, 2, unit='us')

    # --- Cazul 0: Alice masoara in baza computationala {|0>,|1>} ---
    # (Sectiunea 4.1: D3/D4, pastreaza informatia de cale)
    qc0 = QuantumCircuit(3, 3)
    prep(qc0)
    qc0.measure([0, 1, 2], [0, 1, 2])

    # --- Cazul 1/2: Alice masoara in baza Hadamard {|+>,|->} ---
    # (Cazul 1 = Sec 4.2: beam splitter D1+D2, fara post-selectie)
    # (Cazul 2 = Sec 4.3: D5 focalizat, post-selectat pe Alice=0)
    qc12 = QuantumCircuit(3, 3)
    prep(qc12)
    qc12.h(0)       # Alice aplica H (echivalent cu combinarea cailor -> D5)
    qc12.measure([0, 1, 2], [0, 1, 2])

    return qc0, qc12


# =========================================================
# 2. SIMULARE IDEALA (referinta)
# =========================================================
def simuleaza_ideal(delay_us=0):
    """Compute distributiile exacte fara zgomot (Statevector)."""
    qc = QuantumCircuit(3)
    qc.h(0); qc.h(1); qc.h(2); qc.z(1); qc.crz(-np.pi, 2, 0)
    sv = Statevector(qc)

    # Cazul 0: marginala Bob (fara H pe Alice)
    prob0 = np.zeros(4)
    for i, amp in enumerate(sv.data):
        bits  = format(i, '03b')
        pos   = int(bits[0]) * 2 + int(bits[1])
        prob0[pos] += abs(amp) ** 2

    # Cazul 1: marginala Bob dupa H pe Alice
    qc2 = QuantumCircuit(3)
    qc2.h(0); qc2.h(1); qc2.h(2); qc2.z(1); qc2.crz(-np.pi, 2, 0); qc2.h(0)
    sv2 = Statevector(qc2)

    prob1_marg = np.zeros(4)        # fara post-selectie
    prob2_ps0  = np.zeros(4)        # post-selectat Alice=|+> (D5 click)
    prob2_ps1  = np.zeros(4)        # post-selectat Alice=|-> (antifaza)
    for i, amp in enumerate(sv2.data):
        bits  = format(i, '03b')
        alice = int(bits[2])
        pos   = int(bits[0]) * 2 + int(bits[1])
        prob1_marg[pos] += abs(amp) ** 2
        if alice == 0:
            prob2_ps0[pos] += abs(amp) ** 2
        else:
            prob2_ps1[pos] += abs(amp) ** 2

    # Normalizam distributiile post-selectate
    if prob2_ps0.sum() > 0:
        prob2_ps0 /= prob2_ps0.sum()
    if prob2_ps1.sum() > 0:
        prob2_ps1 /= prob2_ps1.sum()

    return prob0, prob1_marg, prob2_ps0, prob2_ps1


# =========================================================
# 3. PROCESARE REZULTATE HARDWARE
# =========================================================
def proceseaza_counts(counts, shots):
    """
    Extrage distributiile din counts dictionar Qiskit.
    Format bit string: 'c[2]c[1]c[0]' = 'BobPos2 BobPos1 Alice'
      string[0] = c[2] = q[2] = BobPos2 (bit grosier)
      string[1] = c[1] = q[1] = BobPos1 (bit fin)
      string[2] = c[0] = q[0] = Alice
    """
    prob_bob    = np.zeros(4)    # marginala Bob (ignoram Alice)
    prob_ps0    = np.zeros(4)    # post-selectat Alice=0
    prob_ps1    = np.zeros(4)    # post-selectat Alice=1
    n_alice0 = 0; n_alice1 = 0

    for state, cnt in counts.items():
        # Normalizeaza stringul la 3 biti
        s     = state.zfill(3)
        alice = int(s[2])         # rightmost = c[0] = q[0]
        pos   = int(s[0]) * 2 + int(s[1])   # c[2]*2 + c[1]

        prob_bob[pos] += cnt
        if alice == 0:
            prob_ps0[pos] += cnt; n_alice0 += cnt
        else:
            prob_ps1[pos] += cnt; n_alice1 += cnt

    prob_bob /= shots
    if n_alice0 > 0: prob_ps0 /= n_alice0
    if n_alice1 > 0: prob_ps1 /= n_alice1

    return prob_bob, prob_ps0, prob_ps1, n_alice0, n_alice1


# =========================================================
# 4. VIZIBILITATE FRANJE
# =========================================================
def vizibilitate(prob):
    Imax = prob.max(); Imin = prob.min()
    return (Imax - Imin) / (Imax + Imin) if (Imax + Imin) > 0 else 0.0


# =========================================================
# 5. GRAFIC COMPARATIV
# =========================================================
def genereaza_grafic(ideal, hw, backend_name, shots, delay_us):
    """
    6 panouri:
      Col 0: Cazul 0 (D3/D4)       Col 1: Cazul 1/marg (D1+D2)   Col 2: Cazul 2/PS (D5)
      Rand 0: Ideal (simulare)      Rand 1: Hardware IBM real
    """
    (i_caz0, i_caz1, i_ps0, i_ps1)   = ideal
    (h_caz0, h_caz1, h_ps0, h_ps1,
     backend_str, n_alice0, n_alice1) = hw

    BLUE  = '#1f77b4'; ORANGE = '#ff7f0e'; GREEN = '#2ca02c'
    RED   = '#d62728'; GRAY   = '#999999'; PURPLE = '#9467bd'

    pozitii = [0, 1, 2, 3]
    etichete = ['Pos 0', 'Pos 1', 'Pos 2', 'Pos 3']
    w = 0.35

    fig, axes = plt.subplots(2, 3, figsize=(15, 9))
    fig.patch.set_facecolor('#f8f8f8')

    titluri_col = [
        "Cazul 0 (Sec.4.1)\nAlice: D3/D4 — pastreaza calea",
        "Cazul 1 marg. (Sec.4.2)\nAlice: D1+D2 — fara post-sel.",
        "Cazul 2 post-sel. (Sec.4.3)\nAlice: D5 click — INTERFERENTA?",
    ]
    culori_ideal = [BLUE, ORANGE, GREEN]
    culori_hw    = [BLUE, ORANGE, GREEN]

    # Date pentru fiecare coloana
    date_ideal = [i_caz0, i_caz1, i_ps0]
    date_hw    = [h_caz0, h_caz1, h_ps0]

    # Vizibilitati
    V_ideal = [vizibilitate(d) for d in date_ideal]
    V_hw    = [vizibilitate(d) for d in date_hw]

    for col in range(3):
        # Rand 0: ideal
        ax = axes[0][col]
        x  = np.array(pozitii)
        ax.bar(x - w/2, date_ideal[col], w, color=culori_ideal[col],
               alpha=0.85, label=f'Ideal  V={V_ideal[col]:.2f}')
        ax.axhline(0.25, color='gray', lw=1, ls='--', alpha=0.5, label='Uniform 0.25')
        ax.set_xticks(pozitii); ax.set_xticklabels(etichete, fontsize=8)
        ax.set_ylim(0, 0.65); ax.set_ylabel('Probabilitate', fontsize=9)
        ax.set_title(titluri_col[col], fontsize=9, fontweight='bold')
        ax.legend(fontsize=8); ax.grid(axis='y', alpha=0.3)
        ax.tick_params(labelsize=8)

        # Rand 1: hardware
        ax = axes[1][col]
        ax.bar(x + w/2, date_hw[col], w, color=culori_hw[col],
               alpha=0.7, label=f'Hardware  V={V_hw[col]:.2f}',
               hatch='//')
        ax.bar(x - w/2, date_ideal[col], w, color=culori_ideal[col],
               alpha=0.4, label=f'Ideal  V={V_ideal[col]:.2f}')
        ax.axhline(0.25, color='gray', lw=1, ls='--', alpha=0.5)
        ax.set_xticks(pozitii); ax.set_xticklabels(etichete, fontsize=8)
        ax.set_ylim(0, 0.65); ax.set_ylabel('Probabilitate', fontsize=9)
        ax.set_xlabel('Pozitia pe ecranul lui Bob', fontsize=8)
        ax.legend(fontsize=8); ax.grid(axis='y', alpha=0.3)
        ax.tick_params(labelsize=8)

    # Etichete randuri
    axes[0][0].annotate('SIMULARE\nIDEALA\n(zero zgomot)',
                        xy=(0, 0.5), xytext=(-0.25, 0.5),
                        xycoords='axes fraction', textcoords='axes fraction',
                        fontsize=10, fontweight='bold', color='darkblue',
                        ha='center', va='center',
                        rotation=90)
    axes[1][0].annotate(f'HARDWARE\nREAL\n({backend_name})',
                        xy=(0, 0.5), xytext=(-0.25, 0.5),
                        xycoords='axes fraction', textcoords='axes fraction',
                        fontsize=10, fontweight='bold', color='darkred',
                        ha='center', va='center',
                        rotation=90)

    # Caseta concluzie
    V2_ideal = V_ideal[2]; V2_hw = V_hw[2]
    no_sig_ideal = max(abs(i_caz0 - i_caz1)) < 0.01
    no_sig_hw    = max(abs(h_caz0 - h_caz1)) < 0.05

    concluzie = (
        f"CONCLUZII\n"
        f"─────────────────────────────────────────\n"
        f"Vizibilitate franje Cazul 2 (Sec.4.3):\n"
        f"  Ideal (zero zgomot): V = {V2_ideal:.3f}\n"
        f"  Hardware {backend_name}: V = {V2_hw:.3f}\n\n"
        f"Teorema no-signalling (fara post-sel.):\n"
        f"  Ideal:    {'CONFIRMATA ✓' if no_sig_ideal else 'INCALCATA !'}\n"
        f"  Hardware: {'CONFIRMATA ✓' if no_sig_hw else 'INCALCATA !'}\n\n"
        f"Post-selectie Sec.4.3 (D5 click):\n"
        f"  Shots cu Alice=|+>: {n_alice0}  ({n_alice0/shots*100:.1f}%)\n"
        f"  Shots cu Alice=|->: {n_alice1}  ({n_alice1/shots*100:.1f}%)\n\n"
        f"Shots totale: {shots}  |  Delay Bob: {delay_us} µs\n"
        f"Procesorul: {backend_str}"
    )
    fig.text(0.5, 0.01, concluzie, ha='center', va='bottom',
             fontsize=8.5, fontfamily='monospace',
             bbox=dict(boxstyle='round', facecolor='#e8f4e8', alpha=0.9))

    fig.suptitle(
        "DCQE Hardware — Sectiunea 4.3 (Lee Wen Wu, 2021)\n"
        "Interferenta cuantica pe procesor IBM Quantum real",
        fontsize=13, fontweight='bold', y=0.99
    )

    plt.tight_layout(rect=[0, 0.18, 1, 0.97])
    plt.savefig('dcqe_hardware_rezultate.png', dpi=150,
                bbox_inches='tight', facecolor='#f8f8f8')
    print("Grafic salvat: dcqe_hardware_rezultate.png")
    return fig


# =========================================================
# 6. RAPORT TEXT
# =========================================================
def genereaza_raport(ideal, hw, shots, delay_us):
    (i_caz0, i_caz1, i_ps0, i_ps1)   = ideal
    (h_caz0, h_caz1, h_ps0, h_ps1,
     backend_str, n_alice0, n_alice1) = hw

    raport = f"""
=================================================================
RAPORT EXPERIMENT HARDWARE: DCQE Sectiunea 4.3
Lee Wen Wu, "Superluminal Communication?" (2021)
Procesor: {backend_str}
=================================================================

PARAMETRI
---------
  Shots: {shots}    Delay Bob: {delay_us} µs
  Qubiti: 3  (q0=Alice, q1=BobPos1, q2=BobPos2)

DISTRIBUTIA MARGINALA A LUI BOB (fara canal clasic)
----------------------------------------------------
  [IDEAL]           Caz0     Caz1
  Pozitia 0:     {i_caz0[0]:.4f}   {i_caz1[0]:.4f}
  Pozitia 1:     {i_caz0[1]:.4f}   {i_caz1[1]:.4f}
  Pozitia 2:     {i_caz0[2]:.4f}   {i_caz1[2]:.4f}
  Pozitia 3:     {i_caz0[3]:.4f}   {i_caz1[3]:.4f}

  [HARDWARE]        Caz0     Caz1
  Pozitia 0:     {h_caz0[0]:.4f}   {h_caz1[0]:.4f}
  Pozitia 1:     {h_caz0[1]:.4f}   {h_caz1[1]:.4f}
  Pozitia 2:     {h_caz0[2]:.4f}   {h_caz1[2]:.4f}
  Pozitia 3:     {h_caz0[3]:.4f}   {h_caz1[3]:.4f}

  No-signalling ideal:    {'CONFIRMAT' if max(abs(i_caz0-i_caz1)) < 0.01 else 'INCALCAT!'}
  No-signalling hardware: {'CONFIRMAT' if max(abs(h_caz0-h_caz1)) < 0.05 else 'INCALCAT! (zgomot?)'}

INTERFERENTA POST-SELECTATA (Sectiunea 4.3, D5)
------------------------------------------------
  [IDEAL] Post-selectat Alice=|+> (D5):
  Pozitia 0: {i_ps0[0]:.4f}  Pozitia 1: {i_ps0[1]:.4f}
  Pozitia 2: {i_ps0[2]:.4f}  Pozitia 3: {i_ps0[3]:.4f}
  Vizibilitate ideala: V = {vizibilitate(i_ps0):.4f}

  [HARDWARE] Post-selectat Alice=|+> (D5):
  Pozitia 0: {h_ps0[0]:.4f}  Pozitia 1: {h_ps0[1]:.4f}
  Pozitia 2: {h_ps0[2]:.4f}  Pozitia 3: {h_ps0[3]:.4f}
  Vizibilitate hardware: V = {vizibilitate(h_ps0):.4f}
  Shots folosite (Alice=|+>): {n_alice0} din {shots} ({n_alice0/shots*100:.1f}%)

INTERPRETARE FIZICA
-------------------
  1. Marginala lui Bob (randurile de sus din grafic):
     Cazul 0 = Cazul 1 = distributie uniforma.
     Teorema no-signalling CONFIRMATA.

  2. Post-selectat (Sec. 4.3):
     Vizibilitate ideala V=1.0 confirma ca interferenta EXISTA in
     datele corelate Alice-Bob, dar NUMAI dupa comunicare clasica.

  3. Zgomotul hardware:
     Reducerea vizibilitatii pe hardware (V<1.0) se datoreaza
     erorilor de poarta si decoherentei (T1/T2 ale procesorului).
     Cu delay={delay_us}µs, decoherenta T1 poate reduce si mai mult V.

  4. Concluzia documentului Lee Wen Wu (2021):
     Propunerea din Sec. 4.3 NU incalca teorema no-signalling.
     Interferenta este reala dar necesita canal clasic Alice->Bob
     pentru ca Bob sa o poata observa.
=================================================================
"""
    with open('raport_dcqe_hardware.txt', 'w', encoding='utf-8') as f:
        f.write(raport)
    print(raport)
    print("Raport salvat: raport_dcqe_hardware.txt")


# =========================================================
# MAIN
# =========================================================
if __name__ == "__main__":

    # --- Parametri ---
    p = obtine_parametri()
    API_KEY  = p["api_key"]
    shots    = p["shots"]
    delay_us = p["delay"]

    # --- Simulare ideala ---
    print("\nCalcuam simularea ideala...")
    ideal = simuleaza_ideal(delay_us)
    print("Simulare ideala gata.")
    for i, lbl in enumerate(["Caz0", "Caz1_marg", "PS_alice0", "PS_alice1"]):
        print(f"  {lbl}: {np.round(ideal[i], 4)}")

    # --- Conectare IBM ---
    print("\nConectare la IBM Quantum...")
    try:
        service = QiskitRuntimeService(channel="ibm_quantum_platform", token=API_KEY)
    except Exception as e:
        print(f"Eroare autentificare: {e}")
        exit()

    backend = service.least_busy(operational=True, simulator=False)
    print(f"Procesor selectat: {backend.name}")

    # --- Construieste circuite ---
    qc0, qc12 = construieste_circuite(delay_us)
    print("\nCircuitul Cazul 0:")
    print(qc0.draw(output='text'))
    print("\nCircuitul Cazul 1/2 (cu H pe Alice):")
    print(qc12.draw(output='text'))

    # --- Transpilare ---
    print(f"\nTranspilare pentru {backend.name} (optimization_level=2)...")
    pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
    isa_qc0  = pm.run(qc0)
    isa_qc12 = pm.run(qc12)
    print(f"  Adancime circuit Caz0: {isa_qc0.depth()} porti")
    print(f"  Adancime circuit Caz12: {isa_qc12.depth()} porti")

    # --- Executie ---
    sampler = Sampler(backend)

    print(f"\nTrimitem Cazul 0 in coada ({shots} shots)...")
    job0 = sampler.run([isa_qc0], shots=shots)

    print(f"Trimitem Cazul 1/2 in coada ({shots} shots)...")
    job12 = sampler.run([isa_qc12], shots=shots)

    print("\nAsteptam finalizarea (poate dura cateva minute)...")
    counts0  = job0.result()[0].data.c.get_counts()
    counts12 = job12.result()[0].data.c.get_counts()
    print("Executie finalizata!")
    print(f"  Caz0 counts:  {dict(sorted(counts0.items()))}")
    print(f"  Caz12 counts: {dict(sorted(counts12.items()))}")

    # --- Procesare ---
    h_caz0_bob, _, _, _, _               = proceseaza_counts(counts0,  shots)
    h_caz1_bob, h_ps0, h_ps1, n0, n1    = proceseaza_counts(counts12, shots)

    hw = (h_caz0_bob, h_caz1_bob, h_ps0, h_ps1, backend.name, n0, n1)

    # --- Grafic ---
    print("\nGeneram graficul comparativ...")
    fig = genereaza_grafic(ideal, hw, backend.name, shots, delay_us)

    # --- Raport ---
    genereaza_raport(ideal, hw, shots, delay_us)

    plt.show()
    print("\nExperiment incheiat cu succes!")
