# -*- coding: utf-8 -*-
# Copyright 2016-2018  Flensburg University of Applied Sciences,
# Europa-Universität Flensburg,
# Centre for Sustainable Energy Systems


# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# File description for read-the-docs
""" This module contains functions for calculating representative days/weeks
based on a pyPSA network object. It is designed to be used for the `lopf`
method. Essentially the tsam package
( https://github.com/FZJ-IEK3-VSA/tsam ), which is developed by
Leander Kotzur is used.

Remaining questions/tasks:

- Does it makes sense to cluster normed values?
- Include scaling method for yearly sums
"""

import pandas as pd
import os
if 'READTHEDOCS' not in os.environ:
    import pyomo.environ as po
    import tsam.timeseriesaggregation as tsam

__copyright__ = ("Flensburg University of Applied Sciences, "
                 "Europa-Universität Flensburg, "
                 "Centre for Sustainable Energy Systems")
__license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)"
__author__ = "Simon Hilpert"


def snapshot_clustering(self):
    """
    """
    if self.args['snapshot_clustering']['active'] == True:

        if self.args['snapshot_clustering']['method'] == 'segmentation' :
            
            self.network = run(network=self.network.copy(),
                      n_clusters=1,
                      segmented_to = self.args['snapshot_clustering']['n_segments'],
                      csv_export = self.args['csv_export'] # can be deleted later, just helpful to see how time steps were clustered
                      )
            
        elif self.args['snapshot_clustering']['method'] == 'typical_periods' :
            
            self.network = run(network=self.network.copy(),
                      n_clusters=self.args['snapshot_clustering']['n_clusters'],
                      how=self.args['snapshot_clustering']['how']
                      )
        else :
                 raise ValueError("Type of clustering should be 'typical_periods' or 'segmentation'")


def tsam_cluster(timeseries_df,
                 typical_periods=10,
                 how='daily',
                 extremePeriodMethod = 'None',
                 segmentation = False,
                 segment_no = 10,
                 segm_hoursperperiod = 24):
    """
    Parameters
    ----------
    df : pd.DataFrame
        DataFrame with timeseries to cluster
    typical_periods: Number of typical Periods (or clusters)
    how: {'daily', 'weekly'}
    extremePeriodMethod: {'None','append','new_cluster_center',
                           'replace_cluster_center'}, default: 'None'
        Method how to integrate extreme Periods into to the typical period profiles.
        'None': No integration at all.
        'append': append typical Periods to cluster centers
        'new_cluster_center': add the extreme period as additional cluster
            center. It is checked then for all Periods if they fit better
            to the this new center or their original cluster center.
        'replace_cluster_center': replaces the cluster center of the
            cluster where the extreme period belongs to with the periodly
            profile of the extreme period. (Worst case system design)
    segmentation: Is given by the run-function, can be True or False
    segment_no: Only used when segmentation is true, the number of segments
    segm_hoursperperiod: Only used when segmentation is true, defines the length of a cluster period

    Returns
    -------
    df_cluster
    cluster_weights
    dates
    hours
    df_i_h
    timeseries : pd.DataFrame
                Clustered timeseries, only used if segmentation is True
    """

    if how == 'daily':
        hours = 24
        period = ' days'
    if how == 'weekly':
        hours = 168
        period = ' weeks'

    if segmentation:
        hoursPerPeriod = segm_hoursperperiod
    else:
        hoursPerPeriod = hours

    aggregation = tsam.TimeSeriesAggregation(
        timeseries_df,
        noTypicalPeriods=typical_periods,
        extremePeriodMethod = extremePeriodMethod,
        addPeakMin = ['residual_load'],
        addPeakMax = ['residual_load'],
        rescaleClusterPeriods=False,
        hoursPerPeriod=hoursPerPeriod,
        clusterMethod='hierarchical',
        segmentation = segmentation,
        noSegments = segment_no)
    
    if segmentation:
        print('Snapshot clustering to ' + str(segment_no) + ' segments' + '\n' +
              'Using extreme period method: ' + extremePeriodMethod + '\n' + 
              'Segmentation: ' + str(segmentation))
    
    else:
        print('Snapshot clustering to ' + str(typical_periods) + period + '\n' + 
          'Using extreme period method: ' + extremePeriodMethod + '\n' + 
          'Segmentation: ' + str(segmentation))


    timeseries_creator = aggregation.createTypicalPeriods()
    timeseries = timeseries_creator.copy()

    #If Segmentation is True, insert 'Dates' and 'SegmentNo' column in timeseries
    if segmentation is True:
        weights=timeseries.index.get_level_values(2)
        dates_df= timeseries_df.index.get_level_values(0)
        dates=[]
        segmentno=[]
        wcount=0
        count=0
        for weight in weights:
            dates.append(dates_df[wcount])
            wcount = wcount + weight
            segmentno.append(count)
            count = count +1
        timeseries.insert(0, "dates", dates, True)
        timeseries.insert(1, "SegmentNo", segmentno, True)
        timeseries.insert(2, "SegmentDuration", weights, True)
        timeseries.set_index(['dates', 'SegmentNo', 'SegmentDuration'], inplace=True)

        if 'Unnamed: 0' in timeseries.columns:
            del timeseries['Unnamed: 0']
        if 'Segment Step' in timeseries.columns:
            del timeseries['Segment Step']
        #print(timeseries)
        
    cluster_weights = aggregation.clusterPeriodNoOccur
    clusterOrder =aggregation.clusterOrder
    clusterCenterIndices= aggregation.clusterCenterIndices

    if extremePeriodMethod  == 'new_cluster_center':
        for i in aggregation.extremePeriods.keys():
            clusterCenterIndices.insert(
                    aggregation.extremePeriods[i]['newClusterNo'],
                    aggregation.extremePeriods[i]['stepNo'])

    if extremePeriodMethod  == 'append':
        for i in aggregation.extremePeriods.keys():
            clusterCenterIndices.insert(
                    aggregation.extremePeriods[i]['clusterNo'],
                    aggregation.extremePeriods[i]['stepNo'])

    # get all index for every hour of 
tha
t day of the clusterCenterIndices
    start = []
    # get the first hour of the clusterCenterIndices (days start with 0)
    for i in clusterCenterIndices:
        start.append(i * hours)

    # get a list with all hours belonging to the clusterCenterIndices
    nrhours = []
    for j in start:
        nrhours.append(j)
        x = 1
        while x < hours:
            j = j + 1
            nrhours.append(j)
            x = x + 1

    # get the origial Datetimeindex
    dates = timeseries_df.iloc[nrhours].index

    #get list of representative days
    representative_day=[]

    #cluster:medoid des jeweiligen Clusters
    dic_clusterCenterIndices = dict(enumerate(clusterCenterIndices))
    for i in clusterOrder:
        representative_day.append(dic_clusterCenterIndices[i])

    #get list of last and first hour of representative days
    last_hour_datetime=[]
    for i in representative_day:
        last_hour = i * hours + hours - 1
        last_h