import numpy as np
import pandas as pd
import plotly.graph_objects as go
from files.energy.input.DSM_optimizer.func.func import genetic_algorithm, stampa_result

###################################################################################################################################################

def ottimizzazione(immission_profile, consumption_profiles,num_intervals, day, create_plot = True, show_plot = True):
    
    ##### Trovare valori permessi immissione nei quali andare a inserire elettrodomestici:
    # Trovare gli indici dei valori maggiori di zero
       
    allowed_intervals = np.where(np.array(immission_profile) > 0)[0].tolist()
    max_power_contract = 3

    # Grid search parameters
    n_devices = len(consumption_profiles)
    
    # consumo totale:
    #consumption_total = sum([sum(consumption_profile) for consumption_profile in consumption_profiles])
    consumption_total = sum(map(np.sum, consumption_profiles))
    # Define energy cost (e.g., cost per kWh)
    # energy_cost_per_hour = [0.3]* num_intervals #- intervallo_funzionamento(35,60,96)*0.2 # Electricity price per kWh
    energy_cost_per_hour = np.full(num_intervals, 0.3)
    # Generation profile
    # # Trova il valore massimo
    # max_value = max(pv_profile)

    # # Normalizza la lista
    # normalized_profile = [x / max_value for x in pv_profile]

    # # scalo in funzione della taglia: 
    # taglia_FV = max(2,1*(math.ceil(n_devices/4)))
    # #taglia_FV = consumption_total/(sum(normalized_profile)*0.04*n_devices)   # la taglia deve rendere il rapporto tra cosnumo singolo dispositivo e totale energia immessa = 0.6
        
    # generation_profile = [x * taglia_FV for x in normalized_profile]   
    
    # Immission profile
    # """ Questa parte è da modificare quando avremo la produzione fotovoltaica"""
    # immission_profile = [
    #     max(0, generation_profile[i] - immission_profile[i])
    #     for i in range(num_intervals)
    # ]
    
    # Calcoliamo il rapporto tra il consumo e l'immissionee, per valutare percentuale possibili condivisione sul totale: 

    # immission_total = sum(immission_profile)
    immission_total = np.sum(immission_profile)
    
    perc_econd= min(consumption_total, immission_total)/immission_total  # varia tra 0 e 1 
    perc_error = 0
    perc_error_1 = 0
    
    # Calcoliamo il rapporto tra il consumo e l'immissionee, per valutare percentuale possibili condivisione sul totale: 
    
    if perc_econd/n_devices < 0.04 and perc_econd < 0.65:  
        # Grid search parameters
        population_size = min(50,max(10,n_devices*2))
        generations = 20
        mutation_rate = 0.05
        
    elif perc_econd/n_devices < 0.06 and perc_econd < 0.55:
        # Grid search parameters
        population_size = min(50,n_devices*3)
        generations = max(20,min(50,n_devices*2))
        mutation_rate = 0.05
    
    elif perc_econd/n_devices < 0.01 and perc_econd > 0.65:
        # Grid search parameters
        """"da mnodificare---->"""
        population_size = 20
        generations = 20
        mutation_rate = 0.05
    
    else:
        # Grid search parameters
        population_size = min(50,n_devices*3)
        generations = max(20,min(50,n_devices*6))
        mutation_rate = 0.05

    # print(f"\nNUMBER DEVICE(S): {n_devices}")
    
    #################### SOLUTIONS:####################
    # Creazione del dizionario di input alla funzione.

    params = {
            "num_intervals": num_intervals,
            "population_size": population_size,
            "generations": generations,
            "mutation_rate": mutation_rate,
            "consumption_profiles": consumption_profiles,
            "immission_profile": immission_profile,
            "max_power_contract": max_power_contract,
            "energy_cost_per_hour": energy_cost_per_hour,
            "allowed_intervals": allowed_intervals
        }

    # vera e propria funzione di ottimizzazione:
    best_solution = genetic_algorithm(**params)
    param = [population_size, generations, mutation_rate]
    
    # funzione per il plot
    #################### PLOT:####################    
    # create the dataframe devices:
    # Possiamo differenziarlo in funzione della tipologia di utenza e del nome del dispositivo: ad esempio user_n_single_washing_machine ecc..
    # magari creo una funzione ad hoc per questo, vediamo dopo  ? 
    
    ##############
    # Creazione delle colonne in modo compatto
    u = 0
    #columns = [f'user_{u}_{key}' for key, count in sched_dev.items() for _ in range(1, count + 1)]
    columns = []    
    for count in range(int(n_devices/2)):
        for key in ['washing_machine','dish_washer']:
            columns.append(f'user_{u}_{key}')
        u = u +1

    # Creazione del DataFrame
    df_plot = pd.DataFrame(data=np.transpose(best_solution[0]),
                        index=np.arange(num_intervals),
                        columns=columns)
    
    # df_plot = pd.DataFrame(data=np.transpose(best_solution[0]),
    #                     index=np.arange(num_intervals),
    #                     columns=[f'user_{u=u+1}_{key}' for u in range(1, n_devices + 1) for key in sched_dev.keys()])
    
    ##############

    # Crea il DataFrame immissione:
    df = pd.DataFrame({
        'immission_profile': immission_profile,
        
    }, index=np.arange(num_intervals))

    # Voglio calcolarmi l'energia totale condivisa. Con ritorno economico dato da incentivo totale: 
    # max(immissione[t], sum(prelievo utenze domestiche[t])*incentivo for 
    # Calcolare la somma degli elementi su ogni riga per il primo DataFrame
    sum_df1_1 = df_plot.sum(axis=1)
    # Sottrarre il valore corrispondente del secondo DataFrame
    energy_shared_1 = np.minimum(sum_df1_1.values, df['immission_profile'].values)
    
    # Creare un nuovo DataFrame con una colonna chiamata 'energy_shared'
    #df_energy_shared_1 = pd.DataFrame({'energy_shared': energy_shared_1})
    #print(f'Vector of energy sherred: {df_energy_shared}')
    #total_energy_shared_1 = df_energy_shared_1['energy_shared'].sum()
    total_energy_shared_1 = np.sum(energy_shared_1)
    total_incentive_1 = total_energy_shared_1*0.11
    total_energy_consumption_1 = np.sum(sum_df1_1)
    total_energy_cost_1 = total_energy_consumption_1*0.3    
    #total_immission_energy_1 = df['immission_profile'].sum()
    total_immission_energy_1 = np.sum(immission_profile)
    perc_error_1 = total_energy_shared_1/min(total_immission_energy_1,total_energy_consumption_1)
    
    if  perc_error_1<=1:  
                 
        e_cond_i = 0
        e_cond_f =  0 
        e_cons_profile = np.zeros(96)
        
        # Crea il DataFrame dispositivi in surplus:
        df_user_over = pd.DataFrame({                        
                                    }, 
                                    index=np.arange(num_intervals))
        # Ciclo
        for column in df_plot.columns:
            e_cons_profile += df_plot[column]
            #e_cond_f = sum([min(immission_profile[t], e_cons_profile[t])for t in range(len(e_cons_profile))])
            e_cond_f = np.sum(np.minimum(immission_profile, e_cons_profile))
            
            if e_cond_f == e_cond_i:
                df_user_over[column] = df_plot[column]
                e_cons_profile -= df_plot[column]
            
            #if e_cond_f < sum(e_cons_profile):
            if e_cond_f < np.sum(e_cons_profile):
                
                df_user_over[column] = df_plot[column]
                e_cons_profile -= df_plot[column]
                e_cond_f = e_cond_i
            
            e_cond_i = e_cond_f

        
        if not df_user_over.dropna().empty:
            df_total_new =df_plot.drop(columns= df_user_over.columns)
                       
            df_total_new_sum = df_total_new.sum(axis=1)
            
            # Trasformiamo per vettorializzare:
            # Calcolo del nuovo profilo di immissione utilizzando Pandas
            #immission_profile_series = pd.Series(immission_profile)
            # Calcolo del nuovo profilo di immissione
            #immission_profile_new = (immission_profile_series - df_total_new_sum).clip(lower=0).tolist()
            immission_profile_new = np.clip(immission_profile - df_total_new_sum.values, 0, None)
            
            ##### Trovare valori permessi immissione nei quali andare a inserire elettrodomestici:
            # Trovare valori permessi di immissione
            allowed_intervals = np.where(np.array(immission_profile) > 0)[0].tolist()

            # Trasposizione e filtraggio dei profili di consumo
            consumption_profiles = [df_user_over.transpose().values[t][df_user_over.transpose().values[t] > 0] for t in range(len(df_user_over.columns))]

            
            population_size = min(50,len(consumption_profiles)*20)
            generations     = min(50,len(consumption_profiles)*20)
            
            params = {
                "num_intervals": num_intervals,
                "population_size": population_size,
                "generations": generations,
                "mutation_rate": mutation_rate,
                "consumption_profiles": consumption_profiles,
                "immission_profile": immission_profile_new,
                "max_power_contract": max_power_contract,
                "energy_cost_per_hour": energy_cost_per_hour,
                "allowed_intervals": allowed_intervals
            }
             
            # second GA
            best_solution_2 = genetic_algorithm(**params)
            param = [population_size, generations, mutation_rate]
            # create the dataframe devices:
            df_user_opt_2 = pd.DataFrame(data=np.transpose(best_solution_2[0]),
                        index=np.arange(num_intervals),
                        columns= df_user_over.columns)
   
            df_total_new = pd.concat([df_total_new, df_user_opt_2], axis=1)
            sum_df1 = df_total_new.sum(axis=1)
            # Sottrarre il valore corrispondente del secondo DataFrame
            energy_shared = np.minimum(sum_df1.values, df['immission_profile'].values)
            # Creare un nuovo DataFrame con una colonna chiamata 'energy_shared'
            # df_energy_shared = pd.DataFrame({'energy_shared': energy_shared})
            # total_energy_shared = df_energy_shared['energy_shared'].sum()
            total_energy_shared = np.sum(energy_shared)
            total_incentive = total_energy_shared*0.11
            #total_energy_consumption = sum_df1.sum()
            total_energy_consumption = np.sum(sum_df1)
            total_energy_cost = total_energy_consumption*0.3
            #total_immission_energy = df['immission_profile'].sum()
            #total_immission_energy = total_immission_energy_1.copy()
            perc_error = total_energy_shared/min(total_immission_energy_1,total_energy_consumption)
            
           
    # teniamo solo quello con percentuale condivisione migliore, quindi: 
    if perc_error < perc_error_1:
        #print("Return first")
        if create_plot:
            stampa_result(df_plot, immission_profile, num_intervals, day, show_plot)
        #print_result(perc_econd,n_devices,total_energy_shared_1,total_incentive_1,total_energy_consumption_1,total_energy_cost_1,perc_error_1,*param)
        #perc_condivisione_utilizzo = total_energy_shared_1/total_energy_consumption_1
        return df_plot
    
    else:
        #print("Return second")
        if create_plot:
            stampa_result(df_total_new, immission_profile, num_intervals, day, show_plot)
        #print_result(perc_econd,n_devices,total_energy_shared,total_incentive,total_energy_consumption,total_energy_cost,perc_error,*param)
        #perc_condivisione_utilizzo = total_energy_shared/total_energy_consumption # type: ignore
        return df_total_new
    
