"""
Implements the substation model.
"""
from __future__ import division
import pandas as pd
import time
import numpy as np
import scipy
import cea.config
from math import ceil
from cea.constants import HEAT_CAPACITY_OF_WATER_JPERKGK, P_WATER_KGPERM3
from cea.technologies.constants import DT_COOL, DT_HEAT, U_COOL, U_HEAT, \
    HEAT_EX_EFFECTIVENESS, DT_INTERNAL_HEX, MAX_NODE_FLOW

BUILDINGS_DEMANDS_COLUMNS = ['Name', 'Ths_sys_sup_aru_C', 'Ths_sys_sup_ahu_C', 'Ths_sys_sup_shu_C',
                             'Qww_sys_kWh', 'Tww_sys_sup_C', 'Tww_sys_re_C', 'mcpww_sys_kWperC',
                             'Qcdata_sys_kWh', 'Tcdata_sys_sup_C', 'Tcdata_sys_re_C', 'mcpcdata_sys_kWperC',
                             'Qcre_sys_kWh', 'Tcre_sys_sup_C', 'Tcre_sys_re_C', 'mcpcre_sys_kWperC',
                             'Ths_sys_re_aru_C', 'Ths_sys_re_ahu_C', 'Ths_sys_re_shu_C',
                             'Tcs_sys_sup_ahu_C', 'Tcs_sys_sup_aru_C',
                             'Tcs_sys_sup_scu_C', 'Tcs_sys_re_ahu_C', 'Tcs_sys_re_aru_C', 'Tcs_sys_re_scu_C',
                             'Qhs_sys_aru_kWh', 'Qhs_sys_ahu_kWh', 'Qhs_sys_shu_kWh',
                             'Qcs_sys_ahu_kWh', 'Qcs_sys_aru_kWh', 'Qcs_sys_scu_kWh', 'mcphs_sys_aru_kWperC',
                             'mcphs_sys_ahu_kWperC', 'mcphs_sys_shu_kWperC', 'mcpcs_sys_ahu_kWperC',
                             'mcpcs_sys_aru_kWperC', 'mcpcs_sys_scu_kWperC', 'E_sys_kWh']

__author__ = "Jimeno A. Fonseca, Shanshan Hsieh"
__copyright__ = "Copyright 2015, Architecture and Building Systems - ETH Zurich"
__credits__ = ["Jimeno A. Fonseca", "Tim Vollrath", "Thuy-An Nguyen", "Lennart Rogenhofer"]
__license__ = "MIT"
__version__ = "0.1"
__maintainer__ = "Daren Thomas"
__email__ = "cea@arch.ethz.ch"
__status__ = "Production"


# ============================
# Substation model
# ============================


def substation_HEX_design_main(buildings_demands, substation_systems, thermal_network):
    """
    This function calculates the temperatures and mass flow rates of the district heating network
    at every costumer. Based on this, the script calculates the hourly temperature of the network at the plant.
    This temperature needs to be equal to that of the customer with the highest temperature requirement plus thermal
    losses in the network.

    :param buildings_demands: Dictionary of DataFrames with all buildings_demands in the area

    :return: ``(substations_HEX_specs, buildings_demands)`` - substations_HEX_specs: dataframe with substation heat
        exchanger specs at each building,  buildings_demands: lists of heating demand/flowrate/supply temperature of all
        buildings connected to the network.
    """

    t0 = time.clock()

    # Calculate disconnected buildings_demands files and substation operation.
    substations_HEX_specs = pd.DataFrame(columns=['HEX_areas', 'HEX_UA', 'HEX_Q'])
    substations_Q = pd.DataFrame()
    for name in buildings_demands.keys():
        print name
        # calculate substation parameters (A,UA) per building and store to .csv (target)
        substation_HEX = substation_HEX_sizing(buildings_demands[name], substation_systems, thermal_network)
        # write into dataframe
        substations_HEX_specs.ix[name] = substation_HEX
        if substations_Q.empty:
            substations_Q = pd.DataFrame(substation_HEX[2])
        else:
            substations_Q = pd.concat([substations_Q, substation_HEX[2]])

    print time.clock() - t0, "seconds process time for the Substation Routine \n"
    return substations_HEX_specs, substations_Q


