__author__ = 'saeedamen' # Saeed Amen

#
# Copyright 2016 Cuemacro
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# See the License for the specific language governing permissions and limitations under the License.
#

"""
EngineTemplate

Implemented by EngineBokeh, EnglineMatplotlib and EnginePlotly to do underlying plotting

"""

import abc

from math import log10, floor
import numpy
import pandas
import datetime
from chartpy.style import Style
from chartpy.chartconstants import ChartConstants

cc = ChartConstants()

class EngineTemplate(object):

    def init(self):
        return

    @abc.abstractmethod
    def plot_chart(self, data_frame, style, type):
        return

    def get_time_stamp(self):
        return str(datetime.datetime.now()).replace(':', '-').replace(' ', '-').replace(".", "-")

    def get_bar_indices(self, data_frame, style, chart_type, bar_ind):
        has_bar = 'no-bar'
        xd = data_frame.index
        no_of_bars = len(data_frame.columns)

        if style.chart_type is not None:
            if isinstance(style.chart_type, list):
                if 'bar' in style.chart_type:
                    xd = bar_ind
                    no_of_bars = style.chart_type.count('bar')
                    has_bar = 'barv'
                elif 'stacked' in style.chart_type:
                    xd = bar_ind
                    no_of_bars = 1
                    has_bar = 'barv'
            elif 'bar' == style.chart_type:
                xd = bar_ind
                has_bar = 'barv'
            elif 'barh' == style.chart_type:
                xd = bar_ind
                has_bar = 'barh'
            elif 'stacked' == style.chart_type:
                xd = bar_ind
                has_bar = 'barh'
        else:
            if chart_type == 'bar' or chart_type == 'stacked':
                xd = bar_ind
                has_bar = 'barv'

        return xd, bar_ind, has_bar, no_of_bars

    def assign(self, structure, field, default):
        if hasattr(structure, field): default = getattr(structure, field)

        return default

    def assign_list(self, style, field, list):
        if hasattr(style, field):
            list = [str(x) for x in getattr(style, field)]

        return list

    def get_linewidth(self, label, linewidth_1, linewidth_2, linewidth_2_series):
        if label in linewidth_2_series:
            return linewidth_2

        return linewidth_1

    def round_to_1(self, x):
        return round(x, -int(floor(log10(x))))

    def split_data_frame_to_list(self, data_frame, style):
        data_frame_list = []

        if isinstance(data_frame, list):
            data_frame_list = data_frame
        else:
            if style.subplots == True:

                for col in data_frame.columns:
                    data_frame_list.append(
                        pandas.DataFrame(index=data_frame.index, columns=[col], data=data_frame[col]))
            else:
                data_frame_list.append(data_frame)

        return data_frame_list

#######################################################################################################################

from bokeh.plotting import figure, output_file, show, gridplot, save
from bokeh.charts import HeatMap

class EngineBokeh(EngineTemplate):

    def plot_chart(self, data_frame, style, chart_type):

        cm = ColorMaster()

        if style.scale_factor > 0:
            scale_factor = abs(style.scale_factor) * 2/3
        else:
            scale_factor = abs(style.scale_factor)

        try:
            if style.bokeh_plot_mode == "offline_jupyter":
                from bokeh.io import output_notebook
                output_notebook()
        except:
            pass

        try:
            if style.file_output is not None:
                style.html_file_output = style.file_output

            html = style.html_file_output

            if (html is None):
                import time
                style.html_file_output = self.get_time_stamp() + "-bokeh.html"

                html = style.html_file_output

            output_file(html)
        except: pass

        data_frame_list = self.split_data_frame_to_list(data_frame, style)

        plot_list = []

        plot_width = int((style.width * scale_factor))
        plot_height = int((style.height * scale_factor) / len(data_frame_list))

        for data_frame in data_frame_list:
            bar_ind = numpy.arange(1, len(data_frame.index) + 1)

            xd, bar_ind, has_bar, no_of_bars = self.get_bar_indices(data_frame, style, chart_type, bar_ind)

            separate_chart = False

            if chart_type == 'heatmap':
                # TODO

                p1 = HeatMap(data_frame,
                             title='Random', plot_width = plot_width, plot_height = plot_height)

                separate_chart = True

            # if has a vertical bar than categorical x-axis
            elif has_bar == 'barv':
                p1 = figure(
                    plot_width = plot_width,
                    plot_height = plot_height,
                    x_range=[str(x).replace(':','.') for x in data_frame.index]
                    )

                from math import pi
                p1.xaxis.major_label_orientation = pi/2
            elif type(data_frame.index) == pandas.tslib.Timestamp:
                p1 = figure(
                    x_axis_type = "datetime",
                    plot_width = plot_width,
                    plot_height = plot_height,
                    x_range = (xd[0], xd[-1])
                )

            # otherwise numerical axis
            else:
                p1 = figure(
                    plot_width = plot_width,
                    plot_height = plot_height,
                    x_range=(xd[0], xd[-1])
                    )

            # set the fonts
            p1.axis.major_label_text_font_size = str(10) + "pt"
            p1.axis.major_label_text_font = cc.bokeh_font
            p1.axis.major_label_text_font_style = cc.bokeh_font_style

            p1.xaxis.axis_label_text_font_size = str(10) + "pt"
            p1.xaxis.axis_label_text_font = cc.bokeh_font
            p1.xaxis.axis_label_text_font_style = cc.bokeh_font_style
            p1.xaxis.axis_label = style.x_title

            p1.yaxis.axis_label_text_font_size = str(10) + "pt"
            p1.yaxis.axis_label_text_font = cc.bokeh_font
            p1.yaxis.axis_label_text_font_style = cc.bokeh_font_style
            p1.yaxis.axis_label = style.y_title

            p1.legend.location = "top_left"
            p1.legend.label_text_font_size = str(10) + "pt"
            p1.legend.label_text_font = cc.bokeh_font
            p1.legend.label_text_font_style = cc.bokeh_font_style
            p1.legend.background_fill_alpha = 0.75
            p1.legend.border_line_width = 0

            # set chart outline
            p1.outline_line_width = 0

            # Plot.title.text
            p1.title.text_font_size = str(14) + "pt"
            p1.title.text_font = cc.bokeh_font

            # TODO fix label
            # if style.display_source_label:
            #     p1.text([30 * scale_factor, 30 * scale_factor], [0, 0], text = [style.brand_label],
            #         text_font_size = str(10 * scale_factor) + "pt", text_align = "left",
            #         text_font = GraphistyleConstants().bokeh_font)

            color_spec = cm.create_color_list(style, data_frame)
            import matplotlib

            bar_space = 0.2
            bar_width = (1 - bar_space) / (no_of_bars)
            bar_index = 0

            has_bar ='no-bar'

            if not(separate_chart):

                # plot each series in the dataframe separately
                for i in range(0, len(data_frame.columns)):
                    label = str(data_frame.columns[i])
                    glyph_name = 'glpyh' + str(i)

                    # set chart type which can differ for each time series
                    if isinstance(chart_type, list): chart_type_ord = chart_type[i]
                    else: chart_type_ord = chart_type

                    # get the color
                    if color_spec[i] is None:
                        color_spec[i] = self.get_color_list(i)

                    try:
                        color_spec[i] = matplotlib.colors.rgb2hex(color_spec[i])
                    except: pass

                    yd = data_frame.ix[:,i]

                    # plot each time series as appropriate line, scatter etc.
                    if chart_type_ord == 'line':
                        linewidth_t = self.get_linewidth(label,
                            style.linewidth, style.linewidth_2, style.linewidth_2_series)

                        if linewidth_t is None: linewidth_t = 1

                        if style.display_legend:
                            p1.line(xd, yd, color = color_spec[i], line_width=linewidth_t, name = glyph_name,
                                    legend = label,
                            )
                        else:
                            p1.line(xd, data_frame.ix[:,i], color = color_spec[i], line_width=linewidth_t, name = glyph_name)

                    elif(chart_type_ord == 'bar'):
                        bar_pos = [k - (1 - bar_space) / 2. + bar_index * bar_width for k in range(1,len(bar_ind) + 1)]
                        bar_pos_right = [x + bar_width for x in bar_pos]

                        if style.display_legend:
                            p1.quad(top=yd, bottom=0 * yd, left=bar_pos, right=bar_pos_right, color=color_spec[i], legend=label)
                        else:
                            p1.quad(top=yd, bottom=0 * yd, left=bar_pos, right=bar_pos_right, color=color_spec[i])

                        bar_index = bar_index + 1
                        bar_ind = bar_ind + bar_width
                    elif (chart_type_ord == 'barh'):
                        # TODO
                        pass

                    elif chart_type_ord == 'scatter':
                        linewidth_t = self.get_linewidth(label,
                            style.linewidth, style.linewidth_2, style.linewidth_2_series)

                        if linewidth_t is None: linewidth_t = 1

                        if style.display_legend:
                            p1.circle(xd, yd, color = color_spec[i], line_width=linewidth_t, name = glyph_name,
                                    legend = label,
                            )
                        else:
                            p1.circle(xd, yd, color = color_spec[i], line_width=linewidth_t, name = glyph_name)

                p1.grid.grid_line_alpha = 0.3

                # p1.min_border_left = -40
                # p1.min_border_right = 0
                # p1.min_border_top = 0
                # p1.min_border_bottom = 0

                p1.min_border = -50

            plot_list.append(p1)

        p_final = gridplot(plot_list, ncols=1)

        try:
            p_final.title.text = style.title
        except: pass

        if style.silent_display:
            save(p_final)
        else:
            show(p_final)  # open a browser

    def get_color_list(self, i):
        color_palette = cc.bokeh_palette

        return color_palette[i % len(color_palette)]

    def generic_settings(self):
        return

#######################################################################################################################

# matplotlib based libraries
from datetime import timedelta

import matplotlib
import matplotlib.pyplot as plt
import numpy as np

from matplotlib.dates import YearLocator, MonthLocator, DayLocator, HourLocator, MinuteLocator
from matplotlib.ticker import MultipleLocator

