import os, jinja2
from gensim import models
from gensim.models import Word2Vec, FastText
from jinja2 import Template
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import date
import tikzplotlib
from tikzplotlib import get_tikz_code

# FUNCTIONS:
def list_from_txt(file_path):
    '''Creates a list of itens based on a .txt file, each line becomes an item.

    Args:
      file_path: the path where the .txt file was created.
    '''

    strings_list = []
    with open (file_path, 'rt', encoding='utf-8') as file:
        for line in file:
            strings_list.append(line.rstrip('\n'))
    return strings_list

def score_func(model, analogies_aml, number_of_analogies_aml, analogies_general, number_of_analogies_general, analogies_biomedical, number_of_analogies_biomedical, topn):
    """ Computes the performance of the model in each analogies categories.

    Args:
        model: Word2Vec or FastText model object,
        analogies_aml:  list of AML analogies,
        number_of_analogies_aml: int,
        analogies_general: list of general Grammar analogies,
        number_of_analogies_general: int,
        analogies_biomedical: list of Biomedical analogies,
        number_of_analogies_biomedical: int,
        topn: number of topn closest words to be retrieved during the cosine similarity search.
    """

    number_of_analogies_all = number_of_analogies_aml + number_of_analogies_general + number_of_analogies_biomedical

    number_of_correct_analogies_all = 0
    number_of_correct_analogies_aml = 0
    number_of_correct_analogies_general = 0
    number_of_correct_analogies_biomedical = 0

    for analogie in analogies_aml:
        words = analogie.split(' ')
        try:
            for pair in model.wv.most_similar(positive=[model.wv[words[1]], model.wv[words[2]]], negative=[model.wv[words[0]]], topn=topn):
                if words[3] == pair[0]:
                    number_of_correct_analogies_all += 1
                    number_of_correct_analogies_aml += 1

        except:
            continue

    for analogie in analogies_general:
        words = analogie.split(' ')
        try:
            for pair in model.wv.most_similar(positive=[model.wv[words[1]], model.wv[words[2]]], negative=[model.wv[words[0]]], topn=topn):
                if words[3] == pair[0]:
                    number_of_correct_analogies_all += 1
                    number_of_correct_analogies_general += 1

        except:
            continue

    for analogie in analogies_biomedical:
        words = analogie.split(' ')
        try:
            for pair in model.wv.most_similar(positive=[model.wv[words[1]], model.wv[words[2]]], negative=[model.wv[words[0]]], topn=topn):
                if words[3] == pair[0]:
                    number_of_correct_analogies_all += 1
                    number_of_correct_analogies_biomedical += 1

        except:
            continue

    return (
        (number_of_correct_analogies_all * 100) / number_of_analogies_all,
        (number_of_correct_analogies_aml * 100) / number_of_analogies_aml,
        (number_of_correct_analogies_general * 100) / number_of_analogies_general,
        (number_of_correct_analogies_biomedical * 100) / number_of_analogies_biomedical
    )

def contains(string, unwanted_words):
    for x in string.split(' '):
        if x in unwanted_words:
            return True

    return False

def get_valid_analogies(model):
    """ Reads the analogies text files and eleiminated the ones that can be analzed by the model - e.g., if the analogy contains a word that is not present in the model's vocabulary.

    Args:
        model: the Word2Vec or FastText model object.

    Returns:
        lists of analogies that can be analyzed by the model, one list for each of the analogies' categories.
    """

    analogies_aml = list_from_txt('../data/analogies_aml.txt')
    analogies_general = list_from_txt('../data/analogies_general.txt')
    analogies_biomedical = list_from_txt('../data/analogies_biomedical.txt')

    analogie_words_present_in_model_vocab = set()
    remove_analogies_with_the_words = []
    for analogie in analogies_aml:
        words = [x.lower() for x in analogie.split(' ')]
        if ':' in words:
            continue

        else:
            for w in words:
                if w not in analogie_words_present_in_model_vocab:
                    if w in list(model.wv.vocab):
                        analogie_words_present_in_model_vocab.add(w)
                    else:
                        remove_analogies_with_the_words.append(w)
                else:
                    continue

    analogies_aml = [x for x in analogies_aml if contains(x, remove_analogies_with_the_words)==False]

    analogie_words_present_in_model_vocab = set()
    remove_analogies_with_the_words = []
    for analogie in analogies_general:
        words = [x.lower() for x in analogie.split(' ')]
        if ':' in words:
            continue

        else:
            for w in words:
                if w not in analogie_words_present_in_model_vocab:
                    if w in list(model.wv.vocab):
                        analogie_words_present_in_model_vocab.add(w)
                    else:
                        remove_analogies_with_the_words.append(w)
                else:
                    continue

    analogies_general = [x for x in analogies_general if contains(x, remove_analogies_with_the_words)==False]

    analogie_words_present_in_model_vocab = set()
    remove_analogies_with_the_words = []
    for analogie in analogies_biomedical:
        words = [x.lower() for x in analogie.split(' ')]
        if ':' in words:
            continue

        else:
            for w in words:
                if w not in analogie_words_present_in_model_vocab:
                    if w in list(model.wv.vocab):
                        analogie_words_present_in_model_vocab.add(w)
                    else:
                        remove_analogies_with_the_words.append(w)
                else:
                    continue

    analogies_biomedical = [x for x in analogies_biomedical if contains(x, remove_analogies_with_the_words)==False]

    # removing duplicates:
    analogies_aml = list(dict.fromkeys(analogies_aml))
    analogies_general = list(dict.fromkeys(analogies_general))
    analogies_biomedical = list(dict.fromkeys(analogies_biomedical))

    return analogies_aml, analogies_general, analogies_biomedical

def get_performance_bar_plot_from_df(df, topn_values, colors):
    models_names = df['Model name'].to_list()
    performance = {
        'All': df['All'].to_list(),
        'AML': df['AML'].to_list(),
        'Grammar': df['Grammar'].to_list(),
        'Biomedical': df['Biomedical'].to_list(),
    }

    x = np.arange(len(models_names))  # the label locations
    width = 0.2  # the width of the bars
    multiplier = 0

    fig, ax = plt.subplots(figsize=(25, 8))

    for attribute, measurement in performance.items():
        offset = width * multiplier
        rects = ax.bar(x + offset, measurement, width, label=attribute, color=colors[multiplier])
        #ax.bar_label(rects, padding=3, rotation=45)
        multiplier += 1

    # Add some text for labels, title and custom x-axis tick labels, etc.
    ax.set_ylabel('Score (%)')
    ax.set_xlabel('Models')
    #ax.set_title('Performance of each Word2Vec model: topn {}'.format(TOPN_VALUES[i]))
    ax.set_xticks(x + width, models_names)
    ax.tick_params(axis='x', rotation=45)
    ax.grid(True)
    ax.set_ylim(0, 100)

    caption = 'Performance of each model over the analogies, topn={}'.format(topn_values[i])

    return fig, caption

if __name__ == '__main__':
    print('Starting')

    # Jinja2 settings, commands to use in the .tex template file:
    latex_jinja_env = jinja2.Environment(
        block_start_string = '\BLOCK{',
        block_end_string = '}',
        variable_start_string = '\VAR{',
        variable_end_string = '}',
        comment_start_string = '\#{',
        comment_end_string = '}',
        line_statement_prefix = '%%',
        line_comment_prefix = '%#',
        trim_blocks = True,
        autoescape = False,
        loader = jinja2.FileSystemLoader(os.path.abspath('.'))
    )

    # loading template from .tex file:
    template = latex_jinja_env.get_template('./hyperparameter_optimization_template.tex')

    # CONSTANT(S):
    TESTING_DEFAULT_MODEL = True
    ANALYZING_FASTTEXT_MODELS = True
    if ANALYZING_FASTTEXT_MODELS:
        MODELS = sorted([f.path for f in os.scandir('../fasttext/models_nolemma/') if f.name.endswith('.model')])
        model_type = 'FastText'

    else:
        MODELS = sorted([f.path for f in os.scandir('./models_nolemma/') if f.name.endswith('.model')])         # paths to the Word2Vec models
        model_type = 'Word2Vec'

    TOPN_VALUES = [10, 20, 30, 40]                                                                              # different values of `topn` to use during the validation of the analogies
    COLORS = ['blue', 'mediumseagreen', 'orange', 'hotpink']                                                # friendly color blidness palette for bar plots

    # table of models names and respective files:
    data_models_files = {
        'model name': [],
        'filepath': [],
    }

    for index, model_name in enumerate(MODELS):
        if 'default' in model_name.split('/')[-1]:
            data_models_files['model name'].append('Default')
            TESTING_DEFAULT_MODEL = True

        else:
            data_models_files['model name'].append('Model {}'.format(index+1))

        data_models_files['filepath'].append(model_name)

    df_models_files = pd.DataFrame(data=data_models_files)
    df_models_files_latex = df_models_files.style.to_latex(position_float='centering', caption="Models' names and their respective filepath.", position='ht', label='models_names_filepaths')

    # table of variable hyperarameters values from each model to be analyzed:
    data_optimization_hyperparameters = {
        'model name': [],
        'vector size': [],
        'learning rate': [],
        'negative sampling': [],
    }

    for index, model_name in enumerate(MODELS):
        model_name_splitted = model_name.split('/')[-1].split('.model')[0].split('_')[3:]
        if 'default' in  model_name.split('/')[-1]:
            data_optimization_hyperparameters['model name'].append('Default')
            TESTING_DEFAULT_MODEL  = True

        else:
            data_optimization_hyperparameters['model name'].append('Model {}'.format(index+1))
        data_optimization_hyperparameters['vector size'].append(float(model_name_splitted[0][1:]))
        data_optimization_hyperparameters['learning rate'].append(float(model_name_splitted[1][1:]))
        data_optimization_hyperparameters['negative sampling'].append(float(model_name_splitted[2][1:]))

    df_optimization_hyperparameters = pd.DataFrame(data=data_optimization_hyperparameters)
    df_optimization_hyperparameters_latex = df_optimization_hyperparameters.style.to_latex(position_float='centering', caption='Variable hyperparameter values.', position='ht', label='variable_hyperparameters')

    models_scores = {model_name: {topn: {'All': 0, 'AML': 0, 'Grammar': 0, 'Biomedical': 0} for topn in TOPN_VALUES} for model_name in MODELS}
    number_of_times_models_were_loaded = 0
    for topn in TOPN_VALUES:
        print('Topn value:', topn)
        for index, model_name in enumerate(MODELS):

            if ANALYZING_FASTTEXT_MODELS:
                model = FastText.load(model_name)

            else:
                model = Word2Vec.load(model_name)

            if number_of_times_models_were_loaded == 0:
                print('Getting valid analogies')
                analogies_aml, analogies_general, analogies_biomedical = get_valid_analogies(model)
                df_analogies = pd.DataFrame(data={'Analogies': ['All', 'AML', 'Grammar', 'Biomedical'], 'Amount': [len(analogies_aml)+len(analogies_general), len(analogies_aml), len(analogies_general), len(analogies_biomedical)]})
                df_analogies_latex = df_analogies.style.to_latex(position_float='centering', caption='Amount of analogies per type.', position='ht', label='analogies_amount')

            print('Computing score for model {}/{}'.format(index+1, len(MODELS)))
            model_score = score_func(model, analogies_aml, len(analogies_aml), analogies_general, len(analogies_general), analogies_biomedical, len(analogies_biomedical), topn=topn)

            models_scores[model_name][topn]['All'] = model_score[0]
            models_scores[model_name][topn]['AML'] = model_score[1]
            models_scores[model_name][topn]['Grammar'] = model_score[2]
            models_scores[model_name][topn]['Biomedical'] = model_score[3]

            number_of_times_models_were_loaded += 1

    print('Generating performance tables')
    performance_tables = []
    for topn in TOPN_VALUES:
        topn_scores = {'topn': topn}
        for model_name, model_scores in models_scores.items():
            topn_scores[model_name] = model_scores[topn]
        performance_tables.append(topn_scores)

    for index, topn_results in enumerate(performance_tables):
        data = {
            'Model name': [],
            'All': [],
            'AML': [],
            'Grammar': [],
            'Biomedical': [],
        }

        copy = topn_results
        del copy['topn']

        i = 0
        for key, score_dict in copy.items():
            if i == 0 and TESTING_DEFAULT_MODEL==True:
                data['Model name'].append('Default')
            else:
                data['Model name'].append('Model {}'.format(i+1))
            data['All'].append(score_dict['All'])
            data['AML'].append(score_dict['AML'])
            data['Grammar'].append(score_dict['Grammar'])
            data['Biomedical'].append(score_dict['Biomedical'])

            i += 1

        performance_tables[index] = pd.DataFrame(data=data)

    performance_tables_latex = [x.style.highlight_max(props='textbf:--rwrap;', axis=0).to_latex(position_float='centering', caption='Performance of each model over the analogies, topn={}.'.format(TOPN_VALUES[i]), position='ht') for i, x in enumerate(performance_tables)]

    print('Generating plots')
    performance_plots, performace_plots_captions = [], []
    for i, df in enumerate(performance_tables):
        fig, caption = get_performance_bar_plot_from_df(df, TOPN_VALUES, COLORS)
        performance_plots.append(fig)
        performace_plots_captions.append(caption)

    for i, plot in enumerate(performance_plots):
        performance_plots[i] = get_tikz_code(plot, axis_width='490', extra_axis_parameters=[
                                                            'legend style={at={(0.5, 1.2)},\
                                                            anchor=north,legend cell align=left},\
                                                            xticklabel style={font=\\tiny},\
                                                            ytick={0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100},\
                                                            ymax=100'
                                                        ])

    print('Generating .tex file')
    report_latex = template.render(
                        title=model_type,
                        hyperparameter_optimization_df=df_optimization_hyperparameters_latex,
                        model_names_filepaths_df=df_models_files_latex,
                        number_analogies_all=len(analogies_aml)+len(analogies_general)+len(analogies_biomedical),
                        number_analogies_AML=len(analogies_aml),
                        number_analogies_general=len(analogies_general),
                        number_analogies_biomedical=len(analogies_biomedical),
                        performance_dfs=performance_tables_latex,
                        performance_plots_and_captions=zip(performance_plots, performace_plots_captions)
                    )

    dat = date.today().strftime("%d/%m/%Y")

    if ANALYZING_FASTTEXT_MODELS:
        with open('../fasttext/ft_hyperparameter_report_{}.{}.tex'.format(dat[0:2], dat[3:5]), 'w') as f:
            f.write(report_latex)

    else:
        with open('./w2v_hyperparameter_report_{}.{}.tex'.format(dat[0:2], dat[3:5]), 'w') as f:
            f.write(report_latex)

    print('END!')