def determine_building_supply_temperatures(building_names, locator, substation_systems, delta_T_supply_return = 20):
    """
    determines thermal network target temperatures (T_supply_DH_C,T_supply_DC) on the network side at each substation.
    :param building_names:
    :param locator:
    :return:
    """
    buildings_demands = {}
    for name in building_names:
        name = str(name)
        buildings_demands[name] = pd.read_csv(locator.get_demand_results_file(name),
                                              usecols=(BUILDINGS_DEMANDS_COLUMNS))
        Q_substation_heating = 0
        T_supply_heating_C = np.nan
        for system in substation_systems['heating']:
            if system == 'ww':
                Q_substation_heating = Q_substation_heating + buildings_demands[name].Qww_sys_kWh
                T_supply_heating_C = np.vectorize(calc_DH_supply)(T_supply_heating_C,
                                                                  np.where(buildings_demands[name].Qww_sys_kWh > 0,
                                                                           buildings_demands[name].Tww_sys_sup_C,
                                                                           np.nan))
            else:
                Q_substation_heating = Q_substation_heating + buildings_demands[name]['Qhs_sys_' + system + '_kWh']
                # set the building side heating supply temperature
                T_supply_heating_C = np.vectorize(calc_DH_supply)(T_supply_heating_C,
                                                                  np.where(buildings_demands[name][
                                                                               'Qhs_sys_' + system + '_kWh'] > 0,
                                                                           buildings_demands[name][
                                                                               'Ths_sys_sup_' + system + '_C'],
                                                                           np.nan))

        Q_substation_cooling = 0
        T_supply_cooling_C = np.nan
        for system in substation_systems['cooling']:
            if system == 'data':
                Q_substation_cooling = Q_substation_cooling + abs(buildings_demands[name].Qcdata_sys_kWh)
                T_supply_cooling_C = np.vectorize(calc_DC_supply)(T_supply_cooling_C,
                                                                  np.where(
                                                                      abs(buildings_demands[name].Qcdata_sys_kWh) > 0,
                                                                      buildings_demands[name].Tcdata_sys_sup_C,
                                                                      np.nan))
            elif system == 're':
                Q_substation_cooling = Q_substation_cooling + abs(buildings_demands[name].Qcre_sys_kWh)
                T_supply_cooling_C = np.vectorize(calc_DC_supply)(T_supply_cooling_C,
                                                                  np.where(
                                                                      abs(buildings_demands[name].Qcre_sys_kWh) > 0,
                                                                      buildings_demands[name].Tcre_sys_sup_C,
                                                                      np.nan))
            else:
                Q_substation_cooling = Q_substation_cooling + abs(buildings_demands[name]['Qcs_sys_' + system + '_kWh'])
                T_supply_cooling_C = np.vectorize(calc_DC_supply)(T_supply_cooling_C,
                                                                  np.where(abs(buildings_demands[name][
                                                                                   'Qcs_sys_' + system + '_kWh']) > 0,
                                                                           buildings_demands[name][
                                                                               'Tcs_sys_sup_' + system + '_C'],
                                                                           np.nan))

        # find the target substation supply temperature
        T_supply_DH_C = np.where(Q_substation_heating > 0, T_supply_heating_C + DT_HEAT, np.nan)
        T_supply_DC_C = np.where(abs(Q_substation_cooling) > 0, T_supply_cooling_C - DT_COOL, np.nan)

        buildings_demands[name]['Q_substation_heating'] = Q_substation_heating
        buildings_demands[name]['Q_substation_cooling'] = abs(Q_substation_cooling)
        buildings_demands[name]['T_sup_target_DH'] = T_supply_DH_C
        buildings_demands[name]['T_sup_target_DC'] = T_supply_DC_C

        buildings_demands[name]['T_re_target_DH'] = T_supply_DH_C - delta_T_supply_return
        buildings_demands[name]['T_re_target_DC'] = T_supply_DC_C + delta_T_supply_return

        buildings_demands[name]['V_substation_heating_m3pers'] = (buildings_demands[name]['Q_substation_heating'] * 1000 / (delta_T_supply_return  * HEAT_CAPACITY_OF_WATER_JPERKGK)) / P_WATER_KGPERM3
        buildings_demands[name]['V_substation_cooling_m3pers'] = (buildings_demands[name]['Q_substation_cooling'] * 1000 / (delta_T_supply_return  * HEAT_CAPACITY_OF_WATER_JPERKGK)) / P_WATER_KGPERM3

    return buildings_demands


def substation_HEX_sizing(building_demand, substation_systems, thermal_network):
    """
    This function size the substation heat exchanger area and the UA values.

    :param building_demand: dataframe with building demand properties
    :return: A list of substation heat exchanger properties (Area & UA) for heating, cooling and DHW
    """
    T_DH_supply_C = building_demand.T_sup_target_DH
    T_DC_supply_C = building_demand.T_sup_target_DC

    area_columns = []
    UA_columns = []
    Q_columns = []
    for system in substation_systems['heating']:
        area_columns.append('A_hex_hs_' + system)
        UA_columns.append('UA_heating_hs_' + system)
        Q_columns.append('Q_hex_h_' + system)
    for system in substation_systems['cooling']:
        area_columns.append('A_hex_cs_' + system)
        UA_columns.append('UA_cooling_cs_' + system)
        Q_columns.append('Q_hex_c_' + system)

    # Dataframes for storage
    hex_areas = pd.DataFrame(columns=area_columns, index=['0'])
    UA_data = pd.DataFrame(columns=UA_columns, index=['0'])
    Q_nom_data = pd.DataFrame(columns=Q_columns, index=[building_demand['Name'].values[0]])

    ## Heating
    for system in substation_systems['heating']:
        if system == 'ww':
            # calculate HEX area and UA for DHW
            hex_areas.A_hex_hs_ww, UA_data.UA_heating_hs_ww, Q_nom_data.Q_hex_h_ww = calc_hex_area_from_demand(
                building_demand, 'ww_sys', '',
                T_DH_supply_C, thermal_network)
        else:
            # calculate HEX area and UA for SH ahu, aru, shu
            hex_areas['A_hex_hs_' + system], UA_data['UA_heating_hs_' + system], Q_nom_data[
                'Q_hex_h_' + system] = calc_hex_area_from_demand(
                building_demand, 'hs_sys', system + '_', T_DH_supply_C, thermal_network)

    ## Cooling
    for system in substation_systems['cooling']:
        if system == 'data':
            # calculate HEX area and UA for the data centers
            hex_areas.A_hex_cs_data, UA_data.UA_cooling_cs_data, Q_nom_data.Q_hex_c_data = calc_hex_area_from_demand(
                building_demand, 'cdata_sys', '', T_DC_supply_C, thermal_network)

        elif system == 're':
            # calculate HEX area and UA for cre
            hex_areas.A_hex_cs_re, UA_data.UA_cooling_cs_re, Q_nom_data.Q_hex_c_re = calc_hex_area_from_demand(
                building_demand, 'cre_sys', '',
                T_DC_supply_C, thermal_network)
        else:
            # calculate HEX area and UA for the aru of cooling costumers
            hex_areas['A_hex_cs_' + system], UA_data['UA_cooling_cs_' + system], Q_nom_data[
                'Q_hex_c_' + system] = calc_hex_area_from_demand(
                building_demand, 'cs_sys',
                system + '_', T_DC_supply_C, thermal_network)
    return [hex_areas, UA_data, Q_nom_data]


