import numpy as np
from PIL import Image

def get_matrix():
    img = Image.open("output2g.png").convert("L")
    P = (np.array(img) < 128).astype(np.uint8)
    return P

def simulate_bf(P, num_errors):
    N_data = 16384
    N_parity = 2048
    
    # Random error indices
    error_idx = np.random.choice(N_data, num_errors, replace=False)
    
    # Calculate syndrome
    s = np.zeros(N_parity, dtype=np.uint8)
    for i in error_idx:
        s ^= P[i]
    
    # Try to find error indices by correlation
    # For a dense matrix, we check how many bits in the syndrome match the signature P[i]
    # Signature matching: count bits where (s[k] == 1 and P[i][k] == 1)
    # This is equivalent to bitwise AND weight.
    
    found = []
    for i in range(N_data):
        # dot product
        match_count = np.sum(np.logical_and(s, P[i]))
        # Expected value for random: weight(s)/2 ?? No.
        # If bit i is in error, s = P[i] + other_errors.
        # match_count = weight(P[i] AND P[i]) + weight(P[i] AND other_errors)
        # weight(P[i] AND other_errors) over GF2 is tricky.
        # Wait, if H is dense, we should look at the "distance" in terms of flips.
        
        # Actually, let's use the property that s XOR P[i] is the syndrome of the other errors.
        # weight(s XOR P[i]) should be less if i in error_idx.
        weight_s = np.sum(s)
        weight_xor = np.sum(s ^ P[i])
        
        # If i is in error, weight_xor is the weight of (s - P[i]) over GF2.
        # For small t, weight_xor < weight_s.
        if weight_xor < weight_s:
            found.append(i)
            
    # Check parity bits too
    for i in range(N_parity):
        # A parity bit error flips one bit in s.
        # s XOR e_i (where e_i is 1 at pos i)
        s_flip = s.copy()
        s_flip[i] ^= 1
        if np.sum(s_flip) < weight_s:
            # This is always true if s[i] == 1
            pass
            
    # Simple hard-decision bit flipping usually needs more checks.
    # But let's see how many of the error_idx were found.
    correct = set(found).intersection(set(error_idx))
    false_positives = set(found).difference(set(error_idx))
    
    return len(correct), len(false_positives)

def main():
    P = get_matrix()
    print("Simulating error correction capability (Bit Flipping approximation)...")
    for t in [1, 2, 5, 10, 20, 50, 100, 150]:
        c, fp = simulate_bf(P, t)
        print(f"Errors injected: {t:3d} | Correctly flagged: {c:3d} | False positives: {fp:d}")

if __name__ == "__main__":
    main()
