import random
import math

# functiile de baza
def valid_state(x, y, z):

    return x >= 10 and y >= 5 and z >= 7 and (5*x + 3*y + z <= 150)

def calculate_impact(x, y, z):
  # funcția obiectiv (impactul total). 
    return 10*x + 15*y + 5*z

def get_random_valid_state():
   # adaugam solutii la intamplare
    x, y, z = 10, 5, 7 # pornim de la minime
    pages_left = 150 - (5*x + 3*y + z)
    
    # adaugam aleator cantități valide
    while pages_left > 0:
        choice = random.choice(['x', 'y', 'z'])
        if choice == 'x' and pages_left >= 5:
            x += 1; pages_left -= 5
        elif choice == 'y' and pages_left >= 3:
            y += 1; pages_left -= 3
        elif choice == 'z' and pages_left >= 1:
            z += 1; pages_left -= 1
        else:
            break  
    return x, y, z

def get_neighbors(x, y, z):
     # generare vecini
    neighbors = []
    moves = [(1,0,0), (-1,0,0), (0,1,0), (0,-1,0), (0,0,1), (0,0,-1)]
    for dx, dy, dz in moves:
        nx, ny, nz = x + dx, y + dy, z + dz
        if valid_state(nx, ny, nz):
            neighbors.append((nx, ny, nz))
    return neighbors

#   Greedy (introduce valoriile maxime pana nu mai incap)
def greedy_optimization():
    print("\n Algoritmul Greedy")
    x, y, z = 10, 5, 7
    pages_used = 5*x + 3*y + z
    
    while pages_used + 3 <= 150: # Cât timp mai încape un y
        y += 1
        pages_used += 3
        
    while pages_used + 1 <= 150: # Cât timp mai încape un z
        z += 1
        pages_used += 1
        
    impact = calculate_impact(x, y, z)
    print(f"Soluție Greedy: x={x}, y={y}, z={z} | Impact: {impact}")
    return impact

# Hill climbing (verifica constant vecinii sa vada care e mai bun)
def hill_climbing():
    current_x, current_y, current_z = get_random_valid_state()
    current_impact = calculate_impact(current_x, current_y, current_z)
    
    while True:
        neighbors = get_neighbors(current_x, current_y, current_z)
        best_neighbor = None
        best_impact = current_impact
        
        for nx, ny, nz in neighbors:
            impact = calculate_impact(nx, ny, nz)
            if impact > best_impact:
                best_impact = impact
                best_neighbor = (nx, ny, nz)
                
        if best_neighbor is None: 
            break
            
        current_x, current_y, current_z = best_neighbor
        current_impact = best_impact
        
    return current_x, current_y, current_z, current_impact

# Simulated annealing (asemanator cu hill, doar ca greseste intentionat pentru a isi face o idee despre date)
def simulated_annealing(temp, cooling_rate, iterations):
    current_x, current_y, current_z = get_random_valid_state()
    current_impact = calculate_impact(current_x, current_y, current_z)
    
    best_x, best_y, best_z, best_impact = current_x, current_y, current_z, current_impact
    
    for _ in range(iterations):
        neighbors = get_neighbors(current_x, current_y, current_z)
        if not neighbors:
            break
            
        nx, ny, nz = random.choice(neighbors)
        new_impact = calculate_impact(nx, ny, nz)
        
        # daca e bun se accepta, daca nu, se accepta cu o probabilitate
        if new_impact > current_impact or random.random() < math.exp((new_impact - current_impact) / temp):
            current_x, current_y, current_z = nx, ny, nz
            current_impact = new_impact
            
            # se salveaza cel mai bun rezultat intalnit
            if current_impact > best_impact:
                best_x, best_y, best_z, best_impact = current_x, current_y, current_z, current_impact
                
        temp *= cooling_rate # Răcim temperatura
        
    return best_x, best_y, best_z, best_impact

# Rularea

# Greedy
greedy_optimization()

# Hill
print("\n Hill Climbing")
for i in range(1, 4):
    x, y, z, impact = hill_climbing()
    print(f"Execuția {i}: x={x}, y={y}, z={z} | Impact: {impact}")

# SA
print("\n Simulated Annealing")
params = [
    (100.0, 0.95, 100),  # T_start=100, Rata racire=0.95, Iterații=100
    (500.0, 0.99, 500),  # Parametri pentru explorare agresivă
    (10.0,  0.80, 50)    # Răcire foarte rapidă (devine similar cu Hill Climbing)
] # Odata ce racirea devine aproape de 0 algoritmul va refuza sa mai faca greseli intentionat

for p_idx, (t, cr, iters) in enumerate(params, 1):
    print(f"\nConfigurația {p_idx} (Temp={t}, Răcire={cr}, Iterații={iters}):")
    for i in range(1, 4):
        x, y, z, impact = simulated_annealing(t, cr, iters)
        print(f"  Execuția {i}: x={x}, y={y}, z={z} | Impact: {impact}")