def calc_hex_area_from_demand(building_demand, load_type, building_system, T_supply_C, thermal_network):
    '''
    This function returns the heat exchanger specifications for given building demand, HEX type and supply temperature.
    primary side: network; secondary side: building
    :param building_demand: DataFrame with demand values
    :param load_type: 'cs_sys' or 'hs_sys' for cooling or heating, 'cdata_sys', 'cre_sys'
    :param building_system: 'aru', 'ahu', 'scu'
    :param T_supply_C: Supply temperature
    :return: HEX area and UA
    '''

    # calculate HEX area and UA for customers
    m = 'mcp' + load_type + '_' + building_system + 'kWperC'
    Q = 'Q' + load_type + '_' + building_system + 'kWh'
    T_sup = 'T' + load_type + '_sup_' + building_system + 'C'
    T_ret = 'T' + load_type + '_re_' + building_system + 'C'

    Qf = (abs(building_demand[Q].values)) * 1000  # in W
    Qnom = max(Qf)  # in W
    if Qnom > 0:
        tpi = T_supply_C + 273  # in K
        tso = building_demand[T_sup].values + 273  # in K
        tsi = building_demand[T_ret].values + 273  # in K
        cs = (abs(building_demand[m].values)) * 1000  # in W/K
        index = np.where(Qf == Qnom)[0][0]
        tpi_0 = tpi[index]  # primary side inlet in K
        tsi_0 = tsi[index]  # secondary side inlet in K
        tso_0 = tso[index]  # secondary side return in K
        cs_0 = cs[index]  # secondary side capacity mass flow
        if 'c' in load_type:  # we have DC
            A_hex, UA = calc_cooling_substation_heat_exchange(cs_0, Qnom, tsi_0, tpi_0, tso_0)
        else:
            A_hex, UA = calc_heating_substation_heat_exchange(cs_0, Qnom, tpi_0, tsi_0, tso_0)

    else:
        A_hex = 0
        UA = 0
        Qnom = 0

    return A_hex, UA, round(Qnom / 1000)


def substation_return_model_main(thermal_network, T_substation_supply, t, consumer_building_names):
    """
    Calculate all substation return temperature and required flow rate at each time-step.

    :param locator: an InputLocator instance set to the scenario to work on
    :param buildings_demands: dictionarz of building demands
    :param substations_HEX_specs: list of dataframes for substation heat exchanger Area and UA for heating, cooling and DHW
    :param T_substation_supply: supply temperature at each substation in [K]
    :param t: time-step
    :param network_type: a string that defines whether the network is a district heating ('DH') or cooling ('DC')
                         network
    :param use_same_temperature_for_all_nodes: flag for calculating nominal flow rate, using one target temperature

    :param thermal_network: container for all the
           thermal network data.
    :type thermal_network: cea.technologies.thermal_network.thermal_network.ThermalNetwork

    :return:

    """
    index = 0
    # combi = [0] * len(building_names)
    T_return_all_K = pd.DataFrame()
    mdot_sum_all_kgs = pd.DataFrame()
    thermal_demand = pd.DataFrame(np.zeros((1, len(consumer_building_names))), columns=consumer_building_names)
    for name in consumer_building_names:
        building = thermal_network.buildings_demands[name].loc[[t]]

        # find substation supply temperature
        T_substation_supply_K = T_substation_supply.loc['T_supply', name]

        if thermal_network.network_type == 'DH':
            for key in thermal_network.substation_heating_systems:
                key = 'hs_' + key
                if not name in thermal_network.ch_old[key][t].columns:
                    thermal_network.ch_old[key][t][name] = 0.0

            # calculate DH substation return temperature and substation flow rate
            T_substation_return_K, \
            mcp_sub, thermal_demand[name] = calc_substation_return_DH(building, T_substation_supply_K,
                                                                      thermal_network.substations_HEX_specs.ix[name],
                                                                      thermal_network, name, t)
        else:
            for key in thermal_network.substation_cooling_systems:
                key = 'cs_' + key
                if not name in thermal_network.cc_old[key][t].columns:
                    thermal_network.cc_old[key][t][name] = 0.0
            # calculate DC substation return temperature and substation flow rate
            T_substation_return_K, mcp_sub, thermal_demand[name] = calc_substation_return_DC(building,
                                                                                             T_substation_supply_K,
                                                                                             thermal_network.substations_HEX_specs.ix[
                                                                                                 name],
                                                                                             thermal_network, name, t)

        T_return_all_K[name] = [T_substation_return_K]
        mdot_sum_all_kgs[name] = [mcp_sub / (HEAT_CAPACITY_OF_WATER_JPERKGK / 1000)]  # [kg/s]

        index += 1

    mdot_sum_all_kgs = np.round(mdot_sum_all_kgs, 5)

    return T_return_all_K, mdot_sum_all_kgs, abs(thermal_demand.values)


