import os
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler

symbol_dict = {'cell': 'Celltrion',
               'hmotor': 'HyundaiMotor',
               'naver': 'NAVER',
               'kakao': 'Kakao',
               'lgchem': 'LGChemical',
               'lghnh': 'LGH&H',
               'bio': 'SamsungBiologics',
               'samsung1': 'SamsungElectronics',
               'samsung2': 'SamsungElectronics2',
               'sdi': 'SamsungSDI',
               'sk': 'SKhynix',
               'kospi': 'KOSPI', }

def symbol_to_path(symbol, base_dir="../data"):
    return os.path.join(base_dir, "{}.csv".format(str(symbol)))

def merge_data(start_date, end_date, symbols):
    dates = pd.date_range(start_date, end_date)

    df = pd.DataFrame(index=dates)
    for symbol in symbols:
        df_temp = pd.read_csv(symbol_to_path(symbol), index_col="Date", parse_dates=True,
                              usecols=['Date', 'Open', 'High', 'Low', 'Close', 'Volume'], na_values=['nan'])
        df_temp = df_temp.rename(columns={'Open': symbol + '_open', 'High': symbol + '_high', 'Low': symbol + '_low',
                                          'Close': symbol + '_close', 'Volume': symbol + '_volume'})
        if symbol == 'NAVER':
            stock_split_date_index = df_temp.index.get_loc(pd.to_datetime('2018-10-12'), method='nearest')

            before_stock_split_df = df_temp.iloc[:stock_split_date_index]
            before_stock_split_df = before_stock_split_df.replace(to_replace=0, method='ffill')
            before_stock_split_df.loc[:, before_stock_split_df.columns != 'NAVER_volume'] =\
                before_stock_split_df.loc[:, before_stock_split_df.columns != 'NAVER_volume']/5
            after_stock_split_df = df_temp.iloc[stock_split_date_index:]

            df_temp = pd.concat([before_stock_split_df, after_stock_split_df], axis=0)

        df = df.join(df_temp)

    # TODO: cleaning or filling missing value
    df = df.dropna()

    # KOSPI_volume 열의 형태 바꾸기 (예: 296,548K —> 296548000)
    if 'KOSPI' in symbols:
        df['KOSPI_volume'] = df['KOSPI_volume'].apply(lambda x: float(x.replace(',', '').replace('K', '') + '000'))

    return df

def rsi(df, period):
    U = np.where(df.diff(1) > 0, df.diff(1), 0)
    D = np.where(df.diff(1) < 0, df.diff(1) * (-1), 0)
    AU = pd.DataFrame(U, index=df.index).rolling(window=period).mean()
    AD = pd.DataFrame(D, index=df.index).rolling(window=period).mean()
    RSI = AU / (AD + AU) * 100
    return RSI

def macd(m_Df, m_NumFast=12, m_NumSlow=26, m_NumSignal=9):
    for col in m_Df:
        if 'close' in col:
            comp_name = col.split('_close')[0]
            m_Df[comp_name + '_EMAFast'] = m_Df[comp_name + '_close'].ewm(span=m_NumFast,
                                                                          min_periods=m_NumFast - 1).mean()
            m_Df[comp_name + '_EMASlow'] = m_Df[comp_name + '_close'].ewm(span=m_NumSlow,
                                                                          min_periods=m_NumSlow - 1).mean()
            m_Df[comp_name + '_MACD'] = m_Df[comp_name + '_EMAFast'] - m_Df[comp_name + '_EMASlow']
            m_Df[comp_name + '_MACDSignal'] = m_Df[comp_name + '_MACD'].ewm(span=m_NumSignal,
                                                                            min_periods=m_NumSignal - 1).mean()
            m_Df[comp_name + '_MACDDiff'] = m_Df[comp_name + '_MACD'] - m_Df[comp_name + '_MACDSignal']
    return m_Df

def make_features(trade_company_list, start_date, end_date, is_training):
    # TODO: Choose symbols to make feature
    # symbol_list = ['Celltrion', 'HyundaiMotor', 'NAVER', 'Kakao', 'LGChemical', 'LGH&H',
    #                 'SamsungElectronics', 'SamsungElectronics2', 'SamsungSDI', 'SKhynix', 'KOSPI']
    feature_company_list = ['cell', 'hmotor', 'naver', 'lgchem', 'lghnh', 'samsung1', 'sdi', 'sk', 'kakao', 'kospi']

    # kospi는 가장 volatility가 낮아서 buffer 역할 수행

    symbol_list = [symbol_dict[c] for c in feature_company_list]

    table = merge_data(start_date, end_date, symbol_list)
    for col in table.columns:
        if 'close' in col:
            comp_name = col.split('_close')[0]
            table[comp_name + '_rsi'] = rsi(table[col], 14)

    table = macd(table)
    # DO NOT CHANGE
    test_days = 10
    open_prices = np.asarray(table[[symbol_dict[c] + '_open' for c in trade_company_list]])
    close_prices = np.asarray(table[[symbol_dict[c] + '_close' for c in trade_company_list]])

    # TODO: select columns to use
    data = dict()

    for c in feature_company_list:
        data[c, 'close'] = table[symbol_dict[c] + '_close']
        # data[c, 'open'] = table[symbol_dict[c] + '_open'] # 종가, 시가 등은 서로 상관관계가 있으니 제거
        data[c, 'close_ema'] = table[symbol_dict[c] + '_close'].ewm(alpha=0.5).mean()
        data[c, 'rsi'] = table[symbol_dict[c] + '_rsi']
        data[c, 'macd'] = table[symbol_dict[c] + '_MACD']
        data[c, 'macd_diff'] = table[symbol_dict[c] + '_MACDDiff']
        data[c, 'close_pc'] = table[symbol_dict[c] + '_close'].pct_change().fillna(0)

    # TODO: make features
    input_days = 1

    features = list()
    for a in range(data['kospi', 'close'].shape[0] - input_days):

        # kospi close price
        kospi_close_feature = data['kospi', 'close'][a:a + input_days]

        # stock close price: cell, sk, kakao
        tmps = list()
        for c in trade_company_list:
            tmp = data[c, 'close'][a:a + input_days]  # 시가 기준 정규화
            tmps.append(tmp)
        close_feature = np.concatenate(tmps, axis=0)

        # stock close ema price: cell, sk, kakao
        tmps = list()
        for symbol in feature_company_list:
            tmp = data[symbol, 'close_ema'][a:a + input_days]
            tmps.append(tmp)
        ema_feature = np.concatenate(tmps, axis=0)

        # 추가 코드: stock close rsi: cell, sk, kakao
        tmps = list()
        for symbol in feature_company_list:
            tmp = data[symbol, 'rsi'][a:a + input_days]
            tmps.append(tmp)
        rsi_feature = np.concatenate(tmps, axis=0)

        # macd
        tmps = list()
        for symbol in feature_company_list:
            tmp = data[symbol, 'macd'][a:a + input_days]
            tmps.append(tmp)
        macd_feature = np.concatenate(tmps, axis=0)

        # macd_dff
        tmps = list()
        for symbol in feature_company_list:
            tmp = data[symbol, 'macd_diff'][a:a + input_days]
            tmps.append(tmp)
        macdDiff_feature = np.concatenate(tmps, axis=0)

        tmps = list()
        for symbol in feature_company_list:
            tmp = data[symbol, 'close_pc'][a:a + input_days]
            tmps.append(tmp)
        pc_feature = np.concatenate(tmps, axis=0)

        features.append(np.concatenate([
            kospi_close_feature,
            close_feature,
            ema_feature,
            rsi_feature,  # 추가 코드: stock close rsi
            # rsi_volume_feature,# 추가 코드: volume rsi
            macd_feature,
            macdDiff_feature,
            pc_feature
        ], axis=0))
    scaler = StandardScaler()
    scaler.fit(features)
    features = scaler.transform(features)
    if not is_training:
        return open_prices[-test_days:], close_prices[-test_days:], features[-test_days:]

    return open_prices[input_days:], close_prices[input_days:], features

if __name__ == "__main__":
    trade_company_list = ['lgchem', 'samsung1']  # 실제로 trade할 company
    open, close, feature = make_features(trade_company_list, '2010-01-01', '2019-05-08', False)
    print(open, '\n')
    print(close, '\n')
    print(*feature[0], sep=' / ')


from django.shortcuts import render
from django.http import HttpResponse
from .models import *
from django.shortcuts import redirect
import re
from django.utils.html import escape

def index(request, id=-1):
    if not request.user.is_authenticated:
        dir_tree = ''
        file_content = ''
        return render(request, 'utils/base.html')

    dirs = Directory.objects.all().filter(parent_dir=None, owner=request.user)

    return render(request, 'utils/base.html', {
        'files' : File.objects.all(),
        'directories' : Directory.objects.all(),
        'currentDir' : None,
    })

def add_dir(request, id=-1):

    if request.method == 'POST':
        name = request.POST.get('dir_name')
        desc = request.POST.get('dir_desc')
        user = request.user
        id = int(request.POST.get('dest_for_dir'))

        parent_set = Directory.objects.all().filter(id=id)

        if id == -1:
            new_dir = Directory(name=name, description=desc, owner=user)
        elif parent_set.exists():
            dir = parent_set[0]
            if dir.availability_flag == False:
                return redirect("/")

            new_dir = Directory(name=name, description=desc, owner=user, parent_dir=parent_set[0])
        else:
            return redirect('/')

        new_dir.save()

        context = {
            'id' : new_dir.id,
        }

        return redirect('/')

    else:
        return redirect('/')

def add_file(request, id=-1):

    if request.method == 'POST':
        name = request.POST.get('file_name')
        desc = request.POST.get('file_desc')
        f = request.FILES.get('file_file')
        user = request.user
        id = int(request.POST.get('dest_for_file'))

        parent_set = Directory.objects.all().filter(id=id)

        if id == -1:
            new_file = File(name=name, description=desc, owner=user, content=f)
        elif parent_set.exists():
            dir = parent_set[0]
            if dir.availability_flag == False:
                return redirect("/")

            new_file = File(name=name, description=desc, owner=user, parent_dir=parent_set[0], content=f)
        else:
            return redirect('/')

        new_file.save()

        context = {
            'id' : new_file.id
        }

        return redirect('/')

    else:
        return redirect('/')

def delete_all(root_dir):
    dirs = Directory.objects.all().filter(parent_dir=root_dir)

    for dir in dirs:
        dir.availability_flag = False
        dir.save()

        delete_all(dir)

    files = File.objects.all().filter(parent_dir=root_dir)

    for file in files:
        file.availability_flag = False
        file.save()

def delete(request):

    if request.method == 'POST':
        id = request.POST.get("to_delete")

        dir = Directory.objects.all().filter(id=id)

        if dir.exists():
            d = dir[0]
            d.availability_flag = False
            d.save()

            delete_all(dir[0])

            return redirect("/")

        file = File.objects.all().filter(id=id)

        if file.exists():
            f = file[0]
            f.availability_flag = False
            f.save()
            return redirect("/")

        return redirect("/")
    else:
        return redirect('/')


# --- carolin schieferstein & jose c. garcia alanis
# --- utf-8
# --- Python 3.7 / mne 0.20
#
# --- eeg pre-processing for dpx-r40
# --- version: january 2020
#
# --- detect and annotate artifact distorted segments in continuous data,
# --- average reference

# ========================================================================
# ------------------- import relevant extensions -------------------------
import os.path as op
from os import mkdir
from glob import glob

from re import findall
import numpy as np
import pandas as pd

from mne.io import read_raw_fif
from mne import pick_types, Annotations

# ========================================================================
# --- global settings
# --- prompt user to set project path
root_path = input("Type path to project directory: ")

# look for directory
if op.isdir(root_path):
    print("Setting 'root_path' to ", root_path)
else:
    raise NameError('Directory not found!')

# derivatives path
derivatives_path = op.join(root_path, 'derivatives')

# path to eeg files
data_path = op.join(derivatives_path, 'extract_blocks')

# create directory for output
if not op.isdir(op.join(derivatives_path, 'artifact_detection')):
    mkdir(op.join(derivatives_path, 'artifact_detection'))

# path for saving output
output_path = op.join(derivatives_path, 'artifact_detection')

# files to be analysed
files = sorted(glob(op.join(data_path, 'sub-*', '*-raw.fif')))