from __future__ import print_function

from functools import partial
import multiprocessing as mp

import numpy as np
import scipy as sp
from scipy.optimize import curve_fit as lsq

import pandas as pd

from tqdm import tqdm

import hyperspy.api as hs

import matplotlib.pyplot as plt
plt.ion()

def _fit_model(data, bkgd_shape=None, lsqKwargs=None):
    '''
    Function that performs curve fitting based on use of
    scipy.optimize.curve_fit

    Params
    ------
    data : ndarray
        The data to be used in the model.  Prepared by the function
        MLLSmodel._prepare_model_data()
    bkgd_shape : string or None
        If not None, must be a string containing the name of one of
        the available background shapes:
        - power law
        - log-linear
        - double power law
    lsqKwargs : dict
        Keword arguments to be taken by scipy.optimize.curve_fit

    Returns
    -------
    opt_cofs : ndarray
        The optimised fit coefficients
    cov : ndarray
        The covariance matrix of the fit
    '''
    if lsqKwargs is None:
        lsqKwargs = {}

    y_obs = data[0,:]
    x = data[1,:]
    components = data[2:,:]

    def background(t, coeffs):
        if bkgd_shape=='power law':
            return coeffs[-2] * pow(t, coeffs[-1])
        if bkgd_shape=='log-linear':
            return np.log(coeffs[-2] * pow(t, coeffs[-1]))
        if bkgd_shape=='double power law':
            return coeffs[-4] * pow(t, coeffs[-3]) + coeffs[-2] * pow(t, coeffs[-1])

    def model(t, *coeffs):
        y = 0.0
        for i in range(components.shape[0]):
            y += coeffs[i] * components[i,:]
        if bkgd_shape:
            y += background(t, coeffs)
        return y

    opt_cofs, cov = lsq(model, x, y_obs, **lsqKwargs)

    # self.last = [opt_cofs, cov]
    return opt_cofs, cov

def _normalEqn(X, y):
    '''
    Calculate solution to linear regression parameters by matrix inversion.
    theta = ((X'X)^-1)X'y

    Parameters
    ----------
    X : ndarray
        Matrix of fit components
    y : ndarray
        Observed data points
    Returns
    -------
    theta : ndarray
        Optimised model parameters
    '''
    theta = np.zeros((X.shape[1], 1), dtype=np.float32)

    theta = np.dot(np.dot(np.linalg.pinv(np.dot(X.T, X)), X.T).astype(np.float32), y.astype(np.float32))

    return theta

def solo_normal_solver(stds, comps, data, data_range, LL=None, plot=False):
    '''
    Solve multivariate linear regression using matrix inversion.

    Parameters
    ----------
    stds :

    comps :

    data :

    data_range :

    LL :

    plot :
    Returns
    -------
    theta :
    '''
    # First, reset standards library
    stds.set_all_inactive()
    # Then use comps to specify which Standards are to be used
    stds.set_active_standards(comps)
    # Now, use the data range provided to set the fit range in the data
    try:
        Y = data.deepcopy()
        Y.crop(start=data_range[0], end=data_range[1], axis=0)
    except:
        raise Exception('Problem cropping observed data.')
    # If crop of data succeeds, apply same to active Standards
    stds.set_spectrum_range(data_range[0], data_range[1])
    # If Low-loss is provided, convolve standards
    if LL:
        stds.convolve_ready(LL, kwargs={'stray':True})
        stds.model = stds.conv
    else:
        stds.model = stds.ready

    # Build component matrix
    x_mat = np.array([stds.model[comp].data for comp in comps]).T
    y_obs = Y.data.T
    # Now compute solution
    theta = _normalEqn(x_mat, y_obs)

    return theta

class MLLSmodel_old:
    '''
    Class for handling standard MLLS fitting of spectra.
    '''
    def __init__(self, core_loss, standards):
        '''
        Parameters
        ----------
        core_loss : SQuEELS Data object
            The core-loss data to be modelled.
        standards : SQuEELS Standards object
            The standards library containing the references to be used
            as fit components
        '''

        self.stds = standards
        self.HL = core_loss

        self.dims = core_loss.data.data.shape
        self.nDims = len(self.dims)
        self.sigDim = core_loss.data.axes_manager.signal_indices_in_array[0]

    def model_point(self, coords, comps, init_guess, fit_background=None, lsqKwargs=None):
        '''

        Parameters
        ----------
        coords : tuple of ints

        comps : list of strings

        init_guess : list of floats

        fit_background : string or None

        Returns
        -------
        coefficients : array of floats

        '''
        if lsqKwargs is None:
            lsqKwargs = {}

        def background(t, coeffs):
            if fit_background=='power law':
                return coeffs[-2] * pow(t, coeffs[-1])
            if fit_background=='log-linear':
                return np.log(coeffs[-2] * pow(t, coeffs[-1]))
            if fit_background=='double power law':
                return coeffs[-4] * pow(t, coeffs[-3]) + coeffs[-2] * pow(t, coeffs[-1])

        def model(t, *coeffs):
            y = 0.0
            for i, comp in enumerate(comps):
                y += coeffs[i] * self.stds.ready[comp].inav[coords].data
            if fit_background:
                y += background(t, coeffs)
            return y

        y_Obs = self.HL.data.inav[coords].data
        t = self.HL.data.axes_manager[self.sigDim].axis

        opt_cofs, cov = lsq(model, t, y_Obs, p0=init_guess, **lsqKwargs)

        self.last = opt_cofs
        return opt_cofs

    def multimodel(self, comps, initial_guesses, background=None, update_guesses=True, lsqKwargs=None):
        '''
        This function is a wraparound for MLLSmodel.model_point which is used
        to process all spectra in a spectrum image.
        '''
        self.comps = comps
        df = pd.DataFrame(columns=['coord',*comps])

        points = np.prod(self.HL.data.axes_manager.shape[:-1])

        with tqdm(desc='Quantifying SI', total=points, unit='spectra') as pbar:
            for idx in np.ndindex(self.HL.data.axes_manager.shape[:-1]):
                results = {'coord': idx, }

                coeffs = self.model_point(idx, comps, initial_guesses, fit_background=background, lsqKwargs=lsqKwargs)

                for i, comp in enumerate(comps):
                    results[comp] = coeffs[i]
                if background:
                    results['bkgd_A'] = coeffs[-2]
                    results['bkgd_r'] = coeffs[-1]
                if update_guesses:
                    initial_guesses = coeffs

                df = df.append(results, ignore_index=True)
                pbar.update(1)

        self.multimodel_results = df
        return df

    def map_model_function(self, comps, initial_guesses, parallel=True, nCores=None, mapUnit=None, background=None, update_guesses=True, lsqKwargs=None):
        '''
        Alternative model function to MLLSmodel.multimodel
        Intended to be compatible with multiprocessing
        '''
        if nCores is None:
            nCores = mp.cpu_count()
        if mapUnit is None:
            mapUnit = nCores

        self.comps = comps

        points = np.prod(self.HL.data.axes_manager.shape[:-1])

        hold = np.empty(points, dtype=object)

        with tqdm(desc='Quantifying SI', total=points, unit='spectra') as pbar:
            idx = np.ndindex(self.HL.data.axes_manager.shape[:-1])

            for j in range(int(np.ceil(points/mapUnit))):

                idx_to_map = list()
                for i in range(mapUnit):
                    idx_to_map.append(next(idx))

                print(idx_to_map)

                params = (comps, initial_guesses)
                kwparams = { 'fit_background':background,
                    'lsqKwargs':lsqKwargs }

                part_func = partial(self.model_point, *params, **kwparams)

                if parallel:
                    pool = mp.Pool(processes=nCores)
                    coeffs = pool.map(part_func, idx_to_map)
                    pool.close()
                else:
                    coeffs = list(map(part_func, idx_to_map))

                t = np.empty(len(coeffs), dtype=object)
                t[:] = coeffs
                hold[j*mapUnit:(j*mapUnit+len(idx_to_map))] = t

            pbar.update(mapUnit)

        return rtn

    def generate_element_maps(self):
        '''
        Using dataframe containing multimodel results, extract elemental
        quantities into ndarrays for ease of inspection.
        '''
        self.qmaps = dict()

        canvas = np.zeros(self.dims[:-1])

        for comp in self.comps:
            qmap = canvas.copy()
            for i in range(len(self.multimodel_results)):
                qmap[self.multimodel_results['coord'][i][::-1]] = self.multimodel_results[comp][i]
            self.qmaps[comp] = qmap

    def generate_percentage_maps(self):
        '''
        Generate composition percentages based on elemental maps created using
        generate_element_maps method.
        '''
        self.pmaps = dict()

        total = np.zeros(self.dims[:-1])

        for comp in self.comps:
            total += self.qmaps[comp]

        for comp in self.comps:
            self.pmaps[comp] = 100* self.qmaps[comp]/total