def calc_substation_return_DH(building, T_DH_supply_K, substation_HEX_specs, thermal_network, name, t):
    """
    calculate individual substation return temperature and required heat capacity (mcp) of the supply stream
    at each time step.
    :param building: list of building informations
    :param T_DH_supply_K: matrix of the substation supply temperatures in K
    :param substation_HEX_specs: substation heat exchanger properties

    :return t_return_DH: the substation return temperature
    :return mcp_DH: the required heat capacity (mcp) from the DH
    """
    temperatures = []
    mass_flows = []
    heat = []

    # Heating ahu
    if 'UA_heating_hs_ahu' in substation_HEX_specs.HEX_UA.columns:
        Qhs_sys_ahu, t_DH_return_hs_ahu, mcp_DH_hs_ahu, ch_value = calc_HEX_heating(building, 'hs_sys', 'ahu_',
                                                                                    T_DH_supply_K,
                                                                                    substation_HEX_specs.HEX_UA.UA_heating_hs_ahu[
                                                                                        '0'],
                                                                                    thermal_network.ch_old['hs_ahu'][t][
                                                                                        name],
                                                                                    thermal_network.delta_cap_mass_flow[
                                                                                        t])
        temperatures.append(t_DH_return_hs_ahu)
        mass_flows.append(mcp_DH_hs_ahu)
        heat.append(Qhs_sys_ahu[0])
        # Store values for next run
        thermal_network.ch_value['hs_ahu'][t][name] = float(ch_value)
        thermal_network.ch_old['hs_ahu'][t][name] = float(ch_value)

    # Heating aru
    if 'UA_heating_hs_aru' in substation_HEX_specs.HEX_UA.columns:
        Qhs_sys_aru, t_DH_return_hs_aru, mcp_DH_hs_aru, ch_value = calc_HEX_heating(building, 'hs_sys', 'aru_',
                                                                                    T_DH_supply_K,
                                                                                    substation_HEX_specs.HEX_UA.UA_heating_hs_aru[
                                                                                        '0'],
                                                                                    thermal_network.ch_old['hs_aru'][t][
                                                                                        name],
                                                                                    thermal_network.delta_cap_mass_flow[
                                                                                        t])
        temperatures.append(t_DH_return_hs_aru)
        mass_flows.append(mcp_DH_hs_aru)
        heat.append(Qhs_sys_aru[0])
        # Store values for next run
        thermal_network.ch_value['hs_aru'][t][name] = float(ch_value)
        thermal_network.ch_old['hs_aru'][t][name] = float(ch_value)

    # Heating shu
    if 'UA_heating_hs_shu' in substation_HEX_specs.HEX_UA.columns:
        Qhs_sys_shu, t_DH_return_hs_shu, mcp_DH_hs_shu, ch_value = calc_HEX_heating(building, 'hs_sys', 'shu_',
                                                                                    T_DH_supply_K,
                                                                                    substation_HEX_specs.HEX_UA.UA_heating_hs_shu[
                                                                                        '0'],
                                                                                    thermal_network.ch_old['hs_shu'][t][
                                                                                        name],
                                                                                    thermal_network.delta_cap_mass_flow[
                                                                                        t])
        temperatures.append(t_DH_return_hs_shu)
        mass_flows.append(mcp_DH_hs_shu)
        heat.append(Qhs_sys_shu[0])
        # Store values for next run
        thermal_network.ch_value['hs_shu'][t][name] = float(ch_value)
        thermal_network.ch_old['hs_shu'][t][name] = float(ch_value)

    if 'UA_heating_hs_ww' in substation_HEX_specs.HEX_UA.columns:
        Qww_sys, t_DH_return_ww, mcp_DH_ww, ch_value = calc_HEX_heating(building, 'ww_sys', '', T_DH_supply_K,
                                                                        substation_HEX_specs.HEX_UA.UA_heating_hs_ww[
                                                                            '0'],
                                                                        thermal_network.ch_old['hs_ww'][t][name],
                                                                        thermal_network.delta_cap_mass_flow[t])
        temperatures.append(t_DH_return_ww)
        mass_flows.append(mcp_DH_ww)
        heat.append(Qww_sys[0])
        # Store values for next run
        thermal_network.ch_value['hs_ww'][t][name] = float(ch_value)
        thermal_network.ch_old['hs_ww'][t][name] = float(ch_value)

    # calculate mix temperature of return DH
    T_DH_return_K = calc_HEX_mix(heat, temperatures, mass_flows)
    mcp_DH_kWK = sum(mass_flows)  # [kW/K]
    heat_demand = sum(heat)

    return T_DH_return_K, mcp_DH_kWK, heat_demand


