# -*- coding: utf-8 -*-
"""
Created on Thu Apr 10 17:43:04 2025

@author: AKourgli
"""

import numpy as np
import matplotlib.pyplot as plt

def gray_code(binary_index):
    """Convertit un index binaire en code Gray"""
    return binary_index ^ (binary_index >> 1)

# Configuration des paramètres
M_values = [4, 8, 16]
num_symbols = 10
fs = 1000  # Fréquence d'échantillonnage
Ts = 1.0    # Durée symbole

for M in M_values:
    n_bits = int(np.log2(M))
    t = np.arange(0, num_symbols*Ts, 1/fs)
    
    # Génération des bits aléatoires
    bits = np.random.randint(0, 2, num_symbols*n_bits)
    symbols_bits = bits.reshape(-1, n_bits)
    
    # Conversion en indices binaires
    binary_indices = [int(''.join(map(str, s)), 2) for s in symbols_bits]
    
    # Calcul des angles selon M
    if M == 4:
        angles = np.pi/2 * np.array(binary_indices)  # Phases à 0°, 90°, 180°, 270°
    else:
        angles = 2*np.pi*np.array(binary_indices)/M
    
    # Génération des symboles complexes
    symbols = np.exp(1j * angles)
    gray_labels = [format(gray_code(k), f'0{n_bits}b') for k in binary_indices]
    
    # Génération des signaux temporels
    sps = int(fs*Ts)
    I = np.real(symbols).repeat(sps)
    Q = np.imag(symbols).repeat(sps)
    phase = np.angle(symbols).repeat(sps)
    
    # Tracé des signaux avec étiquettes Gray
    plt.figure(figsize=(15, 10))
    plt.suptitle(f'Modulation {M}-PSK', fontsize=14, y=1.02)
    
    # Phase avec étiquettes
    plt.subplot(3, 1, 1)
    plt.plot(t, phase, 'b')
    for i in range(num_symbols):
        midpoint = (i + 0.5) * Ts
        plt.text(midpoint, np.max(phase)+0.2, gray_labels[i], ha='center', va='bottom', color='red')
    plt.title('Évolution de la phase')
    plt.ylabel('Phase (rad)')
    plt.grid(True)
    plt.ylim(-np.pi-0.5, np.pi+0.5)
    plt.yticks([-np.pi, 0, np.pi], ['-π', '0', 'π'])
    
    # Signal en phase (I)
    plt.subplot(3, 1, 2)
    plt.plot(t, I, 'r')
    plt.title('Composante en phase (I)')
    plt.ylabel('Amplitude')
    plt.grid(True)
    
    # Signal en quadrature (Q)
    plt.subplot(3, 1, 3)
    plt.plot(t, Q, 'g')
    plt.title('Composante en quadrature (Q)')
    plt.xlabel('Temps (s)')
    plt.ylabel('Amplitude')
    plt.grid(True)
    
    plt.tight_layout()
    
    # Tracé de la constellation
    plt.figure(figsize=(8, 8))
    theoretical_binary = np.arange(M)
    
    # Calcul des positions théoriques
    if M == 4:
        theta = np.pi/2 * theoretical_binary
    else:
        theta = 2*np.pi*theoretical_binary/M
    
    constellation = np.exp(1j * theta)
    gray_labels = [format(gray_code(k), f'0{n_bits}b') for k in theoretical_binary]
    
    plt.scatter(np.real(constellation), np.imag(constellation), 200, c='red', marker='o', edgecolors='k', label='Théorique')
    plt.scatter(symbols.real, symbols.imag, 50, c='blue', marker='x', label='Émis')
    
    # Annotation des labels Gray
    for i, (x, y) in enumerate(zip(constellation.real, constellation.imag)):
        plt.annotate(gray_labels[i], (x, y), xytext=(5, 5), textcoords='offset points',  ha='center', fontsize=10,  color='black')
    
    plt.title(f'Constellation {M}-PSK\nCodage Gray', fontsize=12)
    plt.xlabel('I', fontsize=10)
    plt.ylabel('Q', fontsize=10)
    plt.grid(True)
    plt.axis('equal')
    plt.legend()
    plt.tight_layout()

plt.show()