class MLLSmodel:
    '''
    Class for handling standard MLLS fitting of spectra.
    '''
    def __init__(self, core_loss, standards):
        '''
        Parameters
        ----------
        core_loss : SQuEELS Data object
            The core-loss data to be modelled.
        standards : SQuEELS Standards object
            The standards library containing the references to be used
            as fit components
        '''

        self.stds = standards
        self.HL = core_loss

        self.dims = core_loss.data.data.shape
        self.nDims = len(self.dims)
        self.sigDim = core_loss.data.axes_manager.signal_indices_in_array[0]

    def _prepare_model_data(self, idx, fit_components):
        '''
        Prepares the data required for the fitting model in a single
        numpy ndarray.  The structure of this array is based on rows
        containing:
        Row 0: Observed y-values (i.e. the spectrum)
        Row 1: x-values corresponsing to y-values
        Row 2+: The reference spectra to be fitted to the observed data.

        Parameters
        ----------
        idx : tuple
            The indices of the spectrum within the SI to be extracted into
            the numpy array
        fit_components : list of strings
            The names of the reference spectra to be included as components
            in the fit.

        Returns
        -------
        rtn : ndarray
            An (n_comps+2) by spectrum-length numpy array
        '''
        n_comps = len(fit_components)
        x = self.HL.data.axes_manager[-1].axis

        rtn = np.empty((2+n_comps, self.dims[-1]))

        rtn[0,:] = self.HL.data.inav[idx].data
        rtn[1,:] = x
        for i in range(n_comps):
            comp = fit_components[i]
            rtn[2+i,:] = self.stds.ready[comp].inav[idx].data

        return rtn

    def _fit_model(self, data, bkgd_shape=None, lsqKwargs=None):
        '''
        Function that performs curve fitting based on use of
        scipy.optimize.curve_fit

        Parameters
        ----------
        data : ndarray
            The data to be used in the model.  Prepared by the function
            MLLSmodel._prepare_model_data()
        bkgd_shape : string or None
            If not None, must be a string containing the name of one of
            the available background shapes:
            - power law
            - log-linear
            - double power law
        lsqKwargs : dict
            Keword arguments to be taken by scipy.optimize.curve_fit

        Returns
        -------
        opt_cofs : ndarray
            The optimised fit coefficients
        cov : ndarray
            The covariance matrix of the fit
        '''
        if lsqKwargs is None:
            lsqKwargs = {}

        y_obs = data[0,:]
        x = data[1,:]
        components = data[2:,:]

        def background(t, coeffs):
            if bkgd_shape=='power law':
                return coeffs[-2] * pow(t, coeffs[-1])
            if bkgd_shape=='log-linear':
                return np.log(coeffs[-2] * pow(t, coeffs[-1]))
            if bkgd_shape=='double power law':
                return coeffs[-4] * pow(t, coeffs[-3]) + coeffs[-2] * pow(t, coeffs[-1])

        def model(t, *coeffs):
            y = 0.0
            for i in range(components.shape[0]):
                y += coeffs[i] * components[i,:]
            if bkgd_shape:
                y += background(t, coeffs)
            return y

        opt_cofs, cov = lsq(model, x, y_obs, **lsqKwargs)

        # self.last = [opt_cofs, cov]
        return opt_cofs, cov

    def model_single_spectrum(self, index, fit_components, bkgd_shape=None, rtn_cov=False, lsqKwargs=None):
        '''
        Model the spectrum in the SI at position *index using the references
        specified in fit_components.
        May include a background by specifying bkgd_shape.

        Parameters
        ----------
        index : tuple
            A 2-tuple of ints specifying the inav coordinates of the spectrum
            to be modelled.
        fit_components : list of strings
            A list of the names of reference spectra to be used as components
            in the model.
        bkgd_shape : string or None
            If specified, the name of the background type to be fitted.
            See MLLSmodel._fit_model docstring for available shapes.
        rtn_cov : bool
            If true, the covariance matrix for the fit is also returned.
        lsqKwargs : dict
            Keyword arguments to be passed through to scipy.optimize.curve_fit

        Returns
        -------
        results : ndarray

        '''
        fit_data = self._prepare_model_data(index, fit_components)

        results, cov = self._fit_model(fit_data, bkgd_shape=bkgd_shape, lsqKwargs=lsqKwargs)

        if rtn_cov:
            return results, cov
        else:
            return results

    def model_full_SI(self, fit_components, bkgd_shape=None, rtn_cov=False, lsqKwargs=None):
        '''
        Model all spectra in the SI and return results.

        Parameters
        ----------
        fit_components : list of strings
            A list of the names of reference spectra to be used as components
            in the model.
        bkgd_shape : string or None
            If specified, the name of the background type to be fitted.
            See MLLSmodel._fit_model docstring for available shapes.
        rtn_cov : bool
            If true, the covariance matrices are also stored and returned.
        lsqKwargs : dict
            Dictionary containing keword arguments to be passed to
            scipy.optimize.curve_fit

        Returns
        -------
        rtn : ndarray
        '''
        n_points = np.prod(self.dims[:-1])

        hold = np.empty(self.dims[:-1], dtype=object)

        if rtn_cov:
            hold_cov = np.empty(self.dims[:-1], dtype=object)

        with tqdm(desc='Quantifying SI', total=n_points, unit='spectra') as pbar:
            for index in np.ndindex(self.dims[:-1]):
                fit_data = self._prepare_model_data(index, fit_components)
                results, cov = self._fit_model(fit_data, bkgd_shape=bkgd_shape, lsqKwargs=lsqKwargs)
                hold[index] = results
                if rtn_cov:
                    hold_cov[index] = cov
                pbar.update(1)

        rtn = hold
        if rtn_cov:
            cov = hold_cov
            return rtn, cov
        else:
            return rtn

    def model_full_SI_new(self, fit_components, bkgd_shape=None,
            lsqKwargs=None, parallel=True, n_cores=None, chunk=16):
        '''
        Model all spectra in the SI and return results.

        Parameters
        ----------
        fit_components : list of strings
            A list of the names of reference spectra to be used as components
            in the model.
        bkgd_shape : string or None
            If specified, the name of the background type to be fitted.
            See MLLSmodel._fit_model docstring for available shapes.
        rtn_cov : bool
            If true, the covariance matrices are also stored and returned.
        lsqKwargs : dict
            Dictionary containing keword arguments to be passed to
            scipy.optimize.curve_fit
        parallel : bool
            If true, use python multiprocessing to utilise multiple CPU cores.
        n_cores : int
            The number of cores to execute the calculation on.  If None, will
            detect and use the maximum available cores.
        chunk : int
            To manage memory more efficiently, chunks of the SI are prepared and
            passed to the mapped function.  'chunk' is the max number of spectra
            to be included in an individual chunk.

        Returns
        -------
        rtn : ndarray
        '''
        if n_cores is None:
            n_cores = mp.cpu_count()

        if lsqKwargs is None:
            lsqKwargs = {'p0':np.zeros(len(fit_components))}

        n_points = np.prod(self.dims[:-1])

        hold = np.empty(n_points, dtype=object)

        with tqdm(desc='Quantifying SI', total=n_points, unit='spectra') as pbar:
            indices = np.ndindex(self.dims[:-1])

            for j in range(int(np.ceil(n_points/chunk))):
                fit_data = list()
                for i in range(chunk):
                    fit_data.append(self._prepare_model_data(next(indices), fit_components))

                kwargs = {'bkgd_shape':bkgd_shape, 'lsqKwargs':lsqKwargs}

                part_func = partial(_fit_model, **kwargs)

                if parallel:
                    pool = mp.Pool(processes=n_cores)
                    coeffs = pool.map(part_func, fit_data)
                    pool.close()
                else:
                    coeffs = list(map(part_func, fit_data))

                t = np.empty(len(coeffs), dtype=object)
                t[:] = coeffs
                hold[j*chunk:(j*chunk+len(fit_data))] = t

                pbar.update(len(fit_data))

        rtn = hold
        return rtn

    def model_full_SI_new_nochunk(self, fit_components, bkgd_shape=None,
            lsqKwargs=None, parallel=True, n_cores=None):
        '''
        Model all spectra in the SI and return results.

        Parameters
        ----------
        fit_components : list of strings
            A list of the names of reference spectra to be used as components
            in the model.
        bkgd_shape : string or None
            If specified, the name of the background type to be fitted.
            See MLLSmodel._fit_model docstring for available shapes.
        rtn_cov : bool
            If true, the covariance matrices are also stored and returned.
        lsqKwargs : dict
            Dictionary containing keword arguments to be passed to
            scipy.optimize.curve_fit
        parallel : bool
            If true, use python multiprocessing to utilise multiple CPU cores.
        n_cores : int
            The number of cores to execute the calculation on.  If None, will
            detect and use the maximum available cores.

        Returns
        -------
        rtn : ndarray
        '''
        if n_cores is None:
            n_cores = mp.cpu_count()

        if lsqKwargs is None:
            lsqKwargs = {'p0':np.zeros(len(fit_components))}

        n_points = np.prod(self.dims[:-1])

        hold = np.empty(self.dims[:-1], dtype=object)

        observed = self.HL.data.data
        x = self.HL.data.axes_manager[-1].axis
        x1 = np.repeat(x[np.newaxis,:], self.dims[-2], axis=0)
        x2 = np.repeat(x1[np.newaxis,:,:], self.dims[0], axis=0)
        n_comps = len(fit_components)

        fit_data = np.empty((*self.dims[:-1], 2+n_comps, self.dims[-1]))
        fit_data[...,0,:] = observed
        fit_data[...,1,:] = x2

        for i in range(n_comps):
            comp = fit_components[i]
            fit_data[...,2+i,:] = self.stds.ready[comp].data

        chunk_axis = np.argmin(self.dims[:-1])
        non_chunk_axis = np.argmax(self.dims[:-1])

        with tqdm(desc='Quantifying SI', total=n_points, unit='spectra') as pbar:
            # indices = np.ndindex(self.dims[:-1])

            for j in range(self.dims[chunk_axis]):
                fit_chunk = list(fit_data.take(indices=j, axis=chunk_axis))

                kwargs = {'bkgd_shape':bkgd_shape, 'lsqKwargs':lsqKwargs}

                part_func = partial(_fit_model, **kwargs)

                if parallel:
                    pool = mp.Pool(processes=n_cores)
                    coeffs = pool.map(part_func, fit_chunk)
                    pool.close()
                else:
                    coeffs = list(map(part_func, fit_chunk))

                t = np.empty(len(coeffs), dtype=object)
                t[:] = coeffs
                hold.swapaxes(0, chunk_axis)[j] = t

                pbar.update(self.dims[non_chunk_axis])

        # Once fitting is complete, need to rehash data into usable
        # format.
        try:
            f = hold.ravel()
            fr, fc = zip(*f)
            fra = np.vstack(fr).reshape(hold.shape + (-1,))
            fca = np.vstack(fc).reshape(hold.shape + (-1,))

            rtn = np.rollaxis(fra, -1, 0)
            cov = np.rollaxis(fca, -1, 0)

        except ValueError:
            rtn = hold
            cov = 0

        return rtn, cov


import torch
import gym
import time

import numpy as np
import matplotlib.pyplot as plt

from config import agents_dict
from Components.utils import argsparser
from Envs.gym_repeat import RepeatEnvWrapper
from Components.running_stat import ZFilter