def calc_substation_return_DC(building, T_DC_supply_K, substation_HEX_specs, thermal_network, name, t):
    """
    calculate individual substation return temperature and required heat capacity (mcp) of the supply stream
    at each time step.
    :param building: list of building informations
    :param T_DC_supply_K: matrix of the substation supply temperatures in K
    :param substation_HEX_specs: substation heat exchanger properties

    :return t_return_DC: the substation return temperature
    :return mcp_DC: the required heat capacity (mcp) from the DC
    """

    temperatures = []
    mass_flows = []
    heat = []

    # Cooling ahu
    if 'UA_cooling_cs_ahu' in substation_HEX_specs.HEX_UA.columns:
        Qcs_sys_ahu, t_DC_return_cs_ahu, mcp_DC_hs_ahu, cc_value = calc_HEX_cooling(building, 'cs_sys', 'ahu_',
                                                                                    T_DC_supply_K,
                                                                                    substation_HEX_specs.HEX_UA.UA_cooling_cs_ahu[
                                                                                        '0'],
                                                                                    thermal_network.cc_old['cs_ahu'][t][
                                                                                        name],
                                                                                    thermal_network.delta_cap_mass_flow[
                                                                                        t])
        temperatures.append(t_DC_return_cs_ahu)
        mass_flows.append(mcp_DC_hs_ahu)
        heat.append(Qcs_sys_ahu[0])
        thermal_network.cc_old['cs_ahu'][t][name] = float(cc_value)
        thermal_network.cc_value['cs_ahu'][t][name] = float(cc_value)

    # Cooling aru
    if 'UA_cooling_cs_aru' in substation_HEX_specs.HEX_UA.columns:
        Qcs_sys_aru, t_DC_return_cs_aru, mcp_DC_hs_aru, cc_value = calc_HEX_cooling(building, 'cs_sys', 'aru_',
                                                                                    T_DC_supply_K,
                                                                                    substation_HEX_specs.HEX_UA.UA_cooling_cs_aru[
                                                                                        '0'],
                                                                                    thermal_network.cc_old['cs_aru'][t][
                                                                                        name],
                                                                                    thermal_network.delta_cap_mass_flow[
                                                                                        t])
        temperatures.append(t_DC_return_cs_aru)
        mass_flows.append(mcp_DC_hs_aru)
        heat.append(Qcs_sys_aru[0])
        thermal_network.cc_old['cs_aru'][t][name] = float(cc_value)
        thermal_network.cc_value['cs_aru'][t][name] = float(cc_value)

    # Cooling scu
    if 'UA_cooling_cs_scu' in substation_HEX_specs.HEX_UA.columns:
        Qcs_sys_scu, t_DC_return_cs_scu, mcp_DC_hs_scu, cc_value = calc_HEX_cooling(building, 'cs_sys', 'scu_',
                                                                                    T_DC_supply_K,
                                                                                    substation_HEX_specs.HEX_UA.UA_cooling_cs_scu[
                                                                                        '0'],
                                                                                    thermal_network.cc_old['cs_scu'][t][
                                                                                        name],
                                                                                    thermal_network.delta_cap_mass_flow[
                                                                                        t])
        temperatures.append(t_DC_return_cs_scu)
        mass_flows.append(mcp_DC_hs_scu)
        heat.append(Qcs_sys_scu[0])
        thermal_network.cc_old['cs_scu'][t][name] = float(cc_value)
        thermal_network.cc_value['cs_scu'][t][name] = float(cc_value)

    if 'UA_cooling_cs_data' in substation_HEX_specs.HEX_UA.columns:
        Qcdata_sys, t_DC_return_data, mcp_DC_data, cc_value = calc_HEX_cooling(building, 'cdata_sys', '', T_DC_supply_K,
                                                                               substation_HEX_specs.HEX_UA.UA_cooling_cs_data[
                                                                                   '0'],
                                                                               thermal_network.cc_old['cs_data'][t][
                                                                                   name],
                                                                               thermal_network.delta_cap_mass_flow[t])
        temperatures.append(t_DC_return_data)
        mass_flows.append(mcp_DC_data)
        heat.append(Qcdata_sys[0])
        thermal_network.cc_old['cs_data'][t][name] = float(cc_value)
        thermal_network.cc_value['cs_data'][t][name] = float(cc_value)

    if 'UA_cooling_cs_re' in substation_HEX_specs.HEX_UA.columns:
        Qcre_sys, t_DC_return_re_sys, mcp_DC_re_sys, cc_value = calc_HEX_cooling(building, 'cre_sys', '', T_DC_supply_K,
                                                                                 substation_HEX_specs.HEX_UA.UA_cooling_cs_re[
                                                                                     '0'],
                                                                                 thermal_network.cc_old['cs_re'][t][
                                                                                     name],
                                                                                 thermal_network.delta_cap_mass_flow[t])
        temperatures.append(t_DC_return_re_sys)
        mass_flows.append(mcp_DC_re_sys)
        heat.append(Qcre_sys[0])
        thermal_network.cc_old['cs_re'][t][name] = float(cc_value)
        thermal_network.cc_value['cs_re'][t][name] = float(cc_value)

    # calculate mix temperature of return DC
    T_DC_return_K = calc_HEX_mix(heat, temperatures, mass_flows)
    mcp_DC_kWK = sum(mass_flows)  # [kW/K]
    cooling_demand = sum(heat)

    return T_DC_return_K, mcp_DC_kWK, cooling_demand


