import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.ticker as tkr

from bokeh.plotting import figure, show
from bokeh.layouts import layout, gridplot
from bokeh.models import DatetimeTickFormatter, HoverTool, Legend
from bokeh.palettes import *
from bokeh.io import output_file

from openpyxl import load_workbook
from datetime import datetime
from dateutil.parser import isoparse
import itertools
import pandas as pd
import os
import xlrd as _xlrd


# This file defines different functions for the plotting of the results of the optimization.
# The plots are made at the end of this file, introducing a .xls file previously created during the optimization.
# An important parameter "optMode" needs to be set when running the code (look in the __main__ fragment)


def monthlyBalance(data, bus, new_legends):
    """
    Function for the definition of the monthly summary of a bus
    :param data: dict type, results from the optimization applied to one bus
    :param bus: str type, bus from which the summary is required
    :param new_legends: dict type, new legends to plot on the graph
    :return:
    """
    building = "__" + bus.split("__")[1]
    data_month = data.resample('1m').sum()
    monthShortNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

    while len(data_month) != len(monthShortNames):
        data.drop(data.index[-1], inplace=True)
        data_month = data.resample('1m').sum()
    neg_flow = []
    pos_flow = []
    for i in data.columns:
        a = [i.strip("()").split(", ")]
        if "Bus" in a[0][0]:
            neg_flow.append(i)
        else:
            pos_flow.append(i)
    plt.figure()
    mark = []
    for i in neg_flow:
        plt.bar(monthShortNames, -data_month[i], label=new_legends[i.replace(building, "")], bottom=sum(mark))
        mark.append(-data_month[i])
    mark = []
    for i in pos_flow:
        plt.bar(monthShortNames, data_month[i], label=new_legends[i.replace(building, "")], bottom=sum(mark))
        mark.append(data_month[i])

    ax = plt.gca()
    # Shrink current axis by 20%
    box = ax.get_position()
    ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])

    # Put a legend to the right of the current axis
    ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
    plt.grid(axis='y')
    if "electricity" in bus or "grid" in bus:
        plt.title("Monthly electricity balance for " + building.replace("__", ""))
    elif "spaceHeating" in bus:
        plt.title("Monthly space heating balance for " + building.replace("__", ""))
    else:
        plt.title("Monthly domestic hot water balance for " + building.replace("__", ""))
    plt.show()


def hourlyDailyPlot(data, bus, palette, new_legends):
    """
    Function for the bokeh plot of hourly and daily balance of a bus
    :param data: list of dict type, results from the optimization
    :param bus: list of str type, buses from which the summary is required
    :param palette: palette type form bokeh.palettes (For example, Category10_8), different types can be found on
    https://docs.bokeh.org/en/latest/docs/reference/palettes.html or
    https://docs.bokeh.org/en/latest/_modules/bokeh/palettes.html
    :param new_legends: dict type, new legends to plot on the graph
    For example, Category10_8
    :return:
    """
    p_figs = []
    p_figs_h = []
    p_figs_d = []
    p_plots = []
    a = []
    b = 1
    bus = [x for x in bus if 'electricityProdBus' not in x and 'electricityInBus' not in x]
    for i in range(0, len(data)):
        building = '__' + bus[i].split("__")[1]

        if "electricity" in bus[i]:
            dt = data[i]
            dt.pop(f"(('electricityProdBus{building}', 'electricitySource{building}'), 'flow')")
            data_day = dt.resample('1d').sum()
            p1 = figure(title="Hourly electricity flows for " + building.replace("__", ""), x_axis_label="Date",
                        y_axis_label="Energy (kWh)", sizing_mode="scale_both")
            p1.add_layout(Legend(), 'right')
            p1.add_tools(HoverTool(tooltips=[('Time', '@x{%d/%m/%Y %H:%M:%S}'), ('Energy', '@y{0.00}')],
                                   formatters={'@x': 'datetime'},
                                   mode='mouse'))
            p2 = figure(title="Daily electricity flows for " + building.replace("__", ""), x_axis_label="Date",
                        y_axis_label="Energy (kWh)", sizing_mode="scale_both")
            p2.add_layout(Legend(), 'right')
            p2.add_tools(HoverTool(tooltips=[('Date', '@x{%d/%m/%Y}'), ('Energy', '@y{0.00}')],
                                   formatters={'@x': 'datetime'},
                                   mode='mouse'))
            if len(p_figs_h) > 0:
                p1.x_range = p_figs_h[0].x_range
                p2.x_range = p_figs_d[0].x_range
            colors = itertools.cycle(palette)
            for j, color in zip(dt.columns, colors):
                p1.line(dt.index, dt[j], legend_label=new_legends[j.replace(building, "")], color=color, line_width=1.5)
                p2.line(data_day.index, data_day[j], legend_label=new_legends[j.replace(building, "")], color=color,
                        line_width=1.5)
            p_figs.append([p1, p2])
            p_figs_h.append(p1)
            p_figs_d.append(p2)
            p_plots.append(p1)
            p_plots.append(p2)

        elif "shSource" in bus[i] or "spaceHeatingBus" in bus[i]:
            dt = data[i]
            data_day = dt.resample('1d').sum()
            p3 = figure(title="Hourly space heating flows for " + building.replace("__", ""), x_axis_label="Date",
                        y_axis_label="Energy (kWh)", sizing_mode="scale_both")
            p3.add_layout(Legend(), 'right')
            p3.add_tools(HoverTool(tooltips=[('Time', '@x{%d/%m/%Y %H:%M:%S}'), ('Energy', '@y{0.00}')],
                                   formatters={'@x': 'datetime'},
                                   mode='mouse'))
            p4 = figure(title="Daily space heating flows for " + building.replace("__", ""), x_axis_label="Date",
                        y_axis_label="Energy (kWh)", sizing_mode="scale_both")
            p4.add_layout(Legend(), 'right')
            p4.add_tools(HoverTool(tooltips=[('Date', '@x{%d/%m/%Y}'), ('Energy', '@y{0.00}')],
                                   formatters={'@x': 'datetime'},
                                   mode='mouse'))
            if len(p_figs_h) > 0:
                p3.x_range = p_figs_h[0].x_range
                p4.x_range = p_figs_d[0].x_range
            colors = itertools.cycle(palette)
            for j, color in zip(dt.columns, colors):
                p3.line(dt.index, dt[j], legend_label=new_legends[j.replace(building, "")], color=color, line_width=1.5)
                p4.line(data_day.index, data_day[j], legend_label=new_legends[j.replace(building, "")], color=color,
                        line_width=1.5)
            p_figs.append([p3, p4])
            p_figs_h.append(p3)
            p_figs_d.append(p4)
            p_plots.append(p3)
            p_plots.append(p4)

        else:
            dt = data[i]
            data_day = dt.resample('1d').sum()

            p5 = figure(title="Hourly domestic hot water flows for " + building.replace("__", ""), x_axis_label="Date",
                        y_axis_label="Energy (kWh)", sizing_mode="scale_both")
            p5.add_layout(Legend(), 'right')
            p5.add_tools(HoverTool(tooltips=[('Time', '@x{%d/%m/%Y %H:%M:%S}'), ('Energy', '@y{0.00}')],
                                   formatters={'@x': 'datetime'},
                                   mode='mouse'))
            p6 = figure(title="Daily domestic hot water flows for " + building.replace("__", ""), x_axis_label="Date",
                        y_axis_label="Energy (kWh)", sizing_mode="scale_both")
            p6.add_layout(Legend(), 'right')
            p6.add_tools(HoverTool(tooltips=[('Date', '@x{%d/%m/%Y}'), ('Energy', '@y{0.00}')],
                                   formatters={'@x': 'datetime'},
                                   mode='mouse'))
            if len(p_figs_h) > 0:
                p5.x_range = p_figs_h[0].x_range
                p6.x_range = p_figs_d[0].x_range
            colors = itertools.cycle(palette)
            for j, color in zip(dt.columns, colors):
                p5.line(dt.index, dt[j], legend_label=new_legends[j.replace(building, "")], color=color, line_width=1.5)
                p6.line(data_day.index, data_day[j], legend_label=new_legends[j.replace(building, "")], color=color,
                        line_width=1.5)
            p_figs.append([p5, p6])
            p_figs_h.append(p5)
            p_figs_d.append(p6)
            p_plots.append(p5)
            p_plots.append(p6)

    for p in p_plots:
        p.xaxis[0].formatter = DatetimeTickFormatter(months="%d %b")
        p.legend.click_policy = "hide"
        p.legend.location = "top_left"
        p.legend.label_text_font_size = "8pt"

    return (p_figs_h, p_figs_d)