# ========================================================================
# ----------- loop through files and detect artifacts --------------------
for file in files:

    # --- 1) set up paths and file names -----------------------
    file_path, filename = op.split(file)
    # subject in question
    subj = findall(r'\d+', filename)[0].rjust(3, '0')

    # --- 2) import the preprocessed data ----------------------
    raw = read_raw_fif(file, preload=True)

    # index of eogs and stim channels
    picks_no_eeg = pick_types(raw.info,
                              eeg=False,
                              eog=True,
                              stim=True)

    # channels which are of type "eeg"
    picks_eeg = pick_types(raw.info,
                           eeg=True,
                           eog=False,
                           stim=False)

    # channel names
    channels = raw.info['ch_names']

    # sampling frequency
    sfreq = raw.info['sfreq']

    # channels that should be ignored during the artifact detection procedure
    ignore_ch = {'Fp1', 'Fpz', 'Fp2', 'AF7', 'AF3', 'AFz', 'AF4', 'AF8'}
    # update dict
    ignore_ch.update({raw.info['ch_names'][chan] for chan in picks_no_eeg})

    # --- 3.1) filter the data ---------------------------------
    # copy the file
    raw_copy = raw.copy()
    # apply filter
    raw_copy = raw_copy.filter(l_freq=0.1, h_freq=40.0,
                               picks=['eeg', 'eog'],
                               filter_length='auto',
                               l_trans_bandwidth='auto',
                               h_trans_bandwidth='auto',
                               method='fir',
                               phase='zero',
                               fir_window='hamming',
                               fir_design='firwin')

    # --- 3.2) find distorted segments in data -----------------
    # copy of data
    data = raw_copy.get_data(picks_eeg)

    # channels to be checked by artifact detection procedure
    ch_ix = [channels.index(ch) for ch in channels if ch not in ignore_ch]

    # detect artifacts (i.e., absolute amplitude > 500 microV)
    times = []
    annotations_df = pd.DataFrame(times)
    onsets = []
    duration = []
    annotated_channels = []
    bad_chans = []

    # loop through samples
    for sample in range(0, data.shape[1]):
        if len(times) > 0:
            if sample <= (times[-1] + int(1 * sfreq)):
                continue
        peak = []
        for channel in ch_ix:
            peak.append(abs(data[channel][sample]))
        if max(peak) >= 200e-6:
            times.append(float(sample))
            annotated_channels.append(channels[ch_ix[int(np.argmax(peak))]])
    # if artifact found create annotations for raw data
    if len(times) > 0:
        # get first time
        first_time = raw_copy._first_time
        # column names
        annot_infos = ['onset', 'duration', 'description']

        # save onsets
        onsets = np.asarray(times)
        # include one second before artifact onset
        onsets = ((onsets / sfreq) + first_time) - 1
        # durations and labels
        duration = np.repeat(2, len(onsets))
        description = np.repeat('Bad', len(onsets))

        # get annotations in data
        artifacts = np.array((onsets, duration, description)).T
        # to pandas data frame
        artifacts = pd.DataFrame(artifacts,
                                 columns=annot_infos)
        # annotations from data
        annotations = pd.DataFrame(raw_copy.annotations)
        annotations = annotations[annot_infos]

        # merge artifacts and previous annotations
        artifacts = artifacts.append(annotations, ignore_index=True)

        # create new annotation info
        annotations = Annotations(artifacts['onset'],
                                  artifacts['duration'],
                                  artifacts['description'],
                                  orig_time=raw_copy.annotations.orig_time)
        # apply to raw data
        raw_copy.set_annotations(annotations)

    # save total annotated time
    total_time = sum(duration)
    # save frequency of annotation per channel
    frequency_of_annotation = {x: annotated_channels.count(x)*2
                               for x in annotated_channels}
    # if exceeds 0.9% of total time --> mark as bad channel
    threshold = raw_copy.times[-1] * .01

    # save bads in info structure
    bad_chans = [chan for chan, value in frequency_of_annotation.items()
                 if value >= int(threshold)]
    raw_copy.info['bads'] = bad_chans

    # --- 3.3) plot data and check for inconsistencies  ----------
    raw_copy.plot(scalings=dict(eeg=50e-6),
                  n_channels=len(raw.info['ch_names']),
                  bad_color='red',
                  block=True)

    # save bad channels for summary
    interpolated = raw_copy.info['bads'].copy()

    # --- if bad channels were found, repeat preprocessing ---------
    if bad_chans:
        # re-run artifact detection
        raw_copy = raw.copy()
        raw_copy.info['bads'] = bad_chans

        # interpolate bads
        raw_copy.interpolate_bads(reset_bads=True,
                                  verbose=False,
                                  mode='accurate')

        # apply filter
        raw_copy = raw_copy.filter(l_freq=0.1, h_freq=40.0,
                                   picks=['eeg', 'eog'],
                                   filter_length='auto',
                                   l_trans_bandwidth='auto',
                                   h_trans_bandwidth='auto',
                                   method='fir',
                                   phase='zero',
                                   fir_window='hamming',
                                   fir_design='firwin')

        # --- find distorted segments in data ----------------------
        # copy of data
        data = raw_copy.get_data(picks_eeg)

        # channels to be checked by artifact detection procedure
        ch_ix = [channels.index(chan) for chan in channels
                 if chan not in ignore_ch]

        # detect artifacts (i.e., absolute amplitude > 500 microV)
        times = []
        annotations_df = pd.DataFrame(times)
        onsets = []
        duration = []
        annotated_channels = []
        bad_chans = []

        # loop through samples
        for sample in range(0, data.shape[1]):
            if len(times) > 0:
                if sample <= (times[-1] + int(1 * sfreq)):
                    continue
            peak = []
            for channel in ch_ix:
                peak.append(abs(data[channel][sample]))
            if max(peak) >= 200e-6:
                times.append(float(sample))
                annotated_channels.append(channels[ch_ix[int(np.argmax(peak))]])
        # if artifact found create annotations for raw data
        if len(times) > 0:
            # get first time
            first_time = raw_copy._first_time
            # column names
            annot_infos = ['onset', 'duration', 'description']

            # save onsets
            onsets = np.asarray(times)
            # include one second before artifact onset
            onsets = ((onsets / sfreq) + first_time) - 1
            # durations and labels
            duration = np.repeat(2, len(onsets))
            description = np.repeat('Bad', len(onsets))

            # get annotations in data
            artifacts = np.array((onsets, duration, description)).T
            # to pandas data frame
            artifacts = pd.DataFrame(artifacts,
                                     columns=annot_infos)
            # annotations from data
            annotations = pd.DataFrame(raw_copy.annotations)
            annotations = annotations[annot_infos]

            # merge artifacts and previous annotations
            artifacts = artifacts.append(annotations, ignore_index=True)

            # create new annotation info
            annotations = Annotations(artifacts['onset'],
                                      artifacts['duration'],
                                      artifacts['description'],
                                      orig_time=raw_copy.annotations.orig_time)
            # apply to raw data
            raw_copy.set_annotations(annotations)

    # --- 4) re-reference data to average of 64 electrodes  ----
    raw_copy.set_eeg_reference(ref_channels='average',
                               projection=True)

    # --- 5) plot data and check for inconsistencies  ----------
    raw_copy.plot(scalings=dict(eeg=50e-6),
                  n_channels=len(raw.info['ch_names']),
                  bad_color='red',
                  block=True)

    # --- 6) save segmented data  -----------------------------
    # create directory for save
    if not op.exists(op.join(output_path, 'sub-%s' % subj)):
        mkdir(op.join(output_path, 'sub-%s' % subj))

    # save file
    raw_copy.save(op.join(output_path, 'sub-%s' % subj,
                          'sub-%s_artifact_detection-raw.fif' % subj),
                  overwrite=True)

    # write summary
    name = 'sub-%s_artifact_detection.txt' % subj
    sfile = open(op.join(output_path, 'sub-%s', name) % subj, 'w')
    # channels info
    sfile.write('Channels_interpolated:\n')
    for ch in interpolated:
        sfile.write('%s\n' % ch)
    # frequency of annotation
    sfile.write('Frequency_of_annotation:\n')
    for ch, f in frequency_of_annotation.items():
        sfile.write('%s, %f\n' % (ch, f))
    sfile.write('total_annotated:\n')
    sfile.write(str(round(total_time / raw_copy.times[-1], 3) * 100) + ' %\n')
    sfile.close()

    del raw, raw_copy


"""
    api.middleware
    ~~~~~~~~~~~~~~

    blah blah blah
"""

import json
from werkzeug.local import Local, release_local
from werkzeug.wrappers import Request, Response
from werkzeug.exceptions import BadRequest, NotAcceptable, HTTPException, abort

class BeforeAfterMiddleware(object):
    """A simple middleware base class providing a before/after interface.

    A werkzeug.Local instance called `local` is bound to the middleware for
    saving state in a thread-safe way between the `before` and `after` calls.
    """

    def __init__(self, app):
        # Keep a reference to the wsgi app we're wrapping
        super(BeforeAfterMiddleware, self).__setattr__('app', app)
        super(BeforeAfterMiddleware, self).__setattr__('local', Local())

    def before(self, request):
        """Do stuff before deferring to the wrapped app."""

    def after(self, request, response):
        """Do more stuff after getting a response from the wrapped app"""

    def __call__(self, environ, start_response):
        """Process a request"""
        # Set up the request and do our pre-processing
        request = Request(environ)
        self.before(request)

        # Defer  to the wrapped app, then do our cleanup n stuff
        response = Response.from_app(self.app, environ)
        self.after(request, response)
        release_local(self.local)

        # finally, blah
        return response(environ, start_response)

    def mutate_error(self, *args, **kwargs):
        raise TypeError('Mutating a BeforeAfterMiddleware is (usually) not thread-safe. '
                        'Use the thread-safe `self.local` property.')

    __setattr__ = mutate_error

    __delattr__ = mutate_error

class DataTransformer(BeforeAfterMiddleware):
    """Flexible accept, nice and normalized for internal use.

    Requests:
     * Form-encoded POST requests are transformed to flat key/value json.
     * requests with JSON bodies are passed through

    Responses:
     * JSON-encoded response bodies are transformed to whatever the client
       accepts.
    """

    def before(self, request):
        self.local.target = request.accept_mimetypes.best_match(['application/json'])
        if self.local.target is None:
            raise NotAcceptable()

    def after(self, request, response):
        body = response.get_data(as_text=True)
        if response.headers.get('Content-Type') != 'application/json':
            warnings.warn('leaving non-JSON data as a string')
            data = body
        else:
            data = json.loads(body)

        if self.local.target == 'application/json':
            cereal = json.dumps(data)
            response.set_data(cereal)

class FieldLimiter(BeforeAfterMiddleware):
    """Pares response data down to that set by a ?field= query parameter.
    Assumes JSON response data from app.

    Limit fields by providing field= query args. EG:
    GET http://whatever/?field=code&field=subject

    The limits only work for top-level keys in structured response bodies.
    """

    def limit(self, data, fields):
        # have they asked for fields that don't exist?
        if not all(field in data for field in fields):
            raise BadRequest()
        limited = {key: data[key] for key in fields}
        return limited

    def after(self, request, response):
        if 'field' not in request.args:
            return

        fields = [s.lower() for s in request.args.getlist('field')]

        body = response.get_data(as_text=True)
        data = json.loads(body)

        if isinstance(data, list):
            limited_data = [self.limit(d, fields) for d in data]
        else:
            limited_data = self.limit(data, fields)

        cereal = json.dumps(limited_data)
        response.set_data(cereal)

class PrettyJSON(BeforeAfterMiddleware):
    """Prettify JSON responses"""

    def after(self, request, response):
        if response.headers.get('Content-Type') == 'application/json':
            body = response.get_data(as_text=True)
            data = json.loads(body)
            pretty_data = json.dumps(data, indent=2)
            response.set_data(pretty_data)

