.mean_daily_value = full_ts.y.fillna(0).mean()
        self.days_to_one = round(1/self.mean_daily_value)
        self.is_fitted = True
        self.historical_period_values = time_series.y.values

  
    def make_future_dataframe(self, periods, freq='D', include_history = True):
        
        if not self.is_fitted:
            print('Model is not fitted yet. Please call fit first.')

        last_date = self.history_dates.max()
        self.periods = periods

        dates = pd.date_range(
                start=last_date,
                periods=periods + 1,  # An extra in case we include start
                freq=freq)
        dates = dates[dates > last_date]  # Drop start if equals last_date
        dates = dates[:periods]  # Return correct number of periods
      

        if include_history:
            dates = np.concatenate((np.array(self.history_dates), dates))
            dates = pd.to_datetime(dates)

        return pd.DataFrame({'ds': dates})

    def predict(self, future_df):

        if not self.is_fitted:
            print('Model is not fitted yet. Please call fit first.')

        if self.strategy == 'cyclic':
          future_df_copy = future_df.copy()
          future_df_copy["yhat"] = 0
          future_df_copy["yhat"].iloc[::self.days_to_one] = 1

        if self.strategy =='repeat':
          future_df_copy = future_df.copy()
          
          predictions = self.historical_period_values[-self.periods:]
          future_df_copy["yhat"] = np.concatenate((self.historical_period_values, predictions))
          future_df_copy["yhat"] = future_df_copy["yhat"].fillna(0)

        return future_df_copy


class SimpleProphet(Prophet):
  
  """
  A simple class including Covid modelling and US Holidays.
  In addition, predictions are clipped at zero to not allow for negative values.
  
  """
  
  def __init__(self, add_lockdown = True, *args, **kwargs) -> None:
    super().__init__(*args, **kwargs)
    
    if add_lockdown:
      lockdown1 = pd.DataFrame({
        'holiday': 'lockdown1',
        'ds': pd.to_datetime(["2020-03-11"]),
        'upper_window': 111,
        'lower_window':0
      })
      
      lockdown2 = pd.DataFrame({
        'holiday': 'lockdown2',
        'ds': pd.to_datetime(["2020-07-01"]),
        'upper_window': 91,
        'lower_window':0
      })
      
      lockdown3 = pd.DataFrame({
        'holiday': 'lockdown3',
        'ds': pd.to_datetime(["2020-10-01"]),
        'upper_window': 135,
        'lower_window':0
      })
      
      lockdowns = pd.concat([lockdown1, lockdown2, lockdown3])
      self.holidays = lockdowns
      
    self.add_country_holidays(country_name='US')
      
  def predict(self, *args, **kwargs):
    forecast = super(SimpleProphet, self).predict(*args, **kwargs)
    
    for y in ['yhat', 'yhat_lower', 'yhat_upper']:
      forecast[y] = forecast[y].clip(lower=0)
      
    return forecast
  
  
class ProphetStepWise(Prophet):
  
    def __init__(self, add_lockdown = True, *args, **kwargs) -> None:
      super().__init__(*args, **kwargs)
    
      if add_lockdown:
        lockdown1 = pd.DataFrame({
          'holiday': 'lockdown1',
          'ds': pd.to_datetime(["2020-03-11"]),
          'upper_window': 111,
          'lower_window':0
        })
        
        lockdown2 = pd.DataFrame({
          'holiday': 'lockdown2',
          'ds': pd.to_datetime(["2020-07-01"]),
          'upper_window': 91,
          'lower_window':0
        })
        
        lockdown3 = pd.DataFrame({
          'holiday': 'lockdown3',
          'ds': pd.to_datetime(["2020-10-01"]),
          'upper_window': 135,
          'lower_window':0
        })
      
        lockdowns = pd.concat([lockdown1, lockdown2, lockdown3])
        self.holidays = lockdowns
      
      self.add_country_holidays(country_name='US')
    
    def fit(self, df, **kwargs):
        m = super().fit(df, **kwargs)
        if self.growth == 'flat' and self.changepoints is not None:
            kinit = self.stepwise_growth_init(self.history, self.changepoints)
            self.params['m_'] = kinit[1]
        return self
        

    @staticmethod
    def stepwise_growth_init(df, changepoints):
        k = 0
        if len(changepoints) == 0:
            k = 0
            m = df['y_scaled'].mean()
        else:
            m = []
            last_cp = pd.Timestamp.min
            for cp in changepoints:
                df_  = df[(df.ds > last_cp) & (df.ds <= cp)]['y_scaled']
                m.append(df_.mean())
                last_cp = cp
            cp = changepoints.iloc[-1]
            df_  = df[df.ds > cp]['y_scaled']
            m.append(df_.mean())
        return k, m
    
    @staticmethod
    def stepwise_trend(t, m, changepoint_ts):
        if changepoint_ts[0] == 0:
            m_t = m * np.ones_like(t)
        else:
            m_t = np.ones_like(t)
            last_t_s = changepoint_ts[0]-1
            for s, t_s in enumerate(changepoint_ts):
                indx = (t <= t_s) & (t > last_t_s)
                m_t[indx] = m[s]
                last_t_s = t_s

            t_s = changepoint_ts[-1]
            indx = (t > t_s) 
            m_t[indx] = m[-1]
        return m_t
    
    def sample_predictive_trend(self, df, iteration):
        """Simulate the trend using the extrapolated generative model.
        Parameters
        ----------
        df: Prediction dataframe.
        iteration: Int sampling iteration to use parameters from.
        Returns
        -------
        np.array of simulated trend over df['t'].
        """
        k = self.params['k'][iteration]
        m = self.params['m'][iteration]
        deltas = self.params['delta'][iteration]

        t = np.array(df['t'])
        T = t.max()

        # New changepoints from a Poisson process with rate S on [1, T]
        if T > 1:
            S = len(self.changepoints_t)
            n_changes = np.random.poisson(S * (T - 1))
        else:
            n_changes = 0
        if n_changes > 0:
            changepoint_ts_new = 1 + np.random.rand(n_changes) * (T - 1)
            changepoint_ts_new.sort()
        else:
            changepoint_ts_new = []

        # Get the empirical scale of the deltas, plus epsilon to avoid NaNs.
        lambda_ = np.mean(np.abs(deltas)) + 1e-8

        # Sample deltas
        deltas_new = np.random.laplace(0, lambda_, n_changes)

        # Prepend the times and delt