class EngineMatplotlib(EngineTemplate):

    def plot_chart(self, data_frame, style, chart_type):

        self.apply_style_sheet(style)

        # create figure & add a subplot
        fig = plt.figure(figsize = ((style.width * abs(style.scale_factor))/style.dpi,
                                    (style.height * abs(style.scale_factor))/style.dpi), dpi = style.dpi)

        if style.title is not None:
            fig.suptitle(style.title, fontsize = 14 * abs(style.scale_factor))

        # matplotlib 1.5
        try:
            cyc = matplotlib.rcParams['axes.prop_cycle']
            color_cycle =  [x['color'] for x in cyc]
        except KeyError:
            # pre 1.5
            pass
            # color_cycle =  matplotlib.rcParams['axes.color_cycle']

        cm = ColorMaster()

        data_frame_list = self.split_data_frame_to_list(data_frame, style)

        subplot_no = 1

        for data_frame in data_frame_list:

            bar_ind = np.arange(0, len(data_frame.index))

            # for bar charts, create a proxy x-axis (then relabel)
            xd, bar_ind, has_bar, no_of_bars = self.get_bar_indices(data_frame, style, chart_type, bar_ind)

            if style.subplots == False and len(data_frame_list) == 1:
                ax = fig.add_subplot(111)
            else:
                ax = fig.add_subplot(2,1,subplot_no)

            subplot_no = subplot_no + 1

            if style.x_title != '': ax.set_xlabel(style.x_title)
            if style.y_title != '': ax.set_ylabel(style.y_title)

            plt.xlabel(style.x_title)
            plt.ylabel(style.y_title)

            # format Y axis
            y_formatter = matplotlib.ticker.ScalarFormatter(useOffset = False)
            ax.yaxis.set_major_formatter(y_formatter)

            # create a second y axis if necessary
            ax2 = []

            if style.y_axis_2_series != []:
                ax2 = ax.twinx()

                # do not use a grid with multiple y axes
                ax.yaxis.grid(False)
                ax2.yaxis.grid(False)

            try:
                # get all the correct colors (and construct gradients if necessary eg. from 'blues')

                # for stacked bar
                yoff_pos = np.zeros(len(data_frame.index.values)) # the bottom values for stacked bar chart
                yoff_neg = np.zeros(len(data_frame.index.values)) # the bottom values for stacked bar chart

                zeros = np.zeros(len(data_frame.index.values))

                # for bar chart
                bar_space = 0.2
                bar_width = (1 - bar_space) / (no_of_bars)
                bar_index = 0

                has_matrix = False

                if not(isinstance(chart_type, list)):
                    if chart_type == 'heatmap':
                        ax.set_frame_on(False)

                        # weird hack, otherwise comes out all inverted!
                        data_frame = data_frame.iloc[::-1]

                        color = style.color

                        if style.color == []:
                            color = cc.chartfactory_default_colormap
                        else:
                            if isinstance(style.color, list):
                                color = style.color[subplot_no - 1]

                        ax.pcolor(data_frame.values, cmap=color, alpha=0.8)

                        has_matrix = True

                if (not(has_matrix)):
                    # plot the lines (using custom palettes as appropriate)
                    color_spec = cm.create_color_list(style, data_frame)

                    # some lines we should exclude from the color and use the default palette
                    for i in range(0, len(data_frame.columns.values)):

                        if isinstance(chart_type, list): chart_type_ord = chart_type[i]
                        else: chart_type_ord = chart_type

                        label = str(data_frame.columns[i])

                        ax_temp = self.get_axis(ax, ax2, label, style.y_axis_2_series)

                        yd = data_frame.ix[:,i]

                        if color_spec[i] is None:
                            color_spec[i] = color_cycle[i % len(color_cycle)]

                        if (chart_type_ord == 'line'):
                            linewidth_t = self.get_linewidth(label,
                                                             style.linewidth, style.linewidth_2, style.linewidth_2_series)

                            if linewidth_t is None: linewidth_t = matplotlib.rcParams['axes.linewidth']

                            ax_temp.plot(xd, yd, label = label, color = color_spec[i],
                                         linewidth = linewidth_t)

                        elif(chart_type_ord == 'bar'):
                            # for multiple bars we need to allocate space properly
                            bar_pos = [k - (1 - bar_space) / 2. + bar_index * bar_width for k in range(0,len(bar_ind))]

                            ax_temp.bar(bar_pos, yd, bar_width, label = label, color = color_spec[i])

                            bar_index = bar_index + 1

                        elif (chart_type_ord == 'barh'):
                            # for multiple bars we need to allocate space properly
                            bar_pos = [k - (1 - bar_space) / 2. + bar_index * bar_width for k in range(0, len(bar_ind))]

                            ax_temp.barh(bar_pos, yd, bar_width, label=label, color=color_spec[i])

                            bar_index = bar_index + 1

                        elif(chart_type_ord == 'stacked'):
                            bar_pos = [k - (1 - bar_space) / 2. + bar_index * bar_width for k in range(0,len(bar_ind))]

                            yoff = np.where(yd > 0, yoff_pos, yoff_neg)

                            ax_temp.bar(bar_pos, yd, label = label, color = color_spec[i], bottom = yoff)

                            yoff_pos = yoff_pos + np.maximum(yd, zeros)
                            yoff_neg = yoff_neg + np.minimum(yd, zeros)

                            # bar_index = bar_index + 1

                        elif(chart_type_ord == 'scatter'):
                            ax_temp.scatter(xd, yd, label = label, color = color_spec[i])

                            if style.line_of_best_fit is True:
                                self.trendline(ax_temp, xd.values, yd.values, order=1, color= color_spec[i], alpha=1,
                                               scale_factor = abs(style.scale_factor))

                # format X axis
                self.format_x_axis(ax, data_frame, style, has_bar, bar_ind, has_matrix)

            except: pass

            if style.display_source_label == True and style.source is not None:
                ax.annotate('Source: ' + style.source, xy = (1, 0), xycoords='axes fraction', fontsize=7 * abs(style.scale_factor),
                            xytext=(-5 * abs(style.scale_factor), 10 * abs(style.scale_factor)), textcoords='offset points',
                            ha='right', va='top', color = style.source_color)

            if style.display_brand_label == True:
                self.create_brand_label(ax, anno = style.brand_label, scale_factor = abs(style.scale_factor))

            leg = []
            leg2 = []

            loc = 'best'

            # if we have two y-axis then make sure legends are in opposite corners
            if ax2 != []: loc = 2

            try:
                leg = ax.legend(loc = loc, prop={'size':10 * abs(style.scale_factor)})
                leg.get_frame().set_linewidth(0.0)
                leg.get_frame().set_alpha(0)

                if ax2 != []:
                    leg2 = ax2.legend(loc = 1, prop={'size':10 * abs(style.scale_factor)})
                    leg2.get_frame().set_linewidth(0.0)
                    leg2.get_frame().set_alpha(0)
            except: pass

            try:
                if style.display_legend is False:
                    if leg != []: leg.remove()
                    if leg2 != []: leg.remove()
            except: pass

        try:
            if (style.file_output is None):
                import time
                style.file_output = self.get_time_stamp() + "-matplotlib.png"

            plt.savefig(style.file_output, transparent=False)
        except: pass

        ####### various matplotlib converters are unstable
        # convert to D3 format with mpld3
        try:
            # output matplotlib charts externally to D3 based libraries
            import mpld3

            if style.display_mpld3 == True:
                mpld3.save_d3_html(fig, style.html_file_output)
                mpld3.show(fig)
        except: pass

        # FRAGILE! convert to Bokeh format
        # better to use direct Bokeh renderer
        try:
            if (style.convert_matplotlib_to_bokeh == True):
                from bokeh.plotting import output_file, show
                from bokeh import mpl

                output_file(style.html_file_output)
                show(mpl.to_bokeh())
        except: pass

        # FRAGILE! convert matplotlib chart to Plotly format
        # recommend using AdapterCufflinks instead to directly plot to Plotly
        try:
            import plotly.plotly as py
            import plotly
            import plotly.tools as tls

            if style.convert_matplotlib_to_plotly == True:
                plotly.tools.set_credentials_file(username = style.plotly_username,
                                                  api_key = style.plotly_api_key)

                py_fig = tls.mpl_to_plotly(fig, strip_style = True)
                plot_url = py.plot_mpl(py_fig, filename = style.plotly_url)
        except:
            pass

        # display in matplotlib window
        try:
            if cc.chartfactory_silent_display == True:
                return fig
            elif style.silent_display == False:
                plt.show()
            else:
                return fig

        except:
            pass

    def apply_style_sheet(self, style):
        # set the matplotlib style sheet & defaults
        matplotlib.rcdefaults()

        # first search ChartPy styles, then try matplotlib
        try: plt.style.use(cc.chartfactory_style_sheet[style.style_sheet])
        except: plt.style.use(style.style_sheet)

        # adjust font size for scale factor
        matplotlib.rcParams.update({'font.size': matplotlib.rcParams['font.size'] * abs(style.scale_factor)})

        # do not use offsets/scientific notation
        matplotlib.rcParams.update({'axes.formatter.useoffset': False})

    def format_x_axis(self, ax, data_frame, style, has_bar, bar_ind, has_matrix):

        if has_matrix:
            x_bar_ind = np.arange(0, len(data_frame.columns))
            y_bar_ind = np.arange(0, len(data_frame.index))

            ax.set_xticks(x_bar_ind + 0.5)
            ax.set_xlim([0, len(x_bar_ind)])
            ax.set_yticks(y_bar_ind + 0.5)
            ax.set_ylim([0, len(y_bar_ind)])

            plt.setp(plt.yticks()[1], rotation=90)

            ax.set_xticklabels(data_frame.columns, minor=False)
            ax.set_yticklabels(data_frame.index, minor=False)

            ax.plot([], [])

            for x in range(len(data_frame.index)):
                for y in range(len(data_frame.columns)):
                    plt.text(x + 0.5, y + 0.5, '%.0f' % data_frame.ix[x, y],
                         horizontalalignment='center',
                         verticalalignment='center',
                         )

            return

        if has_bar == 'barv':
            ax.set_xticks(bar_ind)
            ax.set_xticklabels(data_frame.index)
            ax.set_xlim([-1, len(bar_ind)])

            # if lots of labels make text smaller and rotate
            if len(bar_ind) > 6:
                plt.setp(plt.xticks()[1], rotation=90)
                # plt.gca().tight_layout()
                # matplotlib.rcParams.update({'figure.autolayout': True})
                # plt.gcf().subplots_adjust(bottom=5)
                import matplotlib.dates as mdates

                if style.date_formatter is not None:
                    ax.format_xdata = mdates.DateFormatter(style.date_formatter)

                plt.tight_layout()
                # ax.tick_params(axis='x', labelsize=matplotlib.rcParams['font.size'] * 0.5)
            return
        elif has_bar == 'barh':
            ax.set_yticks(bar_ind)
            ax.set_yticklabels(data_frame.index)
            ax.set_ylim([-1, len(bar_ind)])

            # if lots of labels make text smaller and rotate
            if len(bar_ind) > 6:
                #plt.setp(plt.yticks()[1])
                # plt.gca().tight_layout()
                # matplotlib.rcParams.update({'figure.autolayout': True})
                # plt.gcf().subplots_adjust(bottom=5)
                import matplotlib.dates as mdates

                if style.date_formatter is not None:
                    ax.format_ydata = mdates.DateFormatter(style.date_formatter)

                plt.tight_layout()
                # ax.tick_params(axis='x', labelsize=matplotlib.rcParams['font.size'] * 0.5)
            return

        # format X axis
        dates = data_frame.index

        # scaling for time series plots with hours and minutes only (and no dates)
        if hasattr(data_frame.index[0], 'hour') and not(hasattr(data_frame.index[0], 'month')):
            ax.xaxis.set_major_locator(MultipleLocator(86400./3.))
            ax.xaxis.set_minor_locator(MultipleLocator(86400./24.))
            ax.grid(b = True, which='minor', color='w', linewidth=0.5)

        # TODO have more refined way of formating time series x-axis!

        # scaling for time series plots with dates too
        else:
            # to handle dates
            try:
                dates = dates.to_pydatetime()
                diff = data_frame.index[-1] - data_frame.index[0]

                import matplotlib.dates as md

                if style.date_formatter is not None:
                    # from matplotlib.ticker import Formatter
                    #
                    # class MyFormatter(Formatter):
                    #     def __init__(self, dates, fmt='%H:%M'):
                    #         self.dates = dates
                    #         self.fmt = fmt
                    #
                    #     def __call__(self, x, pos=0):
                    #         'Return the label for time x at position pos'
                    #         ind = int(round(x))
                    #         if ind >= len(self.dates) or ind < 0: return ''
                    #
                    #         return self.dates[ind].strftime(self.fmt)
                    #
                    # formatter = MyFormatter(dates)
                    # ax.xaxis.set_major_formatter(formatter)

                    ax.xaxis.set_major_formatter(md.DateFormatter(style.date_formatter))
                elif diff < timedelta(days = 4):

                    date_formatter = '%H:%M'
                    xfmt = md.DateFormatter(date_formatter)
                    ax.xaxis.set_major_formatter(xfmt)

                    if diff < timedelta(minutes=20):
                        ax.xaxis.set_major_locator(MinuteLocator(byminute=range(60), interval=2))
                        ax.xaxis.set_minor_locator(MinuteLocator(interval=1))
                    elif diff < timedelta(hours=1):
                        ax.xaxis.set_major_locator(MinuteLocator(byminute=range(60), interval=5))
                        ax.xaxis.set_minor_locator(MinuteLocator(interval=2))
                    elif diff < timedelta(hours=6):
                        locator = HourLocator(interval=1)
                        ax.xaxis.set_major_locator(locator)
                        ax.xaxis.set_minor_locator(MinuteLocator(interval=30))
                    elif diff < timedelta(days=3):
                        ax.xaxis.set_major_locator(HourLocator(interval=6))
                        ax.xaxis.set_minor_locator(HourLocator(interval=1))

                elif diff < timedelta(days=10):
                    locator = DayLocator(interval=2)
                    ax.xaxis.set_major_locator(locator)
                    ax.xaxis.set_major_formatter(md.DateFormatter('%d %b %y'))

                    day_locator = DayLocator(interval=1)
                    ax.xaxis.set_minor_locator(day_locator)

                elif diff < timedelta(days=40):
                    locator = DayLocator(interval=10)
                    ax.xaxis.set_major_locator(locator)
                    ax.xaxis.set_major_formatter(md.DateFormatter('%d %b %y'))

                    day_locator = DayLocator(interval=1)
                    ax.xaxis.set_minor_locator(day_locator)

                elif diff < timedelta(days=365 * 0.5):
                    locator = MonthLocator(bymonthday=1, interval=2)
                    ax.xaxis.set_major_locator(locator)
                    ax.xaxis.set_major_formatter(md.DateFormatter('%b %y'))

                    months_locator = MonthLocator(interval=1)
                    ax.xaxis.set_minor_locator(months_locator)

                elif diff < timedelta(days=365 * 2):
                    locator = MonthLocator(bymonthday=1, interval=3)
                    ax.xaxis.set_major_locator(locator)
                    ax.xaxis.set_major_formatter(md.DateFormatter('%b %y'))

                    months_locator = MonthLocator(interval=1)
                    ax.xaxis.set_minor_locator(months_locator)

                elif diff < timedelta(days = 365 * 5):
                    locator = YearLocator()
                    ax.xaxis.set_major_locator(locator)
                    ax.xaxis.set_major_formatter(md.DateFormatter('%Y'))

            except:
                try:
                    # otherwise we have integers, rather than dates
                    # TODO needs smarter more generalised mapping of dates
                    max = dates.max()
                    min = dates.min()

                    big_step = self.round_to_1((max - min)/10)

                    small_step = big_step / 5

                    ax.xaxis.set_major_locator(MultipleLocator(big_step))
                    ax.xaxis.set_minor_locator(MultipleLocator(small_step))

                    plt.xlim(min, max)
                except: pass

    def get_axis(self, ax, ax2, label, y_axis_2_series):

        if label in y_axis_2_series: return ax2

        return ax

    def trendline(self, ax, xd, yd, order=1, color='red', alpha=1, Rval=False, scale_factor = 1):
        """ Make a line of best fit """

        # Calculate trendline
        xd[np.isnan(xd)] = 0
        yd[np.isnan(yd)] = 0

        coeffs = np.polyfit(xd, yd, order)

        intercept = coeffs[-1]
        slope = coeffs[-2]
        if order == 2: power = coeffs[0]
        else: power = 0

        minxd = np.min(xd)
        maxxd = np.max(xd)

        xl = np.array([minxd, maxxd])
        yl = power * xl ** 2 + slope * xl + intercept

        # plot trendline
        ax.plot(xl, yl, color = color, alpha = alpha)

        # calculate R squared
        p = np.poly1d(coeffs)

        ybar = np.sum(yd) / len(yd)
        ssreg = np.sum((p(xd) - ybar) ** 2)
        sstot = np.sum((yd - ybar) ** 2)
        Rsqr = ssreg / sstot

        if Rval == False:
            text = 'R^2 = %0.2f, m = %0.4f, c = %0.4f' %(Rsqr, slope, intercept)

            ax.annotate(text, xy=(1, 1), xycoords='axes fraction', fontsize=8 * abs(scale_factor),
                    xytext=(-5 * abs(scale_factor), 10 * abs(scale_factor)), textcoords='offset points',
                    ha='right', va='top')

            # Plot R^2 value
            # ax.text(0.65, 0.95, text, fontsize = 10 * scale_factor,
            #            ha= 'left',
            #            va = 'top', transform = ax.transAxes)
            pass
        else:
            # return the R^2 value:
            return Rsqr

    def create_brand_label(self, ax, anno, scale_factor):
        ax.annotate(anno, xy = (1, 1), xycoords = 'axes fraction',
                    fontsize = 10 * abs(scale_factor), color = 'white',
                    xytext = (0 * abs(scale_factor), 15 * abs(scale_factor)), textcoords = 'offset points',
                    va = "center", ha = "center",
                    bbox = dict(boxstyle = "round,pad=0.0", facecolor = cc.chartfactory_brand_color))