def train(args):
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    device = torch.device('cpu')
    seed = args.seed
    print(device)

    # Create Env
    env = gym.make(args.env)
    # env = RepeatEnvWrapper(args.env)
    env.seed(seed)
    torch.manual_seed(seed)
    np.random.seed(seed)
    o_dim = env.observation_space.shape[0]
    if args.continuous:
        a_dim= env.action_space.shape[0]
    else:
        a_dim = env.action_space.n

    # Set the agent
    network = agents_dict[args.agent]
    if args.continuous:
        agent = network(lr=args.lr, gamma=args.gamma, BS=args.batch_size,
                        o_dim=o_dim, n_actions=a_dim, hidden=args.hidden, args=args,
                        device=device, continuous=True)
    else:
        agent = network(lr=args.lr, gamma=args.gamma, BS=args.batch_size,
                        o_dim= o_dim, n_actions=a_dim, hidden=args.hidden, args=args, device=device)

    # Experiment block starts
    # Create the buffer
    agent.create_buffer(env)

    obfilter = None
    if args.continuous:
        obfilter = ZFilter(env.observation_space.shape)

    ret = step = 0
    rets = []
    ep_lens = []
    avgrets = []
    losses = []
    avglos = []
    op = env.reset()
    if obfilter is not None:
        op = obfilter(op)

    num_steps = 1000000
    checkpoint = 10000
    num_episode = 0
    count = 0
    time = 0
    for steps in range(num_steps):
        # does torch need expand_dims
        a, lprob = agent.act(op)
        if args.continuous:
            scaled_ac = env.action_space.low + (a + 1.) * 0.5 * (env.action_space.high - env.action_space.low)
            scaled_ac = np.clip(scaled_ac, env.action_space.low, env.action_space.high)
            obs, r, done, infos = env.step(scaled_ac)
        else:
            obs, r, done, infos = env.step(a)
        agent.store(op,r,done,a,lprob,time)

        # Observe
        op = obs
        if obfilter is not None:
            op = obfilter(op)
        time += 1
        count += 1

        loss, count = agent.learn(count, obs)
        losses.append(loss)

        # End of Episode
        # ret += r * args.gamma**time
        # For convience
        ret += r
        step += 1
        if done:
            num_episode += 1
            rets.append(ret)
            ep_lens.append(step)
            # print("Episode {} ended with return {:.2f} in {} steps. Total steps: {}".format(num_episode, ret, step, steps))

            ret = 0
            step = 0
            time = 0
            op = env.reset()

        if (steps + 1) % checkpoint == 0:
            # plt.rcParams["figure.figsize"]
            # gymdisplay(env,MAIN)
            avgrets.append(np.mean(rets))
            avglos.append(np.mean(losses))
            rets = []
            losses = []
            # plt.clf()
            # plt.subplot(211)
            # plt.plot(range(checkpoint, (steps + 1) + checkpoint, checkpoint), avgrets)
            # plt.subplot(212)
            # plt.plot(range(checkpoint, (steps + 1) + checkpoint, checkpoint), avglos)
            # # plt.savefig('Hopper_hyper_graph/hopper_ppo_lr_' + floatToString(args.lr) + "_seed_" + str(
            # #     args.seed) + "_agent_" + str(args.agent)  + "_var_" + floatToString(args.var))
            # plt.pause(0.001)
    return avgrets

if __name__ == "__main__":
    args = argsparser()
    tic = time.time()
    avgrets = train(args)
    print("Run took:", time.time() - tic)
    np.savetxt("./results/{}_{}_{}-{}.txt".format(args.env, args.agent, args.naive, args.seed), avgrets)


'''
Question:
You are given a function f(x)=x^2. You are also given K lists. The ith list consists of Ni elements.

You have to pick one element from each list so that the value from the equation below is maximized:
S=(f(x1)+f(x2)+...+f(xk))%M

Xi denotes the element picked from the ith list . Find the maximized value Smax obtained.

% denotes the modulo operator.

Note that you need to take exactly one element from each list, not necessarily the largest element. You add the squares of the chosen elements and perform the modulo operation. The maximum value that you can obtain, will be the answer to the problem.

Input Format
The first line contains  space separated integers K and M.
The next K lines each contains an integer Ni, denoting the number of elements in the ith list, followed by Ni space separated integers denoting the elements in the list.

Output Format
Output a single integer denoting the value Smax.

Sample Input
3 1000
2 5 4
3 7 8 9
5 5 7 8 9 10

Sample Output
206

Explanation
Picking 5 from the 1st list, 9 from the 2nd list and 10 from the 3rd list gives the maximum S value equal to (5^2+9^2+10^2)% 1000= 206.
'''
# Enter your code here. Read input from STDIN. Print output to STDOUT
from itertools import product

def fofx(tup,M):
    lst6 = list()
    res1 = 0
    lst4 = list(tup)
    for i in range(len(lst4)):
        lst6 = lst4[i]
        res1 = res1 + (lst6*lst6)

    return res1%M

def main():
    K, M = map(int,input().split())
    res = 0
    res1 = 0
    lst2 = list()
    lst5 = list()

    for i in range(K):
        lst1 = list(map(int,input().split()))
        lst1.remove(lst1[0])
        lst2.append(lst1)

    lst3 = list(product(*lst2))

    for i in range(len(lst3)):
        res2 = fofx(lst3[i],M)
        lst5.append(res2)

    print(max(lst5))

if __name__=="__main__":
    main()


from dateutil import parser
from datetime import datetime

from ..app_logging import app_logging

time_helper_logger = app_logging.getLogger('time_helper')

def validate_time(timestamp_to_validate):
    try:
        datetime_obj = datetime.fromtimestamp(float(timestamp_to_validate))
    except (ValueError, OverflowError, TypeError, OSError) as e:
        time_helper_logger.warning(f'Error when constructing the datetime object from the timestamp {timestamp_to_validate}:\n{e}')
        raise
    return datetime_obj

def format_time(time_str, tz_to_apply):
    # format the time fields, from the format request
    t_parsed = parser.parse(time_str, ignoretz=True)
    t_formatted = t_parsed.astimezone(tz_to_apply).strftime('%Y-%m-%dT%H:%M:%S%z')

    return t_formatted


#   #   #Imports
import pygame
from sys import exit
from random import randint

#   #   #Initialising
pygame.init()
WINDOW = pygame.display.set_mode((800,400))
CLOCK = pygame.time.Clock()
FONT =  pygame.font.Font("Data\\Pixeltype.ttf", 50)
pygame.display.set_caption("Jump Jump game")
gravity = .0

#   #   #Variables
game_active = False
obstacle_list = []
score = 0
#highscore = 0
with open('data\\High_Score.txt', 'r') as f:
    highscore = int(f.read())

def obstacles(obstacle_list):
    global score
    if obstacle_list:
        for i in obstacle_list:
            i.x -= 4

            if i.top == 175:
                WINDOW.blit(Obstacle1_1, i)
                if i.left == 102:
                    score += 1
            if i.bottom == 45:
                WINDOW.blit(Obstacle1_2, i)
            if i.bottom == 85:
                WINDOW.blit(Obstacle3, i)
                if i.left == 102:
                    score += 1
            if i.top == 215:
                WINDOW.blit(Obstacle3, i)
            if i.bottom == 170:
                WINDOW.blit(Obstacle4, i)
                if i.left == 102:
                    score += 1
            if i.top == 130:
                WINDOW.blit(Obstacle4, i)
                if i.left == 102:
                    score += 1

        obstacle_list = [obstacle for obstacle in obstacle_list if obstacle.x > -100]
        return obstacle_list
    else:
        return []

def collisions(player, obstacle_list):
    global highscore
    global score
    if obstacle_list:
        for i in obstacle_list:
            if player.colliderect(i):
                return False

    return True

#   #   #Loading Images + Making Rects
ground = pygame.image.load("data\\ground1.png").convert()
sky = pygame.image.load('data\\CitySkyline.png').convert()

Space_strt_surface = FONT.render("Press 'X' to Start", False , (204, 119, 33))
Space_strt_rect = Space_strt_surface.get_rect(center = (400, 50))
ScoreDisplay = FONT.render(str(score), False , (204, 119, 33))
ScoreDisplay_rect = ScoreDisplay.get_rect(center = (400, 50))
HighScoreDisplay = FONT.render(str(highscore), False , (204, 119, 33))
HighScoreDisplay_rect = HighScoreDisplay.get_rect(midleft = (25, 375))

#Player
player = pygame.image.load("data\\jumpjumptank.png").convert_alpha()
player_rect = player.get_rect(midbottom = (50,100))

#Rectangles
Obstacle1_1 = pygame.image.load("data\\Obstacle1.png").convert_alpha()
Obstacle1_2 = pygame.image.load("data\\Obstacle2.png").convert_alpha()
Obstacle3 = pygame.image.load("data\\Obstacle3.png").convert_alpha()
Obstacle4 = pygame.image.load("data\\Obstacle4.png").convert_alpha()

#   #   #Events
Spawn_timer = pygame.USEREVENT + 1
pygame.time.set_timer(Spawn_timer,1400)

#   #   #Event Loop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
           pygame.quit()
           exit()

        if game_active:
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE and player_rect.y > 0:
                    gravity = -10
            if event.type == Spawn_timer and game_active:
                rand = randint(0,3)
                if rand == 0:
                    obstacle_list.append(Obstacle1_1.get_rect(midbottom = (850,300)))
                    obstacle_list.append(Obstacle1_2.get_rect(midtop = (850,0)))
                elif rand == 1:
                    obstacle_list.append(Obstacle3.get_rect(midtop = (850,0)))
                    obstacle_list.append(Obstacle3.get_rect(midbottom = (850,300)))
                elif rand == 2:
                    obstacle_list.append(Obstacle4.get_rect(midtop = (850,0)))
                elif rand == 3:
                    obstacle_list.append(Obstacle4.get_rect(midbottom = (850,300)))
        else:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_x:
                game_active = True
                start_time = int((pygame.time.get_ticks() /1000))

#Border Control
    if player_rect.bottom <= -30:
            player_rect.top = -30
    if player_rect.bottom >= 300:
        game_active = False
        player_rect.bottom = 300

    if game_active:
        #Background
        WINDOW.blit(ground, (0, 300))
        WINDOW.blit(sky,(0,0))

        #Gravity
        gravity += .7
        player_rect.y += gravity

        #Player
        if player_rect.bottom >= 300:
            player_rect.bottom = 300
        WINDOW.blit(player, player_rect)

        #Obstacles
        obstacle_list = obstacles(obstacle_list)

        #Score
        ScoreDisplay = FONT.render(str(score), False , (204, 119, 33))
        WINDOW.blit(ScoreDisplay, ScoreDisplay_rect)

        #Collisions
        game_active = collisions(player_rect, obstacle_list)
        print(CLOCK.get_fps())

    else:
        if highscore < score:
            highscore = score
            with open('data\\High_Score.txt', 'w') as f:
                f.write(str(highscore))

        gravity = 0
        player_rect.midbottom = (50, 100)
        WINDOW.blit(ground, (0, 300))
        WINDOW.blit(sky,(0,0))
        WINDOW.blit(Space_strt_surface, Space_strt_rect)
        WINDOW.blit(player, player_rect)
        obstacle_list.clear()
        score = 0

        HighScoreDisplay = FONT.render("Highscore: " + str(highscore), False , (204, 119, 33))
        WINDOW.blit(HighScoreDisplay, HighScoreDisplay_rect)
        print(CLOCK.get_fps())
    pygame.display.update()
    CLOCK.tick(60)


import kivy
kivy.require('1.10.0') # replace with your current kivy version !

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.modalview import ModalView
from kivy.properties import NumericProperty, ObjectProperty, BooleanProperty
from kivy.core.window import Window
from kivy.config import Config
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle, Line, Ellipse
from kivy.core.text import Label as CoreLabel
from math import pi, sin, exp, ceil, sqrt
from random import randint