def toColor(COLORS, obj=None):
    """
    Function from the URBS platform https://github.com/ojdo/urbs/blob/1house/comp.py
    Assign a deterministic pseudo-random color to argument.
    If COLORS[obj] is set, return that. For strings, this value depends only
    on the string content, so that same strings always yield the same color.
    :param COLORS: dict of components and their assigned color
    :param obj: any hashable object
    :return: a (r, g, b) color tuple if COLORS[obj] is set, otherwise a hexstring
    """
    try:
        color = tuple(rgb / 255.0 for rgb in COLORS[obj])
    except KeyError:
        # random deterministic color
        import hashlib
        color = '#' + hashlib.sha1(obj.encode()).hexdigest()[-6:]
    return color


def groupHbarPlots(ax, group_size, inner_sep=None):
    """
    Function from the URBS platform https://github.com/ojdo/urbs/blob/1house/comp.py
    Group bars of a horizontal barplot closer together.
    Given an existing horizontal bar plot handle ax, move bars of a given group size (>=2) closer together,
    reducing the distance within the bars of a group, but increasing the distance between different groups.
    By default, bars are placed within a coordinate system 1 unit apart. The space between two bars has
    size 1 - bar_height, which can be specified in matplotlib (and pandas) using the `width` argument.
    :param ax: matplotlib axis
    :param group_size: int type, how many bars to group together
    :param inner_sep: float type, vertical spacing within group (optional). Default: reduce the distance to a half
    :return:
    """
    handles, labels = ax.get_legend_handles_labels()
    bar_height = handles[0][0].get_height()  # assumption: identical for all

    if inner_sep is None:
        inner_sep = 0.5 * (1 - bar_height)

    for column, handle in enumerate(handles):
        for row, patch in enumerate(handle.patches):
            group_number, row_within_group = divmod(row, group_size)

            group_offset = (group_number * group_size
                            + 0.5 * (group_size - 1) * (1 - inner_sep)
                            - 0.5 * (group_size * bar_height))

            patch.set_y(row_within_group * (bar_height + inner_sep)
                        + group_offset)


def deduplicateLegend(handles, labels):
    """
    Function from the URBS platform https://github.com/ojdo/urbs/blob/1house/comp.py
    Remove double entries from figure legend.
    :param handles: list of legend entry handles
    :param labels: list of legend entry labels
    :return: (handles, labels) tuple of lists with duplicate labels removed
    """
    new_handles = []
    new_labels = []
    for hdl, lbl in zip(handles, labels):
        if not lbl in new_labels:
            new_handles.append(hdl)
            new_labels.append(lbl)
    # also, sort both lists accordingly
    new_labels, new_handles = (list(t) for t in zip(*sorted(zip(new_labels, new_handles))))
    return (new_handles, new_labels)


def resultingDataDiagram(elBus, shBus, dhwBus, costs, env, COLORS, building, newLegends):
    """
    Function inspired from the URBS platform https://github.com/ojdo/urbs/blob/1house/comp.py
    Function plotting the different results of the optimization. First, costs will be plotted, then the energy produced,
    comparing energy for electricity bus, sh and dhw bus, and finally the retrieved energy from the storages.
    :param elBus: dict type, results from the optimization applied to one bus.
    :param shBus: dict type, results from the optimization applied to one bus.
    :param dhwBus: dict type, results from the optimization applied to one bus.
    :param costs: dict type, resulting costs from the optimization
    :param env: dict type, resulting environmental impacts from the optimization
    :param COLORS: list type, different colors for the different components of the system
    :param building: str type, name of the building
    :return: Four bar plots and the costs, environmental impacts, production and storage dict created
    """
    production = {}
    storage = {}
    alpha = 0
    if optMode == "group":
        list = []

    for flow in elBus.keys():
        if "Storage" in flow and "out" in newLegends[flow.replace("__Building" + str(building), "")]:
            storage[newLegends[flow.replace("__Building" + str(building), "")]] = [0, 0, sum(elBus[flow])]
        elif optMode == "group" and ("electricityLink" in flow):
            if "_in" in newLegends[flow.replace("__Building" + str(building), "")]:
                if newLegends[flow.replace("__Building" + str(building), "")].replace("_in", "") in list:
                    production[newLegends[flow.replace("__Building" + str(building), "")].replace("_in", "")][2] -= sum(
                        elBus[flow])
                else:
                    production[newLegends[flow.replace("__Building" + str(building), "")].replace("_in", "")] = [0, 0,
                                                                                                                 -sum(
                                                                                                                     elBus[
                                                                                                                         flow])]
                    list.append(newLegends[flow.replace("__Building" + str(building), "")].replace("_in", ""))
            elif "_out" in newLegends[flow.replace("__Building" + str(building), "")]:
                if newLegends[flow.replace("__Building" + str(building), "")].replace("_out", "") in list:
                    production[newLegends[flow.replace("__Building" + str(building), "")].replace("_out", "")][
                        2] += sum(elBus[flow])
                else:
                    production[newLegends[flow.replace("__Building" + str(building), "")].replace("_out", "")] = [0, 0,
                                                                                                                  sum(
                                                                                                                      elBus[
                                                                                                                          flow])]
                    list.append(newLegends[flow.replace("__Building" + str(building), "")].replace("_out", ""))
        elif "Storage" not in flow and "Demand" not in flow:
            production[newLegends[flow.replace("__Building" + str(building), "")]] = [0, 0, sum(elBus[flow])]

            # specific for negative flows
            if "HP_SH" in flow or "HP_DHW" in flow:
                alpha += sum(elBus[flow])

    production["HP"] = [0, 0, -alpha]

    for flow in shBus.keys():
        if "Storage" in flow and "out" in newLegends[flow.replace("__Building" + str(building), "")]:
            storage[newLegends[flow.replace("__Building" + str(building), "")]] = [0, sum(shBus[flow]), 0]
        elif "Storage" not in flow and "Demand" not in flow:
            production[newLegends[flow.replace("__Building" + str(building), "")]] = [0, sum(shBus[flow]), 0]

    for flow in dhwBus.keys():
        if "Storage__" in flow and "out" in newLegends[flow.replace("__Building" + str(building), "")]:
            storage[newLegends[flow.replace("__Building" + str(building), "")]] = [sum(dhwBus[flow]), 0, 0]
        elif "Storage__" not in flow and "Demand" not in flow:
            production[newLegends[flow.replace("__Building" + str(building), "")]] = [sum(dhwBus[flow]), 0, 0]

    costs = costs.transpose()
    costs = costs.rename(index={0: building})

    env = env.transpose()
    for i in env.columns:
        for j in newLegends.keys():
            if i.replace("__Building" + str(building), "") in j:
                env = env.rename(columns={i: newLegends[j]})

    production = pd.DataFrame.from_dict(production, orient='index')
    production = production.transpose()

    storage = pd.DataFrame.from_dict(storage, orient='index', columns=["dhw", "sh", "elec"])
    storage = storage.transpose()

    fig = plt.figure(figsize=(18, 8))
    gs = gridspec.GridSpec(1, 4, width_ratios=[3, 4, 8, 3], wspace=0.03)

    ax0 = plt.subplot(gs[0])
    costs_colors = [toColor(COLORS, x) for x in costs.columns]
    bp0 = costs.plot(ax=ax0, kind='barh', color=costs_colors, stacked=True, linewidth=0)

    ax1 = plt.subplot(gs[1])
    env_colors = [toColor(COLORS, x) for x in env.columns]
    bp1 = env.plot(ax=ax1, kind='barh', color=env_colors, stacked=True, linewidth=0)

    ax2 = plt.subplot(gs[2])
    production_colors = [toColor(COLORS, x) for x in production.columns]
    bp2 = production.plot(ax=ax2, kind='barh', color=production_colors, stacked=True, linewidth=0, width=.5)

    ax3 = plt.subplot(gs[3])
    storage_colors = [toColor(COLORS, x) for x in storage.columns]
    bp3 = storage.plot(ax=ax3, kind='barh', color=storage_colors, stacked=True, linewidth=0)

    # remove scenario names from other bar plots
    for ax in [ax1, ax2]:
        ax.set_yticklabels('')
    for ax in [ax2, ax3]:
        groupHbarPlots(ax, group_size=3, inner_sep=0.01)
    ax3.yaxis.tick_right()

    # set limits and ticks for both axes
    for ax in [ax0, ax1, ax2, ax3]:
        ax.yaxis.grid(False)
        ax.xaxis.grid(True, 'major', linestyle='-')
        ax.xaxis.set_ticks_position('none')
        ax.yaxis.set_ticks_position('none')

        # group 1,000,000 with commas
        xmin, xmax = ax.get_xlim()
        if xmax > 90 or xmin < -90:
            group_thousands_and_skip_first = tkr.FuncFormatter(
                lambda x, pos: '' if pos == 0 else '{:0,d}'.format(int(x)))
            ax.xaxis.set_major_formatter(group_thousands_and_skip_first)
        else:
            skip_lowest = tkr.FuncFormatter(
                lambda x, pos: '' if pos == 0 else x)
            ax.xaxis.set_major_formatter(skip_lowest)

        # legend
        # set style arguments
        legend_style = {'frameon': False,
                        'loc': 'lower center',
                        'ncol': 2,
                        'bbox_to_anchor': (0.5, .99)}
        # get handels and labels, remove duplicate labels
        handles, labels = deduplicateLegend(*ax.get_legend_handles_labels())
        # set legend to use those
        lg = ax.legend(handles, labels, **legend_style)
        # finally, remove lines from patches
        plt.setp(lg.get_patches(), linewidth=0)

    ax0.set_xlabel('Total costs (CHF)')
    ax1.set_xlabel('Total environmental impacts (kgCo2eq)')
    ax2.set_xlabel('Total energy produced (kWh)')
    ax3.set_xlabel('Retrieved energy (kWh)')
    return fig, costs, env, production, storage