#######################################################################################################################
cf = None

try:
    import plotly
    import cufflinks as cf
except: pass

import plotly.plotly

class EnginePlotly(EngineTemplate):

    def plot_chart(self, data_frame, style, chart_type):

        mode = 'line'

        if style is None: style = Style()

        marker_size = 1

        x = ''; y = ''; z = ''
        fig = None

        scale = 1

        try:
            if (style.plotly_plot_mode == 'offline_html' and style.scale_factor > 0):
                scale = 2/3
        except:
            pass

        # check other plots implemented by Cufflinks
        if fig is None:

            cm = ColorMaster()

            # create figure
            data_frame_list = self.split_data_frame_to_list(data_frame, style)
            fig_list = []
            cols = []

            for data_frame in data_frame_list:
                cols.append(data_frame.columns)

            cols = list(np.array(cols).flat)

            # get all the correct colors (and construct gradients if necessary eg. from 'Blues')
            # need to change to strings for cufflinks

            color_list = cm.create_color_list(style, [], cols=cols)
            color_spec = []

            # if no colors are specified then just use our default color set from chart constants
            if color_list == [None] * len(color_list):
                color_spec = [None] * len(color_list)

                for i in range(0, len(color_list)):
                    # get the color
                    if color_spec[i] is None:
                        color_spec[i] = self.get_color_list(i)

                    try:
                        color_spec[i] = matplotlib.colors.rgb2hex(color_spec[i])
                    except:
                        pass

            else:
                # otherwise assume all the colors are rgba
                for color in color_list:
                    color = 'rgba' + str(color)
                    color_spec.append(color)

            start = 0

            for i in range(0, len(data_frame_list)):
                data_frame = data_frame_list[i]

                if isinstance(chart_type, list):
                    chart_type_ord = chart_type[i]
                else:
                    chart_type_ord = chart_type

                end = start + len(data_frame.columns)
                color_spec1 = color_spec[start:start + end]
                start = end

                if chart_type_ord == 'surface':
                    fig = data_frame.iplot(kind=chart_type,
                                           title=style.title,
                                           xTitle=style.x_title,
                                           yTitle=style.y_title,
                                           x=x, y=y, z=z,
                                           mode=mode,
                                           size=marker_size,
                                           theme=style.plotly_theme,
                                           bestfit=style.line_of_best_fit,
                                           legend=style.display_legend,
                                           colorscale=style.color,
                                           dimensions=(style.width * abs(style.scale_factor) * scale,
                                                       style.height * abs(style.scale_factor) * scale),
                                           asFigure=True)
                elif chart_type_ord == 'heatmap':
                    fig = data_frame.iplot(kind=chart_type,
                                           title=style.title,
                                           xTitle=style.x_title,
                                           yTitle=style.y_title,
                                           x=x, y=y,
                                           mode=mode,
                                           size=marker_size,
                                           theme=style.plotly_theme,
                                           bestfit=style.line_of_best_fit,
                                           legend=style.display_legend,
                                           colorscale=style.color,
                                           dimensions=(style.width * abs(style.scale_factor) * scale,
                                                       style.height * abs(style.scale_factor) * scale),
                                           asFigure=True)

                    # TODO get annotations to work on Plotly/cufflinks heatmaps

                    # z = data_frame.values
                    #
                    # annotations = []
                    # for n, row in enumerate(z):
                    #     for m, val in enumerate(row):
                    #         val = z[n][m]
                    #         annotations.append(
                    #             dict(
                    #                 text=str(val),
                    #                 x=x[m], y=y[n],
                    #                 xref='x1', yref='y1',
                    #                 font=dict(color='white' if val > 0.5 else 'black'),
                    #                 showarrow=False)
                    #         )
                    #
                    # fig['layout'].update(
                    #     annotations=annotations,
                    # )

                elif chart_type_ord == 'line':
                    chart_type_ord = 'scatter'
                elif chart_type_ord == 'scatter':
                    mode = 'markers'
                    marker_size = 5
                elif chart_type_ord == 'bubble':
                    x = data_frame.columns[0]
                    y = data_frame.columns[1]
                    z = data_frame.columns[2]

                # special case for choropleth which has yet to be implemented in Cufflinks
                # will likely remove this in the future
                elif chart_type_ord == 'choropleth':

                    for col in data_frame.columns:
                        try:
                            data_frame[col] = data_frame[col].astype(str)
                        except:
                            pass

                    if style.color != []:
                        color = style.color
                    else:
                        color = [[0.0, 'rgb(242,240,247)'], [0.2, 'rgb(218,218,235)'], [0.4, 'rgb(188,189,220)'], \
                                 [0.6, 'rgb(158,154,200)'], [0.8, 'rgb(117,107,177)'], [1.0, 'rgb(84,39,143)']]

                    text = ''

                    if 'text' in data_frame.columns:
                        text = data_frame['Text']

                    data = [dict(
                        type='choropleth',
                        colorscale=color,
                        autocolorscale=False,
                        locations=data_frame['Code'],
                        z=data_frame[style.plotly_choropleth_field].astype(float),
                        locationmode=style.plotly_location_mode,
                        text=text,
                        marker=dict(
                            line=dict(
                                color='rgb(255,255,255)',
                                width=1
                            )
                        ),
                        colorbar=dict(
                            title=style.units
                        )
                    )]

                    layout = dict(
                        title=style.title,
                        geo=dict(
                            scope=style.plotly_scope,
                            projection=dict(type=style.plotly_projection),
                            showlakes=True,
                            lakecolor='rgb(255, 255, 255)',
                        ),
                    )

                    fig = dict(data=data, layout=layout)

                if chart_type_ord not in ['surface', 'choropleth', 'heatmap']:

                    fig = data_frame.iplot(kind=chart_type_ord,
                                           title=style.title,
                                           xTitle=style.x_title,
                                           yTitle=style.y_title,
                                           x=x, y=y, z=z,
                                           subplots=False,
                                           mode=mode,
                                           size=marker_size,
                                           theme=style.plotly_theme,
                                           bestfit=style.line_of_best_fit,
                                           legend=style.display_legend,
                                           color=color_spec1,
                                           dimensions=(style.width * abs(style.scale_factor) * scale,
                                                       style.height * abs(style.scale_factor) * scale),
                                           asFigure=True)

                fig.update(dict(layout=dict(legend=dict(
                    x=0.05,
                    y=1
                ))))

                import plotly.graph_objs as go

                if style.thin_margin:
                    fig.update(dict(layout=dict(margin=go.Margin(
                        l=20,
                        r=20,
                        b=40,
                        t=40,
                        pad=0
                    ))))

                fig.update(dict(layout=dict(paper_bgcolor='rgba(0,0,0,0)')))
                fig.update(dict(layout=dict(plot_bgcolor='rgba(0,0,0,0)')))

                fig_list.append(fig)

            if len(fig_list) > 1:
                import cufflinks
                fig = cufflinks.subplots(fig_list)
            else:
                fig = fig_list[0]

        self.publish_plot(fig, style)

    def publish_plot(self, fig, style):
        fig.update(dict(layout=dict(paper_bgcolor='rgba(0,0,0,0)')))
        fig.update(dict(layout=dict(plot_bgcolor='rgba(0,0,0,0)')))

        if style.plotly_plot_mode == 'online':
            plotly.tools.set_credentials_file(username=style.plotly_username, api_key=style.plotly_api_key)

            plotly.plotly.plot(fig, filename=style.plotly_url,
                    world_readable=style.plotly_world_readable,
                    auto_open = not(style.silent_display),
                    asImage=style.plotly_as_image)

        elif style.plotly_plot_mode == 'offline_html':
            if style.html_file_output is not None:
                temp_html = style.html_file_output
            else:
                import time
                style.html_file_output = self.get_time_stamp() + "-plotly.html"
                temp_html = style.html_file_output

            plotly.offline.plot(fig, filename=temp_html, auto_open = not(style.silent_display))

        elif style.plotly_plot_mode == 'offline_jupyter':

            # plot in IPython notebook
            plotly.offline.init_notebook_mode()
            plotly.offline.iplot(fig)

        # plotly.offline.plot(fig, filename=style.file_output, format='png',
        #         width=style.width * style.scale_factor, height=style.height * style.scale_factor)
        try:
            plotly.plotly.image.save_as(fig, filename=style.file_output, format='png',
                                width=style.width * abs(style.scale_factor), height=style.height * abs(style.scale_factor))
        except: pass

    def get_color_list(self, i):
        color_palette = cc.plotly_palette

        return color_palette[i % len(color_palette)]

