import tkinter as tk
from tkinter import messagebox, ttk
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit.visualization import plot_histogram

# ==========================================
# DETECTARE LIBRARII DISPONIBILE
# ==========================================
try:
    from qiskit_aer import AerSimulator
    from qiskit_aer.primitives import SamplerV2 as AerSampler
    from qiskit_aer.noise import NoiseModel, thermal_relaxation_error
    AER_AVAILABLE = True
except ImportError:
    AER_AVAILABLE = False

try:
    from qiskit.primitives import StatevectorSampler
    STATEVECTOR_AVAILABLE = True
except ImportError:
    STATEVECTOR_AVAILABLE = False


# ==========================================
# 0. INTERFAȚA GRAFICĂ PENTRU SETĂRI
# ==========================================
def obtine_date_interfata():
    date_returnate = {
        "shots": 2048,
        "delay": 120,
        "zgomot_activ": False,
        "t1_q0": 200,
        "t1_q1": 50,
        "t2_q0": 150,
        "t2_q1": 40,
    }

    def on_submit():
        try:
            shots_input = int(entry_shots.get().strip())
            delay_input = int(entry_delay.get().strip())
            if shots_input < 1 or delay_input < 0:
                raise ValueError
        except ValueError:
            messagebox.showwarning(
                "Eroare",
                "Shots și Delay trebuie să fie numere întregi pozitive!"
            )
            return

        date_returnate["shots"] = shots_input
        date_returnate["delay"] = delay_input
        date_returnate["zgomot_activ"] = var_zgomot.get()

        if var_zgomot.get():
            if not AER_AVAILABLE:
                messagebox.showwarning(
                    "Atenție",
                    "qiskit-aer nu este instalat.\n"
                    "Rulează: pip install qiskit-aer\n\n"
                    "Se continuă în mod ideal (fără zgomot)."
                )
                date_returnate["zgomot_activ"] = False
            else:
                try:
                    t1_q0 = float(entry_t1_q0.get().strip())
                    t1_q1 = float(entry_t1_q1.get().strip())
                    t2_q0 = float(entry_t2_q0.get().strip())
                    t2_q1 = float(entry_t2_q1.get().strip())
                    if any(v <= 0 for v in [t1_q0, t1_q1, t2_q0, t2_q1]):
                        raise ValueError
                    # T2 <= 2*T1 (limita fizica)
                    if t2_q0 > 2 * t1_q0 or t2_q1 > 2 * t1_q1:
                        messagebox.showwarning(
                            "Atenție",
                            "T2 nu poate depăși 2×T1 (limita fizică).\n"
                            "Valorile T2 vor fi ajustate automat la min(T2, 2×T1)."
                        )
                        t2_q0 = min(t2_q0, 2 * t1_q0)
                        t2_q1 = min(t2_q1, 2 * t1_q1)
                    date_returnate["t1_q0"] = t1_q0
                    date_returnate["t1_q1"] = t1_q1
                    date_returnate["t2_q0"] = t2_q0
                    date_returnate["t2_q1"] = t2_q1
                except ValueError:
                    messagebox.showwarning(
                        "Eroare",
                        "Timpii T1/T2 trebuie să fie numere pozitive (în µs)!"
                    )
                    return

        root.destroy()

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

    def toggle_noise_fields():
        stare = "normal" if var_zgomot.get() else "disabled"
        for widget in noise_widgets:
            widget.config(state=stare)

    root = tk.Tk()
    root.title("Experiment Cuantic - Simulator Ideal")
    root.geometry("480x520")
    root.eval('tk::PlaceWindow . center')
    root.protocol("WM_DELETE_WINDOW", on_closing)
    root.resizable(False, False)

    # Titlu
    tk.Label(
        root,
        text="Simulator Cuantic (Zero Zgomot)",
        font=("Arial", 13, "bold"),
        fg="#0f62fe"
    ).pack(pady=(15, 2))
    tk.Label(
        root,
        text="Nu este necesară cheie API — rulează local",
        font=("Arial", 9),
        fg="gray"
    ).pack(pady=(0, 10))

    ttk.Separator(root, orient='horizontal').pack(fill='x', padx=20)

    # Shots
    tk.Label(
        root,
        text="Număr de măsurători / Shots:",
        font=("Arial", 10, "bold")
    ).pack(pady=(12, 2))
    entry_shots = tk.Entry(root, width=20, font=("Arial", 10), justify="center")
    entry_shots.insert(0, "2048")
    entry_shots.pack()

    # Delay
    tk.Label(
        root,
        text="Delay simulat pentru Bob (µs)\n(Relevant doar dacă zgomotul este activ):",
        font=("Arial", 10)
    ).pack(pady=(10, 2))
    entry_delay = tk.Entry(root, width=20, font=("Arial", 10), justify="center")
    entry_delay.insert(0, "120")
    entry_delay.pack()

    ttk.Separator(root, orient='horizontal').pack(fill='x', padx=20, pady=12)

    # Checkbox zgomot
    var_zgomot = tk.BooleanVar(value=False)
    tk.Checkbutton(
        root,
        text="Activează zgomot T1/T2 simulat  (necesită qiskit-aer)",
        variable=var_zgomot,
        command=toggle_noise_fields,
        font=("Arial", 10),
        fg="#c00000"
    ).pack()

    # Cadru T1/T2
    frame_noise = tk.Frame(root, bd=1, relief="groove", padx=10, pady=8)
    frame_noise.pack(padx=20, pady=6, fill="x")

    noise_widgets = []

    def make_row(parent, label, default):
        f = tk.Frame(parent)
        f.pack(fill="x", pady=2)
        lbl = tk.Label(f, text=label, font=("Arial", 9), width=30, anchor="w")
        lbl.pack(side="left")
        ent = tk.Entry(f, width=10, font=("Arial", 9), justify="center", state="disabled")
        ent.insert(0, str(default))
        ent.pack(side="left")
        tk.Label(f, text=" µs", font=("Arial", 9)).pack(side="left")
        noise_widgets.append(ent)
        return ent

    tk.Label(
        frame_noise,
        text="Parametri decoherentă (valori tipice IBM Fez):",
        font=("Arial", 9, "italic"),
        fg="gray"
    ).pack(anchor="w")

    entry_t1_q0 = make_row(frame_noise, "T1  qubit 0 (Alice):", 200)
    entry_t2_q0 = make_row(frame_noise, "T2  qubit 0 (Alice):", 150)
    entry_t1_q1 = make_row(frame_noise, "T1  qubit 1 (Bob):", 50)
    entry_t2_q1 = make_row(frame_noise, "T2  qubit 1 (Bob):", 40)

    # Buton start
    tk.Button(
        root,
        text="▶  Start Experiment",
        command=on_submit,
        bg="#0f62fe",
        fg="white",
        font=("Arial", 11, "bold"),
        padx=12,
        pady=6
    ).pack(pady=14)

    root.bind('<Return>', lambda event: on_submit())
    root.mainloop()

    return date_returnate