def resultingDataDiagramLoop(elec, sh, dhw, costs, env, colors, buildings, newLegends):
    """
    Function inspired from the URBS platform https://github.com/ojdo/urbs/blob/1house/comp.py
    Function plotting the graph comparing the different buildings/scenarios on costs, energy produced and energy
    retrieved from storages
    :param elec: list of dict type, optimization results
    :param sh: list of dict type, optimization results
    :param dhw: list of dict type, optimization results
    :param costs: list of dict type, optimization results
    :param env: list of dict type, optimization results
    :param colors: list type, different colors for the different components of the system
    :param buildings: list of str type, name of the different buildings
    :return: figure created
    """
    n_costs, n_env, n_production, n_storage = resultingDataDiagram(elec[0], sh[0], dhw[0], costs[0], env[0], colors,
                                                                   buildings[0], newLegends)[1:]
    for i in range(1, len(elec)):
        a, b, c, d = resultingDataDiagram(elec[i], sh[i], dhw[i], costs[i], env[i], colors, buildings[i], newLegends)[
                     1:]
        n_costs = pd.concat([n_costs, a])
        n_env = pd.concat([n_env, b])
        n_production = pd.concat([n_production, c])
        n_storage = pd.concat([n_storage, d])

    fig = plt.figure(figsize=(18, 8))
    gs = gridspec.GridSpec(1, 4, width_ratios=[4, 3, 8, 3], wspace=0.03)

    ax0 = plt.subplot(gs[0])
    costs_colors = [toColor(colors, x) for x in n_costs.columns]
    bp0 = n_costs.plot(ax=ax0, kind='barh', color=costs_colors, stacked=True, linewidth=0)

    ax1 = plt.subplot(gs[1])
    env_colors = [toColor(colors, x) for x in n_env.columns]
    bp1 = n_env.plot(ax=ax1, kind='barh', color=env_colors, stacked=True, linewidth=0)

    ax2 = plt.subplot(gs[2])
    production_colors = [toColor(colors, x) for x in n_production.columns]
    bp2 = n_production.plot(ax=ax2, kind='barh', color=production_colors, stacked=True, linewidth=0, width=.5)

    ax3 = plt.subplot(gs[3])
    storage_colors = [toColor(colors, x) for x in n_storage.columns]
    bp3 = n_storage.plot(ax=ax3, kind='barh', color=storage_colors, stacked=True, linewidth=0)

    # remove scenario names from other bar plots
    for ax in [ax1, ax2, ax3]:
        ax.set_yticklabels('')
    for ax in [ax2, ax3]:
        groupHbarPlots(ax, group_size=3, inner_sep=0.01)

    # set limits and ticks for both axes
    for ax in [ax0, ax1, ax2, ax3]:
        ax.yaxis.grid(False)
        ax.xaxis.grid(True, 'major', linestyle='-')
        ax.xaxis.set_ticks_position('none')
        ax.yaxis.set_ticks_position('none')

        # group 1,000,000 with commas
        xmin, xmax = ax.get_xlim()
        if xmax > 90 or xmin < -90:
            group_thousands_and_skip_first = tkr.FuncFormatter(
                lambda x, pos: '' if pos == 0 else '{:0,d}'.format(int(x)))
            ax.xaxis.set_major_formatter(group_thousands_and_skip_first)
        else:
            skip_lowest = tkr.FuncFormatter(
                lambda x, pos: '' if pos == 0 else x)
            ax.xaxis.set_major_formatter(skip_lowest)

        # legend
        # set style arguments
        legend_style = {'frameon': False,
                        'loc': 'lower center',
                        'ncol': 2,
                        'bbox_to_anchor': (0.5, .99)}
        # get handels and labels, remove duplicate labels
        handles, labels = deduplicateLegend(*ax.get_legend_handles_labels())
        # set legend to use those
        lg = ax.legend(handles, labels, **legend_style)
        # finally, remove lines from patches
        plt.setp(lg.get_patches(), linewidth=0)

    ax0.set_xlabel('Total costs (CHF)')
    ax1.set_xlabel('Total environmental impacts (kgCo2eq)')
    ax2.set_xlabel(
        'Total energy produced (kWh)\n First line: electricity, Second line: space heating,\n Third line: domestic hot water')
    ax3.set_xlabel(
        'Retrieved energy (kWh)\n First line: electricity, Second line: space heating,\n Third line: domestic hot water')

    return fig