#######################################################################################################################

class ColorMaster:

    def create_color_list(self, style, data_frame, cols = None):
        if cols is None:
            cols = data_frame.columns

        # get all the correct colors (and construct gradients if necessary eg. from 'blues')
        color = self.construct_color(style, 'color', len(cols) - len(style.color_2_series))
        color_2 = self.construct_color(style, 'color_2', len(style.color_2_series))

        return self.assign_color(cols, color, color_2,
                                 style.exclude_from_color, style.color_2_series)

    def construct_color(self, style, color_field_name, no_of_entries):
        color = []

        if hasattr(style, color_field_name):
            if isinstance(getattr(style, color_field_name), list):
                color = getattr(style, color_field_name, color)
            else:
                try:
                    color = self.create_colormap(no_of_entries, getattr(style, color_field_name))
                except:
                    pass

        return color

    def exclude_from_color(self, style):
        if not (isinstance(style.exclude_from_color, list)):
            style.exclude_from_color = [style.exclude_from_color]

        exclude_from_color = [str(x) for x in style.exclude_from_color]

        return exclude_from_color

    def assign_color(self, labels, color, color_2, exclude_from_color,
                     color_2_series):

        color_list = []

        axis_1_color_index = 0;
        axis_2_color_index = 0

        # convert all the labels to strings
        labels = [str(x) for x in labels]

        # go through each label
        for label in labels:
            color_spec = None

            if label in exclude_from_color:
                color_spec = None

            elif label in color_2_series:
                if color_2 != []:
                    color_spec = self.get_color_code(color_2[axis_2_color_index])
                    axis_2_color_index = axis_2_color_index + 1

            else:
                if color != []:
                    color_spec = self.get_color_code(color[axis_1_color_index])
                    axis_1_color_index = axis_1_color_index + 1

            try:
                color_spec = matplotlib.colors.colorConverter.to_rgba(color_spec)
            except:
                pass

            color_list.append(color_spec)

        return color_list

    def get_color_code(self, code):
        # redefine color names
        dict = cc.chartfactory_color_overwrites

        if code in dict: return dict[code]

        return code

    def create_colormap(self, num_colors, map_name):
        ## matplotlib ref for colors: http://matplotlib.org/examples/color/colormaps_reference.html

        cm = matplotlib.cm.get_cmap(name=map_name)

        return [cm(1. * i / num_colors) for i in range(num_colors)]


import os
import pyowm
import pytz
import requests
import time
from pyowm.utils import timestamps
from datetime import timedelta, datetime
import dotenv
from dotenv import load_dotenv

load_dotenv()
"""Loading the value keys for OPEN_WEATHER_TOKEN and TELEGRAM_BOT_TOKEN from the .env file"""

owm = pyowm.OWM(os.environ['OPEN_WEATHER_TOKEN'])
"""
Using PyOWM wrapper library

PyOWM calls the 5-day weather forecast for selected city from Open Weather Map API
Place is predefined and we want the forecast with 3 hour intervals
"""
mgr = owm.weather_manager()
forecaster = mgr.forecast_at_place('Kuopio', '3h')
forecast = forecaster.forecast
weather_list = forecast.weathers

degree_sign = u'\N{DEGREE SIGN}'
"""Degree sign for the weather message temperatures"""

def degrees_to_cardinal(angle):
    """Converting degrees in Wind Direction to cardinal directions"""

    directions = ['North ↓', 'North East ↙', 'East ←', 'South East ↖',
                  'South ↑', 'South West ↗', 'West →', 'North West ↘']
    ix = int((angle + 22.5) / 45)
    return directions[ix % 8]

for weather in weather_list[0:5]:
    """Selecting the first five 3h intervals from the 5 day forecast using For loop"""

    reference_unix = weather.reference_time()
    finland = pytz.timezone('Europe/Helsinki')
    gmt = pytz.timezone('GMT')
    my_timezone = datetime.utcfromtimestamp(reference_unix)
    my_timezone = gmt.localize(my_timezone)
    my_timezone_finland = my_timezone.astimezone(finland)
    """
    Using PYTZ module for timezone conversions

    Timezone conversion from unix time -> GMT/UTC +2:00
    """

    temp = weather.temperature(unit='celsius')['temp']
    feels_like = weather.temperature(unit='celsius')['feels_like']
    detailed_status = weather.detailed_status
    wind_speed = weather.wind('meters_sec')['speed']
    wind_direction = weather.wind()['deg']
    """Parsing JSON data using PYOWM"""

    wind_direction_text = degrees_to_cardinal(int(wind_direction))
    """The 'degrees_to_cardinal' function defined earlier converts degrees into cardinal directions"""

    snow = forecaster.most_snowy()
    rain = forecaster.most_rainy()
    """
    The highest amount of rain/snow in the forecast will be presented here
    If the value from 'all' key is missing from the JSON response, this returns value 'None'
    """

    weather_message = ('Hello, Risto! Here is the weather forecast for the next few hours:' f'\n{my_timezone_finland.strftime("%d-%m-%Y %H:%M:%S")}' +
                       f'\nTemperature: {temp}{degree_sign}C' + f'\nFeels Like: {feels_like}{degree_sign}C' + f'\nWeather description: {detailed_status}' +
                       f'\nWind speed: {wind_speed} m/s' + f'\nWind direction: {wind_direction_text}' + f'\nSnowfall: {snow}' + f'\nRain: {rain}')
    """Compiling the weather forecast information message to be sent to Telegram"""

    telegram_bot_url = 'https://api.telegram.org/bot' + os.environ['TELEGRAM_BOT_TOKEN'] + '/sendMessage?chat_id=' + os.environ['TELEGRAM_CHAT_ID'] + '&text={}'.format(
        weather_message)
    """Sending the weather message from the Telegram bot to the user"""

    requests.get(telegram_bot_url)
    time.sleep(10800)
    continue
    """
    Sends the message at selected intervals (time in seconds), e.g one hour = 3600, three hours = 10800

    For loop continues until all five forecasts have been sent, then stops the program.
    """


from configparser import RawConfigParser
import os
import json

config_dir_path = os.path.dirname(os.path.realpath(__file__)) + '/config/'

class Configuration(object):

    def __init__(self):
        self.config = RawConfigParser()
        self.config.read(config_dir_path + 'aurora.conf')

        self.core = None
        self.hardware = None
        self.static_light = None
        self.light_show = None

        self.set_core()
        self.set_hardware()
        self.set_static_light()
        self.set_light_show()

    def set_core(self):
        section = 'core'
        core = dict()

        core['port'] = self.config.getint(section, 'port')
        core['hostname'] = self.config.get(section, 'hostname')
        core['start_messaged_service'] = self.config.getboolean(section, 'start_messaged_service')

        self.core = Section(core)

    def set_hardware(self):
        section = 'hardware'
        hdwr = dict()

        hdwr['red_pin'] = self.config.getint(section, 'red_pin')
        hdwr['green_pin'] = self.config.getint(section, 'green_pin')
        hdwr['blue_pin'] = self.config.getint(section, 'blue_pin')

        self.hardware = Section(hdwr)

    def set_static_light(self):
        section = 'static_light'
        sl = dict()

        sl['enabled'] = self.config.getboolean(section, 'enabled')
        sl['run_at_start'] = self.config.getboolean(section, 'run_at_start')
        sl['initial_preset'] = json.loads(self.config.get(section, 'initial_preset'))

        self.static_light = Section(sl)

    def set_light_show(self):
        section = 'light_show'
        ls = dict()

        ls['enabled'] = self.config.getboolean(section, 'enabled')
        ls['run_at_start'] = self.config.getboolean(section, 'run_at_start')

        ls['fifo_path'] = self.config.get(section, 'fifo_path')
        ls['attenuate_pct'] = self.config.getint(section, 'attenuate_pct')
        ls['SD_low'] = self.config.getfloat(section, 'SD_low')
        ls['SD_high'] = self.config.getfloat(section, 'SD_high')
        ls['decay_factor'] = self.config.getfloat(section, 'decay_factor')
        ls['delay'] = self.config.getfloat(section, 'delay')
        ls['chunk_size'] = self.config.getint(section, 'chunk_size')
        ls['sample_rate'] = self.config.getint(section, 'sample_rate')
        ls['min_frequency'] = self.config.getint(section, 'min_frequency')
        ls['max_frequency'] = self.config.getint(section, 'max_frequency')
        ls['input_channels'] = self.config.getint(section, 'input_channels')

        temp = self.config.get(section, 'custom_channel_mapping')
        ls["custom_channel_mapping"] = map(int, temp.split(',')) if temp else 0
        temp = self.config.get(section, 'custom_channel_frequencies')
        ls["custom_channel_frequencies"] = map(int, temp.split(',')) if temp else 0

        self.light_show = Section(ls)

