# RCAIDE/Library/Methods/Geometry/Two_Dimensional/Airfoil/compute_airfoil_properties.py
# 
# 
# Created:  Jul 2024, M. Clarke 

# ----------------------------------------------------------------------------------------------------------------------
#  IMPORT
# ----------------------------------------------------------------------------------------------------------------------    

# RCAIDE imports 
import RCAIDE
from RCAIDE.Framework.Core                                                          import Data , Units 
from RCAIDE.Library.Methods.Aerodynamics.Airfoil_Panel_Method.airfoil_analysis      import airfoil_analysis
from RCAIDE.Library.Methods.Geometry.Airfoil.import_airfoil_polars                  import import_airfoil_polars 
from RCAIDE.Library.Methods.Geometry.Airfoil.compute_naca_4series                   import compute_naca_4series  
from RCAIDE.Library.Methods.Aerodynamics.AERODAS.pre_stall_coefficients             import pre_stall_coefficients
from RCAIDE.Library.Methods.Aerodynamics.AERODAS.post_stall_coefficients            import post_stall_coefficients

# numpy imports 
import numpy as np
from scipy.interpolate     import RegularGridInterpolator
from RCAIDE.Framework.Core import interp2d 

# ----------------------------------------------------------------------------------------------------------------------
#  compute_airfoil_properties
# ----------------------------------------------------------------------------------------------------------------------   
def compute_airfoil_properties(airfoil_geometry, airfoil_polar_files = None,use_pre_stall_data=True):
    """This computes the aerodynamic properties and coefficients of an airfoil in stall regimes using pre-stall
    characterstics and AERODAS formation for post stall characteristics. This is useful for 
    obtaining a more accurate prediction of wing and blade loading as well as aeroacoustics. Pre stall characteristics 
    are obtained in the form of a text file of airfoil polar data obtained from airfoiltools.com
    
    Assumptions:
        None 
        
    Source
        None
        
    Inputs:
    airfoil_geometry                        <data_structure>
    airfoil_polar_files                     <string>
    boundary_layer_files                    <string>
    use_pre_stall_data                      [Boolean]
    Outputs:
    airfoil_data.
        cl_polars                           [unitless]
        cd_polars                           [unitless]      
        aoa_sweep                           [unitless]
         
    
    Properties Used:
    N/A
    """     
    Airfoil_Data   = Data()  
   
    # ----------------------------------------------------------------------------------------
    # Compute airfoil boundary layers properties 
    # ----------------------------------------------------------------------------------------   
    Airfoil_Data   = compute_boundary_layer_properties(airfoil_geometry,Airfoil_Data)
    num_polars     = len(Airfoil_Data.re_from_polar)
     
    # ----------------------------------------------------------------------------------------
    # Compute extended cl and cd polars 
    # ----------------------------------------------------------------------------------------    
    if airfoil_polar_files != None : 
        # check number of polars per airfoil in batch
        num_polars   = 0 
        n_p = len(airfoil_polar_files)
        if n_p < 3:
            raise AttributeError('Provide three or more airfoil polars to compute surrogate') 
        num_polars = max(num_polars, n_p)         
         
        # read in polars from files overwrite panel code  
        airfoil_file_data                          = import_airfoil_polars(airfoil_polar_files)  
        Airfoil_Data.aoa_from_polar                = airfoil_file_data.aoa_from_polar
        Airfoil_Data.re_from_polar                 = airfoil_file_data.re_from_polar 
        Airfoil_Data.lift_coefficients             = airfoil_file_data.lift_coefficients
        Airfoil_Data.drag_coefficients             = airfoil_file_data.drag_coefficients 
        
    # Get all of the coefficients for AERODAS wings
    AoA_sweep_deg         = np.linspace(-14,90,105)
    AoA_sweep_rad         = AoA_sweep_deg*Units.degrees    
    
    # Create an infinite aspect ratio wing
    geometry              = RCAIDE.Library.Components.Wings.Wing()
    geometry.aspect_ratio = np.inf
    geometry.section      = Data()  
    
    # AERODAS   
    CL = np.zeros((num_polars,len(AoA_sweep_deg)))
    CD = np.zeros((num_polars,len(AoA_sweep_deg)))  
    for j in range(num_polars):    
        airfoil_cl  = Airfoil_Data.lift_coefficients[j,:]
        airfoil_cd  = Airfoil_Data.drag_coefficients[j,:]   
        airfoil_aoa = Airfoil_Data.aoa_from_polar[j,:]/Units.degrees 
    
        # compute airfoil cl and cd for extended AoA range 
        CL[j,:],CD[j,:] = compute_extended_polars(airfoil_cl,airfoil_cd,airfoil_aoa,AoA_sweep_deg,geometry,use_pre_stall_data)  
         
    # ----------------------------------------------------------------------------------------
    # Store data 
    # ----------------------------------------------------------------------------------------    
    Airfoil_Data.reynolds_numbers    = Airfoil_Data.re_from_polar
    Airfoil_Data.angle_of_attacks    = AoA_sweep_rad 
    Airfoil_Data.lift_coefficients   = CL 
    Airfoil_Data.drag_coefficients   = CD    
        
    return Airfoil_Data
 