import xml.etree.ElementTree as ET
from ma2_selectableRV import *
from ma2_patternRV import *

import numpy as np
from scipy import optimize

from ma2_track import *
from ma2_pattern import *

mixcolor = lambda t1,t2: tuple((v1+v2)/2 for v1,v2 in zip(t1,t2))
strfloat = lambda f: str(int(f)) if f==int(f) else str(f)

LMMS_scalenotes = 192
LMMS_scalebars = 4
LMMS_transpose = -12

class TrackWidget(Widget):
    active = BooleanProperty(True)

    marker_list = []
    scale_h = 1
    scale_v = 1
    offset_h = 0
    offset_v = 0

    def __init__(self, **kwargs):
        super(TrackWidget, self).__init__(**kwargs)

    def drawTrackList(self, parent):

        tracklist = parent.tracks
        current_track = parent.current_track
        paramlist = parent.synatize_param_list
        current_param = parent.current_param

        pad_l = 10
        pad_r = 20
        pad_t = 9
        row_h = int(21 * self.scale_v)
        gap_h = 4
        beat_w = int(20 * self.scale_h)

        pad_b = 10
        paramview_h = 0 # 2 * row_h + 1 * gap_h
        trackview_h = self.height - paramview_h - pad_b

        font_size = 16
        char_w = 10
        chars_name = 13
        chars_synth = 13
        font_size_small = int(11 * min(self.scale_v, self.scale_h))

        max_visible_tracks = int((trackview_h - 2. * font_size) / (row_h + gap_h))
        number_visible_tracks = min(len(tracklist), max_visible_tracks)
        self.offset_v = min(self.offset_v, abs(len(tracklist) - max_visible_tracks))

        # TODO: fixed size for now (15 tracks, 64 modules).. extend when it becomes necessary

        grid_l = self.x + pad_l + 2 + char_w*(chars_name+chars_synth+2) + 8

        self.canvas.clear()
        with self.canvas:

            Color(*parent.mainBackgroundColor())
            Rectangle(pos = self.pos, size = self.size)

            if self.active:
                Color(1.,0.,1,.5)
                Line(rectangle = [self.x + 3, self.y + 4, self.width - 7, self.height - 7], width = 1.5)

            for i,t in enumerate(tracklist[self.offset_v:self.offset_v+number_visible_tracks]):
                index = i + self.offset_v
                draw_x = self.x + pad_l
                draw_y = self.top - pad_t - (i+1) * row_h - i * gap_h

                Color(*((.2,.2,.2) if index != current_track else (.5,.2,.2)))
                Rectangle(pos = (draw_x, draw_y), size = (char_w*chars_name + 4,row_h))

                ### TRACK NAME ###
                draw_x += 2
                Color(1,1,1,1)
                label = CoreLabel(text = t.name[0:chars_name], font_size = font_size, font_name = self.font_name)
                label.refresh()
                Rectangle(size = label.texture.size, pos = (draw_x, draw_y), texture = label.texture)

                ### SYNTH NAME ###
                draw_x += char_w*(chars_name+1) + 4
                Color(.8,.8,.8,1)
                label = CoreLabel(text = t.getSynthName()[:chars_synth], font_size = font_size, font_name = self.font_name)
                label.refresh()
                Rectangle(size = label.texture.size, pos = (draw_x, draw_y), texture = label.texture)

                ### GRID ###
                draw_x = grid_l
                Color(*((.2,.2,.2) if index != current_track else (.5,.2,.2)), 1 - 0.5 * t.mute)
                max_visible_beats = 0
                while draw_x + beat_w <= self.right - pad_r:
                    Rectangle(pos = (draw_x, draw_y), size = (beat_w-1,row_h))
                    draw_x += beat_w
                    max_visible_beats += 1

                ### TRACK INFO ###
                Color(1,1,1,.7)
                volume_label = str(int(100 * t.par_norm)) + '%'
                if t.mute or (parent.track_solo is not None and parent.track_solo != i):
                    volume_label = "MUTE"
                elif parent.track_solo == i:
                    Color(1,.4,.4,1)
                    volume_label = "SOLO " + volume_label

                label = CoreLabel(text = volume_label, font_size = font_size, font_name = self.font_name)
                label.refresh()
                Rectangle(size = label.texture.size, pos = (self.right - pad_r - label.width, draw_y), texture = label.texture)

                ### MODULES ###
                for m in t.modules:
                    mod_on = m.mod_on - self.offset_h
                    mod_len = m.pattern.length
                    if mod_on < 0:
                        mod_len += mod_on
                        mod_on = 0
                    if mod_len <= 0 or mod_on > max_visible_beats:
                        continue

                    Color(*m.pattern.color, 0.4)
                    Rectangle(pos=(grid_l + beat_w * mod_on, draw_y), size=(beat_w * mod_len - 1,row_h))

                    Color(*(0.5+0.5*c for c in m.pattern.color),1)
                    label = CoreLabel(text = m.pattern.name[0:3*int(m.pattern.length)], font_size = font_size_small, font_name = self.font_name)
                    label.refresh()
                    Rectangle(size = label.texture.size, pos = (grid_l + beat_w * mod_on, draw_y-1), texture = label.texture)

                    if m.transpose != 0:
                        label = CoreLabel(text = "%+d" % m.transpose, font_size = font_size_small, font_name = self.font_name)
                        label.refresh()
                        Rectangle(size = label.texture.size, pos = (grid_l + beat_w * mod_on, draw_y + row_h/2 - 2), texture = label.texture)

                    if m == t.getModule() and index == current_track:
                        Color(1,.5,.5,.8)
                        Line(rectangle = (grid_l + beat_w * mod_on - 1 , draw_y - 1, beat_w * mod_len, row_h + 2), width = 1.5)

            draw_x = grid_l
            draw_y = self.top - pad_t - (number_visible_tracks+.5)*(row_h+gap_h) - 4

            beat_max = (self.right - pad_r - draw_x) // beat_w

            # BEAT ENUMERATION
            for i in range(beat_max+1):
                Color(.6,.8,.8)
                label = CoreLabel(text = str(i + self.offset_h), font_size = font_size_small+1, font_name = self.font_name)
                label.refresh()
                Rectangle(size = label.texture.size, pos = (draw_x-label.width/2, draw_y+2), texture = label.texture)

                draw_x += beat_w

            # MARKERS
            for m in self.marker_list:
                marker_pos = m['pos'] - self.offset_h
                if marker_pos < 0 or marker_pos > max_visible_beats: continue

                draw_x = grid_l + marker_pos * beat_w

                if m['style'] == 'BPM':
                    line_bottom = draw_y - 10
                    line_top = draw_y
                    Color(.3,.3,.7,.7)
                    m_text = m['label'][3:]
                else:
                    line_bottom = draw_y + 2
                    line_top = self.top - pad_t
                    Color(.7,0,.1,.7)
                    m_text = m['label']
                Line(points = [draw_x, line_bottom, draw_x, line_top], width=1.5)

                label = CoreLabel(text = m_text, font_size = font_size_small, font_name = self.font_name)
                label.refresh()
                Rectangle(size = label.texture.size, pos = (draw_x - label.width/2, line_bottom - font_size_small - 6), texture = label.texture)
                Rectangle(size = label.texture.size, pos = (draw_x - label.width/2 - .5, line_bottom - font_size_small - 6), texture = label.texture)

#            ### PARAMETER VIEW ### -- damn, this makes little sense because I need so much information about the parameter segments themselves... --> next aMaySyn version!
#            Color(.5, .4, 0)
#            draw_x = self.x + pad_l
#            draw_y = self.top - self.height + pad_b
#            Rectangle(pos = (draw_x, draw_y), size = (char_w*chars_name + 4,paramview_h))
#            draw_x += 2
#            Color(1,1,1,1)
#            label = CoreLabel(text = "PARAMETER", font_size = font_size, font_name = self.font_name)
#            label.refresh()
#            Rectangle(size = label.texture.size, pos = (draw_x, draw_y), texture = label.texture)
#            draw_x += char_w*(chars_name+1) + 4
#            Color(.8,.8,.8,1)
#            label = CoreLabel(text = paramlist[current_param]['id'], font_size = font_size, font_name = self.font_name)
#            label.refresh()
#            Rectangle(size = label.texture.size, pos = (draw_x, draw_y), texture = label.texture)
#
#            ### GRID ###
#            draw_x = grid_l
#            Color(.25,.2,.0)
#            while draw_x + beat_w <= self.right - pad_r:
#                Rectangle(pos = (draw_x, draw_y), size = (beat_w-1,row_h))
#                draw_x += beat_w

    def addMarker(self, label, position, style = ''):
        self.marker_list.append({'label': label, 'pos': position, 'style': style})

        markers_to_remove = [m for m in self.marker_list if m['pos']<0]
        for m in markers_to_remove:
            self.marker_list.remove(m)

    def removeMarkersContaining(self, label):

        markers_to_remove = [m for m in self.marker_list if label in m['label']]
        for m in markers_to_remove:
            self.marker_list.remove(m)

    def scroll(self, axis, inc):
        if axis == 'horizontal':
            self.offset_h = max(0, self.offset_h + inc)
        elif axis == 'vertical':
            self.offset_v = max(0, self.offset_v + inc)
        else:
            print("tried scrolling on some weird axis:", axis)

    def scaleByFactor(self, axis, factor):
        if axis == 'horizontal':
            self.scale_h = round(self.scale_h * factor, 2)
        elif axis == 'vertical':
            self.scale_v = round(self.scale_v * factor, 2)
        else:
            print("tried scaling by some weird axis:", axis)