class Section(object):
    def __init__(self, config):
        self.config = config
        self.set_values(self.config)

    def set_config(self, config):
        self.config = config
        self.set_values(self.config)

    def get_config(self):
        return self.config

    def set_value(self, key, value):
        setattr(self, key, value)

    def set_values(self, dict_of_items):
        for key, value in dict_of_items.items():
            setattr(self, key, value)

    def get(self, item):
        return getattr(self, item)


#
# Programming Assignment #2
#
# Contents:
#    - ZooAnimal
#

import codecs
import json
import time
import threading

from kazoo.client import KazooClient, KazooState

from .util import local_ip4_addr_list

ZOOKEEPER_ADDRESS = "10.0.0.1"
ZOOKEEPER_PORT = "2181"
ZOOKEEPER_LOCATION = '{zookeeper_ip}:{zookeeper_port}'.format(zookeeper_ip=ZOOKEEPER_ADDRESS,
                                                              zookeeper_port=ZOOKEEPER_PORT)
# For mininet -> 10.0.0.x
NETWORK_PREFIX = "10"

ZOOKEEPER_PATH_STRING = '/topic/{topic}/{role}'
PATH_TO_MASTER_BROKER = "/broker/master"

#####################################################
#
# ZooAnimal for Zookeeper Registrants
# Broker will Overload Zookeeper Register
# Properties must be defined by children:
#   - role
#   - approach
#   - topic
#
######################################################

class ZooAnimal:
    def __init__(self):
        self.zk = KazooClient(hosts=ZOOKEEPER_LOCATION)
        self.zk.start()
        # Use util function to get IP address
        self.ipaddress = [ip for ip in list(local_ip4_addr_list()) if ip.startswith(NETWORK_PREFIX)][0]
        # Inheriting children should assign values to fit the scheme
        # /role/topic
        self.role = None
        self.topic = None
        #Will only be set by pub and sub
        self.broker = None
        # Zookeeper
        #self.election = None
        self.election = self.zk.Election('/broker', self.ipaddress)
        self.zk_seq_id = None
        self.zk_is_a_master = False

    def zookeeper_watcher(self, watch_path):
        @self.zk.DataWatch(watch_path)
        def zookeeper_election(data, stat, event):
            print("Setting election watch.")
            print("Watching node -> ", data)
            if data is None:
                print("Data is none.")
                self.election.run(self.zookeeper_register)
                #self.election.cancel()

    def zookeeper_master(self):
        if not self.zk_is_a_master:
            print("ZOOANIMAL -> Becoming a master.")
            role_topic = "/broker/master"
            data = {'ip': self.ipaddress}
            data_string = json.dumps(data)
            encoded_ip = codecs.encode(data_string, "utf-8")
            self.zk.create(role_topic, ephemeral=True,
                           makepath=True, sequence=True, value=encoded_ip)
            self.zk_is_a_master = True
        return self.zk_is_a_master

    def zookeeper_register(self):
        pass

    # This is a function stub for the get_broker watch callback
    # The child is expected to implement their own logic
    # Pub and Sub need to register_sub()
    def broker_update(self, data):
        print("Broker updated.")
        print("Data -> {}".format(data))
        pass

    def get_broker(self):
        for i in range(10):
            if self.zk.exists(PATH_TO_MASTER_BROKER):
                node_data = self.zk.get(PATH_TO_MASTER_BROKER, watch=self.broker_update)
                broker_data = node_data[0]
                master_broker = codecs.decode(broker_data, 'utf-8')
                if master_broker != '':
                    self.broker = master_broker
                    return self.broker
                else:
                    raise Exception("No master broker.")
            time.sleep(0.2)

###########################################################################

class ZooLoad(ZooAnimal):
    def __init__(self):
        super().__init__()
        self.role = "load"
        self.topic = "balance"
        self.zookeeper_register()

    def zookeeper_register(self):
        role_topic = ZOOKEEPER_PATH_STRING.format(
            role=self.role, topic=self.topic)
        encoded_ip = codecs.encode(self.ipaddress, "utf-8")
        try:
            self.zk.create(role_topic, ephemeral=True,
                           sequence=True, makepath=True, value=encoded_ip)
        except:
            print(
                "Exception -> zooanimal.py -> zookeeper_register -> load elif statement")

#############################################################################################

class ZooProxy(ZooAnimal):
    def __init__(self):
        super().__init__()
        self.role = 'broker'
        self.topic = 'pool'
        self.zk_path = ZOOKEEPER_PATH_STRING.format(role=self.role, topic=self.topic)
        self.zookeeper_register()

    def zookeeper_register(self):
        if self.role == 'broker':
            broker_path = "/broker"
            data = {}
            data['ip'] = self.ipaddress
            data_string = json.dumps(data)
            encoded_ip = codecs.encode(data_string)
            if self.zk_seq_id == None:
                self.zk.create(self.zk_path, ephemeral=True, sequence=True, makepath=True, value=encoded_ip)
                brokers = self.zk.get_children(broker_path)
                brokers = [x for x in brokers if "lock" not in x]
                brokers = [x for x in brokers if "master" not in x]
                print(brokers)
                broker_nums = {y: int(y[4:]) for y in brokers}
                #sort based on the values
                broker_sort = sorted(broker_nums, key=lambda data: broker_nums[data])
                latest_id = broker_sort[-1]
                print(latest_id)
                self.zk_seq_id = latest_id
            for i in range(10):
                if self.zk.exists(broker_path+"/master") == None:
                    self.zookeeper_master()
                    break
                time.sleep(0.2)
            if self.zk.exists(broker_path + "/master"):
                # Get all the children
                path = self.zk.get_children(broker_path)
                # Remove the master

                #path.pop(path.index(self.zk_seq_id))
                # Process out the locks
                path = [x for x in path if "lock" not in x]
                path = [x for x in path if "master" not in x]
                #Convert into a dictionary of znode:sequential
                #We keep the path name as the key
                #Use the sequential number as the value
                # e.g. key pool000001 value 000001
                path_sort = sorted(path, key=lambda data: data[4:])
                previous = path_sort[path_sort.index(self.zk_seq_id)-1]
                watch_path = broker_path + "/" + previous
                self.zookeeper_watcher(watch_path)

##########################################################################################

class ZooClient(ZooAnimal):
    def __init__(self, role=None, topic="00000", history="5"):
        super().__init__()
        self.role = role
        self.topic = topic
        self.history = int(history)
        self.zk_seq_id = None
        self.zk_register()
        self.zk_ownership = self.zk_watch_owner()

    def zk_register(self):
        topic = "/topic/" + self.topic
        if not self.zk_seq_id:
            try:
                topic_clients = self.zk.get_children(topic)
                self.zk_ownership = len([x for x in topic_clients if self.role in x])
            except:
                self.zk_ownership = 0
            topic_role = topic + "/" + self.role
            json_data = {'ip': self.ipaddress, 'history': self.history, 'ownership': self.zk_ownership}
            json_string = json.dumps(json_data)
            json_encoded = codecs.encode(json_string, 'utf-8')
            self.zk.create(topic_role, ephemeral=True, makepath=True, sequence=True, value=json_encoded)
            topic_clients = self.zk.get_children(topic)
            topic_sort = sorted(topic_clients, key=lambda data: int(data[-5:]))
            self.zk_seq_id = topic_sort[-1]
            print("ZOOANIMAL ID -> {}".format(self.zk_seq_id))
        else:
            json_data = {'ip': self.ipaddress, 'history': self.history, 'ownership': self.zk_ownership}
            json_string = json.dumps(json_data)
            json_encoded = codecs.encode(json_string, 'utf-8')
            self.zk.set(topic + "/" + self.zk_seq_id, json_encoded)

    def zk_get_owner_position(self):
        topic = "/topic/" + self.topic
        topic_clients = self.zk.get_children(topic)
        topic_roles = [x for x in topic_clients if self.role in x]
        topic_sort = sorted(topic_roles, key=lambda data: int(data[-5:]))
        return topic_sort.index(self.zk_seq_id)

    def zk_watch_owner(self):
        self.zk_ownership = self.zk_get_owner_position()
        if self.zk_ownership != 0:
            topic = "/topic/" + self.topic
            topic_clients = self.zk.get_children(topic)
            topic_roles = [x for x in topic_clients if self.role in x]
            topic_sort = sorted(topic_roles, key=lambda data: int(data[-5:]))
            topic_index = topic_sort.index(self.zk_seq_id)
            self.zk.get(topic+'/'+topic_sort[topic_index-1], watch=self.zk_owner_reset)
        return self.zk_ownership

    def zk_owner_reset(self, data):
        self.zk_watch_owner()
        self.zk_register()


# -*- coding: utf-8 -*-
##!/usr/bin/python3

'''
sources of inspiration:
https://cseweb.ucsd.edu/~elkan/250Bwinter2011/mixturemodels.pdf
https://www.python-course.eu/expectation_maximization_and_gaussian_mixture_models.php
'''

import time
import numpy as np
from scipy.stats import norm
from matplotlib import pyplot as plt
from matplotlib import style

def gaussian_mix(sample_size, mu, sigma, weights):
    sample = np.empty((sample_size))
    for i in range(sample_size):
        Z = np.random.choice([0,1], p=weights) # latent variable
        sample[i] = (np.random.normal(mu[Z], sigma[Z], 1))
    return sample