setari = obtine_date_interfata()
delay_time_us = setari["delay"]
shots_count   = setari["shots"]
zgomot_activ  = setari["zgomot_activ"]


# ==========================================
# 1. CONFIGURAREA SIMULATORULUI
# ==========================================
if zgomot_activ and AER_AVAILABLE:
    t1_q0_s = setari["t1_q0"] * 1e-6   # conversie µs → s
    t1_q1_s = setari["t1_q1"] * 1e-6
    t2_q0_s = setari["t2_q0"] * 1e-6
    t2_q1_s = setari["t2_q1"] * 1e-6
    delay_s  = delay_time_us * 1e-6

    noise_model = NoiseModel()
    err_q0 = thermal_relaxation_error(t1_q0_s, t2_q0_s, delay_s)
    err_q1 = thermal_relaxation_error(t1_q1_s, t2_q1_s, delay_s)
    noise_model.add_quantum_error(err_q0, "delay", [0])
    noise_model.add_quantum_error(err_q1, "delay", [1])

    backend_sim = AerSimulator(noise_model=noise_model)
    sampler = AerSampler.from_backend(backend_sim)
    mod_simulator = (
        f"AerSimulator cu zgomot T1/T2\n"
        f"  T1(q0)={setari['t1_q0']}µs  T2(q0)={setari['t2_q0']}µs\n"
        f"  T1(q1)={setari['t1_q1']}µs  T2(q1)={setari['t2_q1']}µs"
    )
    print(f"\nSimulator: {mod_simulator}")