class JsonifyHttpException(object):
    """Format http errors as json, but keep the error status in the response

    Should wrap the highest level possible so that any errors thrown in nested
    wrapped apps will be caught.
    """

    def __init__(self, app, error_prefixes=[4, 5]):
        # Keep a reference to the wsgi app we're wrapping
        self.app = app
        self.local = Local()
        self.error_prefixes = error_prefixes

    def jsonify_error(self, http_err, environ):
        """Creates a error response with body as json"""
        data = {
            'status code': http_err.code,
            'error name': http_err.name,
            'description': http_err.description
        }

        response = http_err.get_response(environ)
        response.data = json.dumps(data)
        response.headers['content-type'] = 'application/json'

        return response

    def __call__(self, environ, start_response):
        """Process a request"""
        try:
            # Set up the request
            request = Request(environ)

            # Defer  to the wrapped app, then do our cleanup
            response = Response.from_app(self.app, environ)

            if response.status_code/100 in self.error_prefixes:
                abort(response.status_code)

            release_local(self.local)

            return response(environ, start_response)

        except HTTPException as err:
            response = self.jsonify_error(err, environ)
            return response(environ, start_response)


'''
Created on Sep 19, 2012

@author: jluker
'''

import logging

from config import config
from solrdoc import SolrDocument

from flask import request as current_request, current_app as app
from flask.ext.solrquery import SearchResponseMixin

__all__ = ['SolrResponse']

class SolrResponse(SearchResponseMixin):

    def __init__(self, data, request, http_response, **kwargs):
        self.raw = data
        self.request = request
        self.http_response = http_response
        self.meta = {}

    def is_error(self):
        return self.raw.get('responseHeader',{}).get('status', False)

    def get_http_status(self):
        return self.http_response.status_code

    def search_response(self):

        self.meta.update({
                'query': self.get_query(),
                'qtime': self.get_qtime(),
                'hits': self.get_hits(),
                'count': self.get_count(),
        })

        resp = {
            'meta': self.meta,
            'results': {
                'docs': self.get_docset(),
            }
        }

        if self.request.facets_on():
            resp['results']['facets'] = self.get_all_facets()

        return resp

    def record_response(self, idx=0):
        try:
            return self.get_docset()[idx]
        except IndexError:
            return None

    def get_error_message(self):
        """Function to remove the useless part of the error message coming from SOLR"""
        error_message = self.get_error()
        if error_message:
            if error_message.startswith('org.apache.lucene'):
                error_split = error_message.split(':', 1)
                if len(error_split) > 1:
                    return (''.join(error_split[1])).strip()
                else:
                    return 'Unspecified error from search engine.'
            else:
                return error_message
        else:
            return None

    def add_meta(self, key, value):
        self.meta[key] = value

    def raw_response(self):
        return self.raw

    def get_docset_objects(self):
        return [SolrDocument(x) for x in self.get_docset()]

    def get_doc_object(self, idx):
        doc = self.get_doc(idx)
        if doc:
            return SolrDocument(doc)

    def get_all_facet_queries(self):
        return self.get_all_facets().get('facet_queries',{})

    def get_facets_fields(self, facet_name, hierarchical=False):
        """
        Returns the facets list for a specific facet.
        It takes care of checking if the facet has been selected
        """
        if not self.request.facets_on():
            return []
        solr_field_name = config.ALLOWED_FACETS_FROM_WEB_INTERFACE.get(facet_name, None)

        #I extract the facets from the raw response
        if self.raw.has_key('facet_counts'):
            raw_facet_fields = self.raw['facet_counts']['facet_fields']
        else:
            raw_facet_fields = {}
        facets_list = raw_facet_fields.get(solr_field_name, [])

        #I split the list in tuples
        facets_tuples_list = [tuple(facets_list[i:i+2]) for i in xrange(0, len(facets_list), 2)]

        #I extract the facet parameter submitted
        query_parameters = self.get_facet_param_field(facet_name)

        if not hierarchical:
            return sorted([(elem[0], elem[1], 'selected') if elem[0] in query_parameters else (elem[0], elem[1], '') for elem in facets_tuples_list], key= lambda x: (-x[1], x[0]))
        else:
            return sorted([tuple(elem[0].split('/') + [elem[1], 'selected', elem[0]]) if elem[0] in query_parameters else tuple(elem[0].split('/') + [elem[1], '', elem[0]]) for elem in facets_tuples_list], key= lambda x: (-x[-3], x[-4]))

    def get_hier_facets_fields(self, facet_name):
        """
        Like get_facets_fields but returns a more complex structure for the hierarchical facets
        """
        def update_multidict(fac_dict, hier_facet):
            """ Function to create the data structure for the facets"""
            x = fac_dict
            level = hier_facet[0]
            fac_list = hier_facet[1:-2]
            last_value = hier_facet[-2]
            selection = hier_facet[-1]
            for i in range(int(level) +1):
                if i != int(level):
                    x = x[fac_list[i]][2]
                else:
                    x[fac_list[i]] = (last_value, selection, {})
        def fac_dict_to_tuple(fac_dict):
            """Returns a tuple version of the dictionary of facets"""
            tuple_facets = sorted(fac_dict.items(), key= lambda x: (-x[1][0], x[0]))
            ret_list = []
            for elem in tuple_facets:
                if not elem[1][2]:
                    ret_list.append(elem)
                else:
                    ret_list.append((elem[0], (elem[1][0], elem[1][1], fac_dict_to_tuple(elem[1][2]))))
            return tuple(ret_list)

        if not self.request.facets_on():
            return []
        solr_field_name = config.ALLOWED_FACETS_FROM_WEB_INTERFACE.get(facet_name, None)

        raw_facet_fields = self.raw['facet_counts']['facet_fields']
        facets_list = raw_facet_fields.get(solr_field_name, [])
        #I split the list in tuples
        facets_tuples_list = [tuple(facets_list[i:i+2]) for i in xrange(0, len(facets_list), 2)]

        #I extract the facet parameter submitted
        query_parameters = self.get_facet_param_field(facet_name)
        #then I put all the levels of the hierarchical facets in a unique tuple
        hier_facets_split = [tuple(elem[0].split('/') + [elem[1], 'selected']) if elem[0] in query_parameters else tuple(elem[0].split('/') + [elem[1], '']) for elem in facets_tuples_list]
        #I sort the tuples because I need them in the right order to fill in the dictionary
        hier_facets_split.sort(key = lambda x: (x[0], -x[-2]))
        #I re organize the facets
        final_facets = {}
        for elem in hier_facets_split:
            update_multidict(final_facets, elem)
        #then I convert them back to lists of tuples and I return it
        return fac_dict_to_tuple(final_facets)

    def get_facet_param_field(self, facet_name):
        """
        Returns the list of query parameters for the current facet name
        """
        return [elem[1] for elem in self.get_facet_parameters() if elem[0] == facet_name]

    def get_facet_parameters(self):
        """
        Returns the list of query parameters
        """
        if not hasattr(self, 'request_facet_params'):
            facet_params = []
            #first I extract the query parameters excluding the default ones
            search_filters = self.request.get_param('ui_filters')
            #I extract only the parameters of the allowed facets
            inverted_allowed_facet_dict = dict((v,k) for k,v in config.ALLOWED_FACETS_FROM_WEB_INTERFACE.iteritems())
            for filter_val in search_filters:
                filter_split = filter_val.split(':', 1)
                filter_query_name = filter_split[0]

                #if the filter starts with a "-" sign (exclude), need to remove it and prepend to the value
                negative_filter = filter_query_name.startswith(u'-')
                if negative_filter:
                    filter_query_name = filter_query_name[1:]

                #remove the filter query parser if present
                if config.SOLR_FILTER_QUERY_PARSER:
                    if filter_query_name.startswith(u"{!%s}" %  config.SOLR_FILTER_QUERY_PARSER):
                        filter_query_name = filter_query_name[len(u"{!%s}" %  config.SOLR_FILTER_QUERY_PARSER):]

                if filter_query_name in inverted_allowed_facet_dict:
                    facet_name = inverted_allowed_facet_dict[filter_query_name]
                    filter_value = filter_split[1].strip('"')
                    if negative_filter:
                        filter_value = u'-%s' % filter_value
                    facet_params.append((facet_name, filter_value))

            self.request_facet_params = facet_params
        return self.request_facet_params

    def get_pagination(self):
        """
        wrap default pagination but use our row count setting
        """
        try:
            num_rows = int(self.request.params.rows)
        except (ValueError, TypeError):
            num_rows = int(config.SEARCH_DEFAULT_ROWS)
        return super(SolrResponse, self).get_pagination(rows_per_page=num_rows)


import argparse
import time
import io
import zenoh
import json
from servo import *
from pycdr2 import IdlStruct
from pycdr2.types import int8, int32, uint32, float64

@dataclass
class Vector3(IdlStruct, typename="Vector3"):
    x: float64
    y: float64
    z: float64

@dataclass
class Twist(IdlStruct, typename="Twist"):
    linear: Vector3
    angular: Vector3

DEVICENAME                  = '/dev/ttyACM0'
PROTOCOL_VERSION            = 2.0
BAUDRATE                    = 115200
MOTOR_ID                    = 200

parser = argparse.ArgumentParser(
    prog='drive_motors',
    description='zenoh drive_motors example')
parser.add_argument('-m', '--mode', type=str, choices=['peer', 'client'],
                    help='The zenoh session mode.')
parser.add_argument('-e', '--connect', type=str, metavar='ENDPOINT', action='append',
                    help='zenoh endpoints to connect to.')
parser.add_argument('-l', '--listen', type=str, metavar='ENDPOINT', action='append',
                    help='zenoh endpoints to listen on.')
parser.add_argument('-d', '--delay', type=float, default=0.1,
                    help='delay between each iteration in seconds')
parser.add_argument('-p', '--prefix', type=str, default='rt/turtle1',
                    help='resources prefix')
parser.add_argument('-c', '--config', type=str, metavar='FILE',
                    help='A zenoh configuration file.')

args = parser.parse_args()

count = 0
cmd = Twist(Vector3(0.0, 0.0, 0.0), Vector3(0.0, 0.0, 0.0))

conf = zenoh.config_from_file(args.config) if args.config is not None else zenoh.Config()
if args.connect is not None:
    conf.insert_json5(zenoh.config.CONNECT_KEY, json.dumps(args.connect))
if args.mode is not None:
    conf.insert_json5(zenoh.config.MODE_KEY, json.dumps(args.mode))
if args.listen is not None:
    conf.insert_json5(zenoh.config.LISTEN_KEY, json.dumps(args.listen))

print('[INFO] Open zenoh session...')
zenoh.init_logger()
z = zenoh.open(conf)

publ = z.declare_publisher('{}/heartbeat'.format(args.prefix))

def listener(sample):
    global cmd
    cmd = Twist.deserialize(sample.value.payload)

print('[INFO] Connect to motor...')
servo = Servo(DEVICENAME, PROTOCOL_VERSION, BAUDRATE, MOTOR_ID)
if servo is None:
    print('[WARN] Unable to connect to motor.')
else:
    servo.write1ByteTxRx(IMU_RE_CALIBRATION, 1)
    sub = z.declare_subscriber('{}/cmd_vel'.format(args.prefix), listener)

time.sleep(3.0)

print('[INFO] Running!')
while True:
    if servo is not None:
        servo.write1ByteTxRx(HEARTBEAT, count)
        servo.write4ByteTxRx(CMD_VELOCITY_LINEAR_X, int(cmd.linear.x))
        servo.write4ByteTxRx(CMD_VELOCITY_LINEAR_Y, int(cmd.linear.y))
        servo.write4ByteTxRx(CMD_VELOCITY_LINEAR_Z, int(cmd.linear.z))
        servo.write4ByteTxRx(CMD_VELOCITY_ANGULAR_X, int(cmd.angular.x))
        servo.write4ByteTxRx(CMD_VELOCITY_ANGULAR_Y, int(cmd.angular.y))
        servo.write4ByteTxRx(CMD_VELOCITY_ANGULAR_Z, int(cmd.angular.z))
    cmd = Twist(Vector3(0.0, 0.0, 0.0), Vector3(0.0, 0.0, 0.0))

    publ.put(count)

    count += 1
    if count > 255:
        count = 0

    time.sleep(args.delay)


"""

    *Prefix*

  A prefix.

"""

from abc import ABCMeta

from typing import TypeVar

__all__ = ["Prefix"]

class Prefix:
    __metaclass__ = ABCMeta

    Meta = TypeVar("Meta")
    Control = TypeVar("Control")
    Shift = TypeVar("Shift")
    Hyper = TypeVar("Hyper")
    Super = TypeVar("Super")
    Alt = TypeVar("Alt")


"""
构造MeshSegNet数据集
"""

