import pandas as pd
import numpy as np

STAT_TIMESTEP = 1.0

def determine_zone(bpm, zones):
    if len(zones) == 3:
        if (bpm > 0) & (bpm < zones[0]):
            return "Zone 2 (Easy)"
        elif (bpm >= zones[0]) & (bpm < zones[1]):
            return "Zone 3 (Aerobic)"
        elif (bpm >= zones[1]) & (bpm < zones[2]):
            return "Zone 4 (Threshold)"
        elif (bpm >= zones[2]):
            return "Zone 5 (Maximum)"
            
    elif len(zones) == 4:
        if (bpm > 0) & (bpm < zones[0]):
            return "Zone 1 (Warm Up)"
        elif (bpm >= zones[0]) & (bpm < zones[1]):
            return "Zone 2 (Easy)"
        elif (bpm >= zones[1]) & (bpm < zones[2]):
            return "Zone 3 (Aerobic)"
        elif (bpm >= zones[2]) & (bpm < zones[3]):
            return "Zone 4 (Threshold)"
        elif (bpm >= zones[3]):
            return "Zone 5 (Maximum)"
        
def create_dataframe(raw_data):
    column_names = [
        'timeSeconds',
        'tempOral',
        'tempNasal',
        'signalPeriodSec',
        'signalFrequencyBpm',
        'dateTime'
    ]
    df = pd.DataFrame(raw_data, columns=column_names)
    return df
    
def estimateONratio(raw_df, N_window=30, alpha=0.75, flagPlot=False):
    # Function for estimating the Oral/Nasal (O/N) breathing ratio
    # Assumes signal data has a sampling rate of 10 Hz
    #
    # Some definitions
    #   signalTime: np.array with time signal data in seconds
    #   signalOral: np.array with oral temperature signal in degrees Celcius
    #   signalNasal: np.array with nasal temperature signal in degrees Celcius
    #   N_window: size of the moving window used for DC and RMS calculations 
    #   (default = 30, which implies a window of 3 seconds)
    #   alpha: reduction factor for Oral AC RMS signal to decide between prue oral and oral exp/nasal insp cases
    
    raw_df = raw_df.iloc[:-100] #Last 100 records not taken into account
    
    signalTime = raw_df['timeSeconds'].values
    signalOral = raw_df['tempOral'].values
    signalNasal = raw_df['tempNasal'].values
    
    # compute AC / DC temperature signals
    N = len(signalTime)

    # Option 3 for DC: use moving average for DC signal
    signalNasalDC, signalOralDC = np.zeros(N), np.zeros(N)
    for i in range(N_window, N):
        signalOralDC[i] = np.mean(signalOral[i - N_window: i])
        signalNasalDC[i] = np.mean(signalNasal[i - N_window: i])
    
    # compute AC and plot AC & DC signals
    signalNasalAC = signalNasal - signalNasalDC
    signalOralAC = signalOral - signalOralDC
        
    # compute moving RMS continuous time for AC signal
    signalOralACrms = np.zeros(N)
    signalNasalACrms = np.zeros(N)
    
    if (N_window > 2*N): 
        print('Error: Acquisition time is too short. Try longer activity times')
    
    for i in range(2*N_window,N):
        window_OralAC = signalOralAC[i - N_window: i]
        window_NasalAC = signalNasalAC[i - N_window: i]
        
        signalOralACrms[i] = np.sqrt(1 / N_window*np.sum(window_OralAC**2))
        signalNasalACrms[i] =  np.sqrt(1 / N_window*np.sum(window_NasalAC**2))    
    
    #plt.figure()
    i_start = 2*N_window # needed to avoid spurious signal in the beginning
    
    # Classification algorithm
    signalOralSwitch, signalNasalSwitch = np.zeros(N), np.zeros(N)
    
    alpha = 0.850  # AC signal threshold in %
    
    for i in range(i_start, N):
        if (signalNasalDC[i] > signalOralDC[i]):    #Pure nasal breathing
            signalOralSwitch[i] = 0.0
            signalNasalSwitch[i] = 1.0
        else:
            if (signalNasalACrms[i] < alpha*signalOralACrms[i]) : # Pure oral breathing 
                signalOralSwitch[i] = 1.0
                signalNasalSwitch[i] = 0.0
            else:   # Nasal Insp / Oral exp
                signalOralSwitch[i] = 0.0
                signalNasalSwitch[i] = 1.0           
            
    # pad switch signals with initial value
    signalOralSwitch[0: i_start] = signalOralSwitch[i_start]
    signalNasalSwitch[0: i_start] = signalNasalSwitch[i_start]
    
    # Compute Oral ratio
    OralRatio = np.mean(signalOralSwitch)
    return OralRatio

def generate_resampled_data(df, resamp_secs=1, data_points=None):
    df['timeSeconds'] = df['timeSeconds'].astype('float')
    df['signalFrequencyBpm'] = df['signalFrequencyBpm'].astype('float')

    if data_points:
        full_time = df['timeSeconds'][-1:].values[0]
        inc = full_time / data_points
        if inc > resamp_secs:
            resamp_secs = round(inc)

    # Convert timeSeconds to time
    df["time"] = pd.to_timedelta(df["timeSeconds"], unit="s")

    # Drop unnecessary columns
    clean_df = df.drop(["timeSeconds", "tempOral", "tempNasal", "signalPeriodSec", "dateTime"], axis=1)

    # Set time as index
    clean_df.set_index("time", inplace=True)

    resample_bin = '{}S'.format(resamp_secs)
    resampled_df = clean_df["signalFrequencyBpm"].resample(resample_bin).mean().reset_index()
    resampled_df["time"] = resampled_df["time"].dt.total_seconds()
    return resampled_df

def generate_histogram(df):
    resampled_df = generate_resampled_data(df, 1, 100)
    resampled_array = resampled_df.values.tolist()
    return resampled_array

def generate_stats(df, zones):
    resampled_df = generate_resampled_data(df, STAT_TIMESTEP)

    resampled_df["bpmZone"] = resampled_df["signalFrequencyBpm"].apply(lambda x: determine_zone(x, zones))
    zones_pct = resampled_df["bpmZone"].value_counts(normalize=True).sort_index().values.tolist()
    zones_pct = zones_pct + [0] * ((len(zones) + 1) - len(zones_pct))

    data_stats = dict()
    data_stats["min"] = resampled_df["signalFrequencyBpm"].min()
    data_stats["max"] = resampled_df["signalFrequencyBpm"].max()
    data_stats["avg"] = resampled_df["signalFrequencyBpm"].mean()
    data_stats["zones"] = zones_pct
    return data_stats

def generate_report(raw_data, zones):
    data = create_dataframe(raw_data)
    data_report = generate_stats(data, zones)
    data_report["histogram"] = generate_histogram(data)
    #data_report["onratio"] = estimateONratio(data)
    return data_report