else:
    # Simulator ideal — zero zgomot
    if not STATEVECTOR_AVAILABLE:
        print("EROARE: qiskit.primitives.StatevectorSampler nu este disponibil.")
        print("Instalează Qiskit >= 1.0: pip install qiskit")
        exit()
    sampler = StatevectorSampler()
    mod_simulator = "StatevectorSampler (zero zgomot, ideal)"
    print(f"\nSimulator: {mod_simulator}")
    print("NOTĂ: Delay-ul este ignorat în simularea ideală (niciun efect fizic).")


# ==========================================
# 2. CONSTRUIREA CIRCUITELOR LOGICE
# ==========================================
print(f"\nConstruim circuitele...")

# Cazul 0: Alice PASTREAZA informatia despre cale
# H(0) → CNOT(0,1) → masoara qubit 0 → delay qubit 1 → masoara qubit 1
qc_0 = QuantumCircuit(2, 2)
qc_0.h(0)
qc_0.cx(0, 1)
qc_0.measure(0, 0)
qc_0.delay(delay_time_us, 1, unit='us')
qc_0.measure(1, 1)

# Cazul 1: Alice STERGE informatia despre cale (aplica H inainte de masurare)
# H(0) → CNOT(0,1) → H(0) → masoara qubit 0 → delay qubit 1 → H(1) → masoara qubit 1
qc_1 = QuantumCircuit(2, 2)
qc_1.h(0)
qc_1.cx(0, 1)
qc_1.h(0)
qc_1.measure(0, 0)
qc_1.delay(delay_time_us, 1, unit='us')
qc_1.h(1)
qc_1.measure(1, 1)

print("Circuite construite.")
print(f"\nCircuit Cazul 0 (Alice pastreaza calea):")
print(qc_0.draw(output='text'))
print(f"\nCircuit Cazul 1 (Alice sterge calea):")
print(qc_1.draw(output='text'))


# ==========================================
# 3. EXECUTAREA PE SIMULATOR
# ==========================================
print(f"\nRulam simularea ({shots_count} shots fiecare circuit)...")

if zgomot_activ and AER_AVAILABLE:
    job_0 = sampler.run([qc_0], shots=shots_count)
    job_1 = sampler.run([qc_1], shots=shots_count)
    counts_0 = job_0.result()[0].data.c.get_counts()
    counts_1 = job_1.result()[0].data.c.get_counts()
else:
    # StatevectorSampler foloseste PUB-uri (Primitive Unified Blocs)
    pub_0 = (qc_0,)
    pub_1 = (qc_1,)
    job_0 = sampler.run([pub_0], shots=shots_count)
    job_1 = sampler.run([pub_1], shots=shots_count)
    counts_0 = job_0.result()[0].data.c.get_counts()
    counts_1 = job_1.result()[0].data.c.get_counts()

# Normalizeaza: asigura ca toate cele 4 stari sunt prezente (pentru grafic)
for state in ['00', '01', '10', '11']:
    counts_0.setdefault(state, 0)
    counts_1.setdefault(state, 0)

print("Simulare finalizata cu succes!")


# ==========================================
# 4. PROCESAREA REZULTATELOR PENTRU BOB
# ==========================================
# Format bit string Qiskit: 'c[1]c[0]' = 'Qubit_Bob Qubit_Alice'
def obtine_statistici_bob(counts):
    bob_0 = sum(count for state, count in counts.items() if state[0] == '0')
    bob_1 = sum(count for state, count in counts.items() if state[0] == '1')
    return bob_0, bob_1

bob_0_caz0, bob_1_caz0 = obtine_statistici_bob(counts_0)
bob_0_caz1, bob_1_caz1 = obtine_statistici_bob(counts_1)

proc_0_caz0 = bob_0_caz0 / shots_count
proc_0_caz1 = bob_0_caz1 / shots_count

deviatia_max = max(abs(proc_0_caz0 - 0.5), abs(proc_0_caz1 - 0.5))