def resultingDataDemandDiagram(elBus, shBus, dhwBus, COLORS, building, newLegends):
    """
    Function inspired from the URBS platform https://github.com/ojdo/urbs/blob/1house/comp.py
    Function plotting the different results of the optimization. First, costs will be plotted, then the energy produced,
    comparing energy for electricity bus, sh and dhw bus, and finally the retrieved energy from the storages.
    :param elBus: dict type, results from the optimization applied to one bus. Called like "solph.views.node(results, bus)"
    :param shBus: dict type, results from the optimization applied to one bus. Called like "solph.views.node(results, bus)"
    :param dhwBus: dict type, results from the optimization applied to one bus. Called like "solph.views.node(results, bus)"
    :param COLORS: list type, different colors for the different components of the system
    :param building: str type, name of the building
    :return: Three bar plots and the elec, sh and dhw dict created
    """
    elec = {}
    sh = {}
    dhw = {}
    if optMode == "group":
        list = []
    for flow in elBus.keys():
        if "Demand" in flow:
            elec[newLegends[flow.replace("__Building" + str(building), "")]] = [0, 0, sum(elBus[flow])]
        elif "producedElectricity" in flow or "gridElectricity" in flow:
            elec[newLegends[flow.replace("__Building" + str(building), "")]] = [0, sum(elBus[flow]), 0]
        elif optMode == "group" and "electricityLink" in flow and "_in" not in newLegends[
            flow.replace("__Building" + str(building), "")]:
            if newLegends[flow.replace("__Building" + str(building), "")] in list:
                elec[newLegends[flow.replace("__Building" + str(building), "")]][0] += sum(elBus[flow])
            else:
                elec[newLegends[flow.replace("__Building" + str(building), "")]] = [sum(elBus[flow]), 0, 0]
                list.append(newLegends[flow.replace("__Building" + str(building), "")])
        else:
            elec[newLegends[flow.replace("__Building" + str(building), "")]] = [sum(elBus[flow]), 0, 0]

        if "Storage" in flow and "in" in newLegends[flow.replace("__Building" + str(building), "")]:
            del elec[newLegends[flow.replace("__Building" + str(building), "")]]
        elif "electricity" not in flow[5:] and "Demand" not in flow:
            del elec[newLegends[flow.replace("__Building" + str(building), "")]]
        elif "Resource" in flow or "excess" in flow:
            del elec[newLegends[flow.replace("__Building" + str(building), "")]]

    for flow in shBus.keys():
        if "Demand" in flow:
            sh[newLegends[flow.replace("__Building" + str(building), "")]] = [0, 0, sum(shBus[flow])]
        else:
            sh[newLegends[flow.replace("__Building" + str(building), "")]] = [0, sum(shBus[flow]), 0]
        if "Storage" in flow and "in" in newLegends[flow.replace("__Building" + str(building), "")]:
            del sh[newLegends[flow.replace("__Building" + str(building), "")]]

    for flow in dhwBus.keys():
        if "Demand" in flow:
            dhw[newLegends[flow.replace("__Building" + str(building), "")]] = [0, 0, sum(dhwBus[flow])]
        else:
            dhw[newLegends[flow.replace("__Building" + str(building), "")]] = [0, sum(dhwBus[flow]), 0]

        if "Storage_" in flow:
            del dhw[newLegends[flow.replace("__Building" + str(building), "")]]

    elec = pd.DataFrame.from_dict(elec, orient='index')
    elec = elec.transpose()

    sh = pd.DataFrame.from_dict(sh, orient='index')
    sh = sh.transpose()

    dhw = pd.DataFrame.from_dict(dhw, orient='index')
    dhw = dhw.transpose()

    fig = plt.figure(figsize=(18, 8))
    gs = gridspec.GridSpec(1, 3, wspace=0.1)

    ax0 = plt.subplot(gs[0])
    elec_colors = [toColor(COLORS, x) for x in elec.columns]
    bp0 = elec.plot(ax=ax0, kind='barh', color=elec_colors, stacked=True, linewidth=0, width=.5)

    ax1 = plt.subplot(gs[1])
    sh_colors = [toColor(COLORS, x) for x in sh.columns]
    bp1 = sh.plot(ax=ax1, kind='barh', color=sh_colors, stacked=True, linewidth=0)

    ax2 = plt.subplot(gs[2])
    dhw_colors = [toColor(COLORS, x) for x in dhw.columns]
    bp2 = dhw.plot(ax=ax2, kind='barh', color=dhw_colors, stacked=True, linewidth=0)

    # remove scenario names from other bar plots
    ax0.set_yticklabels(('', '', 'Building ' + str(building)))
    for ax in [ax1, ax2]:
        ax.set_yticklabels('')
    for ax in [ax0, ax1, ax2]:
        groupHbarPlots(ax, group_size=3, inner_sep=0.0)

    # set limits and ticks for both axes
    for ax in [ax0, ax1, ax2]:
        ax.yaxis.grid(False)
        ax.xaxis.grid(True, 'major', linestyle='-')
        ax.xaxis.set_ticks_position('none')
        ax.yaxis.set_ticks_position('none')

        # group 1,000,000 with commas
        xmin, xmax = ax.get_xlim()
        if xmax > 90 or xmin < -90:
            group_thousands_and_skip_first = tkr.FuncFormatter(
                lambda x, pos: '' if pos == 0 else '{:0,d}'.format(int(x)))
            ax.xaxis.set_major_formatter(group_thousands_and_skip_first)
        else:
            skip_lowest = tkr.FuncFormatter(
                lambda x, pos: '' if pos == 0 else x)
            ax.xaxis.set_major_formatter(skip_lowest)

        # legend
        # set style arguments
        legend_style = {'frameon': False,
                        'loc': 'lower center',
                        'ncol': 3,
                        'bbox_to_anchor': (0.5, .99)}
        # get handels and labels, remove duplicate labels
        handles, labels = deduplicateLegend(*ax.get_legend_handles_labels())
        # set legend to use those
        lg = ax.legend(handles, labels, **legend_style)
        # finally, remove lines from patches
        plt.setp(lg.get_patches(), linewidth=0)

    ax0.set_xlabel('Demand and energy produced\n for electricity (kWh)')
    ax1.set_xlabel('Demand and energy produced\n for space heating (kWh)')
    ax2.set_xlabel('Demand and energy produced\n for domestic hot water (kWh)')

    return fig, elec, sh, dhw


def resultingDataDemandDiagramLoop(elec, sh, dhw, colors, buildings, newLegends):
    """
    Function inspired from the URBS platform https://github.com/ojdo/urbs/blob/1house/comp.py
    Function plotting the graph comparing the different buildings/scenarios on costs, energy produced and energy
    retrieved from storages
    :param elec: list of dict type, optimization results
    :param sh: list of dict type, optimization results
    :param dhw: list of dict type, optimization results
    :param colors: list type, different colors for the different components of the system
    :param buildings: list of str type, name of the different buildings
    :return: figure created
    """
    n_elec, n_sh, n_dhw = resultingDataDemandDiagram(elec[0], sh[0], dhw[0], colors, int(buildings[0]))[1:]
    for i in range(1, len(elec)):
        a, b, c = resultingDataDemandDiagram(elec[i], sh[i], dhw[i], colors, buildings[i])[1:]
        n_elec = pd.concat([n_elec, a])
        n_sh = pd.concat([n_sh, b])
        n_dhw = pd.concat([n_dhw, c])

    fig = plt.figure(figsize=(18, 8))
    gs = gridspec.GridSpec(1, 3, wspace=0.1)

    ax0 = plt.subplot(gs[0])
    elec_colors = [toColor(colors, x) for x in n_elec.columns]
    bp0 = n_elec.plot(ax=ax0, kind='barh', color=elec_colors, stacked=True, linewidth=0, width=.5)

    ax1 = plt.subplot(gs[1])
    sh_colors = [toColor(colors, x) for x in n_sh.columns]
    bp1 = n_sh.plot(ax=ax1, kind='barh', color=sh_colors, stacked=True, linewidth=0)

    ax2 = plt.subplot(gs[2])
    dhw_colors = [toColor(colors, x) for x in n_dhw.columns]
    bp2 = n_dhw.plot(ax=ax2, kind='barh', color=dhw_colors, stacked=True, linewidth=0)

    # remove scenario names from other bar plots
    liste = []
    for i in buildings:
        liste.append('')
        liste.append('')
        liste.append('Building ' + str(i))
    ax0.set_yticklabels(liste)
    for ax in [ax1, ax2]:
        ax.set_yticklabels('')
    for ax in [ax0, ax1, ax2]:
        groupHbarPlots(ax, group_size=3, inner_sep=0.0)

    # set limits and ticks for both axes
    for ax in [ax0, ax1, ax2]:
        ax.yaxis.grid(False)
        ax.xaxis.grid(True, 'major', linestyle='-')
        ax.xaxis.set_ticks_position('none')
        ax.yaxis.set_ticks_position('none')

        # group 1,000,000 with commas
        xmin, xmax = ax.get_xlim()
        if xmax > 90 or xmin < -90:
            group_thousands_and_skip_first = tkr.FuncFormatter(
                lambda x, pos: '' if pos == 0 else '{:0,d}'.format(int(x)))
            ax.xaxis.set_major_formatter(group_thousands_and_skip_first)
        else:
            skip_lowest = tkr.FuncFormatter(
                lambda x, pos: '' if pos == 0 else x)
            ax.xaxis.set_major_formatter(skip_lowest)

        # legend
        # set style arguments
        legend_style = {'frameon': False,
                        'loc': 'lower center',
                        'ncol': 3,
                        'bbox_to_anchor': (0.5, .99)}
        # get handels and labels, remove duplicate labels
        handles, labels = deduplicateLegend(*ax.get_legend_handles_labels())
        # set legend to use those
        lg = ax.legend(handles, labels, **legend_style)
        # finally, remove lines from patches
        plt.setp(lg.get_patches(), linewidth=0)

    ax0.set_xlabel('Demand and energy produced\n for electricity (kWh)')
    ax1.set_xlabel('Demand and energy produced\n for space heating (kWh)')
    ax2.set_xlabel('Demand and energy produced\n for domestic hot water (kWh)')

    return fig