import os
import vtk
from shutil import copyfile

def convert(dataset: str, img_path: str, label_path: str):
    """
    提供原始数据路径和目标数据路径
    """
    reader = vtk.vtkPLYReader()
    writer = vtk.vtkPolyDataWriter()
    # 当前文件夹下的所有子文件夹
    folders = [name for name in os.listdir(dataset) if os.path.isdir(os.path.join(dataset, name))]

    for folder in folders:
        files = [name for name in os.listdir(os.path.join(dataset, folder))]

        for file in files:
            reader.SetFileName(os.path.join(dataset, folder, file))
            reader.Update()

            # 将PLY数据转换为VTK数据
            ply_data = reader.GetOutput()

            if 'label' in file:
                writer.SetFileName(os.path.join(label_path, folder.split('_')[0] + '_' + file.split('.')[0] + '.vtk'))

            else:
                writer.SetFileName(os.path.join(img_path, folder.split('_')[0] + '_' + file.split('.')[0] + '.vtk'))
            writer.SetInputData(ply_data)
            writer.Write()

def copyto(dataset: str, img_path: str, label_path: str):
    """
    拷贝原始文件到其他文件夹
    """
    # 当前文件夹下的所有子文件夹
    folders = [name for name in os.listdir(dataset) if os.path.isdir(os.path.join(dataset, name))]

    for folder in folders:
        files = [name for name in os.listdir(os.path.join(dataset, folder))]

        for file in files:
            if 'label' in file:
                copyfile(os.path.join(dataset, folder, file),
                         os.path.join(label_path, folder.split('_')[0] + '_' + file.split('.')[0] + '.ply'))
            else:
                copyfile(os.path.join(dataset, folder, file),
                         os.path.join(img_path, folder.split('_')[0] + '_' + file.split('.')[0] + '.ply'))

def main():
    datasets = ['/media/why/77B8B456EE73FE06/users/xsf_ubuntu/Dataset/OralScan/dataset_labelled_cell_color_downsampled_10000']
    img_path = '/media/why/77B8B456EE73FE06/users/xsf_ubuntu/Dataset/OralScan/img'
    label_path = '/media/why/77B8B456EE73FE06/users/xsf_ubuntu/Dataset/OralScan/label'

    # datasets = ['D:\\users\\xsf\\Dataset\\OralScan\\dataset_labelled_cell_color_downsampled_50000_colorrefine']
    # img_path = 'D:\\users\\xsf\\Dataset\\OralScan\\img'
    # label_path = 'D:\\users\\xsf\\Dataset\\OralScan\\label'

    for dataset in datasets:
        copyto(dataset, img_path, label_path)

if __name__ == '__main__':
    main()


''' Elabore um programa em Python que gere uma matriz aleatória (9x9), com números entre 0 e 10, imprima-a. Após, peça
o quadrante desejado e imprima os elementos desse quadrante. '''

from random import randint

from termcolor import colored

m = [0] * 9
for i in range(9):
    m[i] = [0] * 9
    for j in range(9):
        m[i][j] = randint(0,10)
        if i == 4 or j == 4:  # Criando a "divisão" entre os quadrantes
            print(colored(f"{m[i][j]:02}","blue"),end=" ")
        else:
            print(f"{m[i][j]:02}",end=" ")
    print()

while True:
    quadrante = int(input("\nInforme o quadrante [1-4] >> "))
    if quadrante in range(1,5):
        break
    print("Valor inválido!! Digite novamente...")

if quadrante == 1:
    for i in range(4):
        for j in range(4):
            print(f"{m[i][j]:02}", end=" ")
        print()
elif quadrante == 2:
    for i in range(4):
        for j in range(5,9):
            print(f"{m[i][j]:02}", end=" ")
        print()
elif quadrante == 3:
    for i in range(5,9):
        for j in range(4):
            print(f"{m[i][j]:02}", end=" ")
        print()
else:
    for i in range(5,9):
        for j in range(5,9):
            print(f"{m[i][j]:02}", end=" ")
        print()

'''
from random import randint
from termcolor import colored

m = []
for i in range(9):
    m.append([])
    for j in range(9):
        m[i].append(randint(0,10))
        print(f'{m[i][j]:02}', end=' ')
    print()

while True:
    q = int(input('\nInforme o quadrante >> '))
    if 1 <= q <= 4:
        break
    print('Quadrante inválido. Digite um valor entre 1 e 4')

print()
if q == 1:
    for i in range(9):
        for j in range(9):
            if i < 4 and j < 4:
                print(colored(f'{m[i][j]:02}','red'), end=' ')
            else:
                print(f'{m[i][j]:02}', end=' ')
        print()
elif q == 2:
    for i in range(9):
        for j in range(9):
            if i < 4 and j > 4:
                print(colored(f'{m[i][j]:02}','red'), end=' ')
            else:
                print(f'{m[i][j]:02}', end=' ')
        print()
elif q == 3:
    for i in range(9):
        for j in range(9):
            if i > 4 and j < 4:
                print(colored(f'{m[i][j]:02}','red'), end=' ')
            else:
                print(f'{m[i][j]:02}', end=' ')
        print()
else:
    for i in range(9):
        for j in range(9):
            if i > 4 and j > 4:
                print(colored(f'{m[i][j]:02}','red'), end=' ')
            else:
                print(f'{m[i][j]:02}', end=' ')
        print()
'''


from collections import Counter

import numpy as np
import pandas as pd
from matplotlib import pyplot

from backfit.BackfitUtils import init_objects
from backfit.utils.utils import load_new_diffs, load_mcmc_diffs
from utils.utils import extract_runs_w_timestamp

if __name__ == '__main__':
    n_users = -1
    cats, cat_lookup, all_qids, users, _stretches_, levels, cat_ixs = init_objects(n_users, seed=666)
    passdiffs, stretches, passquals, all_qids = load_new_diffs()
    all_qids = list(all_qids)

    users = np.unique(users)
    n_users = len(users)
    print("kickoff for n_users?",n_users)

    n_qids = 1+len(all_qids) #+1 to represent ROOT

    usersf = open("direct_mcmc_users.txt","w")

    udf = pd.read_csv("users_all.csv")
    students = udf[ (udf["role"]=="STUDENT") & (udf["date_of_birth"].notnull()) ]
    print(students["date_of_birth"])
    ages = ( pd.to_datetime(students['registration_date']) - pd.to_datetime(students['date_of_birth'])).dt.total_seconds() / (86400*365.2425)
    print(ages)

    binwidth=0.25
    binz = np.arange(min(ages), max(ages) + binwidth, binwidth)
    ages.plot.hist(alpha=0.5, bins=binz)
    print(np.median(ages))
    print(np.mean(ages))
    print(np.std(ages))
    pyplot.show()


# -*- coding: utf-8 -*-
"""
-------------------------------------------------------------------------------
GUFY - Copyright (c) 2019, Fabian Balzer

Distributed under the terms of the GNU General Public License v3.0.

The full license is in the file LICENSE.txt, distributed with this software.
-------------------------------------------------------------------------------

@author: Fabian Balzer (fabian.balzer@studium.uni-hamburg.de)

A script for writing plot scripts.

The module is structured as follows:
    - The WriteToScriptDialog class for the write-to-script options
    - Functions for saving the file and writing it
    - Functions for constructing the slice and projection plot strings
    - Functions for constructing the line and phase plot strings
    - Function for constructing the profile plot strings
          (All variants including time series)
    - Function for constructing the plot-making string
    - Functions to construct time series strings
    - Functions to construct miscellaneous strings like derived fields, file
          loading and annotations (alphabetical order)
"""
import PyQt5.QtWidgets as QW
import PyQt5.QtCore as QC
import PyQt5.QtGui as QG
from datetime import datetime
import math
import yt
from simgui_modules.utils import getCalcQuanName, getCalcQuanString
from simgui_modules.additionalWidgets import GUILogger
from simgui_modules.checkBoxes import coolCheckBox

# %% The WriteToScriptDialog class for the write-to-script options
class WriteToScriptDialog(QW.QDialog):
    """A dialog that pops up if the user wants to write the settings of the GUI
    or the plot to a reproducible script.
    Params:
        Param_Dict: For receiving information that has to be written to script
        PlotWindow: Whether the instance belongs to a plot window or the
                    general GUI
    """
    def __init__(self, Param_Dict, PlotWindow=True):
        super().__init__()
        if PlotWindow:
            GUILogger.info("Options for writing the plot displayed as a reproducible skript")
            self.setWindowTitle("Options for writing the plot to script")
            self.text = "that were used to create the plot"
        else:
            GUILogger.info("Options for writing settings of the GUI as a reproducible skript")
            self.setWindowTitle("Options for writing the settings to script")
            self.text = "of the GUI as they are specified in the Plot options"
        GUILogger.info("Detailed information for making plots using yt can "
                       "also be found "
                       'here, '
                       'here and '
                       'here.')
        self.noComments = False
        self.plotAll = False
        self.Param_Dict = Param_Dict
        self.setModal(True)
        self.initUi(PlotWindow)
        self.signalsConnection()
        self.resize(400, 300)
        self.setWindowIcon(QG.QIcon('simgui_registry/CoverIcon.png'))
        self.show()

    def initUi(self, PlotWindow):
        """Initialize all of the ingredients for this UI."""
        # Buttons for closing the window:
        self.buttonBox = QW.QDialogButtonBox(self)
        self.buttonBox.addButton("Create script", QW.QDialogButtonBox.AcceptRole)
        self.buttonBox.addButton("Cancel", QW.QDialogButtonBox.RejectRole)
        self.textBrowser = QW.QTextBrowser()
        text = ""
        if not PlotWindow:
            text += ("""
Warning: Due to the way
                     the script writing works, the script might be
                     incomplete concerning some of the extrema or other
                     settings.
""")
        text += f"""
If you want, I can now produce a script with the
        settings {self.text} for you.

Pressing the Create script-button will prompt you
        to enter a filename of where the script is to be created.

If you don't want comments in the script - which is understandable
        if you're experienced, for beginners they might be useful - just
        check the checkbox below.
"""
        self.commentCheckBox = coolCheckBox(text="Disable comments", width=None)
        layout = QW.QVBoxLayout()
        layout.addWidget(self.textBrowser)
        layout.addWidget(self.commentCheckBox)
        series = self.Param_Dict["isValidSeries"]
        noMultiple = not self.Param_Dict["TimeSeriesProf"]
        notTime = not self.Param_Dict["XAxis"] == "time"
        if series and noMultiple and notTime:
            text += """
Since you have loaded a time series, you may transfer
            the Plot all functionality to the script as well. Just tick
            the corresponding CheckBox"""
            self.makeSeriesCheckBox = coolCheckBox(text="Write script to plot all", width=None)
            self.makeSeriesCheckBox.toggled.connect(self.getSeriesInput)
            layout.addWidget(self.makeSeriesCheckBox)
        self.textBrowser.setHtml(text)
        layout.addWidget(self.buttonBox)
        self.setLayout(layout)

    def signalsConnection(self):
        """Connects all the signals and emits some of them for a proper start
        """
        self.buttonBox.accepted.connect(self.applyPressed)
        self.buttonBox.rejected.connect(self.cancelPressed)
        self.commentCheckBox.toggled.connect(self.getCommentInput)
        self.accepted.connect(lambda: saveFile(self))

    def applyPressed(self):
        """Handles the Button Press of 'Create n plots'"""
        self.accept()

    def cancelPressed(self):
        """Handles the Button press of 'Cancel'"""
        self.reject()

    def getCommentInput(self, state):
        """Saves the state of the checkbox"""
        self.noComments = state

    def getSeriesInput(self, state):
        self.plotAll = state

# %% Functions for saving the file and writing it
def saveFile(dialog):
    """Ask the user to give a name for the file to be written out and save
    the plot with its current settings as a script."""
    filename = QW.QFileDialog.getSaveFileName(dialog, 'Write Plot to file',
                                              dialog.Param_Dict["Directory"],
                                              "Python files (*.py);; "
                                              "All files (*)")[0]
    if filename != "":
        writePlotToScript(dialog.Param_Dict, filename, dialog.noComments,
                          dialog.plotAll)
        GUILogger.log(29, "A script to reproduce the plot has been writt"
                      f"en to {filename}.")
    else:
        GUILogger.info("No filename selected.")