def compute_extended_polars(airfoil_cl,airfoil_cd,airfoil_aoa,AoA_sweep_deg,geometry,use_pre_stall_data): 
    """ Computes the aerodynamic polars of an airfoil over an extended angle of attack range
    
    Assumptions:
    Uses AERODAS formulation for post stall characteristics 
    Source:
    Models of Lift and Drag Coefficients of Stalled and Unstalled Airfoils in Wind Turbines and Wind Tunnels
    by D Spera, 2008
        
    Inputs:
    airfoil_cl          [unitless]
    airfoil_cd          [unitless]
    airfoil_aoa         [degrees]
    AoA_sweep_deg       [unitless]
    geometry            [N/A]
    use_pre_stall_data  [boolean]
    Outputs: 
    CL                  [unitless]
    CD                  [unitless]       
    
    Properties Used:
    N/A
    """    
    
    # Create dummy settings and state
    settings                                              = Data()
    state                                                 = Data()
    state.conditions                                      = Data()
    state.conditions.aerodynamics                         = Data()
    state.conditions.aerodynamics.pre_stall_coefficients  = Data()
    state.conditions.aerodynamics.post_stall_coefficients = Data()  
    
    # computing approximate zero lift aoa
    AoA_sweep_radians    = AoA_sweep_deg*Units.degrees
    airfoil_cl_plus      = airfoil_cl[airfoil_cl>0]
    idx_zero_lift        = np.where(airfoil_cl == min(airfoil_cl_plus))[0][0]
    airfoil_cl_crossing  = airfoil_cl[idx_zero_lift-1:idx_zero_lift+1]
    airfoil_aoa_crossing = airfoil_aoa[idx_zero_lift-1:idx_zero_lift+1]
    try:
        A0  = np.interp(0,airfoil_cl_crossing, airfoil_aoa_crossing)* Units.deg 
    except:
        A0 = airfoil_aoa[idx_zero_lift] * Units.deg 

    # max lift coefficent and associated aoa
    CL1max                  = np.max(airfoil_cl)
    idx_aoa_max_prestall_cl = np.where(airfoil_cl == CL1max)[0][0]
    ACL1                    = airfoil_aoa[idx_aoa_max_prestall_cl] * Units.degrees

    # computing approximate lift curve slope
    linear_idxs             = [int(np.where(airfoil_aoa==0)[0]),int(np.where(airfoil_aoa==4)[0])]
    cl_range                = airfoil_cl[linear_idxs]
    aoa_range               = airfoil_aoa[linear_idxs] * Units.degrees
    S1                      = (cl_range[1]-cl_range[0])/(aoa_range[1]-aoa_range[0])

    # max drag coefficent and associated aoa
    CD1max                  = np.max(airfoil_cd) 
    idx_aoa_max_prestall_cd = np.where(airfoil_cd == CD1max)[0][0]
    ACD1                    = airfoil_aoa[idx_aoa_max_prestall_cd] * Units.degrees     
    
    # Find the point of lowest drag and the CD
    idx_CD_min              = np.where(airfoil_cd==min(airfoil_cd))[0][0]
    ACDmin                  = airfoil_aoa[idx_CD_min] * Units.degrees
    CDmin                   = airfoil_cd[idx_CD_min]    
    
    # Setup data structures for this run
    ones                                                      = np.ones_like(AoA_sweep_radians)
    settings.section_zero_lift_angle_of_attack                = A0
    state.conditions.aerodynamics.angle_of_attack             = AoA_sweep_radians* ones  
    geometry.section.angle_attack_max_prestall_lift           = ACL1 * ones 
    geometry.pre_stall_maximum_drag_coefficient_angle         = ACD1 * ones 
    geometry.pre_stall_maximum_lift_coefficient               = CL1max * ones 
    geometry.pre_stall_maximum_lift_drag_coefficient          = CD1max * ones 
    geometry.section.minimum_drag_coefficient                 = CDmin * ones 
    geometry.section.minimum_drag_coefficient_angle_of_attack = ACDmin
    geometry.pre_stall_lift_curve_slope                       = S1
    
    # Get prestall coefficients
    CL1, CD1 = pre_stall_coefficients(state,settings,geometry)
    
    # Get poststall coefficents
    CL2, CD2 = post_stall_coefficients(state,settings,geometry)
    
    # Take the maxes
    CL_ij = np.fmax(CL1,CL2)
    CL_ij[AoA_sweep_radians<=A0] = np.fmin(CL1[AoA_sweep_radians<=A0],CL2[AoA_sweep_radians<=A0])
    
    CD_ij = np.fmax(CD1,CD2)
    
    # Pack this loop
    CL = CL_ij
    CD = CD_ij
    
    if use_pre_stall_data == True:
        CL, CD = apply_pre_stall_data(AoA_sweep_deg, airfoil_aoa, airfoil_cl, airfoil_cd, CL, CD)
        
    return CL,CD