# ============================
# substation cooling
# ============================


def calc_cooling_substation_heat_exchange(ch_0, Qnom, thi_0, tci_0, tho_0):
    """
    this function calculates the state of the heat exchanger at the substation of every customer with cooling needs
    cold/primary side: network; hot/secondary side: building
    :param Qnom: nominal cooling load
    :param thi_0: inflow temperature of secondary/building side
    :param tho_0: outflow temperature of secondary/building side
    :param tci_0: inflow temperature of primary/network side
    :param ch_0: capacity mass flow rate on secondary/building side
    :return: ``(Area_HEX_cooling, UA_cooling)``, area of heat excahnger, ..?
    """
    eff = HEAT_EX_EFFECTIVENESS
    tco_0 = thi_0  # some initial value
    # nominal conditions network side
    while (tco_0 + DT_INTERNAL_HEX) > thi_0:
        eff = eff - 0.05
        # nominal conditions network side
        cc_0 = ch_0 * (thi_0 - tho_0) / ((thi_0 - tci_0) * eff)  # FIXME
        tco_0 = Qnom / cc_0 + tci_0
    dTm_0 = calc_dTm_HEX(thi_0, tho_0, tci_0, tco_0)
    # Area heat exchange and UA_heating
    Area_HEX_cooling, UA_cooling = calc_area_HEX(Qnom, dTm_0, U_COOL)

    return Area_HEX_cooling, UA_cooling


# ============================
# substation heating
# ============================


def calc_heating_substation_heat_exchange(cc_0, Qnom, thi_0, tci_0, tco_0):
    '''
    This function calculates the Area and UA of each substation heat exchanger.
    Primary side = network, Secondary side = Building
    :param cc_0: nominal capacity mass flow rate primary side
    :param Qnom: nominal heating load
    :param thi_0: nominal inflow temperature of primary/network side
    :param tci_0: nominal inflow temperature of secondary/building side
    :param tco_0: nominal outflow temperature of secondary/building side

    :return Area_HEX_heating: Heat exchanger area in [m2]
    :return UA_heating: UA
    '''
    eff = HEAT_EX_EFFECTIVENESS
    tho_0 = tci_0  # some initial value
    while (tho_0 - DT_INTERNAL_HEX) < tci_0:
        eff = eff - 0.05
        # nominal conditions network side
        ch_0 = cc_0 * (tco_0 - tci_0) / ((thi_0 - tci_0) * eff)  # FIXME
        tho_0 = thi_0 - Qnom / ch_0
    dTm_0 = calc_dTm_HEX(thi_0, tho_0, tci_0, tco_0)
    # Area heat exchange and UA_heating
    Area_HEX_heating, UA_heating = calc_area_HEX(Qnom, dTm_0, U_HEAT)
    return Area_HEX_heating, UA_heating


# ============================
# Heat exchanger model
# ============================