def writePlotToScript(Param_Dict, filename, noComments, plotAll):
    """Write the plot passed through Param_Dict to a file filename.
    Params:
        Param_Dict: Parameter dictionary containing all of the settings
        filename: Name of the file to be written (including directory)
        noComments: bool if the user doesn't want comments
        plotAll: For time series if the user wants to have iteration
    """
    if not filename.endswith(".py"):
        filename += ".py"
    text = constructCompleteString(Param_Dict, plotAll, filename)
    if noComments:
        textLines = text.split("\n")
        # remove all lines that begin with a hashtag:
        textLines = [line for line in textLines if not line.strip().startswith("#")]
        textLines.insert(0, "# -*- coding: utf-8 -*-")
        # remove all in-line-comments:
        text = ""
        for line in textLines:
            if "#" in line:
                text += line.split("  # ")[0]
            elif not line.isspace():  # check if all of the characters are spaces
                text += line
            text += "\n"
        # remove all 'Note:'-blocks:
        textBlocks = text.split('"""\nNote:')
        text = ""
        for i, block in enumerate(textBlocks):
            if i != 0:
                block = "".join(block.split('"""')[1:])
            text += block
    with open(filename, "w") as file:
        file.write(text)

# %% Function for writing the introduction
def constructCompleteString(Param_Dict, plotAll, filename):
    """Construct a long string that can be used to replot the plot with its
    current settings.
    Params:
        Param_Dict: Parameter dictionary containing all of the plot data
    Returns:
        text: String that can be used to replot the plot
    """
    introString = f'''# -*- coding: utf-8 -*-
"""
{filename.split("/")[-1]}
Script containing the plot produced using GUFY - GUI for FLASH Code simulations
based on yt.

Created on {datetime.now().strftime("%a, %b %d %X %Y")}.

Contains a suggestion to reproduce the plot(s).
Since it is dynamically created, it may not be the prettiest but I hope it can
help to get started.\n
If you detect any bugs or have questions, please contact me via email through
fabian.balzer@studium.uni-hamburg.de.
"""
import yt
import matplotlib.pyplot as plt
'''
    if Param_Dict["DimMode"] == "2D":
        introString += "from mpl_toolkits.axes_grid1 import make_axes_locatable\n"
    if plotAll:
        introString += "import os"
    introString += "\n\n"
    introString += constructDerFieldString(Param_Dict)
    if checkTimeSeriesPlot(Param_Dict, plotAll):
        plotString = constructSeriesString(Param_Dict, plotAll)
    elif Param_Dict["isValidFile"]:
        plotString = constructFileString(Param_Dict)
    else:
        return "# This shouldn't happen. Something went wrong writing the file"
    text = introString + plotString
    return text

def constructFileString(Param_Dict):
    """Constructs the string when handling a single file"""
    loadingString = "# Load the file through yt and save the dataset as ds:\n"
    loadingString += f'ds = yt.load("{Param_Dict["Directory"]}/{str(Param_Dict["CurrentDataSet"])}")\n\n'
    plotString = eval("construct"+Param_Dict["PlotMode"]+"Plot(Param_Dict)")
    text = loadingString + plotString
    return text

# %% Functions for constructing the slice and projection plot strings
def constructSlicePlot(Param_Dict):
    """Constructs the Slice plot script"""
    ds = Param_Dict["CurrentDataSet"]
    gridUnit = Param_Dict["GridUnit"]
    c0, c1, c2 = Param_Dict["XCenter"], Param_Dict["YCenter"], Param_Dict["ZCenter"]
    width = f'(({Param_Dict["HorWidth"]}, "{gridUnit}"), ({Param_Dict["VerWidth"]}, "{gridUnit}"))'
    field = Param_Dict["ZAxis"]
    if Param_Dict["NormVecMode"] == "Axis-Aligned":
        plotString = (
f'''
# Initialize a yt axis-aligned slice plot with the dataset ds, normal vector {Param_Dict["NAxis"]},
# field {field} and the optional parameters axes_unit, center, width and fontsize:
c0 = yt.YTQuantity({c0}, "{gridUnit}")  # Get the center coordinates.
c1 = yt.YTQuantity({c1}, "{gridUnit}")  # Unfortunately, using a YTArray
c2 = yt.YTQuantity({c2}, "{gridUnit}")  # does not work properly.
slc = yt.SlicePlot(ds, "{Param_Dict["NAxis"]}", "{field}", axes_unit="{gridUnit}",
                   center=[c0, c1, c2], width={width},
                   fontsize=14)
''')
    else:
        normVec = [Param_Dict[axis + "NormDir"] for axis in ["X", "Y", "Z"]]
        northVec = [Param_Dict[axis + "NormNorth"] for axis in ["X", "Y", "Z"]]
        plotString = (
f'''# Initialize a yt off-axis slice plot with the dataset ds, normal vector,
# field {field} and the optional parameters north_vector, axes_unit, center and
# fontsize. The north vector is the vector that defines the 'up'-direction:
c0 = yt.YTQuantity({c0}, "{gridUnit}")  # Get the center coordinates.
c1 = yt.YTQuantity({c1}, "{gridUnit}")  # Unfortunately, using a YTArray
c2 = yt.YTQuantity({c2}, "{gridUnit}")  # does not work properly.
slc = yt.OffAxisSlicePlot(ds, {normVec}, "{field}", north_vector={northVec},
                          axes_unit="{gridUnit}", fontsize=14, center=[c0, c1, c2])
''')
    plotString += f'# Hint: You can access the generated data using slc.frb["{field}"]\n\n\n'
    modString = ""
    fieldMin = Param_Dict["ZMin"]  # Float
    fieldMax = Param_Dict["ZMax"]
    if field not in Param_Dict["FieldMins"].keys():
        modString = (
f'''# It seems that you have not calculated extrema for {field}.
# You can do this by using
# minMaxArray = ds.all_data().quantities.extrema({field})
# which will return a YTArray with two entries plus the units.
''')
    unit = Param_Dict["ZUnit"]
    cmap = Param_Dict["ColorScheme"]
    modString += ("# Set unit, minimum, maximum and color scheme:\n"
                 'slc.set_unit("{0}", "{1}")\nslc.set_zlim("{0}", {2}, {3})  '
                 '# These are given in the same unit, {1}.\n'
                 'slc.set_cmap("{0}", "{4}")\n'.format(field, unit, fieldMin, fieldMax, cmap))
    log = Param_Dict["ZLog"]  # Boolean
    if fieldMin != "":
        modString += "# Set our field scaling logarithmic if wanted:\n"
        if min(fieldMin, fieldMax) <= 0 and log:
            modString += ('slc.set_log("{0}", True, linthresh=(({1}-{2})/1000))  '
            "# linthresh sets a linear scale for a small portion and then a symbolic one "
            "for negative values\n".format(field, fieldMax, fieldMin))
        else:
            modString += f'slc.set_log("{field}", {log})  # This may be redundant in some cases\n'
    zoom = Param_Dict["Zoom"]
    modString += f"slc.zoom({zoom})\n\n\n"

    # Do the annotations:
    annoString = ""
    title = Param_Dict["PlotTitle"]
    if title != "":
        annoString += f'slc.annotate_title("{title}")  # Give the plot the title it deserves.\n'
    if Param_Dict["Timestamp"]:
        annoString += ("slc.annotate_timestamp(corner='upper_left', draw_inset_box=Tr"
        "ue)  # There are many more modifications for the timestamp.\n")
    if Param_Dict["Geometry"] == "cartesian":
        if Param_Dict["Scale"]:
            annoString += "slc.annotate_scale(corner='upper_right')\n"
        if Param_Dict["Grid"]:
            annoString += ('WARNING = "There is a yt-internal bug where grid '
                          'annotation doesn\'t work if a center coordinate is '
                          'set to 0!"\nslc.annotate_grids()\n')
        if Param_Dict["ParticleAnno"]:
            if Param_Dict["PSlabWidth"] == "" or float(Param_Dict["PSlabWidth"]) == 0:
                Param_Dict["PSlabWidth"] = 1
            height = abs(Param_Dict["FieldMins"]["DomainHeight"] - Param_Dict["FieldMaxs"]["DomainHeight"])
            width = float(Param_Dict["PSlabWidth"])*height
            annoString += (f"slc.annotate_particles({width})\n")
        if Param_Dict["VelVectors"]:
            annoString += "slc.annotate_velocity(normalize=True)\n"
        if Param_Dict["VelStreamlines"]:
            annoString += ('WARNING = "There is a yt-internal bug where streamline '
                          'annotation doesn\'t work if a center coordinate is '
                          'set to 0!"\n')
            if Param_Dict["NAxis"] == "x":
                annoString += "slc.annotate_streamlines('velocity_y', 'velocity_z')\n"
            elif Param_Dict["NAxis"] == "y":
                annoString += "slc.annotate_streamlines('velocity_x', 'velocity_z')\n"
            elif Param_Dict["NAxis"] == "z":
                annoString += "slc.annotate_streamlines('velocity_x', 'velocity_y')\n"
        if Param_Dict["MagVectors"]:
            annoString += "slc.annotate_magnetic_field(normalize=True)\n"
        if Param_Dict["MagStreamlines"]:
            annoString += ('WARNING = "There is a yt-internal bug where streamline '
                          'annotation doesn\'t work if a center coordinate is '
                          'set to 0!"\n')
            if Param_Dict["NAxis"] == "x":
                annoString += "slc.annotate_streamlines('magy', 'magz')\n"
            elif Param_Dict["NAxis"] == "y":
                annoString += "slc.annotate_streamlines('magx', 'magz')\n"
            elif Param_Dict["NAxis"] == "z":
                annoString += "slc.annotate_streamlines('magx', 'magy')\n"
        if Param_Dict["Contour"]:
            annoString += "slc.annotate_contour('{}')\n".format(field)
    elif Param_Dict["Geometry"] == "cylindrical":
        if Param_Dict["Grid"]:
            annoString += "slc.annotate_grids()\n"
    if len(annoString) > 0:  # If annotations are made, declare them:
        annoString = "# Annotations for the plot:\n" + annoString + "\n\n"
    figureString = constructUsingMPL(Param_Dict, "slc")

    text = plotString + modString + annoString + figureString
    return text