class gaussian_mix_solver:
    def __init__(self, sample, max_iterations, tolerance):
        self.sample = sample
        self.max_iterations = max_iterations
        self.tolerance = tolerance
        # Start with arbitrary mu, sigma and weights
        self.mu = [0,0]
        self.sigma = [1,10]
        self.weights = [1/2,1/2]

        self.iter_mu = [self.mu]
        self.iter_sigma = [self.mu]
        self.iter_weights = [self.mu]

        self.log_likelihoods = []

    def run(self, plot):

        ### Plot initial ###
        if plot:
            fig = plt.figure(figsize=(10,10))
            ax0 = fig.add_subplot(111)
            """Plot the data"""
            for i in range(n):
                ax0.scatter(self.sample[i],0,s=100)
                #ax0.scatter(self.sample[i],0,c=np.array([membership[i][0],membership[i][1],membership[i][2]]),s=100)
            """Plot the gaussians"""
            for g,c in zip([norm(loc=self.mu[0],scale=self.sigma[0]).pdf(np.linspace(-20,20,num=n)),
                            norm(loc=self.mu[1],scale=self.sigma[1]).pdf(np.linspace(-20,20,num=n))],['r','b']):
                ax0.plot(np.linspace(-20,20,num=n),g,c=c)
            plt.title("estimation after 0 iterations:\n mu={},\n sigma={},\n weights={}".format(self.mu, self.sigma, self.weights))
            plt.show()

            print("\nEstimation at iteration no: 0")
            print(" mu= {}".format(self.mu))
            print(" sigma= {}".format(self.sigma))
            print(" weights= {}".format(self.weights))

        for iter in range(self.max_iterations):

            log_likelihood = np.log(np.sum([k*norm(self.mu[c],self.sigma[c]).pdf(self.sample) for k,c in zip(self.weights,range(2))]))
            self.log_likelihoods.append(log_likelihood)

            ### E-Step ###

            """Calculate probability for each datapoint x_i to belong to gaussian g"""
            membership = np.zeros((n,2))
            for column,g,weight in zip(range(2),
                                       [norm(loc=self.mu[0],scale=self.sigma[0]),
                                        norm(loc=self.mu[1],scale=self.sigma[1])],
                                       self.weights):
                # Write probability in column of membership
                membership[:,column] = weight*g.pdf(self.sample)
            """
            Now, Normalize the probabilities such that each row of membership sums to 1
            and weight it by mu_c == the fraction of points belonging to cluster c
            """
            for i in range(n):
                membership[i] = membership[i]/(np.sum(self.weights)*np.sum(membership,axis=1)[i])

            ### M-Step ###

            # For each cluster c, calculate the mean and add it to the list mean_c
            membership_cumulative = []
            for c in range(2):
                cumulative = np.sum(membership[:,c])
                membership_cumulative.append(cumulative)

            # For each cluster c, calculate the fraction of points which belongs to cluster c
            for c in range(2):
                fraction = membership_cumulative[c] / n
                self.weights[c] = fraction

            # calculate mu_c
            for c in range(2):
                mean  = np.sum(membership[:,c] * self.sample)
                mean /= membership_cumulative[c]
                self.mu[c] = mean

            # calculate var_c
            for c in range(2):
                var  = np.sum(membership[:,c] * np.power((self.sample-self.mu[c]), 2))
                var /= membership_cumulative[c]
                self.sigma[c] = np.sqrt(var)

            self.iter_mu.append(self.mu)
            self.iter_sigma.append(self.sigma)
            self.iter_weights.append(self.weights)

        best_iteration = np.argmax(self.log_likelihoods)
        self.max_mu = self.iter_mu[best_iteration]
        self.max_sigma = self.iter_sigma[best_iteration]
        self.max_weights = self.iter_weights[best_iteration]

        ### Plot final ###
        if plot:
            fig = plt.figure(figsize=(10,10))
            ax0 = fig.add_subplot(111)
            """Plot the data"""
            for i in range(n):
                ax0.scatter(self.sample[i],0,s=100)
                #ax0.scatter(self.sample[i],0,c=np.array([membership[i][0],membership[i][1],membership[i][2]]),s=100)
            """Plot the gaussians"""
            for g,c in zip([norm(loc=self.max_mu[0],scale=self.max_sigma[0]).pdf(np.linspace(-20,20,num=n)),
                            norm(loc=self.max_mu[1],scale=self.max_sigma[1]).pdf(np.linspace(-20,20,num=n))],['r','b']):
                ax0.plot(np.linspace(-20,20,num=n),g,c=c)
            plt.title("after {} iterations:\n mu={},\n sigma={},\n weights={}".format(iter, self.max_mu, self.max_sigma, self.max_weights))
            plt.show()

            print("\nBest result found at teration no: {}".format(best_iteration))
            print(" mu= {}".format(self.max_mu))
            print(" sigma= {}".format(self.max_sigma))
            print(" weights= {}".format(self.max_weights))

if __name__ == '__main__':

    n_samples = 5000
    n_samples = 500
    n = 250

    mu = 0
    var1 = 1
    var2 = 10

    max_iterations = 7
    tolerance = 0.0
    # real parameters for mixed gaussian
    real_mu = [0, 0]
    real_sigma = [1, np.sqrt(10)]
    real_weights = [.7, .3]

    print("---------------------------------------------------------------\n")
    print("            Uebung 4_5 David Blacher, Johannes Kurz            \n")
    print("---------------------------------------------------------------\n")
    print("Ziehe {}-mal Stichproben mit n={} aus Mischung "
          "von Normalverteilungen mit mu={}, var1={} & var2={}"
          .format(n_samples, n, mu,var1,var2))

    estimated_mu = np.empty((n_samples, 2))
    estimated_sigma = np.empty((n_samples, 2))
    estimated_weights = np.empty((n_samples, 2))

    for simulation in range(n_samples):
        entire_sample = gaussian_mix(n, real_mu, real_sigma, real_weights)
        gaussian_model = gaussian_mix_solver(entire_sample,max_iterations, tolerance)
        if simulation == 0:
            print("Beispielhaft wird eine numerische Berechnung grafisch sichtbar gemacht.")
            gaussian_model.run(True)

            plt.plot ( range(len(gaussian_model.log_likelihoods)), gaussian_model.log_likelihoods, label="priorA"  )
            plt.title("log likelyhood maximisation")
            plt.show()

            print("\nBitte geben Sie uns 10 sekunden für den Rest der Simulationen...")
        else:
            gaussian_model.run(False)

        estimated_mu[simulation] = gaussian_model.max_mu
        estimated_sigma[simulation] = gaussian_model.max_sigma
        estimated_weights[simulation] = gaussian_model.max_weights

    overall_mean_mu = np.sum(estimated_mu, axis=0) / n_samples
    overall_mean_sigma = np.sum(estimated_sigma, axis=0) / n_samples
    overall_mean_weights = np.sum(estimated_weights, axis=0) / n_samples

    print("\nNach {} Simulationen, sind dies die gemittelten Ergebnisse:".format(n_samples))
    print(" overall_mu= {}".format(overall_mean_mu))
    print(" overall_sigma= {}".format(overall_mean_sigma))
    print(" overall_weights= {}".format(overall_mean_weights))


import numpy as np
import matplotlib.pyplot as plt
from gaussian_elimination_bandmatrix import gauss_solv

def matrix_vector(n):
	A = np.zeros((n, n))
	b = np.zeros((n, 1))
	A[0,0] = -2
	A[0,1] = 1
	b[0,0] = (1/(n+1))**2
	for i in range(1, n-1):
		A[i,i-1] = 1
		A[i,i] = -2
		A[i,i+1] = 1
		b[i,0] = ((i+1)/(n+1))**2
	A[n-1, n-2] = 1
	A[n-1, n-1] = -2
	b[n-1,0] = (n/(n+1))**2
	return (((n+1)**2)*A, b)

def discrete_sol(n):
	tmp = matrix_vector(n)
	return np.hstack((0, gauss_solv(tmp[0], tmp[1], 1), 0))

def visualize(n):
	x1 = np.linspace(0, 1, 100*n)
	x2 = np.linspace(0, 1, n+2)
	y1 = u(x1)
	plt.plot(x1, y1)
	plt.plot(x2, d)
	plt.show()

def variation(n):
	h = 1/(n+1)
	sum = 0
	for i in range(1, n+3):
		sum += abs(u(i*h)-d[i-1])
	return (1/(n+2))*sum

n = int(input("Please enter 'n': "))
u = lambda x: (1/12)*(x**4)-(1/12)*x
d = discrete_sol(n)
print("Variation: {}".format(variation(n)))
visualize(n)


import contextlib
from typing import List, Any, Dict

import ray
import ray.cloudpickle
from ray.util.serialization import register_serializer, deregister_serializer

from ray.experimental.workflow.common import Workflow, WorkflowInputs

def _resolve_workflow_outputs(index: int) -> Any:
    raise ValueError("There is no context for resolving workflow outputs.")

def _resolve_objectrefs(index: int) -> ray.ObjectRef:
    raise ValueError("There is no context for resolving object refs.")

@contextlib.contextmanager
def workflow_args_serialization_context(
        workflows: List[Workflow], object_refs: List[ray.ObjectRef]) -> None:
    """
    This serialization context reduces workflow input arguments to three
    parts:

    1. A workflow input placeholder. It is an object without 'Workflow' and
       'ObjectRef' object. They are replaced with integer indices. During
       deserialization, we can refill the placeholder with a list of
       'Workflow' and a list of 'ObjectRef'. This provides us great
       flexibility, for example, during recovery we can plug an alternative
       list of 'Workflow' and 'ObjectRef', since we lose the original ones.
    2. A list of 'Workflow'. There is no duplication in it.
    3. A list of 'ObjectRef'. There is no duplication in it.

    We do not allow duplication because in the arguments duplicated workflows
    and object refs are shared by reference. So when deserialized, we also
    want them to be shared by reference. See
    "tests/test_object_deref.py:deref_shared" as an example.

    The deduplication works like this:
        Inputs: [A B A B C C A]
        Output List: [A B C]
        Index in placeholder: [0 1 0 1 2 2 0]

    Args:
        workflows: Workflow list output.
        object_refs: ObjectRef list output.
    """
    workflow_deduplicator: Dict[Workflow, int] = {}
    objectref_deduplicator: Dict[ray.ObjectRef, int] = {}

    def workflow_serializer(workflow):
        if workflow in workflow_deduplicator:
            return workflow_deduplicator[workflow]
        i = len(workflows)
        workflows.append(workflow)
        workflow_deduplicator[workflow] = i
        return i

    register_serializer(
        Workflow,
        serializer=workflow_serializer,
        deserializer=_resolve_workflow_outputs)

    def objectref_serializer(obj_ref):
        if obj_ref in objectref_deduplicator:
            return objectref_deduplicator[obj_ref]
        i = len(object_refs)
        object_refs.append(obj_ref)
        objectref_deduplicator[obj_ref] = i
        return i

    # override the default ObjectRef serializer
    # TODO(suquark): We are using Ray internal APIs to access serializers.
    # This is only a workaround. We need alternatives later.
    ray_objectref_reducer_backup = ray.cloudpickle.CloudPickler.dispatch[
        ray.ObjectRef]
    register_serializer(
        ray.ObjectRef,
        serializer=objectref_serializer,
        deserializer=_resolve_objectrefs)

    try:
        yield
    finally:
        # we do not want to serialize Workflow objects in other places.
        deregister_serializer(Workflow)
        # restore original dispatch
        ray.cloudpickle.CloudPickler.dispatch[
            ray.ObjectRef] = ray_objectref_reducer_backup

@contextlib.contextmanager
def workflow_args_resolving_context(
        workflow_output_mapping: List[Any],
        objectref_mapping: List[ray.ObjectRef]) -> None:
    """
    This context resolves workflows and objectrefs inside workflow
    arguments into correct values.

    Args:
        workflow_output_mapping: List of workflow outputs.
        objectref_mapping: List of object refs.
    """
    global _resolve_workflow_outputs, _resolve_objectrefs
    _resolve_workflow_outputs_bak = _resolve_workflow_outputs
    _resolve_objectrefs_bak = _resolve_objectrefs

    _resolve_workflow_outputs = workflow_output_mapping.__getitem__
    _resolve_objectrefs = objectref_mapping.__getitem__

    try:
        yield
    finally:
        _resolve_workflow_outputs = _resolve_workflow_outputs_bak
        _resolve_objectrefs = _resolve_objectrefs_bak