def calc_HEX_cooling(building, type, name, tci, UA, cc_old, delta_cap_mass_flow):
    """
    This function calculates the mass flow rate, temperature of return (secondary side)
    and heat exchanger area for a plate heat exchanger.
    Method of Number of Transfer Units (NTU)

    :param Q: cooling load
    :param UA: coefficient representing the area of heat exchanger times the coefficient of transmittance of the
        heat exchanger
    :param thi: in temperature of primary side
    :param tho: out temperature of primary side
    :param tci: in temperature of secondary side
    :param ch: capacity mass flow rate primary side
    :return: ``(tco, cc)`` out temperature of secondary side (district cooling network), capacity mass flow rate
        secondary side
    """

    m_name = 'mcp' + type + '_' + name + 'kWperC'
    Q_name = 'Q' + type + '_' + name + 'kWh'
    T_sup_name = 'T' + type + '_sup_' + name + 'C'
    T_ret_name = 'T' + type + '_re_' + name + 'C'

    Q = abs(building[Q_name].values) * 1000  # in W
    if abs(Q).max() > 0:
        tho = building[T_sup_name].values + 273  # in K
        thi = building[T_ret_name].values + 273  # in K
        ch = building[m_name].values * 1000  # in W/K
        if ch > 0:
            eff = [0.1, 0]  # FIXME
            Flag = False
            tol = 0.00000001
            while abs((eff[0] - eff[1]) / eff[0]) > tol:
                if Flag == True:
                    eff[0] = eff[1]
                else:
                    cmin = ch * (thi - tho) / ((thi - tci) * eff[0])
                if cmin < ch:
                    cc = cmin
                    cmax = ch
                else:
                    cc = cmin
                    cmax = cc
                    cmin = ch
                cr = cmin / cmax
                NTU = UA / cmin
                eff[1] = calc_plate_HEX(NTU, cr)
                cmin = ch * (thi - tho) / ((thi - tci) * eff[1])
                tco = tci + eff[1] * cmin * (thi - tci) / cc
                Flag = True
        else:
            tco = 0.0
            cc = 0.0
        if cc > 0.0:
            if delta_cap_mass_flow > 0 or cc_old.any() > 0:
                if cc_old.any() > 0:
                    cc = np.array(
                        cc_old + delta_cap_mass_flow * HEAT_CAPACITY_OF_WATER_JPERKGK)  # todo:improve this
                else:  # first run through so no previous values for cc_old
                    cc = np.array(
                        cc + delta_cap_mass_flow * HEAT_CAPACITY_OF_WATER_JPERKGK)
                # recalculate temperature
                tco = tci + eff[1] * cmin * (thi - tci) / cc
        t_return = np.float(tco)
        mcp_return = np.float(cc / 1000)

    else:
        t_return = np.float(tci)
        mcp_return = 0.0
        cc = 0.0

    if np.isnan(t_return):
        t_return = 0.0

    return Q, t_return, abs(mcp_return), abs(cc)


def calc_plate_HEX(NTU, cr):
    '''
    This function calculates the efficiency of exchange for a plate heat exchanger according to the NTU method of
    AShRAE 90.1

    :param NTU: number of transfer units
    :param cr: ratio between min and max capacity mass flow rates
    :return:
        eff: efficiency of heat exchange
    '''
    eff = 1 - scipy.exp((1 / cr) * (NTU ** 0.22) * (scipy.exp(-cr * (NTU) ** 0.78) - 1))
    return eff


def calc_shell_HEX(NTU, cr):
    '''
    This function calculates the efficiency of exchange for a tube-shell heat exchanger according to the NTU method of
    AShRAE 90.1

    :param NTU: number of transfer units
    :param cr: ratio between min and max capacity mass flow rates
    :return:
        eff: efficiency of heat exchange
    '''
    eff = 2 * ((1 + cr + (1 + cr ** 2) ** (1 / 2)) * (
            (1 + scipy.exp(-(NTU) * (1 + cr ** 2))) / (1 - scipy.exp(-(NTU) * (1 + cr ** 2))))) ** -1
    return eff


def calc_HEX_mix(heat, temperatures, mass_flows):
    '''
    This function computes the average  temperature between two vectors of heating demand.
    In this case, domestic hotwater and space heating.

    :param heat: load heating
    :param temperatures: out temperature of heat exchanger for different heating modes
    :param mass_flows: mass flows for each heating mode
    :return:
        tavg: average out temperature.
    '''
    if sum(mass_flows) > 0:
        weighted = [0] * len(heat)
        for g in range(len(heat)):
            if not abs(heat[g]) > 0:  # check if we have a heat load
                mass_flows[g] = 0
            weighted[g] = temperatures[g] * mass_flows[g] / sum(mass_flows)
        tavg = sum(weighted)
    else:
        tavg = np.nanmean(temperatures)
    return np.float(tavg)