class PatternWidget(Widget):
    active = BooleanProperty(False)

    drumkit = []
    scale_h = 1
    scale_v = 1
    offset_h = 0
    offset_v = 12
    scale_drum_h = 1
    scale_drum_v = 1
    offset_drum_h = 0
    offset_drum_v = 0
    # TODO: before automatic scrolling: prompt to enter offset_h, offset_v, scale_h, scale_v; AND also cut off everything not on screen

    def __init__(self, **kwargs):
        super(PatternWidget, self).__init__(**kwargs)

    claviature = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
    def isKeyBlack(self, key): return '#' in self.claviature[key % 12]

    def drawPianoRoll(self, parent):

        pattern = parent.getPattern()
        transpose = 0 # don't display parent.getModuleTranspose() anymore
        isDrum = parent.isDrumTrack()

        def isKeyBlack(key): return self.isKeyBlack(key) if not isDrum else (key % 2 == 0)

        if isDrum:
            offset_v = self.offset_drum_v
            offset_h = self.offset_drum_h
            scale_v = self.scale_drum_v
            scale_h = self.scale_drum_h
            key = offset_v
            transpose = 0
        else:
            offset_v = self.offset_v
            offset_h = self.offset_h
            scale_v = self.scale_v
            scale_h = self.scale_h
            key = offset_v

        pad_l = 10
        pad_r = 20
        pad_t = 8
        pad_b = 22
        key_h = int(9 * scale_v * (1 if not isDrum else 3))
        key_w = int(32 * scale_h)
        font_size = int(11 * min(scale_h, scale_v))
        par_font_size = font_size * sqrt(scale_h)
        bars = 4 * (0 if not pattern else max(0, pattern.length - offset_h))
        bar_w = int(48 * scale_h)

        #all the note_visible functions!
        #function to clearly calculate position of note TODO, draw current note, draw grid TODO but this time, respect pattern length!

        draw_y = self.y + pad_b
        number_keys_visible = int((self.top - pad_t - self.y - pad_b)/(key_h+1))

        if isDrum:
            self.offset_drum_v = max(0, min(self.offset_drum_v, len(self.drumkit) - number_keys_visible))
            offset_v = self.offset_drum_v
            key = offset_v

        self.canvas.clear()
        with self.canvas:

            Color(*parent.mainBackgroundColor())
            Rectangle(pos = self.pos, size = self.size)

            if self.active:
                Color(1.,0.,1,.5)
                Line(rectangle = [self.x + 3, self.y + 4, self.width - 7, self.height - 4], width = 1.5)

            ### KEYBOARD ###
            while draw_y + key_h <= self.top - pad_t:
                draw_x = self.x + pad_l

                Color(*((1,1,1) if not isKeyBlack(key) else (.1,.1,.1)))
                Rectangle(pos = (draw_x, draw_y), size = (key_w,key_h + 0.5 * (not isKeyBlack(key))))

                if pattern and pattern.notes and key == (pattern.getNote().note_pitch + transpose) % (len(self.drumkit) if isDrum else 100):
                    Color(.7,1,.3,.6)
                    Rectangle(pos = (draw_x, draw_y), size = (key_w, key_h + 0.5 * (not isKeyBlack(key))))

                Color(*((1,1,1) if isKeyBlack(key) else (.3,.3,.3)),1)
                if not isDrum:
                    label = CoreLabel(text = self.claviature[key % 12] + str(key//12), font_size = font_size, font_name = self.font_name)
                    label.refresh()
                    Rectangle(size = label.texture.size, pos = (draw_x+2, draw_y-2.5), texture = label.texture)
                    Rectangle(size = label.texture.size, pos = (draw_x+2, draw_y-3), texture = label.texture)
                else:
                    label_len = len(self.drumkit[key])
                    if label_len < 5:
                        label1 = CoreLabel(text = self.drumkit[key][0:4], font_size = font_size, font_name = self.font_name)
                        label1.refresh()
                        Rectangle(size = label1.texture.size, pos = (draw_x+2, draw_y-1 + .5*label1.height), texture = label1.texture)
                        Rectangle(size = label1.texture.size, pos = (draw_x+2, draw_y-1.5 + .5*label1.height), texture = label1.texture)
                    elif label_len < 9:
                        label1 = CoreLabel(text = self.drumkit[key][0:4], font_size = font_size, font_name = self.font_name)
                        label1.refresh()
                        label2 = CoreLabel(text = self.drumkit[key][4:8], font_size = font_size, font_name = self.font_name)
                        label2.refresh()
                        Rectangle(size = label1.texture.size, pos = (draw_x+2, draw_y-2 + label1.height), texture = label1.texture)
                        Rectangle(size = label1.texture.size, pos = (draw_x+2, draw_y-2.5 + label1.height), texture = label1.texture)
                        Rectangle(size = label2.texture.size, pos = (draw_x+2, draw_y), texture = label2.texture)
                        Rectangle(size = label2.texture.size, pos = (draw_x+2, draw_y-.5), texture = label2.texture)
                    elif label_len < 11:
                        label1 = CoreLabel(text = self.drumkit[key][0:5], font_size = font_size-1, font_name = self.font_name)
                        label1.refresh()
                        label2 = CoreLabel(text = self.drumkit[key][5:10], font_size = font_size-1, font_name = self.font_name)
                        label2.refresh()
                        Rectangle(size = label1.texture.size, pos = (draw_x+1, draw_y-1.5 + label1.height), texture = label1.texture)
                        Rectangle(size = label1.texture.size, pos = (draw_x+1, draw_y-2 + label1.height), texture = label1.texture)
                        Rectangle(size = label2.texture.size, pos = (draw_x+1, draw_y+.5), texture = label2.texture)
                        Rectangle(size = label2.texture.size, pos = (draw_x+1, draw_y), texture = label2.texture)
                    else:
                        label1 = CoreLabel(text = self.drumkit[key][0:5], font_size = font_size-1, font_name = self.font_name)
                        label1.refresh()
                        label2 = CoreLabel(text = self.drumkit[key][5:10], font_size = font_size-1, font_name = self.font_name)
                        label2.refresh()
                        label3 = CoreLabel(text = self.drumkit[key][10:15], font_size = font_size-1, font_name = self.font_name)
                        label3.refresh()
                        Rectangle(size = label1.texture.size, pos = (draw_x+1, draw_y-11.5 + 2*label1.height), texture = label1.texture)
                        Rectangle(size = label1.texture.size, pos = (draw_x+1, draw_y-11 + 2*label1.height), texture = label1.texture)
                        Rectangle(size = label2.texture.size, pos = (draw_x+1, draw_y-7 + label2.height), texture = label2.texture)
                        Rectangle(size = label2.texture.size, pos = (draw_x+1, draw_y-6.5 + label2.height), texture = label2.texture)
                        Rectangle(size = label3.texture.size, pos = (draw_x+1, draw_y-3), texture = label3.texture)
                        Rectangle(size = label3.texture.size, pos = (draw_x+1, draw_y-2.5), texture = label3.texture)

                draw_x += 2 + key_w
                Color(*((.1,.1,.1) if isKeyBlack(key) else (.1,.3,.2)))
                Rectangle(pos = (draw_x, draw_y), size = (self.right - pad_r - draw_x, key_h))

                draw_y += key_h+1
                key += 1

                if isDrum and key > len(self.drumkit) - 1: break

            draw_x = self.x + pad_l + key_w + 2
            draw_y = self.y + pad_b
            draw_h = (key_h+1) * (key - offset_v)

            ### GRID ###
            Color(.6,.8,.8)
            label = CoreLabel(text = "%.2f" % offset_h, font_size = 12, font_name = self.font_name)
            label.refresh()
            Rectangle(size = label.texture.size, pos = (draw_x-label.width/2, draw_y - label.height), texture = label.texture)
            for b in range(int(bars)):
                Color(0.05,0,0.05,0.8)
                Line(points = [draw_x, draw_y, draw_x, draw_y + draw_h], width = 1.5 if b % 4 == 0 else 1)
                Line(points = [draw_x + .25*bar_w, draw_y, draw_x + .25*bar_w, draw_y + draw_h], width = .3)
                Line(points = [draw_x + .50*bar_w, draw_y, draw_x + .50*bar_w, draw_y + draw_h], width = .3)
                Line(points = [draw_x + .75*bar_w, draw_y, draw_x + .75*bar_w, draw_y + draw_h], width = .3)

                draw_x += bar_w

                Color(.6,.8,.8)
                label = CoreLabel(text = "%.2f" % (.25*(b + 1) + offset_h), font_size = 12, font_name = self.font_name)
                label.refresh()
                Rectangle(size = label.texture.size, pos = (draw_x-label.width/2, draw_y - label.height), texture = label.texture)

            Color(0,0,0)
            Line(points = [draw_x, draw_y, draw_x, draw_y + draw_h], width = 1.5)

            Color(0,0,0,.3)
            Rectangle(pos = (draw_x, draw_y), size = (self.right - pad_r - draw_x, draw_h))

            ### NOTES ###
            if pattern:

                showVelocities = False
                for n in pattern.notes:
                    if n.note_vel != 100:
                        showVelocities = True
                        break

                for n in pattern.notes:
                    note_on = n.note_on - offset_h
                    draw_x = self.x + pad_l + key_w + 2 + 4 * bar_w * note_on
                    draw_y = self.y + pad_b + (key_h + 1) * ((n.note_pitch + transpose - offset_v) if not isDrum else (n.note_pitch - offset_v))

                    if note_on < 0 or draw_x > self.right - pad_r: continue
                    if draw_y < self.y + pad_b: continue

                    Color(*pattern.color,.55) # idea: current simultaneous notes in darker in the same pattern view --> makes editing WAY EASIER (TODO)
                    Rectangle(pos = (draw_x + 1, draw_y), size = (4 * bar_w * n.note_len - 2, key_h))

                    if n == pattern.getNote():
                        Color(1,1,1,.6)
                        Rectangle(pos = (draw_x + 1, draw_y), size = (4 * bar_w * n.note_len - 2, key_h))

                        if pattern.current_gap:
                            Color(1,1,1,.4)
                            Rectangle(pos = (draw_x + 4 * bar_w * n.note_len - 1, draw_y), size = (4 * bar_w * pattern.current_gap, key_h / 4))

                    if n.note_pan != 0:
                        Color(*(mixcolor((1,1,1),pattern.color)))
                        label = CoreLabel(text = strfloat(n.note_pan), font_size = par_font_size + (-1 if n.note_len >= .125 else -2), font_name = self.font_name)
                        label.refresh()
                        Rectangle(size = label.texture.size, \
                                  pos = (draw_x + 4 * bar_w * n.note_len - label.width - 2, draw_y - .25*label.height - (1 if not isDrum else 0.5) * key_h), \
                                  texture = label.texture)

                    vel_and_aux_info = ''
                    if n.note_aux != 0:
                        vel_and_aux_info = '(' + strfloat(n.note_aux) + ')'
                    if showVelocities:
                        vel_and_aux_info += strfloat(n.note_vel)
                    if vel_and_aux_info != '':
                        Color(*(mixcolor((0,0,0),pattern.color) if n == pattern.getNote() else mixcolor((1,1,1),pattern.color)))
                        label = CoreLabel(text = vel_and_aux_info, font_size = par_font_size + (-1 if n.note_len >= .125 else -2), font_name = self.font_name)
                        label.refresh()
                        Rectangle(size = label.texture.size, \
                                  pos = (draw_x + 4 * bar_w * n.note_len - label.width - 2, draw_y - .25*label.height + 1), \
                                  texture = label.texture)

                    if n.note_slide != 0:
                        Color(*(mixcolor((1,1,1),pattern.color)))
                        label = CoreLabel(text = strfloat(n.note_slide), font_size = par_font_size + (-1 if n.note_len >= .125 else -2), font_name = self.font_name)
                        label.refresh()
                        Rectangle(size = label.texture.size, \
                                  pos = (draw_x + 4 * bar_w * n.note_len - label.width - 2, draw_y - .25*label.height + 1 + key_h), \
                                  texture = label.texture)

            ### NUMBERINPUT ###
            Color(1,0,1,.9)
            label = CoreLabel(text = parent.numberInput, font_size = 12, font_name = self.font_name)
            label.refresh()
            Rectangle(size = label.texture.size, pos = (self.x + self.width - pad_r - label.width, self.y + pad_b - label.height), texture = label.texture)

    def scroll(self, axis, inc, is_drum = False):
        if is_drum:
            if axis == 'horizontal':
                self.offset_drum_h = max(0, self.offset_drum_h + inc)
            elif axis == 'vertical':
                self.offset_drum_v = max(0, self.offset_drum_v + inc)
            else:
                print("tried scrolling on some weird axis:", axis)
        else:
            if axis == 'horizontal':
                self.offset_h = max(0, self.offset_h + inc)
            elif axis == 'vertical':
                self.offset_v = max(0, self.offset_v + inc)
            else:
                print("tried scrolling on some weird axis:", axis)

    def scaleByFactor(self, axis, factor, is_drum = False):
        if is_drum:
            if axis == 'horizontal':
                self.scale_drum_h = round(self.scale_drum_h * factor, 2)
            elif axis == 'vertical':
                self.scale_drum_v = round(self.scale_drum_v * factor, 2)
            else:
                print("tried scaling by some weird axis:", axis)
        else:
            if axis == 'horizontal':
                self.scale_h = round(self.scale_h * factor, 2)
            elif axis == 'vertical':
                self.scale_v = round(self.scale_v * factor, 2)
            else:
                print("tried scaling by some weird axis:", axis)

    def updateDrumkit(self, drumkit):
        if len(drumkit) < len(self.drumkit):
            self.offset_drum_v += len(self.drumkit) - len(drumkit)
        self.drumkit = drumkit

#    def updateScale(self, scale_v = None, scale_h = None, delta_h = 0, delta_v = 0):
#        if scale_v and scale_v > 0: self.scale_v = scale_v
#        if scale_h and scale_h > 0: self.scale_h = scale_h
#        if delta_h > 0: self.scale_v += delta_h
#        if delta_v > 0: self.scale_h += delta_v
#        # not accessible yet, but probably implemented :)

class CurveWidget(Widget):

    w = 1200
    h = 600
    b = 150
    o = 15

    res = 100

    c_x = 0
    c_y = 0

    fit_plot = []
    fit_dots = 8

    latest_pars = [.5, 0, 0.5, -.2, 10, pi/2, -.2, .2] # I like this one

    def __init__(self, parent, **kwargs):
        super(CurveWidget, self).__init__(**kwargs)
        self.c_x = parent.center_x
        self.c_y = parent.center_y
        self.update()

    # TODO: don't remove points, but make them drag'n'droppable!
    def on_touch_down(self, touch):
        self.fit_plot.append([touch.x, touch.y])

        self.update()

        if len(self.fit_plot) == self.fit_dots:
            print("FIT NOW!")

            def testfunc(x, p0, p1, p2, p3, p4, p5, p6, p7):
                return self.automation_curve(x, pars=[p0,p1,p2,p3,p4,p5,p6,p7])

            x_data = [self.coord_plot2internal(f)[0] for f in self.fit_plot]
            y_data = [self.coord_plot2internal(f)[1] for f in self.fit_plot]

            try:
                pars, par_covars = optimize.curve_fit(testfunc, x_data, y_data, self.latest_pars)
            except RuntimeError as e:
                print('Shit.', e)
            else:
                self.latest_pars = pars

            self.fit_plot = []
            self.update()

    def coord_internal2plot(self, iCoord):
        return (self.c_x + (self.w-2*self.o)*iCoord[0], self.c_y - (self.b+self.o) + (self.h-2*self.o)*iCoord[1])

    def coord_plot2internal(self, pCoord): #float?
        return ((pCoord[0] - self.c_x)/(self.w-2*self.o), (pCoord[1] - self.c_y + (self.b+self.o))/(self.h-2*self.o))

    def update(self):
        self.canvas.clear()
        with self.canvas:

            Color(0,0,0)
            Rectangle(pos=(self.c_x - self.w/2, self.c_y - self.b), size=(self.w, self.h))

            Color(1,0,1,.5)
            Line(rectangle = (self.c_x - self.w/2, self.c_y - self.b, self.w, self.h), width = 4)

            plot = []
            for ix in range(self.res):
                plot.append(self.coord_internal2plot((ix / (self.res+1) - .5, self.automation_curve(ix / self.res))))

            Color(.6,.8,.8)
            Line(points = plot, width = 3)

            for ic in range(len(self.fit_plot)):
                Color(.1,.3,.2)
                Ellipse(pos = (self.fit_plot[ic][0] - self.o/2, self.fit_plot[ic][1] - self.o/2), size=(self.o, self.o))

    def automation_curve(self,x,pars=[]):
        if not pars or len(pars)!=8:
            pars = self.latest_pars

        return np.clip(pars[0] + pars[1]*x + pars[2]*x*x + pars[3]*np.sin(pars[4]*x + pars[5]) + pars[6]*np.exp(-pars[7]*x), 0, 1)

class EditSynthDialog(ModalView):
    synthNameInput = ObjectProperty(None)

    def __init__(self, **kwargs):
        self.synth_name = kwargs.pop('synth_name')
        self.synth_isdrum = kwargs.pop('is_drum')
        super(EditSynthDialog, self).__init__(**kwargs)
        self.synthNameInput.text = self.synth_name
        self.synthNameInput.select_all()

        self.root = App.get_running_app().root

    def synthChangeName(self, name):
        self.root.synthChangeName(name, drum = self.synth_isdrum)

    def synthDeactivate(self):
        self.root.synthDeactivate(drum = self.synth_isdrum)

class ImportPatternDialog(ModalView):
    importPatternFilenameInput = ObjectProperty(None)
    importPatternFilterInput = ObjectProperty(None)
    importPatternList = ObjectProperty(None)
    importPatternButton = ObjectProperty(None)

    XML_filename = None
    filter = ''
    return_patterns = []

    def __init__(self, **kwargs):
        lastFilename = kwargs.pop('filename')
        self.filter = kwargs.pop('filter') if 'filter' in kwargs else ''
        super(ImportPatternDialog, self).__init__(**kwargs)
        if lastFilename:
            self.importPatternFilenameInput.text = lastFilename
            self.importPatternFilterInput.text = self.filter
            self.parseFile()

    def parseFile(self):
        self.XML_filename = self.importPatternFilenameInput.text
        self.filter = self.importPatternFilterInput.text
        try:
            open(self.XML_filename, 'r').close()
        except FileNotFoundError:
            self.importPatternList.data = self.importPatternList.empty_data
            self.importPatternFilenameInput.text = "FILE NOT FOUND (i guess you're one of the dumber ones)"
            self.importPatternFilenameInput.select_all()
            self.XML_filename = None
            return

        XML_root = ET.parse(self.XML_filename).getroot()
        pattern_data = []
        for element in XML_root.iter():
                if element.tag == 'pattern':
                        elem_name = element.attrib['name']
                        if elem_name[0:len(self.filter)] == self.filter:
                            elem_pos = float(element.attrib['pos'])/LMMS_scalenotes
                            pattern_data.append({'text': elem_name + '@' + strfloat(elem_pos), 'name': elem_name, 'pos': elem_pos, 'element': element})

        pattern_data.sort(key = lambda item: (item['name'], item['pos']))
        self.importPatternList.data = pattern_data

    def clearFile(self):
        self.importPatternList.data = self.importPatternList.empty_data
        self.importPatternFilenameInput.text = ""
        self.importPatternFilenameInput.focus = True

    def parsePatterns(self, XML_data_list = []):
        patterns = []
        for XML_data in XML_data_list:
            selected_pattern = Pattern(name = XML_data['text'], synth_type = 'I')
            pattern_length = 1

            for elem_note in XML_data['element']:
                print(elem_note.tag, elem_note.attrib, end='')
                if elem_note.tag == 'note':
                    note_on = float(elem_note.attrib['pos'])/LMMS_scalenotes
                    note_len = float(elem_note.attrib['len'])/LMMS_scalenotes
                    selected_pattern.notes.append(
                        Note(
                            note_on    = note_on,
                            note_len   = note_len,
                            note_pitch = int(float(elem_note.attrib['key']) + LMMS_transpose),
                            note_pan   = int(float(elem_note.attrib['pan'])),
                            note_vel   = int(float(elem_note.attrib['vol'])),
                            note_slide = 0)
                        )
                    pattern_length = max(pattern_length, ceil(note_on + note_len))
                    print(' --> read', end='')
                print()
            selected_pattern.length = pattern_length
            patterns.append(selected_pattern)
        return patterns

    def parseSelectedPattern(self):
        if not self.importPatternList.data: return

        XML_data = self.importPatternList.getSelectedData()
        print(XML_data)
        if not XML_data or not XML_data['text']:
            return
        self.return_patterns = self.parsePatterns(XML_data_list = [XML_data])
        self.dismiss()

    def parseAllPatterns(self):
        print(self.importPatternList.data)
        self.return_patterns = self.parsePatterns(XML_data_list = self.importPatternList.data)
        self.dismiss()

class ExportPatternDialog(ModalView):
    pass

class SelectSynthDialog(ModalView):
    selectSynthFilterInput = ObjectProperty(None)
    selectSynthList = ObjectProperty(None)

    synths = []
    selected_synth = None
    complete_synthlist = []

    def __init__(self, **kwargs):
        self.synths = kwargs.pop('synths')
        self.selected_synth = kwargs.pop('current_synth')
        super(SelectSynthDialog, self).__init__(**kwargs)

        self.complete_synthlist = []
        for index, synth in enumerate(self.synths):
            synth_name = synth[2:]
            self.complete_synthlist.append({'text': synth_name, 'index': index})
        self.selectSynthList.data = self.complete_synthlist

    def applyFilter(self):
        self.selectSynthList.data = [synth for synth in self.complete_synthlist if self.selectSynthFilterInput.text in synth['text']]
        # check whether selected synth is in list or not..?

    def clearFilter(self):
        self.selectSynthList.data = self.complete_synthlist
        self.selectSynthFilterInput.focus = True
        # check whether selected synth is in list or not..?

    def switchToSelected(self):
        self.selected_synth = self.selectSynthList.getSelectedData()['index']
        self.dismiss()

    def switchToRandom(self):
        self.selected_synth = randint(0, len(self.synths) - 2)
        self.dismiss()

    def dontSwitch(self):
        self.selected_synth = None
        self.dismiss()


from bs4 import BeautifulSoup
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import undetected_chromedriver as uc

from selenium import webdriver
import json
import time
import os

cwd = os.getcwd()

# create options with proxy, user agent, and window settings
options = uc.ChromeOptions()
options.add_argument("--disable-gpu")  # Disable GPU acceleration (useful for headless mode)
options.add_argument("--start-maximized")  # Start Chrome maximized
options.add_argument("--no-sandbox")  # Disable sandboxing for headless mode
options.add_argument("--disable-dev-shm-usage")  # Disable /dev/shm usage for headless mode
options.add_argument("--ignore-certificate-errors")  # Ignore certificate errors
options.add_argument("--disable-extensions")  # Disable extensions
options.add_argument("--disable-infobars")  # Disable infobars
options.add_argument("--disable-notifications")  # Disable notifications
options.add_argument("--disable-popup-blocking")  # Disable popup blocking
options.add_argument("--disable-translate")  # Disable translation prompts
options.add_argument("--disable-web-security")  # Disable web security
options.add_argument("--disable-logging")  # Disable logging
options.add_argument("--log-level=3")  # Set log level to minimal
options.add_argument("--disable-software-rasterizer")  # Disable software rasterizer
options.add_argument("--disable-features=VizDisplayCompositor")  # Disable VizDisplayCompositor
#options.add_argument("--headless")

prefs = {"profile.managed_default_content_settings.images": 2}
options.add_experimental_option("prefs", prefs)

driver = uc.Chrome(options=options)

# Go to the target URL
driver.get("https://casinoguru-en.com/free-casino-games/slots")

def click_load_more(driver, max_retries):
    retries = 0
    while retries < max_retries:
        try:
            more_button = WebDriverWait(driver, 10).until(
                EC.visibility_of_element_located((By.XPATH, "//span[contains(.,'Show More Games')]"))
            )
            time.sleep(1)
            driver.execute_script("arguments[0].click();", more_button)
            retries += 1
            print(f"Successfully clicked 'Show more' {retries} times.")
            # Scroll down one page
            driver.execute_script("window.scrollBy(0, window.innerHeight);")
        except TimeoutException:
            print("No more 'Show more' button found. Breaking loop.")
            break

# Initialize variables
game_list = []

# Scroll and click the "Load More" button until no more games are loaded
while click_load_more(driver, 700):
    pass

# Parse the page source with BeautifulSoup
page_source = driver.page_source
soup = BeautifulSoup(page_source, 'html.parser')

# Find game items and extract information
game_items = soup.find_all('a', class_='game-item-name')
for item in game_items:
    game_name = item.text.strip()
    href = item['href']
    provider_name = item.find('span').text.strip()

    game_list.append({
        'game_name': game_name.split("\n")[0],
        'href': href,
        'provider_name': provider_name,
    })

gamelist_directory = os.path.join(cwd, 'gamelists')
os.makedirs(gamelist_directory, exist_ok=True)

json_file_path = os.path.join(gamelist_directory, f"CasinoGuruRaw.json")
with open(json_file_path, "w") as gamenames_file:
    json.dump(game_list, gamenames_file, indent=4)
    print(f"Game info JSON saved: {json_file_path}")

# Close the WebDriver
driver.quit()


import pathlib
import shutil

import pytest

import ipsuite as ips

@pytest.mark.parametrize(
    "random, max_rattel_shift, cell_fraction",
    [(True, None, [1.0, 1.0]), (False, 0.1, [0.5, 0.5])],
)
def test_rattle_atoms(proj_path, traj_file, random, max_rattel_shift, cell_fraction):
    traj_file = pathlib.Path(traj_file)
    shutil.copy(traj_file, ".")

    n_conf_per_dist = [5, 5]
    z_dist_list = [1, 2]

    with ips.Project() as project:
        data = ips.AddData(file=traj_file.name)

        scan = ips.bootstrap.SurfaceRasterScan(
            data=data.atoms,
            symbol="O",
            n_conf_per_dist=n_conf_per_dist,
            z_dist_list=z_dist_list,
            random=random,
            max_rattel_shift=max_rattel_shift,
            cell_fraction=cell_fraction,
            seed=0,
        )
    project.run()

    data.load()
    scan.load()
    atoms = scan.atoms

    desired_num_configs = len(z_dist_list) * n_conf_per_dist[0] * n_conf_per_dist[1]
    assert len(atoms) == desired_num_configs

    # check if distances are equally distributed
    dist1 = atoms[0].positions[-1, 1] - atoms[1].positions[-1, 1]
    dist2 = atoms[2].positions[-1, 1] - atoms[3].positions[-1, 1]
    if random is False:
        assert dist1 == dist2
    else:
        assert dist1 != dist2

    # check if bulk hase still the atom positions of the init structure
    pos1 = data.atoms[0].positions[0, 0]
    pos2 = scan.atoms[0].positions[0, 0]
    if max_rattel_shift is None:
        assert pos1 == pos2
    else:
        assert pos1 != pos2

    # check if the additive just is added in the correct cell fraction
    pos_additive = []
    for atom in scan.atoms:
        pos_additive.append(atom.positions[-1, :2])

    max_x = max(pos_additive[0])
    max_y = max(pos_additive[1])

    cell = data.atoms[0].cell

    assert max_x * cell_fraction[0] <= cell[0, 0]
    assert max_y * cell_fraction[1] <= cell[1, 1]


import pandas as pd
import requests
from io import StringIO
import numpy as np

orig_url='https://drive.google.com/file/d/1FW9JcMoovgV3pEq4TJiqQ4DqSWC0JbfE/view'

file_id = orig_url.split('/')[-2]
dwn_url='https://drive.google.com/uc?export=download&id=' + file_id

class calcData:
    def __init__(self):
        pass
    def GloBal_Total(self):
        url = requests.get(dwn_url).text
        csv_raw = StringIO(url)
        dfs = pd.read_csv(csv_raw)
        max_date = max(dfs["Date"])
        total_cases = np.sum(dfs[dfs["Date"]==max_date]["Confirmed"])
        new_cases = np.sum(dfs[dfs["Date"]==max_date]["newConfirmed"])
        total_deaths = np.sum(dfs[dfs["Date"]==max_date]["Death"])
        new_deaths = np.sum(dfs[dfs["Date"]==max_date]["newDeath"])
        total_recovered = np.sum(dfs[dfs["Date"]==max_date]["Recovered"])
        new_recovered = np.sum(dfs[dfs["Date"]==max_date]["newRecovered"])
        total_active = (total_cases-total_deaths) - total_recovered
        return [total_cases, new_cases, total_deaths, new_deaths, total_recovered,new_recovered, total_active]

    def country_data(self, country_name):
        url = requests.get(dwn_url).text
        csv_raw = StringIO(url)
        dfs = pd.read_csv(csv_raw)
        dfs["Country"] =  dfs["Country"].apply(lambda x: x.lower())
        country_q = country_name.lower()
        max_date = max(dfs[dfs["Country"]==country_q]["Date"])
        cond1 = dfs["Country"]==country_q
        cond2 = dfs["Date"]==max_date
        total_cases = np.sum(dfs[cond1 & cond2]["Confirmed"])
        new_cases = np.sum(dfs[cond1 & cond2]["newConfirmed"])
        total_deaths = np.sum(dfs[cond1 & cond2]["Death"])
        new_deaths = np.sum(dfs[cond1 & cond2]["newDeath"])
        total_recovered = np.sum(dfs[cond1 & cond2]["Recovered"])
        new_recovered = np.sum(dfs[cond1 & cond2]["newRecovered"])
        total_active = (total_cases-total_deaths) - total_recovered
        return [total_cases, new_cases, total_deaths, new_deaths, total_recovered, new_recovered, total_active, country_q]

    def country_list(self):
        url = requests.get(dwn_url).text
        csv_raw = StringIO(url)
        dfs = pd.read_csv(csv_raw)
        list_cntry = dfs["Country"].unique()
        text_cntry = ["Countries: \n"]
        for index, item in enumerate(list_cntry):
            text_cntry.append(str(index + 1)+ ": "+ item +"\n")
        message_cntry = " ".join(text_cntry)
        return message_cntry


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

# Utility function to read the README file.
# Used for the long_description.  It's nice, because now 1) we have a top level
# README file and 2) it's easier to type in the README file than to put a raw
# string in below ...
def read(fname):
    return open(os.path.join(os.path.dirname(__file__), fname)).read()

setup(
    name = "SoCCo",
    version = read(join('SoCCo','__init__.py')),
    author = "Brian Beckage",
    author_email = "brian.beckage@uvm.edu",
    description = ("Studying the effects of social interactions on climate "
                   "change"),
    license = "BSD",
    keywords = "climate change, social interactions",
    url = "https://github.com/OpenClimate/climate_change_model",
    packages=find_packages(),
    long_description=read('README.md'),
    classifiers=[
        "License :: OSI Approved :: BSD License",
    ],
)


from django.shortcuts import render

# Create your views here.

def product(request, product="BananaChips"):
    price = request.GET.get('price', 'N/A')
    nutrition = request.GET.get('nutrition', 'N/A')
    origin = request.GET.get('origin', 'N/A')
    # Create an information dictionary
    info = {'product': product, 'price': price,
            'nutrition': nutrition, 'origin': origin}
    return render(request, 'product/details.html', {'info': info})


from flask import Flask
import sys
import logging
import json

App = Flask(__name__)
@App.route('/palindrome/<word>', methods = ['GET'])
def palindrome(word):
    response = {'message': 'success'}
    logging.info("Word received: [" + str(word) + "]")
    jsonResponse = buildJSON(word)
    logging.info("JSON obtained: {" + jsonResponse + "}")

    return jsonResponse

def buildJSON(word):
    def voltearPal(x):
      return x[::-1]
    palindromo = {}
    palabras = {}
    dct = {}
        # palindromo = str(input("Please type the string: "))
    palindromo = word
        # print(palindromo)

    cant = len (palindromo)
    palabras = voltearPal(palindromo)
    for i in palindromo:
        if i in dct:
            dct[i] +=1
        else:
            dct[i] =1

    if (palindromo == palabras):
        falsePal = True
        x = {
            "name": palindromo,
            "palindrome": falsePal,
            "sorted": dct,
            "length": cant
            }

        y = json.dumps(x)
        # jsonFile = open("data.json", "w")
        # jsonFile.write(y)
        # jsonFile.close()

        print(y)

    else:
        truePal = False
        j = {
            "name": palindromo,
            "palindrome": truePal
            }

        y = json.dumps(j)
        print(y)
    return y

if __name__ == "__main__":
    App.run(host='0.0.0.0', debug=True, port=8080)


'''
Created on 08.07.2013

@author: vau
'''

import time
import json
import xmlrpc.client
import lemon
import core
import queue

ERROR_NOT_IDENTIFIED  = '0000001'
ERROR_NOT_AUTHORIZED  = '0000002'

TASK_SUCCESSFULLY_ADDED = '11'

RECONNECT_INTERVAL      = 10

class XMLRPC_Client(lemon.BaseAgentLemon):
    '''
    classdocs
    '''

    def __init__(self, _logger, _config, _info):
        self._agentID   = None
        self._taskManager   = None
        self._server_addr   = _config['xmlrpc_server_addr']
        self._server_port   = _config['xmlrpc_server_port']
        self._last_refresh  = 0
        self._reqQueue  = queue.Queue()
        lemon.BaseAgentLemon.__init__(self,_logger,_config, _info)

    def run(self):
        self._taskManager   = core.getCoreInstance().getInstance('TASK_MANAGER')
        self._agentID       = core.getCoreInstance().getItem('agent')['__id']
        self._entityManager = core.getCoreInstance().getInstance('ENTITY_MANAGER')
        self._createConnection(self._server_addr, self._server_port)
        self._setReady()

        while(self._running):
            self._process()
            time.sleep(0.01)

    def _process(self):
        req = self._reqQueue.get()
        method  = req['method']
        args    = req['args']
        callback    = req['callback']
        if callback:
            try:
                result  = method(*args)
                callback(result)
            except ConnectionError as e:
                self._logger.exception(e)
                self._reconnect()
                self._put(method,args,callback)
            except xmlrpc.client.Fault as e:
                self._logger.exception(e)
        req['ready']    = True

    def _createConnection(self, addr, port):
        self._connection    = xmlrpc.client.ServerProxy('http://{0}:{1}'.format(addr, port))

    def _reconnect(self):
        print('Reconnecting...')
        time.sleep(RECONNECT_INTERVAL)
        self._createConnection(self._server_addr, self._server_port)

    def _put(self, method, args=None, callback=None):
        request  = {'method': method, 'args': args, 'callback':callback, 'ready': False}
        self._reqQueue.put(request)
        return request

    def _formRequest(self, method, *args):
        result  = {'return': None}
        def callback(_result):
            result['return']   = _result
        p_task  = self._put(method, args, callback)
        while p_task['ready'] is not True:
            time.sleep(0.01)
        return result['return']

    def _reqREFRESH(self, agentID):
        return self._formRequest(self._connection.refresh, agentID)

    def _reqGET(self, agentID, key, *args, **kwargs):
        return self._formRequest(self._connection.get, agentID, key, *args, **kwargs)

    def _reqPOSTDATA(self, agentID, json_data):
        return self._formRequest(self._connection.postData, agentID, json_data)

    def post(self, data):
        data['time']    = time.time()
        code    = self._reqPOSTDATA(self._agentID,  json.dumps(data))
        if code is TASK_SUCCESSFULLY_ADDED:
            return True
        return False

    def get(self, key=None, *args):
        try:
            if key is None:
                try:
                    result = self._reqREFRESH(self._agentID)
                    if result - self._last_refresh > 0:
                        self._taskManager.new_task('sync')
                        self._last_refresh  = self._entityManager.getRevision()
                except Exception as e:
                    self._logger.exception(e)
            else:
                result  = json.loads(self._reqGET(self._agentID, key, *args))
                return result
        except Exception as e:
            self._logger.exception(e)


from PIL import Image
import torchvision.transforms as transforms
import cv2 as cv
import numpy as np

img = Image.open(r'../data/oxbuild_images-v1/all_souls_000001.jpg')
img = img.convert('RGB')
print(img)
transform = transforms.ToTensor()
img = transform(img)
print(img.shape)
img = img.permute((1, 2, 0))[:, :, [2, 1, 0]]
img = img.numpy()
img = img * 255
img = img.astype(np.uint8)
cv.imshow('img', img)

img_cv = cv.imread(r'../data/oxbuild_images-v1/all_souls_000001.jpg')
print(img_cv.shape)

cv.imshow('img_cv', img_cv)

cv.waitKey(0)