from unittest import TestCase
import pandas as pd
import numpy as np
import pytest

from wisdem.landbosse.model import WeatherDelay


SEASON_WINTER = 'winter'
SEASON_SPRING = 'spring'
SEASON_SUMMER = 'summer'
SEASON_FALL = 'fall'

month_numbers_to_seasons = {
    1: SEASON_WINTER,
    2: SEASON_WINTER,
    3: SEASON_WINTER,
    4: SEASON_SPRING,
    5: SEASON_SPRING,
    6: SEASON_SPRING,
    7: SEASON_SUMMER,
    8: SEASON_SUMMER,
    9: SEASON_SUMMER,
    10: SEASON_FALL,
    11: SEASON_FALL,
    12: SEASON_FALL
}

days_in_months = {
    'jan': 31,
    'feb': 28,
    'mar': 31,
    'apr': 30,
    'may': 31,
    'jun': 30,
    'jul': 31,
    'aug': 31,
    'sep': 30,
    'oct': 31,
    'nov': 30,
    'dec': 31
}


def generate_a_year(num_delays,
                    avg_hours_per_delay,
                    std_dev_hours_per_delay,
                    delay_speed_m_per_s,
                    seed):
    """
    This function generates a year of data. It creates a dataframe with
    the columns: 'Year', 'Month', 'Day', 'Hour', 'Season', 'Time window'

    Parameters
    ----------
    num_delays : int
        Number of delays in the year

    avg_hours_per_delay : int
        Average duration in hours for each delay.

    std_dev_hours_per_delay : int
        The standard deviation of blackout durations

    delay_speed_m_per_s : float
        Wind speed that will cause a weather delay. This may be separate
        from the critical wind speed for the crane. The critical wind speed
        for the crane is calculated according to the wind shear power law.
        The delay speed is the reference wind speed.

    seed : int
        NumPy random number generator seed. All the data is generated by
        random number generators. Byt giving the same value for this seed
        you can reproduce the same dataframe repeatedly for testing.

    Returns
    -------
    list
        A list of dictionaries. Each dictionary has the following keys
        month, day, year
    """
    np.random.seed(seed)
    year = []
    for month, days_in_month in zip(range(1, 13), days_in_months.values()):
        for day in range(1, days_in_month + 1):
            for hour in range(24):
                row = dict()
                row['Month'] = month
                row['Day'] = day
                row['Hour'] = hour
                row['Season'] = month_numbers_to_seasons[month]
                row['Time window'] = 'normal' if 8 <= hour <= 18 else 'long'
                year.append(row)
    speed_m_per_s = np.zeros(len(year))
    durations = np.random.normal(loc=avg_hours_per_delay, scale=std_dev_hours_per_delay, size=num_delays)
    durations = np.ceil(durations).astype('int')
    positions = np.random.uniform(0, len(year), num_delays)
    positions = np.ceil(positions).astype('int')
    for duration, position in zip(durations, positions):
        delay = np.empty(duration)
        delay.fill(delay_speed_m_per_s)
        speed_m_per_s[position:position + duration] = delay
    df = pd.DataFrame(data=year)
    df['Speed m per s'] = speed_m_per_s
    return df


class TestWeatherDelay(TestCase):
    def setUp(self):
        # Use all default parameters shown above, including the default
        # random seed.
        self.num_delays = 7
        self.avg_hours_per_delay = 20
        self.std_dev_hours_per_delay = 5
        self.delay_speed_m_per_s = 9
        self.seed = 101
        self.weather_window = generate_a_year(num_delays=self.num_delays,
                                              avg_hours_per_delay=self.avg_hours_per_delay,
                                              std_dev_hours_per_delay=self.std_dev_hours_per_delay,
                                              delay_speed_m_per_s=self.delay_speed_m_per_s,
                                              seed=self.seed)

    def test_count_delays_over_all_hours(self):
        """
        Tests delay detection count for 24 hours per day, 365 days per year.
        """
        weather_delay_input_dict = dict()
        weather_delay_input_dict['weather_window'] = self.weather_window
        weather_delay_input_dict['season_construct'] = ['winter', 'spring', 'summer', 'fall']
        weather_delay_input_dict['time_construct'] = ['normal', 'long']
        weather_delay_input_dict['start_delay_hours'] = 0
        weather_delay_input_dict['mission_time_hours'] = 8760
        weather_delay_input_dict['critical_wind_speed_m_per_s'] = 6.0
        weather_delay_input_dict['wind_height_of_interest_m'] = 25
        weather_delay_input_dict['wind_shear_exponent'] = 0.25
        output_dict = dict()
        wd = WeatherDelay(input_dict=weather_delay_input_dict, output_dict=output_dict)
        wind_delays = output_dict['wind_delays']
        self.assertEqual(self.num_delays, len(wind_delays), 'WeatherDelay does not count proper number of delays.')

    def test_delay_durations_over_all_hours(self):
        """
        Tests delay duration detection for 24 hours per day 365 days per year
        """
        weather_delay_input_dict = dict()
        weather_delay_input_dict['weather_window'] = self.weather_window
        weather_delay_input_dict['season_construct'] = ['winter', 'spring', 'summer', 'fall']
        weather_delay_input_dict['time_construct'] = ['normal', 'long']
        weather_delay_input_dict['start_delay_hours'] = 0
        weather_delay_input_dict['mission_time_hours'] = 8760
        weather_delay_input_dict['critical_wind_speed_m_per_s'] = 6.0
        weather_delay_input_dict['wind_height_of_interest_m'] = 25
        weather_delay_input_dict['wind_shear_exponent'] = 0.25
        output_dict = dict()
        wd = WeatherDelay(input_dict=weather_delay_input_dict, output_dict=output_dict)
        expected = [16, 25, 19, 24, 34, 23, 24]
        actual = output_dict['wind_delays']
        self.assertEqual(expected, actual, 'WeatherDelay does not match delay durations.')

    def test_keys_present(self):
        """
        This test deliberately passes a bad input dictionary to ensure that an
        exception is raised when the bad input dictionary does not pass
        validation.
        """
        bad_input_dict = dict()
        bad_input_dict['weather_window'] = self.weather_window
        bad_input_dict['season_construct'] = ['winter', 'spring', 'summer', 'fall']
        output_dict = dict()
        self.assertRaises(ValueError, WeatherDelay, bad_input_dict, output_dict)