def constructProjectionPlot(Param_Dict):
    """Constructs the projection plot script"""
    ds = Param_Dict["CurrentDataSet"]
    field = Param_Dict["ZAxis"]
    if Param_Dict["WeightField"] is None:
        weightField = "None"
    else:
        weightField = '"{}"'.format(Param_Dict["WeightField"])
    plotString = ""
    if Param_Dict["DomainDiv"]:
        # in case the user wants to divide everything by the domain_height,
        # we define a new field which is just the old field divided by height
        # and then do a projectionPlot for that.
        height = Param_Dict["FieldMaxs"]["DomainHeight"] - Param_Dict["FieldMins"]["DomainHeight"]
        plotString += (
f"""# We want to norm our projection by the domain height:
domainHeight = {height}  # You can obtain this by calculating
# ds.domain_right_edge - ds.domain_left_edge for all dimensions
# To do this, we can define a new field for the dataset:
def _NormField(field, data):
    return data["{field}"]/yt.units.YTQuantity(ds.arr(domainHeight, "code_length"))  # This way, yt will understand the units
""")
        field = "Normed " + field
        plotString += f'unit = yt.units.unit_object.Unit("{Param_Dict["ZUnit"]}/cm")'
        plotString += ("# Unfortunately add_field doesn't understand lambda functions.\n"
                       'ds.add_field(("gas", "{field}"), function=_NormField,\n'
                       "             units='auto', dimensions=unit.dimensions)\n\n\n")
    NVector, gridUnit = Param_Dict["NAxis"], Param_Dict["GridUnit"]
    c0, c1, c2 = Param_Dict["XCenter"], Param_Dict["YCenter"], Param_Dict["ZCenter"]
    width = f'(({Param_Dict["HorWidth"]}, "{gridUnit}"), ({Param_Dict["VerWidth"]}, "{gridUnit}"))'
    field = Param_Dict["ZAxis"]
    if Param_Dict["ParticlePlot"]:
        plotString += (
f'''
# Initialize a yt Particle Projection plot with the dataSet ds, Normal
# Vector {NVector}, field {field} and the optional parameters axes_unit,
# weight_field, center and fontsize:
c0 = yt.YTQuantity({c0}, "{gridUnit}")  # Get the center coordinates.
c1 = yt.YTQuantity({c1}, "{gridUnit}")  # Unfortunately, using a YTArray
c2 = yt.YTQuantity({c2}, "{gridUnit}")  # does not work properly.
proj = yt.ParticleProjectionPlot(ds, "{Param_Dict["NAxis"]}", "{field}",
                                 axes_unit="{gridUnit}", center=[c0, c1, c2],
                                 weight_field={weightField}, width={width},
                                 fontsize=14)
# Warning: Particle plots are still an experimental feature of the GUI and may
# produce errors
''')
    elif Param_Dict["NormVecMode"] == "Axis-Aligned":
        plotString += (
f'''
# Initialize a yt Projection plot with the dataSet ds, Normal Vector {NVector},
# field {field} and the optional parameters axes_unit, weight_field,
# center and fontsize:
c0 = yt.YTQuantity({c0}, "{gridUnit}")  # Get the center coordinates.
c1 = yt.YTQuantity({c1}, "{gridUnit}")  # Unfortunately, using a YTArray
c2 = yt.YTQuantity({c2}, "{gridUnit}")  # does not work properly.
proj = yt.ProjectionPlot(ds, "{Param_Dict["NAxis"]}", "{field}", axes_unit="{gridUnit}",
                         center=[c0, c1, c2], weight_field={weightField},
                         width={width}, fontsize=14)
''')
    else:
        normVec = [Param_Dict[axis + "NormDir"] for axis in ["X", "Y", "Z"]]
        northVec = [Param_Dict[axis + "NormNorth"] for axis in ["X", "Y", "Z"]]
        plotString += (
f'''
# Initialize a yt Projection plot with the dataSet ds, Normal Vector {NVector},
# field {field} and the optional parameters axes_unit, weight_field,
# center and fontsize:
c0 = yt.YTQuantity({c0}, "{gridUnit}")  # Get the center coordinates.
c1 = yt.YTQuantity({c1}, "{gridUnit}")  # Unfortunately, using a YTArray
c2 = yt.YTQuantity({c2}, "{gridUnit}")  # does not work properly.
proj = yt.OffAxisProjectionPlot(ds, {normVec}, "{field}", north_vector={northVec},
                                axes_unit="{gridUnit}", center=[c0, c1, c2],
                                weight_field={weightField}, width={width},
                                fontsize=14)
''')
    plotString += f'# Hint: You can access the generated data using proj.frb["{field}"]\n\n\n'
    modString = ""
    fieldMin = Param_Dict["ZMin"]  # Float
    fieldMax = Param_Dict["ZMax"]
    if field not in Param_Dict["FieldMins"].keys():
        modString = (
f'''# It seems that you have not calculated extrema for {field}.
# You can do this by using
# minMaxArray = ds.all_data().quantities.extrema({field})
# which will return a YTArray with two entries plus the units.
''')
    unit = Param_Dict["ZUnit"]
    cmap = Param_Dict["ColorScheme"]
    modString += ("# Set unit, minimum, maximum and color scheme:\n"
                 'proj.set_unit("{0}", "{1}")\nproj.set_zlim("{0}", {2}, {3})  '
                 '# These are given in the same unit, {1}.\n'
                 'proj.set_cmap("{0}", "{4}")\n'.format(field, unit, fieldMin, fieldMax, cmap))
    if fieldMin == "":
        log = Param_Dict["ZLog"]  # Boolean
        modString += "# Set our field scaling logarithmic if wanted:\n"
        if min(fieldMin, fieldMax) <= 0 and log:
            modString += ("proj.set_log('{0}', True, linthresh=(({1}-{2})/1000)  "
            "# linthresh sets a linear scale for a small portion and then a symbolic one "
            "for negative values\n".format(field, fieldMax, fieldMin))
        else:
            modString += f'proj.set_log("{field}", {log})  # This may be redundant in some cases.\n'
    zoom = Param_Dict["Zoom"]
    modString += f"proj.zoom({zoom})\n\n"
    # Do the annotations:
    annoString = ""
    title = Param_Dict["PlotTitle"]
    if title != "":
        annoString += f'proj.annotate_title("{title}")  # Give the plot the title it deserves.\n'
    if Param_Dict["Timestamp"]:
        annoString += ("proj.annotate_timestamp(corner='upper_left', draw_inset_box=Tr"
        "ue)  # There are many more modifications for the timestamp.\n")
    if Param_Dict["Geometry"] == "cartesian":
        if Param_Dict["Scale"]:
            annoString += "proj.annotate_scale(corner='upper_right')\n"
        if Param_Dict["Grid"]:
            annoString += ('WARNING = "There is a yt-internal bug where grid '
                          'annotation doesn\'t work if a center coordinate is '
                          'set to 0!"\n')
            annoString += "proj.annotate_grids()\n"
        if Param_Dict["ParticleAnno"]:
            if Param_Dict["PSlabWidth"] == "" or float(Param_Dict["PSlabWidth"]) == 0:
                Param_Dict["PSlabWidth"] = 1
            height = abs(Param_Dict["FieldMins"]["DomainHeight"] - Param_Dict["FieldMaxs"]["DomainHeight"])
            width = float(Param_Dict["PSlabWidth"])*height
            annoString += (f"slc.annotate_particles({width})\n")
        if Param_Dict["VelVectors"]:
            annoString += "proj.annotate_velocity(normalize=True)\n"
        if Param_Dict["VelStreamlines"]:
            annoString += ('WARNING = "There is a yt-internal bug where streamline '
                          'annotation doesn\'t work if a center coordinate is '
                          'set to 0!"\n')
            if Param_Dict["NAxis"] == "x":
                annoString += "proj.annotate_streamlines('velocity_y', 'velocity_z')\n"
            elif Param_Dict["NAxis"] == "y":
                annoString += "proj.annotate_streamlines('velocity_x', 'velocity_z')\n"
            elif Param_Dict["NAxis"] == "z":
                annoString += "proj.annotate_streamlines('velocity_x', 'velocity_y')\n"
        if Param_Dict["MagVectors"]:
            annoString += "proj.annotate_magnetic_field(normalize=True)\n"
        if Param_Dict["MagStreamlines"]:
            annoString += ('WARNING = "There is a yt-internal bug where streamline '
                          'annotation doesn\'t work if a center coordinate is '
                          'set to 0!"\n')
            if Param_Dict["NAxis"] == "x":
                annoString += "proj.annotate_streamlines('magy', 'magz')\n"
            elif Param_Dict["NAxis"] == "y":
                annoString += "proj.annotate_streamlines('magx', 'magz')\n"
            elif Param_Dict["NAxis"] == "z":
                annoString += "proj.annotate_streamlines('magx', 'magy')\n"
        if Param_Dict["Contour"]:
            annoString += "proj.annotate_contour('{}')\n".format(field)
    elif Param_Dict["Geometry"] == "cylindrical":
        if Param_Dict["Grid"]:
            annoString += "proj.annotate_grids()\n"
    annoString += "\n"
    figureString = constructUsingMPL(Param_Dict, "proj")

    text = plotString + modString + annoString + figureString
    return text

# %% Functions for constructing the line and phase plot strings
def constructLinePlot(Param_Dict):
    """Constructs the line plot script"""
    ds = Param_Dict["CurrentDataSet"]
    startends = ["XLStart", "YLStart", "ZLStart", "XLEnd", "YLEnd", "ZLEnd"]
    valueList = []
    for key in startends:
        value = float(yt.units.YTQuantity(Param_Dict[key], Param_Dict["oldGridUnit"]).to(ds.quan(1, 'code_length').units).value)
        valueList.append(value)
    field = Param_Dict["YAxis"]
    plotString = (
f"""# Initialize a yt Line plot with the dataSet ds, field {field}, the
# given start- and end points in code_length and the number of sampling points:
lplot = yt.LinePlot(ds, "{field}", {valueList[:3]}, {valueList[3:]},
                    npoints=512, fontsize=14)
""")
    plotString += ("# Note that you can also add more than one field for the "
                   "line plot and that you\n# can label them independently "
                   'using field_labels={"field":label}.\n\n')
    modString = (f'lplot.annotate_legend("{field}")  # Optional, but looks nice\n')
    fieldMin = Param_Dict["YMin"]  # Float
    fieldMax = Param_Dict["YMax"]
    unit = Param_Dict["ZUnit"]
    modString += f'lplot.set_x_unit("{Param_Dict["LineUnit"]}")\n'
    modString += f'lplot.set_unit("{field}", "{unit}")\n'
    modString += ("# Unfortunately, yt line-plots don't have built in min and "
                  "max settings, so we use pyplot later.\n")
    log = Param_Dict["ZLog"]  # Boolean
    modString += "# Set our field scaling logarithmic if wanted:\n"
    if min(fieldMin, fieldMax) <= 0 and log:
        modString += (f'lplot.set_log("{field}", True, linthresh=(({fieldMax}-{fieldMin})/1000)  '
        "# linthresh sets a linear scale for a small portion and then a symbolic one "
        "for negative values\n")
    else:
        modString += (f'lplot.set_log("{field}", {log})  # This may be redundant '
                      "in some cases.\n")
    annoString = ""
    title = Param_Dict["PlotTitle"]
    if title != "":
        annoString += (f'lplot.annotate_title("{Param_Dict["YAxis"]}", "{title}")'
                       "#  Give the plot the title it deserves.\n")
    figureString = constructUsingMPL(Param_Dict, "lplot")

    text = plotString + modString + annoString + figureString
    return text

def constructPhasePlot(Param_Dict):
    """Constructs the phase plot script"""
    XField, YField, ZField = Param_Dict["XAxis"], Param_Dict["YAxis"], Param_Dict["ZAxis"]
    plotString = ("ad = ds.all_data()  # through e.g. ad = ds.sphere('c', (50, 'kpc"
                  "')) you could also only select a region of the dataset.\n\n")
    if Param_Dict["WeightField"] is None:
        weightField = "None"
    else:
        weightField = f'"{Param_Dict["WeightField"]}"'
    plotString += (
f"""# Initialize a yt phase plot with the data ad, XField {XField},
# YField {YField}, ZField {ZField} and the optional parameters
# weight_field, fractional, the number of bins and fontsize:
""")
    if Param_Dict["ParticlePlot"]:
        plotString += (
f'''phas = yt.ParticlePhasePlot(ad, "{XField}", "{YField}", "{ZField}",
                            weight_field={weightField}, x_bins=128, y_bins=128,
                            fontsize=14)
# Warning: Particle plots are still an experimental feature of the GUI and may
# produce errors

''')
    else:
        plotString += (
f'''phas = yt.PhasePlot(ad, "{XField}", "{YField}", "{ZField}",
                    weight_field={weightField}, fontsize=14)

''')
    cmap = Param_Dict["ColorScheme"]
    modString = ("# Set our field scaling logarithmic if wanted. "
                 "Phase plots don't support symlog scales.\n")
    for axis in ["X", "Y", "Z"]:
        log = Param_Dict[axis + "Log"]  # Boolean
        field = Param_Dict[axis + "Axis"]
        modString += f'phas.set_log("{field}", {log})  # This may be redundant in some cases.\n'
    modString += "# Set unit, minimum, maximum and color scheme:\n"
    for axis in ["X", "Y", "Z"]:
        modString += (f'phas.set_unit("{Param_Dict[axis +"Axis"]}", "{Param_Dict[axis + "Unit"]}")\n')
    if XField not in Param_Dict["FieldMins"].keys():
        modString += (
f'''# It seems that you have not calculated extrema for {XField}.
# You can do this by using
# minMaxArray = ds.all_data().quantities.extrema({XField})
# which will return a YTArray with two entries plus the units.
''')
    if YField not in Param_Dict["FieldMins"].keys():
        modString += (
f'''# It seems that you have not calculated extrema for {YField}.
# You can do this by using
# minMaxArray = ds.all_data().quantities.extrema({YField})
# which will return a YTArray with two entries plus the units.
''')
    if ZField not in Param_Dict["FieldMins"].keys():
        modString += (
f'''# It seems that you have not calculated extrema for {ZField}.
# You can do this by using
# minMaxArray = ds.all_data().quantities.extrema({ZField})
# which will return a YTArray with two entries plus the units.
''')
    XMin = Param_Dict["XMin"]
    XMax = Param_Dict["XMax"]
    YMin = Param_Dict["YMin"]
    YMax = Param_Dict["YMax"]
    ZMin = Param_Dict["ZMin"]
    ZMax = Param_Dict["ZMax"]
    modString += (f'phas.set_xlim({XMin}, {XMax})\n'
                  f'phas.set_ylim({YMin}, {YMax})\n'
                  f'phas.set_zlim("{ZField}", {ZMin}, {ZMax})  # These are given in the same unit, {Param_Dict["ZUnit"]}.\n')
    modString += f'phas.set_cmap("{ZField}", "{cmap}")\n'
    annoString = ""
    title = Param_Dict["PlotTitle"]
    if title != "":
        annoString += (f'phas.annotate_title("{Param_Dict["YAxis"]}", "{title}")'
                       "#  Give the plot the title it deserves.\n")
    figureString = constructUsingMPL(Param_Dict, "phas")

    text = plotString + modString + annoString + figureString
    return text