class _KeepWorkflowOutputs:
    def __init__(self, index: int):
        self._index = index

    def __reduce__(self):
        return _resolve_workflow_outputs, (self._index, )

class _KeepObjectRefs:
    def __init__(self, index: int):
        self._index = index

    def __reduce__(self):
        return _resolve_objectrefs, (self._index, )

@contextlib.contextmanager
def workflow_args_keeping_context() -> None:
    """
    This context only read workflow arguments. Workflows and objectrefs inside
    are untouched and can be serialized again properly.
    """
    global _resolve_workflow_outputs, _resolve_objectrefs
    _resolve_workflow_outputs_bak = _resolve_workflow_outputs
    _resolve_objectrefs_bak = _resolve_objectrefs

    # we must capture the old functions to prevent self-referencing.
    def _keep_workflow_outputs(index: int):
        return _KeepWorkflowOutputs(index)

    def _keep_objectrefs(index: int):
        return _KeepObjectRefs(index)

    _resolve_workflow_outputs = _keep_workflow_outputs
    _resolve_objectrefs = _keep_objectrefs

    try:
        yield
    finally:
        _resolve_workflow_outputs = _resolve_workflow_outputs_bak
        _resolve_objectrefs = _resolve_objectrefs_bak

def make_workflow_inputs(args_list: List[Any]) -> WorkflowInputs:
    workflows: List[Workflow] = []
    object_refs: List[ray.ObjectRef] = []
    with workflow_args_serialization_context(workflows, object_refs):
        # NOTE: When calling 'ray.put', we trigger python object
        # serialization. Under our serialization context,
        # Workflows and ObjectRefs are separated from the arguments,
        # leaving a placeholder object with all other python objects.
        # Then we put the placeholder object to object store,
        # so it won't be mutated later. This guarantees correct
        # semantics. See "tests/test_variable_mutable.py" as
        # an example.
        input_placeholder: ray.ObjectRef = ray.put(args_list)
        if object_refs:
            raise ValueError(
                "There are ObjectRefs in workflow inputs. However "
                "workflow currently does not support checkpointing "
                "ObjectRefs.")
    return WorkflowInputs(input_placeholder, object_refs, workflows)


from setuptools import setup, find_packages
from os.path import join

version = '4.0.dev0'

tests_require=[
      'cssselect',
      'lxml',
      'mock',
      'plone.api >=1.8.5',
      'plone.app.robotframework',
      'plone.app.testing [robot]',
      'plone.browserlayer',
      'plone.cachepurging',
      'plone.testing',
      'robotsuite',
      'testfixtures',
      'transaction',
      'tzlocal',
]

setup(name='Products.Maps',
      version=version,
      description="A simple, easy to use Plone integration with Google Maps",
      long_description=open("README.rst").read() + '\n' +
                       open(join('docs','HISTORY.rst')).read(),
      classifiers=[
        "Framework :: Zope2",
        "Programming Language :: Python",
        "Programming Language :: Python :: 2.7",
        "Programming Language :: Python :: 3.7",
        "Topic :: Software Development :: Libraries :: Python Modules",
        "Framework :: Plone",
        "Framework :: Plone :: 5.0",
        "Framework :: Plone :: 5.1",
        "Framework :: Plone :: 5.2",
        "License :: OSI Approved :: GNU General Public License (GPL)",
      ],
      keywords='Google Maps Zope Plone',
      author='Florian Schulze',
      author_email='fschulze@jarn.com',
      maintainer='Luca Fabbri',
      maintainer_email='luca@keul.it',
      url='http://plone.org/products/maps',
      license='GPL',
      packages=find_packages(exclude=['ez_setup']),
      namespace_packages=['Products'],
      include_package_data=True,
      zip_safe=False,
      tests_require=tests_require,
      extras_require=dict(test=tests_require),
      install_requires=[
          'setuptools',
          'six',
          'plone.formwidget.geolocation',
      ],
)


import requests
from bs4 import BeautifulSoup

def getValues():
    url = '3dhubs.com'
    r = requests.get('http://' + url)
    data = r.text
    soup = BeautifulSoup(data, 'html.parser')

    elements = soup.find_all('p', 'h3d-stat')
    values = {}
    values['parts'] = elements[0].find(class_='h3d-stat__value').string
    values['services'] = elements[2].find(class_='h3d-stat__value').string
    # values['reviews_number'] = elements[3].find(class_='h3d-stat__label').string
    values['reviews'] = '95,462'

    return values


from zope.component import getUtility, getMultiAdapter

from plone.portlets.interfaces import IPortletType
from plone.portlets.interfaces import IPortletManager
from plone.portlets.interfaces import IPortletAssignment
from plone.portlets.interfaces import IPortletDataProvider
from plone.portlets.interfaces import IPortletRenderer

from plone.app.portlets.storage import PortletAssignmentMapping

from optilux.cinemacontent.portlets import promotions

from optilux.cinemacontent.tests.base import CinemaContentTestCase

class TestPortlet(CinemaContentTestCase):

    def afterSetUp(self):
        self.setRoles(('Manager',))

    def testPortletTypeRegistered(self):
        portlet = getUtility(IPortletType, name='optilux.Promotions')
        self.assertEquals(portlet.addview, 'optilux.Promotions')

    def testInterfaces(self):
        portlet = promotions.Assignment()
        self.failUnless(IPortletAssignment.providedBy(portlet))
        self.failUnless(IPortletDataProvider.providedBy(portlet.data))

    def testInvokeAddview(self):
        portlet = getUtility(IPortletType, name='optilux.Promotions')
        mapping = self.portal.restrictedTraverse('++contextportlets++plone.leftcolumn')
        for m in mapping.keys():
            del mapping[m]
        addview = mapping.restrictedTraverse('+/' + portlet.addview)

        addview.createAndAdd(data={})

        self.assertEquals(len(mapping), 1)
        self.failUnless(isinstance(mapping.values()[0], promotions.Assignment))

    def testInvokeEditView(self):
        mapping = PortletAssignmentMapping()
        request = self.folder.REQUEST

        mapping['foo'] = promotions.Assignment()
        editview = getMultiAdapter((mapping['foo'], request), name='edit')
        self.failUnless(isinstance(editview, promotions.EditForm))

    def testRenderer(self):
        context = self.folder
        request = self.folder.REQUEST
        view = self.folder.restrictedTraverse('@@plone')
        manager = getUtility(IPortletManager, name='plone.rightcolumn', context=self.portal)
        assignment = promotions.Assignment()

        renderer = getMultiAdapter((context, request, view, manager, assignment), IPortletRenderer)
        self.failUnless(isinstance(renderer, promotions.Renderer))

class TestRenderer(CinemaContentTestCase):

    def afterSetUp(self):
        self.setRoles(('Manager',))
        self.portal.invokeFactory('Cinema Folder', 'cf1')
        self.portal.invokeFactory('Cinema Folder', 'cf2')
        self.portal.cf1.invokeFactory('Promotion', 'p1')
        self.portal.cf1.invokeFactory('Promotion', 'p2')
        self.portal.cf1.invokeFactory('Promotion', 'p3')
        self.portal.cf1.invokeFactory('Promotion', 'p4')
        self.portal.cf1.invokeFactory('Promotion', 'p5')
        self.portal.cf2.invokeFactory('Promotion', 'p6')
        self.portal.cf2.invokeFactory('Promotion', 'p7')

    def renderer(self, context=None, request=None, view=None, manager=None, assignment=None):
        context = context or self.folder
        request = request or self.folder.REQUEST
        view = view or self.folder.restrictedTraverse('@@plone')
        manager = manager or getUtility(IPortletManager, name='plone.rightcolumn', context=self.portal)
        assignment = assignment or promotions.Assignment()

        return getMultiAdapter((context, request, view, manager, assignment), IPortletRenderer)

    def test_count(self):
        r = self.renderer(context=self.portal.cf1, assignment=promotions.Assignment(count=5))
        self.assertEquals(5, len([p for p in r.promotions()]))

    def test_randomize(self):
        r = self.renderer(context=self.portal.cf1, assignment=promotions.Assignment(count=5, randomize=True))
        self.assertEquals(5, len([p for p in r.promotions()]))
        # Mmmm, hard to test for random things :)

    def test_sitewide(self):
        r = self.renderer(context=self.portal.cf1, assignment=promotions.Assignment(count=10, sitewide=True))
        p6_url = self.portal.cf2.p6.absolute_url()
        self.failUnless(p6_url in [p['url'] for p in r.promotions()])

def test_suite():
    from unittest import TestSuite, makeSuite
    suite = TestSuite()
    suite.addTest(makeSuite(TestPortlet))
    suite.addTest(makeSuite(TestRenderer))
    return suite


import numpy as np
import csv
from shapely.geometry import LineString

# for presentations
protocol = [[1,'G'],
[2,'HG'],
[3,'FG'],
[4,'BGD'],
[5,'CGD'],
[6,'EDFG'],
[7,'AFGH'],
[8,'GEABC'],
[9,'CHBDCE'],
[10,'DAFGHEB']]

box_positions = {
        "A": (0.85, 1.0),
        "B": (1.2, 3.6),
        "C": (1.7, 5.1),
        "D": (1.75, 1.4),
        "E": (2.3, 3.9),
        "F": (3.0, 5.2),
        "G": (3.2, 0.8),
        "H": (4.0, 2.0),
        "I": (3.45, 3.25)
}

def complete_dict(d, is_column = True):
    for i in range(1,5+1 if is_column else 4+1):
        if i not in d:
            d[i] = 0
    return d

def number_per_column(sequence):
    columns = [ box_positions[b][1] for b in sequence]
    return complete_dict({x:columns.count(x) for x in columns})

def number_per_row(sequence):
    rows = [ box_positions[b][0] for b in sequence]
    return complete_dict({x:rows.count(x) for x in rows}, is_column=False)

def leftness(sequence):
    columns = number_per_column(sequence)

    ret = 0
    multipliers = [ -2, -1, 0, 1, 2]
    for i in range(1,5+1):
        ret += columns[i] * multipliers[i-1]

    return ret

def frontness(sequence):
    rows = number_per_row(sequence)

    ret = 0
    multipliers = [ 2, 1, -1, -2]
    for i in range(1,4+1):
        ret += rows[i] * multipliers[i-1]

    return ret

def distances(sequence):
    d = []
    for i in range(len(sequence)-1):
        a = box_positions[sequence[i]]
        b = box_positions[sequence[i+1]]
        d.append(np.linalg.norm(np.array(a)-np.array(b)))

    return d

def all_distances():
    ret = []
    for i in list(box_positions.keys()):
        for j in list(box_positions.keys()):
            if i<j:
                ret.append([i,j, distances([i,j])[0]])

    return ret