def getData(filepath):
    """
    Function for the results recovery from an Excel file
    :param filepath: path to the Excel containing the results of the optimization
    :return: the different dicts created during the optimization
    """
    dict_sheet = {}
    if "xlsx" in filepath:
        wb = load_workbook(filepath, read_only=True, keep_links=False)
        sheet_names = wb.sheetnames
        engine = 'openpyxl'
    elif "xls" in filepath:
        wb = _xlrd.open_workbook(filepath)
        sheet_names = wb.sheet_names()
        engine = 'xlrd'
    else:
        raise NotImplementedError

    for sheet in sheet_names:
        dict_sheet[sheet] = pd.read_excel(filepath, sheet_name=sheet, index_col=0, engine=engine)

    return dict_sheet


def loadPlottingData(resultFilePath, numberOfBuildings):
    """
    Function for loading the data from excel file into variables
    :param resultFilePath: path to the Excel containing the results of the optimization
    :return: the different variables created after reading the excel file
    """
    buses = getData(resultFilePath)
    elec_names = []
    elec_dict = []
    sh_names = []
    sh_dict = []
    dhw_names = []
    dhw_dict = []
    costs_names = []
    costs_dict = []
    env_names = []
    env_dict = []

    buildings_dict = []
    buildings_names = []
    buildings_number = []
    beta = list(buses.keys())
    for i in beta:
        alpha = []
        alpha_a = []
        # Merged buses, if present, are assigned to Building 1 for plotting
        if i in ["electricityBus", "electricityInBus", "domesticHotWaterBus", "dhwDemandBus", "spaceHeatingBus",
                 "shDemandBus"]:
            i = i + f'__Building{building}'
        building = i.split("Building")[1]  # building number
        beta_bis = beta.copy()
        firstElBus = True
        firstShBus = True
        firstDhwBus = True
        for j in beta:
            if any('Building' in v for v in beta) and any(f'Building{building}' in v for v in beta):
                j_buses = j
                # Merged buses, if present, are assigned to Building 1 for plotting
                if j in ["electricityBus", "electricityInBus", "domesticHotWaterBus", "dhwDemandBus", "spaceHeatingBus",
                         "shDemandBus"]:
                    j = j + f'__Building{building}'
                if j.endswith(building) and (
                        "electricityBus" in j or "electricityInBus" in j or "electricityProdBus" in j or "spaceHeatingBus" in j or "shDemandBus" in j or "shSourceBus" in j or "domesticHotWaterBus" in j or "dhwDemandBus" in j):
                    """if "shDemandBus" not in j and "shSourceBus" not in j and "dhwDemandBus" not in j:
                        alpha.append(j)"""
                    if firstElBus and "electricity" in j:
                        alpha.append(f"electricityBus__Building{building}")
                        firstElBus = False
                    elif firstShBus and ("spaceHeatingBus" in j or "shDemandBus" in j or "shSourceBus" in j):
                        alpha.append(f"spaceHeatingBus__Building{building}")
                        firstShBus = False
                    elif firstDhwBus and ("domesticHotWaterBus" in j or "dhwDemandBus" in j):
                        alpha.append(f"domesticHotWaterBus__Building{building}")
                        firstDhwBus = False
                    if j_buses != j:
                        building_df = buses[j_buses].loc[:, buses[j_buses].columns.str.contains(
                            f'Building{building}')]  # extract columns related to a given building
                        if building == '1':
                            building_df = pd.concat(
                                [building_df, buses[j_buses].loc[:, ~buses[j_buses].columns.str.contains(f'Building')]],
                                axis=1)
                    else:
                        building_df = buses[j_buses]
                    if ("electricityBus" in j) or ("electricityInBus" in j) or ("electricityProdBus" in j):
                        # find the index of electricity bus to merge the results of electricityBus, electricityInBus and electricityProdBus
                        index = -1
                        for ind in range(len(alpha_a)):
                            if "electricityBus" in alpha_a[ind].keys()[0] or "electricityInBus" in alpha_a[ind].keys()[
                                0] or "electricityProdBus" in alpha_a[ind].keys()[0]:
                                index = ind
                        if index != -1:
                            alpha_a[index] = pd.concat((alpha_a[index], building_df), axis=1)
                        else:
                            alpha_a.append(building_df)
                    elif ("spaceHeatingBus" in j) or ("shDemandBus" in j) or ("shSourceBus" in j):
                        # find the index of spaceHeatingBus to merge the results of spaceHeatingBus, shDemandBus and shSourceBus
                        index = -1
                        for ind in range(len(alpha_a)):
                            if "spaceHeatingBus" in alpha_a[ind].keys()[0] or "shDemandBus" in alpha_a[ind].keys()[
                                0] or "shSourceBus" in alpha_a[ind].keys()[0]:
                                index = ind
                        if index != -1:
                            alpha_a[index] = pd.concat((alpha_a[index], building_df), axis=1)
                        else:
                            index = len(alpha_a)
                            alpha_a.append(building_df)
                        flows_to_delete = [
                            f"(('spaceHeating__Building{building}', 'shDemandBus__Building{building}'), 'flow')",
                            f"(('shSourceBus__Building{building}', 'shSource__Building{building}'), 'flow')",
                            f"(('spaceHeating', 'shDemandBus'), 'flow')"]
                        for flow in flows_to_delete:
                            if flow in alpha_a[index].columns:
                                alpha_a[index].pop(flow)
                    else:
                        # find the index of domesticHotWaterBus to merge the results of domesticHotWaterBus and dhwDemandBus
                        index = -1
                        for ind in range(len(alpha_a)):
                            if "domesticHotWaterBus" in alpha_a[ind].keys()[0] or "dhwDemandBus" in alpha_a[ind].keys()[
                                0]:
                                index = ind
                        if index != -1:
                            alpha_a[index] = pd.concat((alpha_a[index], building_df), axis=1)
                        else:
                            index = len(alpha_a)
                            alpha_a.append(building_df)
                        flows_to_delete = [
                            f"(('domesticHotWater__Building{building}', 'dhwDemandBus__Building{building}'), 'flow')",
                            f"(('domesticHotWater', 'dhwDemandBus'), 'flow')"]
                        for flow in flows_to_delete:
                            if flow in alpha_a[index].columns:
                                alpha_a[index].pop(flow)
                    beta_bis.remove(j_buses)
        if alpha != []:
            buildings_dict.append(alpha_a)
            buildings_names.append(alpha)
            buildings_number.append(building)
        """for a in alpha:
            beta.remove(a)"""
        beta = [x for x in beta if f"Building{building}" not in x]

    firstShBus = True
    firstDhwBus = True
    mergedElBuses = []
    mergedShBuses = []
    mergedDhwBuses = []
    mergedLinks = False
    b = 1  # initial building number
    for i in buses.keys():
        i_buses = i
        if 'Building' in i:
            b = int(i.split("Building")[1])  # building number
        # Merged buses, if present, are assigned to Building 1 for plotting
        if i in ["electricityBus", "electricityInBus", "domesticHotWaterBus", "dhwDemandBus", "spaceHeatingBus",
                 "shDemandBus"]:
            i = i + f'__Building{b}'
        tt = {}

        if i_buses != i:
            if 'electricity' in i:
                mergedElBuses.append(i_buses)
            elif 'spaceHeating' in i or 'shDemand' in i:
                mergedShBuses.append(i_buses)
            else:
                mergedDhwBuses.append(i_buses)
            mergedLinks = True
            building_df = buses[i_buses].loc[:, buses[i_buses].columns.str.contains(
                f'Building{b}')]  # extract columns related to a given building
            if b == 1:
                building_df = pd.concat(
                    [building_df, buses[i_buses].loc[:, ~buses[i_buses].columns.str.contains(f'Building')]], axis=1)
        else:
            building_df = buses[i_buses]

        if ("electricityBus" in i) or ("electricityInBus" in i) or ("electricityProdBus" in i):
            if b != 1 and mergedLinks:
                for elBusName in mergedElBuses:
                    elec_names.append(elBusName + f"__Building{b}")
                    mergedbus_df = buses[elBusName].loc[:, buses[elBusName].columns.str.contains(f'Building{b}')]
                    if b <= len(
                            elec_dict):  # to merge the data of the two electricity buses of the same  (elec_dict[0] should have all the electricity related data of building 1, and so on...)
                        elec_dict[b - 1] = pd.concat((elec_dict[b - 1], mergedbus_df), axis=1)
                    else:
                        elec_dict.append(mergedbus_df)
            elec_names.append(i)
            if b <= len(
                    elec_dict):  # to merge the data of the two electricity buses of the same  (elec_dict[0] should have all the electricity related data of building 1, and so on...)
                elec_dict[b - 1] = pd.concat((elec_dict[b - 1], building_df), axis=1)
            else:
                elec_dict.append(building_df)

        if ("spaceHeating" in i) or ("shDemand" in i) or ("shSource" in i):
            if any(str(b) not in v for v in sh_names):
                firstShBus = True
            if b != 1 and mergedLinks:
                for shBusName in mergedShBuses:
                    mergedbus_df = buses[shBusName].loc[:, buses[shBusName].columns.str.contains(f'Building{b}')]
                    if firstShBus:
                        sh_names.append(i)
                        sh_dict.append(mergedbus_df)
                        firstShBus = False
                    else:
                        ind = len(sh_names) - 1
                        sh_dict[ind] = pd.concat((sh_dict[ind], mergedbus_df), axis=1)
                        flows_to_delete = [f"(('spaceHeating__Building{b}', 'shDemandBus__Building{b}'), 'flow')",
                                           f"(('shSourceBus__Building{b}', 'shSource__Building{b}'), 'flow')",
                                           f"(('spaceHeating', 'shDemandBus'), 'flow')"]
                        for flow in flows_to_delete:
                            if flow in sh_dict[ind].columns:
                                sh_dict[ind].pop(flow)
            if firstShBus:
                sh_names.append(i)
                sh_dict.append(building_df)
                firstShBus = False
            else:
                ind = len(sh_names) - 1
                sh_dict[ind] = pd.concat((sh_dict[ind], building_df), axis=1)
                flows_to_delete = [f"(('spaceHeating__Building{b}', 'shDemandBus__Building{b}'), 'flow')",
                                   f"(('shSourceBus__Building{b}', 'shSource__Building{b}'), 'flow')",
                                   f"(('spaceHeating', 'shDemandBus'), 'flow')"]
                for flow in flows_to_delete:
                    if flow in sh_dict[ind].columns:
                        sh_dict[ind].pop(flow)
        if ("domesticHotWater" in i) or ("dhwDemand" in i):
            if any(str(b) not in v for v in dhw_names):
                firstDhwBus = True
            if firstDhwBus:
                dhw_names.append(i)
                dhw_dict.append(building_df)
                firstDhwBus = False
            else:
                ind = len(dhw_names) - 1
                dhw_dict[ind] = pd.concat((dhw_dict[ind], building_df), axis=1)
                flows_to_delete = [f"(('domesticHotWater__Building{b}', 'dhwDemandBus__Building{b}'), 'flow')",
                                   f"(('domesticHotWater', 'dhwDemandBus'), 'flow')"]
                for flow in flows_to_delete:
                    if flow in dhw_dict[ind].columns:
                        dhw_dict[ind].pop(flow)
        if b != 1 and mergedLinks and "dhwStorageBus" in i:
            if any(str(b) not in v for v in dhw_names):
                firstDhwBus = True
            for dhwBusName in mergedDhwBuses:
                mergedbus_df = buses[dhwBusName].loc[:, buses[dhwBusName].columns.str.contains(f'Building{b}')]
                if firstDhwBus:
                    dhw_names.append(f'domesticHotWaterBus__Building{b}')
                    dhw_dict.append(mergedbus_df)
                    firstDhwBus = False
                else:
                    ind = len(dhw_names) - 1
                    dhw_dict[ind] = pd.concat((dhw_dict[ind], mergedbus_df), axis=1)
                    flows_to_delete = [f"(('domesticHotWater__Building{b}', 'dhwDemandBus__Building{b}'), 'flow')",
                                       f"(('domesticHotWater', 'dhwDemandBus'), 'flow')"]
                    for flow in flows_to_delete:
                        if flow in dhw_dict[ind].columns:
                            dhw_dict[ind].pop(flow)
        if "costs" in i:
            costs_names.append(i)
            costs_dict.append(buses[i_buses])
        if "env_impacts" in i:
            env_names.append(i)
            env_dict.append(buses[i_buses])

    return buses, elec_names, elec_dict, sh_names, sh_dict, dhw_names, dhw_dict, costs_names, costs_dict, env_names, env_dict, \
        buildings_dict, buildings_names, buildings_number