# %% Function for constructing the profile plot strings
# (All variants including time series)
def constructProfilePlot(Param_Dict, time=False, multiPlot=False):
    """Constructs the Profile plot script.
    Params:
        time: bool to indicate whether 'time' has been used for the x-axis
        multiPlot: bool to indicate whether the user wants to do plots at
                   multiple times
    """
    if Param_Dict["WeightField"] is None:
        weightField = "None"
    else:
        weightField = "'{}'".format(Param_Dict["WeightField"])
    ds = Param_Dict["CurrentDataSet"]
    if time:
        if weightField == "None":
            weightField = "'ones'"
        calcQuanName = getCalcQuanName(Param_Dict)
        calcQuanString = getCalcQuanString(Param_Dict).replace("'", '"')
        dataString = (
f'''# Loop over the datasets of the series to get the datapoints at each time:
# This does NOT reflect the extrema you chose in the GUI, please
# add them by hand by only using ts[start:(end+1)]
i = 0
time_data = []
y_data = []
length = len(ts)
for ds in ts:
    time_data.append(ds.current_time.to_value("{Param_Dict["XUnit"]}"))
    ad = ds.all_data()  # You could select an arbitrary yt region here
    # for available quantities to calculate see
    # https://yt-project.org/doc/analyzing/objects.html as well.
    yResult = {calcQuanString}
    y_data.append(yResult)
    i += 1
    print(f"Progress: {{i}}/{{length}} data points calculated.")
arr_x = yt.YTArray(time_data, "{Param_Dict["XUnit"]}")  # In a YTArray, the units
arr_y = yt.YTArray(y_data, "{Param_Dict["YUnit"]}")  # are stored as well.
"""
Note: Another way to do this is loading the series through yt using
ts = yt.load("{Param_Dict["Directory"]}/{Param_Dict["Seriesname"]}")
and then using the built-in parallel iteration tool, piter, like this:
yt.enable_parallelism(suppress_logging=True)
storage = {{}}  # A storage dictionary to store the data during parallel iteration
for store, ds in ts.piter(storage=storage):
    ad = ds.all_data()
    yResult = {calcQuanString}
    store.result = yResult
    i += 1
    print(f"Progress: {{i}}/{{length}} data points calculated."
y_arr = yt.YTArray(list(storage.values()), "{Param_Dict["YUnit"]}")
"""
''')
        # If possible, pass the values that have already been calculated to the user
        times, values = [], []
        field = Param_Dict["YAxis"]
        for ds in Param_Dict["DataSeries"]:
            try:
                time = Param_Dict["DataSetDict"][str(ds) + "Time"].to_value(Param_Dict["XUnit"])
                value = Param_Dict["DataSetDict"][str(ds) + field + calcQuanName]
                value = yt.YTQuantity(value, Param_Dict["FieldUnits"][field]).to_value(Param_Dict["YUnit"])
                times.append(time)
                values.append(value)
            except KeyError:
                pass
        if len(times) > 0:
            dataString += (
f'''# It seems that you have already calculated some plot points using the GUI.
# I have stored them for you, and you can reuse them if wanted:
calcTimes = yt.YTArray({times}, "{Param_Dict["XUnit"]}")
calcValues = yt.YTArray({values}, "{Param_Dict["YUnit"]}")

''')
    elif multiPlot:
        onlyEvery = Param_Dict["ProfOfEvery"]
        suf = lambda n: "%d%s "%(n,{1:"st",2:"nd",3:"rd"}.get(n if n<20 else n%10,"th"))
        numString = suf(onlyEvery)
        dataString = (
f'''# Loop over the datasets of the series to make a profile at each time:
i = 0
onlyEvery = {onlyEvery}  # If you only want to plot every {numString} plot
length = {math.ceil(len(Param_Dict["DataSeries"])/onlyEvery)}  # You can use math.ceil(len(ts)/onlyEvery) to calculate this.
labels = []
profiles = []  # The data for the y-axis will be stored in YTArrays
for ds in ts:
    if i % onlyEvery == 0:
        # Create a data container to hold the whole dataset.
        ad = ds.all_data()  # you can use an arbitrary yt region here.
        # Create a 1d profile of xfield vs. yfield. Through n_bins the number
        # of bins may be modified as well:
        prof = yt.create_profile(ad, "{Param_Dict["XAxis"]}",
                                 fields=["{Param_Dict["YAxis"]}"],
                                 weight_field={weightField}, n_bins=64)
        # Add labels
        time = ds.current_time.to_value("kyr")
        label = f"{Param_Dict["YAxis"]} at {{time:.2e}} kyr"
        labels.append(label)
        profiles.append(prof["{Param_Dict["YAxis"]}"])
        print(f"Progress: {{int(i/onlyEvery)+1}}/{{length}} profiles done.")
    i += 1
arr_x = prof.x  # get the data for the x-axis
"""
Note: Another way to do this is loading the series through yt using
ts = yt.load("{Param_Dict["Directory"]}/{Param_Dict["Seriesname"]}")
and then using the built-in parallel iteration tool, piter, like this:
yt.enable_parallelism(suppress_logging=True)
storage = {{}}  # A storage dictionary to store the data during parallel iteration
for store, ds in ts.piter(storage=storage):
    if i % onlyEvery == 0:
        ad = ds.all_data()
        prof = yt.create_profile(ad, "{Param_Dict["XAxis"]}",
                                 fields=["{Param_Dict["YAxis"]}"],
                                 weight_field={weightField}, n_bins=64)
        # Add labels
        time = ds.current_time.to_value("kyr")
        label = f"{Param_Dict["YAxis"]} at {{time:.2e}} kyr"
        labels.append(label)
        store.result = prof["{Param_Dict["YAxis"]}"]
        print("Progress: {{int(i/onlyEvery+1}}/{{length}} profiles done."
    i += 1
arr_y = list(storage.values())
"""
''')
    else:
        dataString = (
f'''# First reate a data container to hold the whole dataset.
ad = ds.all_data()  # you can use an arbitrary yt region here.
# Create a 1d profile of the x-field vs. the y-field. You could also pass
# multiple fields for the vertical axis.
prof = yt.create_profile(ad, "{Param_Dict["XAxis"]}",
                         fields=["{Param_Dict["YAxis"]}"],
                         weight_field={weightField})
label = "{Param_Dict["YAxis"]}"
arr_x = prof.x
arr_y = prof["{Param_Dict["YAxis"]}"]

''')
    figureString = (
f'''# Now that we have created the data, we can set up a figure and plot it.
# More information: https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.pyplot.figure.html
fig, axes = plt.subplots(**{{'figsize': (10, 7), 'dpi': 100}})

# Add the plot:
x_values = arr_x.to_value("{Param_Dict["XUnit"]}")
''')
    if multiPlot:
        plotString = (
f'''for i, arr_y in enumerate(profiles):
    label = labels[i]
    y_values = arr_y.to_value("{Param_Dict["YUnit"]}")
    axes.plot(x_values, y_values, "-", linewidth=3, label=label)
''')
    else:
        plotString = (f'y_values = arr_y.to_value("{Param_Dict["YUnit"]}")\n'
                      f'axes.plot(x_values, y_values, "-", linewidth=3, label="{Param_Dict["YAxis"]}")\n')
    field = Param_Dict['XAxis']
    xUnit = yt.YTQuantity(1, Param_Dict["XUnit"]).units.latex_repr  # get latex repr for unit
    if xUnit != "":  # do not add empty brackets
        xUnit = r"$\:\left[" + xUnit + r"\right]$"
    if field == "dens" or field == "temp":
        xName = eval(f"ds.fields.flash.{field}.get_latex_display_name()")
    elif field == "time":
        xName = r"$\rm{Time}$"
    else:
        xName = eval(f"ds.fields.gas.{field}.get_latex_display_name()")
    field = Param_Dict["YAxis"]
    yUnit = yt.YTQuantity(1, Param_Dict["YUnit"]).units.latex_repr  # get latex repr for unit
    if yUnit != "":  # do not add empty brackets
        yUnit = r"$\:\left[" + yUnit + r"\right]$"
    if field == "dens" or field == "temp":
        yName = eval(f"ds.fields.flash.{field}.get_latex_display_name()")
    elif field == "time":
        yName = r"$\rm{Time}$"
    else:
        yName = eval(f"ds.fields.gas.{field}.get_latex_display_name()")
    if time:
        plotString += (
f'''
"""
Note:
Since you might not want to recalculate all the data, you can save the values
in a csv-file the following way:
import csv
with open("time_profile_{calcQuanName}_{field}_data.csv", "w") as writefile:
    # configure writer to write standard csv file
    writer = csv.writer(writefile, delimiter=',', lineterminator='\\n')
    writer.writerow(["time", "{field}"])
    writer.writerow(["{Param_Dict["XUnit"]}", "{Param_Dict["YUnit"]}"])
    for time, y in zip(time_data, y_data):
        writer.writerow([time, y])
    print("The data points have been saved to\\n"
          "time_profile_{calcQuanName}_{field}_data.csv.")
"""

''')
    elif multiPlot:
        pass
    else:
        plotString +=(
f'''
"""
Note:
Since you might not want to recalculate all the data, you can save the values
in a csv-file the following way:
import csv
with open("profile_{Param_Dict["XAxis"]}_{Param_Dict["YAxis"]}_data.csv", "w") as writefile:
    # configure writer to write standard csv file
    writer = csv.writer(writefile, delimiter=',', lineterminator='\\n')
    writer.writerow(["{Param_Dict["XAxis"]}", "{Param_Dict["YAxis"]}"])
    writer.writerow(["{Param_Dict["XUnit"]}", "{Param_Dict["YUnit"]}"])
    for time, y in zip(time_data, y_data):
        writer.writerow([time, y])
    print("The data points have been saved to\\n"
          "profile_{Param_Dict["XAxis"]}_{Param_Dict["YAxis"]}_data.csv.")
"""

''')
    plotString += (
'''
"""
Note:
We could also use yt's built-in yt.ProfilePlot.from_profiles(profiles, labels),
but we're using matplotlib.pyplot since it offers more control.
"""
# Modify the settings for x- and y-axis:
# with axes.set_xscale you can set it log(arithmic) or lin(ear).
''')
    if Param_Dict["XLog"]:
        plotString += 'axes.set_xscale("log")\n'
    if Param_Dict["YLog"]:
        plotString += 'axes.set_yscale("log")\n'
    plotString += (
f'''# We can freely customize the axes' names. You can get a units' LaTeX rep
# like this: yt.YTQuantity(1, "yourUnit").units.latex_repr
# and the display name with ds.fields.gas.YOURFIELD.get_latex_display_name()
axes.set_xlabel(r"{xName + xUnit}")
axes.set_ylabel(r"{yName + yUnit}")
axes.set_xlim({Param_Dict["XMin"]}, {Param_Dict["XMax"]})  # be careful about the units here
axes.set_ylim({Param_Dict["YMin"]}, {Param_Dict["YMax"]})
axes.legend()  # Display the labels we have given
axes.grid()  # You can customize the grid even further if wanted
axes.set_title(r"{Param_Dict["PlotTitle"]}")
''')
    if Param_Dict["AddProfile"]:
        plotString += (
'''
"WARNING: The second profile functionality is not a feature yet."
"""
Note: I have not implemented adding a second plot to this script writer yet.
It's actually pretty simple. Just use
twinaxes = axes.twinx()
twinaxes.tick_params(axis='y')
axes.tick_params(axis='y')
and plot the desired data on the twinaxes, which is going to use the same
x-axis.
"""
''')
    if Param_Dict["Timestamp"] and Param_Dict["XAxis"] != "time" and not multiPlot:
        plotString += ("# Use a custom function to annotate the timestamp (see above):\n"
                       "drawTimestampBox(axes, ds)\n")
    plotString += "# fig.show()  # Works best in iPython console or jupyter\n\n\n"
    saveString = '# example of how you could name the file:\n'
    saveString += f'plotfilename = "{str(Param_Dict["CurrentDataSet"])}_'
    if multiPlot:
        saveString += 'Multi'
    saveString += f'{Param_Dict["PlotMode"]}plot_{Param_Dict["YAxis"]}.png"\n'
    saveString += "fig.savefig(plotfilename)  # Takes the name of the file as an argument.\n"
    saveString += 'print(f"The file has been saved as {plotfilename}")\n'
    text = dataString + figureString + plotString + saveString
    if Param_Dict["Timestamp"] and Param_Dict["XAxis"] != "time" and not multiPlot:
        text = createCustomTimestampString() + text
    return text