def apply_pre_stall_data(AoA_sweep_deg, airfoil_aoa, airfoil_cl, airfoil_cd, CL, CD): 
    '''Applies prestall data to lift and drag curve slopes
    
    Assumptions:
    None
    
    Source:
    None
    
    Inputs:
    AoA_sweep_deg  [degrees]
    airfoil_aoa    [degrees]
    airfoil_cl     [unitless]
    airfoil_cd     [unitless]
    CL             [unitless] 
    CD             [unitless]
    
    Outputs
    CL             [unitless]
    CD             [unitless] 
    
    
    Properties Used:
    N/A
    
    '''

    # Coefficients in pre-stall regime taken from experimental data:
    aoa_locs    = (AoA_sweep_deg>=airfoil_aoa[0]) * (AoA_sweep_deg<=airfoil_aoa[-1])
    aoa_in_data = AoA_sweep_deg[aoa_locs]
    
    # if the data is within experimental use it, if not keep the surrogate values
    CL[aoa_locs] = airfoil_cl[abs(aoa_in_data[:,None] - airfoil_aoa[None,:]).argmin(axis=-1)]
    CD[aoa_locs] = airfoil_cd[abs(aoa_in_data[:,None] - airfoil_aoa[None,:]).argmin(axis=-1)]
    
    # remove kinks/overlap between pre- and post-stall                
    data_lb       = np.where(CD == airfoil_cd[0])[0][0]
    data_ub       = np.where(CD == airfoil_cd[-1])[0][-1]
    CD[0:data_lb] = np.maximum(CD[0:data_lb], CD[data_lb]*np.ones_like(CD[0:data_lb]))
    CD[data_ub:]  = np.maximum(CD[data_ub:],  CD[data_ub]*np.ones_like(CD[data_ub:])) 
    
    return CL, CD
 
def compute_boundary_layer_properties(airfoil_geometry,Airfoil_Data): 
    '''Computes the boundary layer properties of an airfoil for a sweep of Reynolds numbers 
    and angle of attacks. 
    
    Source:
    None
    
    Assumptions:
    None 
    
    Inputs:
    airfoil_geometry   <data_structure>
    Airfoil_Data       <data_structure>
    
    Outputs:
    Airfoil_Data       <data_structure>
    
    Properties Used:
    N/A
    '''
    if airfoil_geometry == None:
        print('No airfoil defined, NACA 0012 surrogates will be used') 
        a_names                       = ['0012']                
        airfoil_geometry              = compute_naca_4series(a_names, npoints= 100)    
    
    AoA_sweep = np.array([-4,0,2,4,8,10,14])*Units.degrees 
    Re_sweep  = np.array([1,5,10,30,50,75,100])*1E4  
    AoA_vals  = np.tile(AoA_sweep[None,:],(len(Re_sweep) ,1))
    Re_vals   = np.tile(Re_sweep[:,None],(1, len(AoA_sweep)))     
    
    # run airfoil analysis  
    af_res  = airfoil_analysis(airfoil_geometry,AoA_vals,Re_vals) 
    
    # store data
    Airfoil_Data.aoa_from_polar                                     = np.tile(AoA_sweep[None,:],(len(Re_sweep),1)) 
    Airfoil_Data.re_from_polar                                      = Re_sweep 
    Airfoil_Data.lift_coefficients                                  = af_res.cl_invisc
    Airfoil_Data.drag_coefficients                                  = af_res.cd_invisc
    Airfoil_Data.cm                                                 = af_res.cm_invisc
    Airfoil_Data.boundary_layer                                     = Data() 
    Airfoil_Data.boundary_layer.angle_of_attacks                    = AoA_sweep 
    Airfoil_Data.boundary_layer.reynolds_numbers                    = Re_sweep
    fL                                                              = np.transpose(af_res.fL, axes=[1,2,0])
    fD                                                              = np.transpose(af_res.fD, axes=[1,2,0]) 
    Airfoil_Data.lift_distribution_func                             = RegularGridInterpolator((AoA_sweep,Re_sweep),fL,method = 'linear',   bounds_error=False, fill_value=None)
    Airfoil_Data.drag_distribution_func                             = RegularGridInterpolator((AoA_sweep,Re_sweep),fD,method = 'linear',   bounds_error=False, fill_value=None)
    Airfoil_Data.lift_distribution                                  = fL 
    Airfoil_Data.drag_distribution                                  = fD  
    
    return Airfoil_Data