def createPlot(resultFilePath, basePath, numberOfBuildings, plotLevel, plotType, flowType, plotAnnualHorizontalBar,
               newLegends):
    # load the plotting data from excel file into variables
    buses, elec_names, elec_dict, sh_names, sh_dict, dhw_names, dhw_dict, costs_names, costs_dict, env_names, env_dict, \
        buildings_dict, buildings_names, buildings_number = loadPlottingData(resultFilePath, numberOfBuildings)

    if flowType == "all":
        if plotType == "energy balance":
            names = elec_names + sh_names + dhw_names
        elif plotType == "bokeh":
            names = buildings_names
            dict = buildings_dict
    elif flowType == "electricity":
        names = elec_names
        if plotType == "bokeh":
            dict = elec_dict
    elif flowType == "space heat":
        names = sh_names
        if plotType == "bokeh":
            dict = sh_dict
    elif flowType == "domestic hot water":
        names = dhw_names
        if plotType == "bokeh":
            dict = dhw_dict
    else:
        raise ValueError("Illegal value for the parameter flow type")

    months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
    if plotType == "energy balance":
        if plotLevel == "allMonths":
            for i in names:
                if "electricityBus" not in i:
                    monthlyBalance(buses[i], i, newLegends)
    elif plotType == "bokeh":
        ncols = 2
        if plotLevel in months:
            if flowType == "all":
                for i in range(len(buildings_names)):
                    tempDict = dict[i]
                    for j in range(len(tempDict)):
                        tempDict[j] = tempDict[j][tempDict[j].index.strftime('%b') == plotLevel]
                    dict[i] = tempDict
            else:
                for i in range(len(dict)):
                    dict[i] = dict[i][dict[i].index.strftime('%b') == plotLevel]

        elif any(chr.isdigit() for chr in plotLevel):
            if isoparse(plotLevel):
                if flowType == "all":
                    for i in range(len(buildings_names)):
                        tempDict = dict[i]
                        for j in range(len(tempDict)):
                            tempDict[j] = tempDict[j][
                                tempDict[j].index.date == datetime.strptime(plotLevel, "%Y-%m-%d").date()]
                        dict[i] = tempDict
                else:
                    for i in range(len(dict)):
                        dict[i] = dict[i][dict[i].index.date == datetime.strptime(plotLevel, "%Y-%m-%d").date()]

        if flowType == "all":
            plotsHourly = []
            plotsDaily = []
            for i in range(len(buildings_names)):
                plotsH, plotsD = hourlyDailyPlot(dict[i], names[i], Category20_12, newLegends)
                plotsHourly.extend(plotsH)
                plotsDaily.extend(plotsD)
        else:
            plotsHourly, plotsDaily = hourlyDailyPlot(dict, names, Set1_9, newLegends)

        if not os.path.exists(basePath):
            os.makedirs(basePath)

        output_file(os.path.join(basePath, "HourlyBokehPlots.html"))
        grid = gridplot(plotsHourly, ncols=ncols, sizing_mode="scale_both")
        show(grid)
        if not any(chr.isdigit() for chr in plotLevel):
            output_file(os.path.join(basePath, "DailyBokehPlots.html"))
            grid = gridplot(plotsDaily, ncols=ncols, sizing_mode="scale_both")
            show(grid)
    else:
        raise ValueError("Illegal value for the parameter plot type")

    if plotAnnualHorizontalBar == True:
        my_colors = {
            'Battery_out': (100, 160, 200),
            'Battery_in': (100, 160, 200),
            'Storage_sh_out': (196, 7, 27),
            'Storage_dhw_out': (196, 7, 27),
            'HP_dhw': (62, 173, 0),
            'HP_sh': (62, 173, 0),
            'HP': (62, 173, 0),
            'CHP_elec': (0, 101, 189),
            'CHP_sh': (0, 101, 189),
            'CHP_dhw': (128, 0, 128),
            'CHP': (0, 101, 189),
            'Gas_sh': (0, 110, 120),
            'GasBoiler': (0, 110, 120),
            'PV_elec': (0, 128, 90),
            'SolarCollector': (188, 128, 90),
            'Inputs': (252, 93, 93),
            'Operation': (252, 93, 93),
            'Investment': (0, 119, 138),
            'Feed-in': (0, 215, 203),
            'Demand_elec': (131, 166, 151),
            'Demand_sh': (131, 166, 151),
            'Demand_dhw': (131, 166, 151),
            'Grid_purchase': (237, 127, 16)
        }
        if optMode == "group":
            my_colors['electricityLink_in'] = (255, 215, 0)
            my_colors['electricityLink_out'] = (255, 215, 0)
            my_colors['electricityLink'] = (255, 215, 0)

        COLORS = {}
        for name, color in my_colors.items():
            COLORS[name] = color
        """
        for i in range(len(buildings_names)):
            fig1 = resultingDataDiagram(elec_dict[i], sh_dict[i], dhw_dict[i], costs_dict[i], env_dict[i], COLORS, buildings_number[i])[0]
            fig2 = resultingDataDemandDiagram(elec_dict[i], sh_dict[i], dhw_dict[i], COLORS, buildings_number[i])[0]
        """
        fig3 = resultingDataDiagramLoop(elec_dict, sh_dict, dhw_dict, costs_dict, env_dict, COLORS, buildings_number,
                                        newLegends)
        fig4 = resultingDataDemandDiagramLoop(elec_dict, sh_dict, dhw_dict, COLORS, buildings_number, newLegends)
        plt.show()