if deviatia_max > 0.08:
    status_text = "De cercetat ce se intampla!"
    box_color = "lightcoral"
else:
    status_text = (
        "Experiment reusit! Distributia ~50/50 confirma Teorema Fara Semnalizare.\n"
        "Bob nu primeste nicio informatie FTL privind propriul detector."
    )
    box_color = "lightgreen"


# ==========================================
# 5. GENERAREA GRAFICULUI
# ==========================================
print("Generam graficul...")

fig, ax = plt.subplots(figsize=(10, 7))

titlu = (
    f"Distributia starilor: Cazul 0 vs Cazul 1\n"
    f"({mod_simulator.splitlines()[0]}, Delay: {delay_time_us}µs, Shots: {shots_count})"
)

plot_histogram(
    [counts_0, counts_1],
    legend=['Cazul 0 (Pastrare cale)', 'Cazul 1 (Stergere cale)'],
    title=titlu,
    ax=ax,
    bar_labels=True
)

plt.figtext(
    0.5, 0.02, status_text, ha="center", fontsize=11, fontweight="bold",
    bbox={"facecolor": box_color, "alpha": 0.6, "pad": 8, "boxstyle": "round,pad=0.5"}
)
plt.subplots_adjust(bottom=0.22)

plt.savefig('grafic_rezultate_simulator.png', dpi=150, bbox_inches='tight')
print("Graficul salvat ca 'grafic_rezultate_simulator.png'")


# ==========================================
# 6. GENERAREA RAPORTULUI TEXT
# ==========================================
print("Generam raportul text...")

raport = f"""=======================================================
RAPORT EXPERIMENT: COMUNICARE CUANTICA
Simulator utilizat: {mod_simulator}
Numar de masuratori (shots): {shots_count}
Delay parametrizat pentru Bob: {delay_time_us} microsecunde
=======================================================

1. DATE BRUTE (Format: 'Qubit_Bob Qubit_Alice' : Numar_Aparitii)
-------------------------------------------------------
Cazul 0 (Alice a masurat calea - PASTREAZA): {dict(sorted(counts_0.items()))}
Cazul 1 (Alice a sters calea - H inainte):   {dict(sorted(counts_1.items()))}

2. ANALIZA STATISTICA A LUI BOB
-------------------------------------------------------
In CAZUL 0 (Alice pastreaza informatia):
  - Bob a masurat '0' de {bob_0_caz0} ori ({proc_0_caz0*100:.2f}%)
  - Bob a masurat '1' de {bob_1_caz0} ori ({(bob_1_caz0/shots_count)*100:.2f}%)

In CAZUL 1 (Alice sterge informatia):
  - Bob a masurat '0' de {bob_0_caz1} ori ({proc_0_caz1*100:.2f}%)
  - Bob a masurat '1' de {bob_1_caz1} ori ({(bob_1_caz1/shots_count)*100:.2f}%)

3. INTERPRETARE TEORETICA
-------------------------------------------------------
In simularea IDEALA (zero zgomot):
  - Ambele cazuri produc ~50/50 pentru Bob.
  - Diferenta intre cazuri e INVIZIBILA fara canalul clasic al Alicei.
  - Aceasta confirma Teorema No-Signalling (no-communication theorem).
  - Anomalia observata pe hardware IBM (71% vs 50%) a fost cauzata
    exclusiv de decohenta T1 (relaxare termica) in cei {delay_time_us} µs de delay.

4. CONCLUZIE EXPERIMENTALA AUTOMATA
-------------------------------------------------------
Mesaj generat:
{status_text}
"""

with open('raport_experiment_simulator.txt', 'w', encoding='utf-8') as f:
    f.write(raport)

print("Raportul salvat ca 'raport_experiment_simulator.txt'")
print("\nProces incheiat cu succes!")
print("\n" + "=" * 55)
print(f"  BOB in Cazul 0: {proc_0_caz0*100:.2f}% zero  (asteptat ideal: 50%)")
print(f"  BOB in Cazul 1: {proc_0_caz1*100:.2f}% zero  (asteptat ideal: 50%)")
print("=" * 55)