# %% Function for constructing the plot-making string
def constructUsingMPL(Param_Dict, plotName):
    """Construct the part of the script where the yt plot is plotted as a mpl
    figure.
    params:
        Param_Dict: for retrieving information about what to plot
        plotName: the name the plot has been given, e.g. 'slc' for slice
    returns:
        text: constructed script text
    """
    mode = Param_Dict["PlotMode"]
    plotString = ("# Everything that's necessary for setting up the figure.\n"
                  "# More information: https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.pyplot.figure.html\n"
                  'fig, axes = plt.subplots(**{"figsize": (10, 7), "dpi": 100})\n')
    if Param_Dict["DimMode"] == "2D":
        plotString += ("# Since we're using the color axis, we need the following:\n"
                       "divider = make_axes_locatable(axes)\n"
                       'cax = divider.append_axes("right", size="5%", pad=0.05)\n')
    plotString += ("\n\n# Now we need to pass our axes to yt so it can setup the"
                   " plots for us.\n")
    if mode == "Line":
        field = Param_Dict["YAxis"]
        plotString += f'{plotName}.plots["{field}"].axes = axes\n'
    else:
        field = Param_Dict["ZAxis"]
        if Param_Dict["DomainDiv"]:
            field = "Normed " + field
        plotString += f'{plotName}.plots["{field}"].axes = axes\n'
    if Param_Dict["DimMode"] == "2D":
        plotString += f'{plotName}.plots["{field}"].cax = cax\n'
    plotString += ("{0}._setup_plots()  # This runs the yt-internal command for"
                   " plotting. It's different for each plot type.\n".format(plotName))
    if mode == "Line":
        plotString += "axes.set_ylim({0}, {1})\n".format(Param_Dict["YMin"], Param_Dict["YMax"])
        if Param_Dict["LineAnno"]:
            plotString += "annotateStartEnd(axes, ds)  # annotate custom start and end points\n"
    if Param_Dict["Timestamp"] and mode in ["Line", "Phase"]:
        plotString += ("# Use a custom function to annotate the timestamp (see below):\n"
                       "drawTimestampBox(axes, ds)\n")
    if mode in ["Slice", "Projection"]:
        if Param_Dict["SetAspect"]:
            plotString += '# Set the aspect ratio. If "1", equal distances will be equally long.\n'
            plotString += 'axes.set_aspect("auto")  # For "auto", the figure is filled.\n'
    plotString += "# fig.show()  # Works best in iPython console or jupyter\n\n"
    saveString = ('\nplotfilename = "{0}_{1}plot_{2}.png"  # example of how you could name the file\n'
                 .format(str(Param_Dict["CurrentDataSet"]), Param_Dict["PlotMode"], field))
    saveString += "fig.savefig(plotfilename)  # Takes the name of the file as an argument.\n"
    saveString += 'print("The file has been saved as {0}".format(plotfilename))\n'
    text = plotString + saveString
    if Param_Dict["Timestamp"] and mode in ["Line", "Phase"]:
        text = createCustomTimestampString() + text
    if Param_Dict["LineAnno"] and mode == "Line":
        text = createLinePointAnnoString(Param_Dict) + text
    return text

# %% Functions to construct time series strings
def checkTimeSeriesPlot(Param_Dict, plotAll):
    """Helper function to check whether the user really used the time series
    attribute."""
    # These are the only occurances where the time series is actually used
    if (Param_Dict["XAxis"] == "time" or Param_Dict["TimeSeriesProf"] or
        plotAll):
        return True
    else:
        return False

def constructSeriesString(Param_Dict, plotAll):
    """Constructs the string when handling a time series"""
    loadingString = ("# Load the series through yt and save it as ts:\n"
                     f'ts = yt.load("{Param_Dict["Directory"]}/{Param_Dict["Seriesname"]}")\n\n\n')
    if Param_Dict["XAxis"] == "time":
        plotString = constructProfilePlot(Param_Dict, time=True)
    elif Param_Dict["TimeSeriesProf"]:
        plotString = constructProfilePlot(Param_Dict, multiPlot=True)
    elif plotAll:
        plotString = constructTimeSeriesPlots(Param_Dict)
    text = loadingString + plotString
    return text

def constructTimeSeriesPlots(Param_Dict):
    """Constructs a string to reproduce the loop used to plot pictures of a
    whole series."""
    plotMode = Param_Dict["PlotMode"]
    onlyEvery = Param_Dict["OnlyEvery"]
    loopString = "\n# We are now performing a loop over each file of the dataset:\n"
    loopString += f"directory = '{Param_Dict['Directory']}/{plotMode}plot_{datetime.now().strftime('%d_%m_%Y_%H_%M_%S')}'\n"
    loopString += "os.mkdir(directory)  # Create a directory to save the frames in\n"
    loopString += f"onlyEvery = {onlyEvery}"
    loopString += "i = 0  # for convenient progress updates\n"
    loopString += "for ds in ts:\n"
    suf = lambda n: "%d%s "%(n,{1:"st",2:"nd",3:"rd"}.get(n if n<20 else n%10,"th"))
    numString = suf(onlyEvery)
    length = len(Param_Dict["DataSeries"])
    loopString += (
f"""    if i % onlyEvery == 0:  # if you only want every {numString} file
        make{plotMode}Plot(ds)
        print(f"Progress: {{(i/{onlyEvery}+1)}}/{math.ceil(length/onlyEvery)} {plotMode}plots done.")
        saveName = f"{{directory}}/{plotMode}plot_{{i+1}}"
        plt.savefig(saveName)
    i += 1
""")
    plotString = "# Here we define the actual plot function that is performed for each frame:\n"
    plotString += f"def make{plotMode}Plot(ds):\n"
    plotString += (
f'''    """A function to produce a {plotMode}plot of the dataset ds using the
    desired parameters.
    Parameters:
        ds: (FLASH)-Dataset loaded using yt
    """
''')
    funcString = eval("construct"+Param_Dict["PlotMode"]+"Plot(Param_Dict)")
    # We need to insert the four spaces because we use this inside of a function
    funcString = funcString.split("\n")
    for line in funcString:
        if line != "":
            plotString += f"    {line}\n"
        else:
            plotString += "\n"
    text = plotString + loopString  # this way the function is defined before it is called
    return text

# %% Functions to construct miscellaneous strings like derived fields, file
# loading and annotations (alphabetical order)
def constructDerFieldString(Param_Dict):
    """Construct a string that reflects the implementation of used custom
    derived fields.
    Returns:
        text: String that is empty in case no custom fields are used
    """
    foundFields = []
    for axis in ["X", "Y", "Z"]:
        fieldName = Param_Dict[axis + "Axis"]
        if (fieldName in Param_Dict["NewDerFieldDict"].keys() and
            fieldName not in foundFields):
            foundFields.append(fieldName)
    text = ""
    for i, fieldName in enumerate(foundFields):
        if i == 0:
            text += "# Adding the custom derived fields that are used:\n"
        displayName = Param_Dict["NewDerFieldDict"][fieldName]["DisplayName"]
        func = Param_Dict["NewDerFieldDict"][fieldName]["FunctionText"]
        unit = Param_Dict["NewDerFieldDict"][fieldName]["Unit"]
        dim = Param_Dict["NewDerFieldDict"][fieldName]["Dimensions"]
        override = Param_Dict["NewDerFieldDict"][fieldName]["Override"]
        if "np." in func:
            text += "import numpy as np\n"
        text += (
f'''# First, define the function that is used for the field:
{func}

# Now we can just add this new field to yt. Another way would be loading the
# dataset(s) first and then add it using ds.add_field(...)
yt.add_field(("gas", "{fieldName}"), function=_{fieldName},
             units="{unit}", dimensions="{dim}", force_override={override},
             display_name=r"{displayName}", take_log=False)

''')
    return text

def createLinePointAnnoString(Param_Dict):
    """Creates a string that can be used to reproduce the lineplot annotation"""
    sNumbers = [Param_Dict[axis + "LStart"] for axis in ["X", "Y", "Z"]]
    eNumbers = [Param_Dict[axis + "LEnd"] for axis in ["X", "Y", "Z"]]
    pointString = (
f'''

# The following functions are used for custom annotation of the start and end
# points used for line plotting
def createLineText(numbers, mode, ds):
    """Creates and returns the text for the line edit.
    Parameters:
        numbers: list of coordinates
        mode: 'Start' or 'End'
        ds: dataset object
    """
    # It is advisable to turn them into a easy-to-read numbers. This is of
    # course optional.
    numbers = yt.YTArray(numbers, "{Param_Dict["LineUnit"]}")
    lengthUnit = ds.get_smallest_appropriate_unit(max(numbers),
                                                  quantity="distance")
    lengths = numbers.to_value(lengthUnit)
    return f"{{mode}}: ({{lengths[0]:.1f}}, {{lengths[1]:.1f}}, {{lengths[2]:.1f}}) {{lengthUnit}}"

def annotateStartEnd(axes, ds):
    """Annotates two boxes for start and end coordinates.
    Parameters:
        axes: mpl axes object for plotting
        ds: yt dataset object for receiving nice units"""
    bboxArgs = {{'boxstyle': 'square,pad=0.3', 'facecolor': 'white',
                'linewidth': 2, 'edgecolor': 'black', 'alpha': 0.5}}
    startText = createLineText({sNumbers}, "Start", ds)
    endText = createLineText({eNumbers}, "End", ds)
    axes.text(x=0.0, y=-0.14, s=startText, size=10, bbox=bboxArgs,
              ha="left", va="bottom", transform=axes.transAxes,
              **{{"color": "black"}})
    axes.text(1.0, -0.14, endText, size=10, bbox=bboxArgs, ha="right",
              va="bottom", transform=axes.transAxes, **{{"color": "black"}})
    print("Annotating line points. Sometimes the positions x and y have "
          "to be adjusted.")
''')
    return pointString

def createCustomTimestampString():
    """Write a string that can be to reproduce the custom timestamp annotation.
    The function call itself has to be added above."""
    stampString = (
'''

# The following two functions are used for a custom timestamp annotation
# because they are not supported for line, phase and profile plots by yt:
def createTimestampText(ds):
    """Creates the text for a timestamp annotation.
    Parameters:
        ds: yt dataset object: To extract the time information from
    """
    timeUnit = ds.get_smallest_appropriate_unit(ds.current_time,
                                                quantity="time")
    time = ds.current_time.to_value(timeUnit)
    return f"t = {time:.1f} {timeUnit}"  # give it a nice look.

def drawTimestampBox(axes, ds):
    """Draw a custom timestamp annotation box like the one
    used by yt on a given axes axes using the dataset ds for information.
    Parameters:
        axes: mpl ax object: The ax to draw the timestamp on
        ds: yt dataset object: To extract the time information from
    """
    bboxArgs = {'boxstyle': 'square,pad=0.3', 'facecolor': 'black',
                'linewidth': 3, 'edgecolor': 'white', 'alpha': 0.5}
    text = createTimestampText(ds)
    axes.text(x=0.03, y=0.96, s=text, size=15, bbox=bboxArgs,
              ha="left", va="top", transform=axes.transAxes, **{"color": "w"})
    # if you leave out the "transform"-keyword, xpos and ypos will be
    # interpreted as data positions.

''')
    return stampString