def plot(excelFileName, figureFilePath, numberOfBuildings, plotLevel, plotType, flowType, plotlabels='default',
         plotAnnualHorizontalBar=False):
    #####################################
    ########## Classic plots  ###########
    #####################################
    # New legends need to be defined by hand for each new flow added to the energy network
    if plotlabels == 'default':
        newLegends = {
            "(('naturalGasResource', 'naturalGasBus'), 'flow')": "NaturalGas",
            "(('qSource', 'heatSourceBus'), 'flow')": "qSource",
            "(('electricityBus', 'excesselectricityBus'), 'flow')": "Feed-in",
            "(('electricityProdBus', 'excesselectricityProdBus'), 'flow')": "Feed-in",
            "(('electricityProdBus', 'electricalStorage'), 'flow')": "Battery_in",
            "(('electricityInBus', 'electricityDemand'), 'flow')": "Demand_elec",
            "(('electricityInBus', 'emobilityDemand'), 'flow')": "Demand_mobility",
            "(('electricityInBus', 'HP'), 'flow')": "HP",
            "(('electricityInBus', 'GWHP'), 'flow')": "GWHP",
            "(('CHP', 'electricityProdBus'), 'flow')": "CHP_elec",
            "(('electricalStorage', 'electricityBus'), 'flow')": "Battery_out",
            "(('electricitySource', 'electricityBus'), 'flow')": "Elect_produced (not stored)",
            "(('electricityResource', 'gridBus'), 'flow')": "Grid_purchase",
            "(('dhwStorage', 'domesticHotWaterBus'), 'flow')": "Storage_dhw_out",
            "(('dhwStorageBus', 'dhwStorage'), 'flow')": "Storage_dhw_in",
            "(('domesticHotWaterBus', 'domesticHotWater'), 'flow')": "dhw_direct_to_load",
            "(('dhwDemandBus', 'domesticHotWaterDemand'), 'flow')": "Demand_dhw",
            "(('HP', 'dhwStorageBus'), 'flow')": "HP_dhw",
            "(('GWHP', 'dhwStorageBus'), 'flow')": "GWHP_dhw",
            "(('CHP', 'dhwStorageBus'), 'flow')": "CHP_dhw",
            "(('shSource', 'spaceHeatingBus'), 'flow')": "SH_produced (not stored)",
            "(('shStorage', 'spaceHeatingBus'), 'flow')": "Storage_sh_out",
            "storage_content": "Storage_content",
            "(('shSourceBus', 'shStorage'), 'flow')": "Storage_sh_in",
            "(('spaceHeatingBus', 'spaceHeating'), 'flow')": "SH_direct_to_load",
            "(('shDemandBus', 'spaceHeatingDemand'), 'flow')": "Demand_sh",
            "(('shDemandBus', 'excessshDemandBus'), 'flow')": "Excess SH production",
            "(('shSourceBus', 'excessshSourceBus'), 'flow')": "Excess SH production",
            "(('CHP', 'shSourceBus'), 'flow')": "CHP_sh",
            "(('GasBoiler', 'shSourceBus'), 'flow')": "Gas_sh",
            "(('GasBoiler', 'dhwStorageBus'), 'flow')": "Gas_dhw",
            "(('HP', 'shSourceBus'), 'flow')": "HP_sh",
            "(('GWHP', 'shSourceBus'), 'flow')": "GWHP_sh",
            "(('electricityBus', 'producedElectricity'), 'flow')": "Self_consumption",
            "(('gridBus', 'gridElectricity'), 'flow')": "Electricity_grid",
            "(('pv', 'electricityProdBus'), 'flow')": "PV_elec",
            "(('solarCollectorSH', 'dhwStorageBus'), 'flow')": "SolarCollector",
            "(('electricityInBus', 'solarCollectorSH'), 'flow')": "SolarCollector",
            "(('gridElectricity', 'electricityInBus'), 'flow')": "Electricity_grid",
            "(('producedElectricity', 'electricityInBus'), 'flow')": "Self_consumption",
            "(('electricityProdBus', 'electricitySource'), 'flow')": "Battery_bypass",
            "(('domesticHotWaterBus', 'domesticHotWaterDemand'), 'flow')": "Demand_dhw",
            "(('electricityInBus', 'ElectricRod'), 'flow')": "Electric_rod",
            "(('ElectricRod', 'shSourceBus'), 'flow')": "Electric_rod_sh",
            "(('ElectricRod', 'dhwStorageBus'), 'flow')": "Electric_rod_dhw",
            "(('electricityInBus', 'GWHP35'), 'flow')": "GWHP35",
            "(('electricityInBus', 'GWHP60'), 'flow')": "GWHP60",
            "(('GWHP60', 'dhwStorageBus'), 'flow')": "GWHP60_dhw",
            "(('GWHP35', 'shSourceBus'), 'flow')": "GWHP35_sh",
        }
        newLegends["(('electricityBus', 'electricityLink'), 'flow')"] = "electricityLink_out"
        newLegends["(('electricityLink', 'electricityInBus'), 'flow')"] = "electricityLink_in"
        newLegends["(('spaceHeatingBus', 'shLink'), 'flow')"] = "shLink_out"
        newLegends["(('shLink', 'shDemandBus'), 'flow')"] = "shLink_in"
        newLegends["(('domesticHotWaterBus', 'dhwLink'), 'flow')"] = "dhwLink_out"
        newLegends["(('dhwLink', 'dhwDemandBus'), 'flow')"] = "dhwLink_in"
    else:
        newLegends = {
            "(('naturalGasResource', 'naturalGasBus'), 'flow')": plotlabels["naturalGas"],
            "(('qSource', 'heatSourceBus'), 'flow')": plotlabels["qSource"],
            "(('electricityBus', 'excesselectricityBus'), 'flow')": plotlabels["excessEl"],
            "(('electricityProdBus', 'electricalStorage'), 'flow')": plotlabels["StorageEl"] + "_in",
            "(('electricityInBus', 'electricityDemand'), 'flow')": plotlabels["DemandEl"],
            "(('electricityInBus', 'emobilityDemand'), 'flow')": plotlabels["DemandMob"],
            "(('electricityInBus', 'HP'), 'flow')": plotlabels["HP"],
            "(('electricityInBus', 'GWHP'), 'flow')": plotlabels["GWHP"],
            "(('CHP', 'electricityProdBus'), 'flow')": plotlabels["CHP"] + "_el",
            "(('electricalStorage', 'electricityBus'), 'flow')": plotlabels["StorageEl"] + "_out",
            "(('electricitySource', 'electricityBus'), 'flow')": plotlabels["localEl"],
            "(('electricityResource', 'gridBus'), 'flow')": plotlabels["grid"],
            "(('dhwStorage', 'domesticHotWaterBus'), 'flow')": plotlabels["StorageDhw"] + "_out",
            "(('dhwStorageBus', 'dhwStorage'), 'flow')": plotlabels["StorageDhw"] + "_in",
            "(('domesticHotWaterBus', 'domesticHotWater'), 'flow')": "DHW_direct_to_load",
            "(('dhwDemandBus', 'domesticHotWaterDemand'), 'flow')": plotlabels["DemandDhw"],
            "(('HP', 'dhwStorageBus'), 'flow')": plotlabels["HP"] + "_dhw",
            "(('GWHP', 'dhwStorageBus'), 'flow')": plotlabels["GWHP"] + "_dhw",
            "(('CHP', 'dhwStorageBus'), 'flow')": plotlabels["CHP"] + "_dhw",
            "(('shSource', 'spaceHeatingBus'), 'flow')": plotlabels["prodSH"] + " (not stored)",
            "(('shStorage', 'spaceHeatingBus'), 'flow')": plotlabels["StorageSh"] + "_out",
            "storage_content": "Storage_content",
            "(('shSourceBus', 'shStorage'), 'flow')": plotlabels["StorageSh"] + "_in",
            "(('spaceHeatingBus', 'spaceHeating'), 'flow')": "SH_direct_to_load",
            "(('shDemandBus', 'spaceHeatingDemand'), 'flow')": plotlabels["DemandSh"],
            "(('shDemandBus', 'excessshDemandBus'), 'flow')": plotlabels["excessSh"],
            "(('shSourceBus', 'excessshSourceBus'), 'flow')": plotlabels["excessSh"],
            "(('CHP', 'shSourceBus'), 'flow')": plotlabels["CHP"] + "_sh",
            "(('GasBoiler', 'shSourceBus'), 'flow')": plotlabels["gasBoiler"] + "_sh",
            "(('GasBoiler', 'dhwStorageBus'), 'flow')": plotlabels["gasBoiler"] + "_dhw",
            "(('HP', 'shSourceBus'), 'flow')": plotlabels["HP"] + "_sh",
            "(('GWHP', 'shSourceBus'), 'flow')": plotlabels["GWHP"] + "_sh",
            "(('electricityBus', 'producedElectricity'), 'flow')": "Self_consumption",
            "(('gridBus', 'gridElectricity'), 'flow')": plotlabels["grid"],
            "(('pv', 'electricityProdBus'), 'flow')": plotlabels["pv"] + "_el",
            "(('solarCollectorSH', 'dhwStorageBus'), 'flow')": plotlabels["solarCollector"],
            "(('electricityInBus', 'solarCollectorSH'), 'flow')": plotlabels["solarCollector"],
            "(('gridElectricity', 'electricityInBus'), 'flow')": plotlabels["grid"],
            "(('producedElectricity', 'electricityInBus'), 'flow')": "Self_consumption",
            "(('electricityProdBus', 'electricitySource'), 'flow')": plotlabels["StorageEl"] + "_bypass",
            "(('domesticHotWaterBus', 'domesticHotWaterDemand'), 'flow')": plotlabels["DemandDhw"],
            "(('electricityInBus', 'ElectricRod'), 'flow')": plotlabels["ElectricRod"],
            "(('ElectricRod', 'shSourceBus'), 'flow')": plotlabels["ElectricRod"] + "_sh",
            "(('ElectricRod', 'dhwStorageBus'), 'flow')": plotlabels["ElectricRod"] + "_dhw",
            "(('electricityInBus', 'GWHP35'), 'flow')": plotlabels["GWHP"] + "35",
            "(('electricityInBus', 'GWHP60'), 'flow')": plotlabels["GWHP"] + "60",
            "(('GWHP60', 'dhwStorageBus'), 'flow')": plotlabels["GWHP"] + "60_dhw",
            "(('GWHP35', 'shSourceBus'), 'flow')": plotlabels["GWHP"] + "35_sh",
        }
        newLegends["(('electricityBus', 'electricityLink'), 'flow')"] = plotlabels["elBus"] + " Link (out)"
        newLegends["(('electricityLink', 'electricityInBus'), 'flow')"] = plotlabels["elBus"] + " Link (in)"
        newLegends["(('spaceHeatingBus', 'shLink'), 'flow')"] = plotlabels["shBus"] + " Link (out)"
        newLegends["(('shLink', 'shDemandBus'), 'flow')"] = plotlabels["shBus"] + " Link (in)"
        newLegends["(('domesticHotWaterBus', 'dhwLink'), 'flow')"] = plotlabels["dhwBus"] + " Link (out)"
        newLegends["(('dhwLink', 'dhwDemandBus'), 'flow')"] = plotlabels["dhwBus"] + " Link (in)"

    createPlot(excelFileName, figureFilePath, numberOfBuildings, plotLevel, plotType, flowType, plotAnnualHorizontalBar,
               newLegends)


if __name__ == '__main__':
    optMode = "group"  # parameter defining whether the results file corresponds to "indiv" or "group" optimization
    numberOfBuildings = 4
    plotOptim = 1  # defines the number of the optimization to plot
    plotLevel = "allMonths"  # permissible values (for energy balance plot): "allMonths" {for all months}
    # or specific month {"Jan", "Feb", "Mar", etc. three letter abbreviation of the month name}
    # or specific date {format: YYYY-MM-DD}
    plotType = "energy balance"  # permissible values: "energy balance", "bokeh"
    flowType = "electricity"  # permissible values: "all", "electricity", "space heat", "domestic hot water"
    plotAnnualHorizontalBar = False  # determines whether the annual horizontal bar is plot or not

    plot(os.path.join(r"..\data\Results", f"results{numberOfBuildings}_{plotOptim}_{optMode}.xlsx"), r"..\data\Figures",
         plotLevel, plotType, flowType, plotAnnualHorizontalBar)