def calc_HEX_heating(building, type, name, thi, UA, ch_old, delta_cap_mass_flow):
    """
    This function calculates the mass flow rate, temperature of return (secondary side)
    and heat exchanger area for a shell-tube pleat exchanger in the heating case.

    Method of Number of Transfer Units (NTU)

    :param Q: load
    :param UA: coefficient representing the area of heat exchanger times the coefficient of transmittance of the
        heat exchanger
    :param thi: in temperature of secondary side
    :param tco: out temperature of primary side
    :param tci: in temperature of primary side
    :param cc: capacity mass flow rate primary side

    :return: tho = out temperature of secondary side (district cooling network), ch = capacity mass flow rate secondary side
    """

    m_name = 'mcp' + type + '_' + name + 'kWperC'
    Q_name = 'Q' + type + '_' + name + 'kWh'
    T_sup_name = 'T' + type + '_sup_' + name + 'C'
    T_ret_name = 'T' + type + '_re_' + name + 'C'

    Q = building[Q_name].values * 1000  # in W
    if Q.max() > 0:
        tco = building[T_sup_name].values + 273  # in K
        tci = building[T_ret_name].values + 273  # in K
        cc = np.array(building[m_name].values * 1000)  # in W/K
        if cc.max() > 0:
            eff = [0.1, 0]  # FIXME
            Flag = False
            tol = 0.00000001
            while abs((eff[0] - eff[1]) / eff[0]) > tol:
                if Flag == True:
                    eff[0] = eff[1]
                else:
                    cmin = cc * (tco - tci) / ((thi - tci) * eff[0])
                if cmin < cc:
                    ch = cmin
                    cmax = cc
                else:
                    ch = cmin
                    cmax = cmin
                    cmin = cc
                cr = cmin / cmax
                NTU = UA / cmin
                eff[1] = calc_shell_HEX(NTU, cr)
                cmin = cc * (tco - tci) / ((thi - tci) * eff[1])
                tho = thi - eff[1] * cmin * (thi - tci) / ch
                Flag = True
        else:
            tho = 0.0
            ch = 0.0
        if ch > 0.0:  # we have flows
            if delta_cap_mass_flow > 0 or ch_old.any() > 0:  # we have too low mass flows
                if ch_old.any() > 0:  # use information from previous iteration
                    ch = np.array(
                        ch_old + delta_cap_mass_flow * HEAT_CAPACITY_OF_WATER_JPERKGK)  # todo:improve this
                else:  # first run through so no previous values for ch_old
                    ch = np.array(
                        ch + delta_cap_mass_flow * HEAT_CAPACITY_OF_WATER_JPERKGK)
                # recalculate return temperature
                tho = thi - eff[1] * cmin * (thi - tci) / ch
        t_return = np.float(tho)
        mcp_return = np.float(ch / 1000)

    else:
        t_return = np.float(thi)
        mcp_return = 0.0
        ch = 0.0

    if np.isnan(t_return):
        t_return = 0.0
    return Q, t_return, abs(mcp_return), abs(ch)


def calc_dTm_HEX(thi, tho, tci, tco):
    '''
    This function estimates the logarithmic temperature difference between two streams

    :param thi: in temperature hot stream
    :param tho: out temperature hot stream
    :param tci: in temperature cold stream
    :param tco: out temperature cold stream
    :param flag: heat: when using for the heating case, 'cool' otherwise
    :return:
        dtm = logaritimic temperature difference
    '''
    dT1 = thi - tco
    dT2 = tho - tci
    if dT1 == dT2:
        dTm = dT1
    else:
        dTm = (dT1 - dT2) / scipy.log(dT1 / dT2)
    return abs(dTm.real)


def calc_area_HEX(Qnom, dTm_0, U):
    """
    This function calculates the area of a het exchanger at nominal conditions.

    :param Qnom: nominal load
    :param dTm_0: nominal logarithmic temperature difference
    :param U: coeffiicent of transmissivity
    :return: ``(area, UA)``: area: area of heat exchange,  UA: coefficient representing the area of heat exchanger times
        the coefficient of transmittance of the heat exchanger
    """
    area = Qnom / (dTm_0 * U)  # Qnom in W
    UA = U * area
    return area, UA


# ============================
# Other functions
# ============================
def calc_DC_supply(t_0, t_1):
    """
    This function calculates the temperature of the district cooling network according to the minimum observed
    (different to zero) in all buildings connected to the grid.
    :param t_0: last minimum temperature
    :param t_1:  current minimum temperature to evaluate
    :return tmin: new minimum temperature
    """
    a = np.array([t_0, t_1])
    tmin = np.nanmin(a)
    return tmin


def calc_DH_supply(t_0, t_1):
    """
    This function calculates the heating temperature requirement of the building side according to the maximum
    temperature requirement at that time-step.
    :param t_0: temperature requirement from one heating application
    :param t_1: temperature requirement from another heating application
    :return: ``tmax``: maximum temperature requirement
    """
    a = np.array([t_0, t_1])
    tmax = np.nanmax(a)
    return tmax


def calc_total_network_flow(Q_all, flowrate):
    return Q_all + flowrate


# ============================
# Test
# ============================
def main(config):
    """
    run the whole network summary routine
    """
    from cea.technologies.thermal_network.thermal_network import ThermalNetwork
    import cea.inputlocator as inputlocator

    locator = cea.inputlocator.InputLocator(config.scenario)

    network_type = config.thermal_network.network_type
    network_name = ''
    file_type = config.thermal_network.file_type
    temperature_control = 'VT'
    thermal_network = ThermalNetwork(locator, network_type, network_name, file_type, temperature_control, config)

    t = 1000  # FIXME
    T_DH = 60  # FIXME
    network = 'DH'  # FIXME
    delta_cap_mass_flow = 0  # Assume all edge mass flows sufficiently high
    cc_old_sh = 0  # not relevant here.
    cc_old_dhw = 0
    ch_old = 0

    thermal_network.buildings_demands = determine_building_supply_temperatures(thermal_network.building_names, locator)
    thermal_network.substations_HEX_specs = substation_HEX_design_main(thermal_network.buildings_demands)
    T_substation_supply_K = pd.DataFrame([[T_DH + 273.0] * len(thermal_network.building_names)],
                                         columns=thermal_network.building_names, index=['T_supply'])
    substation_return_model_main(thermal_network, T_substation_supply_K, t, thermal_network.building_names)

    print('substation_main() succeeded')


if __name__ == '__main__':
    main(cea.config.Configuration())