def greedy_long_path():
    connected = []
    dist = all_distances()
    used = []

    i = 0

    heaviest = sorted(dist, key=lambda x: x[2], reverse=True)[0]
    connected.append((heaviest[0], heaviest[1]))

    head = heaviest[0]
    tail = heaviest[1]

    used.append(head)
    used.append(tail)

    print(heaviest, used, head, tail)

    while len(used)<len(list(box_positions.keys())):
        tail_sel = sorted([ x for x in dist if (x[0] == tail and x[1] not in used) or (x[1] == tail and x[0] not in used)]
            , key=lambda x: x[2], reverse=True)[0]

        head_sel = sorted([ x for x in dist if (x[0] == head and x[1] not in used) or (x[1] == head and x[0] not in used)]
            , key=lambda x: x[2], reverse=True)[0]

        if tail_sel[2]>head_sel[2]:
            heaviest = tail_sel
            new_tail = heaviest[0] if heaviest[1]==tail else heaviest[1]
            used.append(new_tail)
            connected.append((tail, new_tail))
            tail = new_tail
        else:
            heaviest = head_sel
            new_head = heaviest[0] if heaviest[1]==head else heaviest[1]
            used = [new_head] + used
            connected = [(new_head, head)] + connected
            head = new_head

        print(heaviest, used, head, tail)

        i+=1
        if i >= 30:
            print("bardo")
            break

    print(connected)

def save_protocol_csv(protocol, file_name="test.csv"):
    data = []
    data.append(["Ensayo","Sequencia","leftness","frontness","length","distances", "intersections","overlaps"])
    for [ensayo, sequence] in protocol:
        inter = intersections(sequence)
        data.append([ensayo,
                sequence,
                leftness(sequence),
                frontness(sequence),
                "%.2f"%sum(distances(sequence)),
                inter[0],
                inter[1]])

    with open(file_name, 'w') as fp:
        a = csv.writer(fp, delimiter=';')
        a.writerows(data)

def save_csv(file_name="test.csv"):
    data = []
    data.append(["Grupo", "Nivel","Ensayo","Sequencia","number_per_row","number_per_column","leftness","frontness","length","distances", "intersections","overlaps"])
    for (g,gr) in enumerate(trials_group):
        for [nivel, ensayo, sequence] in gr:
            inter = intersections(sequence)
            data.append([g+1,nivel, ensayo, " - ".join(sequence), number_per_row(sequence), number_per_column(sequence),
                leftness(sequence), frontness(sequence),
                "%.2f"%sum(distances(sequence)), ["%.2f" %x for x in distances(sequence)],
                inter[0],inter[1]])

    with open(file_name, 'w') as fp:
        a = csv.writer(fp, delimiter=';')
        a.writerows(data)

def make_segments(p):
    ret = []

    for i in range(0, len(p)-1):
        ret.append([box_positions[p[i]], box_positions[p[i+1]]])

    return ret

def intersections(path):

    segments = make_segments(path)
    count_intersection = 0
    count_overlap = 0

    for i in range(0, len(segments)-1):
        for j in range(i + 1, len(segments)):
            l1 = LineString(segments[i])
            l2 = LineString(segments[j])
            if l1.intersects(l2):
                inter = l1.intersection(l2)
                if i+1 == j and inter.length:
                    count_overlap += 1
                elif i+1!=j and inter.length:
                    count_overlap += 1
                elif i+1!=j and inter.length == 0.0:
                    count_intersection += 1

    return (count_intersection, count_overlap)


"""
Copyright [2009-2019] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import os
import pathlib

"""
Any setting defined here can be overridden by:

Settings the appropriate environment variable, eg. to override FOOBAR, `export APP_FOOBAR="whatever"`.
This is useful in production for secrets you do not wish to save in code and
also plays nicely with docker(-compose). Settings will attempt to convert environment variables to match the
type of the value here. See also activate.settings.sh.

Or, passing the custom setting as a keyword argument when initialising settings (useful when testing)
"""

# producer folder, where media, static, templates and other subfolders are located
PROJECT_ROOT = pathlib.Path(__file__).parent.parent

CONSUMER_SUBMIT_JOB_URL = 'submit-job'
CONSUMER_SUBMIT_INFERNAL_JOB_URL = 'submit-infernal-job'

MIN_QUERY_LENGTH = 10
MAX_QUERY_LENGTH = 7000

ENVIRONMENT = os.getenv('ENVIRONMENT', 'LOCAL')

# add settings from environment-specific files
if ENVIRONMENT == "LOCAL":
    from .local import *
elif ENVIRONMENT == "TEST":
    from .test import *
elif ENVIRONMENT == "DOCKER-COMPOSE":
    from .docker_compose import *
elif ENVIRONMENT == "PRODUCTION":
    from .production import *

EBI_SEARCH_PROXY_URL = 'https://search.rnacentral.org/api/post-rnacentral-ids'

def substitute_environment_variables():
    """
    Substitute environment variables into settings.

    This function is stolen from the default project, generated by
    aiohttp-devtools 'adev start' command.
    """
    for attr_name in globals():
        env_var = os.getenv(attr_name, None)

        if attr_name.startswith('_') or attr_name.upper() != attr_name:
            continue
        elif env_var is not None:
            # convert environment variable to the same type as the variable in settings
            original_type = type(globals()[attr_name])
            if issubclass(original_type, bool):
                env_var = env_var.upper() in ('1', 'TRUE')
            elif issubclass(original_type, int):
                env_var = int(env_var)
            elif issubclass(original_type, float):
                env_var = float(env_var)
            elif issubclass(original_type, pathlib.Path):
                env_var = pathlib.Path(env_var)
            elif issubclass(original_type, bytes):
                env_var = env_var.encode()

            globals()[attr_name] = env_var

substitute_environment_variables()


from django.shortcuts import render,redirect
from ecom import settings

from shop.models import Watch,Category
from django.views import View

# Example of CBV (post and Get both using same function)
# Class Base View
# class watch(View):
#     def post(self, request):
#         prod = request.POST.get('product')
#         # print(prod)
#         cart = request.session.get('cart')
#         if cart:
#             quantity = cart.get(prod)
#             if quantity:
#                 cart[prod] = quantity+1
#             else:
#                 cart[prod] = 1
#         else:
#             cart = {}
#             cart[prod] = 1
#         request.session['cart'] = cart
#         print('cart' ,request.session['cart'])
#         return redirect('/shop/pag')

#     def get(self, request):
#         shp = None
#         # request.session.get('cart').clear()
#         catego = Category.objects.all()
#         categoryID = request.GET.get('category')

#         if categoryID:
#             shp = Watch.objects.filter(category_id = categoryID)
#         else:
#             shp = Watch.objects.all()
#         data = {}
#         data['shop'] = shp
#         data['categ'] = catego
#         data['BASE_URL'] = settings.BASE_URL
#         print('you are : ', request.session.get('email'))
#         return render(request,'shop/watch.html',data)

# Authnticate APIs
def new(request,id):
    prod = request.POST.get('product')
    remove = request.POST.get('remove')
    cart = request.session.get('cart')
    if cart:
        quantity = cart.get(prod)
        if quantity:
            if remove:
                if quantity<=1:
                    cart.pop(prod)
                else:
                    cart[prod] = quantity-1
            else:
                cart[prod] = quantity+1
        else:
            cart[prod] = 1
    else:
        cart = {}
        cart[prod] = 1
    request.session['cart'] = cart
    print('cart' ,request.session['cart'])
    # show add product in list
    ids = list(request.session.get('cart').keys())
    allimages = Watch.objects.filter(id__in=ids)
    return render(request,'pages/cart.html',{"BASE_URL":settings.BASE_URL,'images':allimages})

def watch(request):
    shp = None
    catego = Category.objects.all()
    categoryID = request.GET.get('category')

    if categoryID:
        shp = Watch.objects.filter(category_id = categoryID)
    else:
        shp = Watch.objects.all()
    data = {}
    data['shop'] = shp
    data['categ'] = catego
    data['BASE_URL'] = settings.BASE_URL
    print('you are : ', request.session.get('email'))
    return render(request,'shop/watch.html',data)

# For Product detail
def product_detail(request,id):
    allimages = Watch.objects.filter(id=id)
    return render(request,'letest/product_details.html',{"BASE_URL":settings.BASE_URL,'images':allimages})


# This files contains your custom actions which can be used to run
# custom Python code.
#
# See this guide on how to implement these action:
# https://rasa.com/docs/rasa/custom-actions

# This is a simple example for a custom action which utters "Hello World!"

# from typing import Any, Text, Dict, List
#
# from rasa_sdk import Action, Tracker
# from rasa_sdk.executor import CollectingDispatcher
#
#
# class ActionHelloWorld(Action):
#
#     def name(self) -> Text:
#         return "action_hello_world"
#
#     def run(self, dispatcher: CollectingDispatcher,
#             tracker: Tracker,
#             domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
#
#         dispatcher.utter_message(text="Hello World!")
#
#         return []

from typing import Text, List, Optional, Dict, Any

from rasa_sdk import Tracker, Action
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.forms import FormValidationAction
from dotenv import load_dotenv
import os
import requests
import json
import uuid

load_dotenv()

airtable_api_key = os.getenv("AIRTABLE_API_KEY")
base_id = os.getenv("BASE_ID")
table_name = os.getenv("TABLE_NAME")

def create_health_log(confirm_exercise, exercise, sleep, diet, stress, goal):
    request_url = f"https://api.airtable.com/v0/{base_id}/{table_name}"

    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "Authorization": f"Bearer {airtable_api_key}",
    }

    data = {
        "fields": {
            "Id": str(uuid.uuid4()),
            "Exercised?": confirm_exercise,
            "Type of exercise": exercise,
            "Amount of sleep": sleep,
            "Stress": stress,
            "Diet": diet,
            "Goal": goal,
        }
    }

    print(request_url)
    print(headers)
    print(json.dumps(data))

    try:
        response = requests.post(
            request_url, headers=headers, data=json.dumps(data)
        )
        response.raise_for_status()
    except requests.exceptions.HTTPError as err:
        raise SystemExit(err)

    print(f"Response status code: {response.status_code}")
    return response

class ValidateHealthForm(FormValidationAction):
    def name(self) -> Text:
        return "validate_health_form"

    async def required_slots(
            self,
            slots_mapped_in_domain: List[Text],
            dispatcher: "CollectingDispatcher",
            tracker: "Tracker",
            domain: "DomainDict",
    ) -> Optional[List[Text]]:
        print(f"confirm_exercise is {tracker.get_slot('confirm_exercise')}")
        if not tracker.get_slot("confirm_exercise"):
            slots_mapped_in_domain.remove("exercise")

        print(slots_mapped_in_domain)
        return slots_mapped_in_domain

class SubmitHealthForm(Action):

    def name(self) -> Text:
        return "submit_health_form"

    async def run(
            self, dispatcher, tracker: Tracker, domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:
        confirm_exercise = tracker.get_slot("confirm_exercise")
        exercise = tracker.get_slot("exercise")
        sleep = tracker.get_slot("sleep")
        stress = tracker.get_slot("stress")
        diet = tracker.get_slot("diet")
        goal = tracker.get_slot("goal")

        response = create_health_log(
            confirm_exercise, exercise, sleep, stress, diet, goal)

        dispatcher.utter_message("Thanks, your answers have been recorded!")

        return []