#!/cs/home/jl236/virtual_env/study_160125/bin/python
# coding: utf-8

import sys, traceback
import random, json, bottle, csv, uuid, httpagentparser, time
from bottle import Bottle, request, route, run, debug, template, get, post, response, static_file, error
from datetime import datetime, date
from itertools import combinations
from psy_files import StaircaseProcedure

import mysql.connector as mariadb
import StringIO

from db_credentials import db_creds
from prolific_completeCodes import end_links


class feedback_db():
    ''' Provide an interface to the database '''

    def __init__(self, dbname=db_creds['name']):
        '''Constructor for the database, name is an optional parameter'''
        self.dbname = dbname
        self.connect()
        self.conn.text_factory = str

    def connect(self):
        '''Connect to the database'''
        self.conn = mariadb.connect(user=db_creds['user'], password=db_creds['password'], database=db_creds['name'],
                                    host=db_creds['host'])

    def cursor(self):
        '''Return a cursor on the database'''
        try:
            return self.conn.cursor()
        except mariadb.OperationalError:
            print
            'meh'
            self.connect()
        return self.conn.cursor()

    def commit(self):
        '''Commit pending changes'''
        self.conn.commit()

    def store_row(self, entry_data, table):
        '''Store a row in the specified table'''

        cursor = self.cursor()
        field_names = list(entry_data.keys())
        input_fragment = ','.join(field_names)
        value_fragment = ','.join(['%(' + name + ')s' for name in field_names])
        stmt = 'INSERT INTO %s (' % table + input_fragment + ') VALUES (' + value_fragment + ')'
        cursor.execute(stmt, entry_data)
        self.commit()
        cursor.close()

    def store_cell(self, entry_data, table, ask_for):
        '''Store a special cell in a row where the ask_for is found'''

        cursor = self.cursor()
        updates = ', '.join(["%s = '%s'" % (k, v) for k, v in entry_data.items()])
        condition = ' AND '.join(["%s = '%s'" % (k, v) for k, v in ask_for.items()])
        stmt = 'UPDATE %s ' % table + ' SET ' + updates + ' WHERE ' + condition
        cursor.execute(stmt, entry_data)
        self.commit()
        cursor.close()

    def get_highest_value(self, value, table, ask_for, debug_text=None):
        '''Fetch all entries in the table user_input and return it.'''

        cursor = self.cursor()
        condition = ' AND '.join(["%s = '%s'" % (k, v) for k, v in ask_for.items()])

        stmt = "SELECT MAX(trialCount) FROM %s " % table + " WHERE " + condition
        # print debug_text, 'The statement being sent to the db:', stmt
        result = None
        try:
            cursor.execute(stmt)
            result = cursor.fetchone()

            # if (result is None) or  len(result) == 0:
            #     print debug_text, 'Error message from inside the get highest function: ', stmt, condition 

            cursor.close()
            return result

        except mariadb.Error as error:
            print
            debug_text, "Error: {}".format(error)
            print
            debug_text, 'Getting highest failed. The type of the variable "result": ', type(result)

    def show_all(self, ask_for=None):
        '''Fetch all entries in the table and return it.'''

        cursor = self.cursor()
        if ask_for != None:
            condition = ' AND '.join(["%s = '%s'" % (k, v) for k, v in ask_for.items()])
            stmt = "SELECT * FROM combined_field" + " WHERE " + condition
        else:
            stmt = "SELECT * FROM combined_field"
        cursor.execute(stmt)
        result = [[column[0] for column in cursor.description]]
        result += cursor.fetchall()
        cursor.close()
        return result

    def clear_tables(self):
        '''Deletes all entries of the three tables user_input, trials and feedback.'''

        cursor = self.cursor()
        stmt = "TRUNCATE TABLE comments"
        cursor.execute(stmt)
        stmt = "TRUNCATE TABLE trials"
        cursor.execute(stmt)
        stmt = "TRUNCATE TABLE users"
        cursor.execute(stmt)


experiments = ['scale', 'JND', 'tm', 'field']
parameters = {
    "slant": {"minimum": -20, "s_min": -20, "default": 0, 'math_middle': 0, 'tm_steps': [0.5], 'JND_points': 8,
              "maximum": 20, "s_max": 20, "step_unit": 0.5, "JND_step": 0.5, "decimals": 1,
              'info': "Slant is a common parameter that modifies the tilting of the vertical strokes. Depending on the design this is also known as italic, cursive or oblique. Usually the slanting is only applied in the writing direction but in this study you will also see backwards leaning typefaces. The more the word leans to the right the highter the slant is."},
    "slantL": {"minimum": -20, "s_min": -20, "default": 0, 'math_middle': -10, 'tm_steps': [0.5], 'JND_points': 8,
               "maximum": 0, "s_max": 0, "step_unit": 0.5, "JND_step": 0.5, "decimals": 1,
               'info': "Slant is a common parameter that modifies the tilting of the vertical strokes. Depending on the design this is also known as italic, cursive or oblique. The more the word leans to the right the highter the slant is."},
    "slantR": {"minimum": 0, "s_min": 0, "default": 0, 'math_middle': 10, 'tm_steps': [0.5], 'JND_points': 8,
               "maximum": 20, "s_max": 20, "step_unit": 0.5, "JND_step": 0.5, "decimals": 1,
               'info': "Slant is a common parameter that modifies the tilting of the vertical strokes. Depending on the design this is also known as italic, cursive or oblique. Usually the slanting is applied in the writing direction but in this study you will see backwards leaning typefaces. The more the word leans the highter the slant is."},
    "weight": {"minimum": 10, "s_min": 30, "default": 85, 'math_middle': 95, 'tm_steps': [0.5], 'JND_points': 8,
               "maximum": 200, "s_max": 160, "step_unit": 1, "JND_step": 2, "decimals": 0,
               'info': "Weight is a very common parameter for fonts. A lot of typefaces have a Regular and a Bold font. But there are several other weights like Light, Semi-Bold, Medium or Black. The weight parameter defines the modulation of the thickness of the strokes."},
    "width": {"minimum": 0.75, "s_min": 0.8, "default": 1, 'math_middle': 1.15, 'tm_steps': [0.5], 'JND_points': 8,
              "maximum": 1.5, "s_max": 1.5, "step_unit": 0.005, "JND_step": 0.005, "decimals": 3,
              'info': "The width parameter defines the horizontal streching of the letterforms. The axis ranges from a compressed or condensed typeface to a wide or extended font. While the structure is changed the stroke weight is kept constant."},
    "xHeight": {"minimum": 300, "s_min": 360, "default": 500, 'math_middle': 480, 'tm_steps': [0.5], 'JND_points': 8,
                "maximum": 600, "s_max": 600, "step_unit": 1, "JND_step": 3, "decimals": 0,
                'info': "The x-height defines the distance from the baseline to the mean line. As the name tells it is the height of the lowercase letter x. The x-height can be modified while all other vertical proportions stay the same."},
    "contrast": {"minimum": -2, "s_min": -2, "default": -1, 'math_middle': -1.25, 'tm_steps': [0.5], 'JND_points': 8,
                 "maximum": 0, "s_max": -0.5, "step_unit": 0.05, "JND_step": 0.05, "decimals": 2,
                 'info': "For this study contrast defines the relation of thick and thin strokes within the letters. If the difference of thickness is very big it is a high contrast. Very low or no contrast means the strokes appear to have the same thickness."},
    "aperture": {"minimum": 0.5, "s_min": 0.5, "default": 1.2, 'math_middle': 1.15, 'tm_steps': [0.5], 'JND_points': 8,
                 "maximum": 1.8, "s_max": 1.8, "step_unit": 0.025, "JND_step": 0.025, "decimals": 3,
                 'info': "The aperture defines how small or big the openings of the lettershapes are (eg in the a, e or c). Not all letters are affected by this parameter and the difference can be very subtle. In the fonts of this study this parameter also defines how high or low the joining or bifurcation of two strokes is (eg in the b and d or the m and n)."},
    "serifs": {"minimum": 0, "s_min": 0, "default": 65, 'math_middle': 40, 'tm_steps': [0.5], 'JND_points': 8,
               "maximum": 85, "s_max": 80, "step_unit": 1, "JND_step": 1, "decimals": 0,
               'info': "Serifs are extensions at the end of some of the strokes. They can have very different forms and vary in length or thickness. If the length is zero there are no serifs – so it is a sans serif typeface."},
    'slant_weight': {},
    'slant_width': {},
    'slant_xHeight': {},
    'slant_contrast': {},
    'slant_aperture': {},
    'slant_serifs': {},
    'weight_width': {},
    'weight_xHeight': {},
    'weight_contrast': {},
    'weight_aperture': {},
    'weight_serifs': {},
    'width_xHeight': {},
    'width_contrast': {},
    'width_aperture': {},
    'width_serifs': {},
    'xHeight_contrast': {},
    'xHeight_aperture': {},
    'xHeight_serifs': {},
    'contrast_aperture': {},
    'contrast_serifs': {},
    'aperture_serifs': {}
}

# para_combos = ['slant_weight', 'slant_width', 'slant_xHeight', 'slant_contrast', 'slant_aperture', 'slant_serifs', 'weight_width', 'weight_xHeight', 'weight_contrast', 'weight_aperture', 'weight_serifs', 'width_xHeight', 'width_contrast', 'width_aperture', 'width_serifs', 'xHeight_contrast', 'xHeight_aperture', 'xHeight_serifs', 'contrast_aperture', 'contrast_serifs', 'aperture_serifs']

user_data_list = ['prolificID', 'user_key', 'age', 'totalStartTime', 'experimentID', 'parameter', 'gender', 'glasses',
                  'sharp', 'dyslexic', 'fontDesigned', 'fontLicensed', 'fontDownloaded', 'browser', 'dpr', 'screen_x',
                  'screen_y']
all_user_JNDs = {}

db = feedback_db()
app = application = Bottle()


def check_url(exp, para):
    if exp not in experiments or para not in parameters:
        print('Something is wrong with the URL')
        print(exp, experiments)
        print(para, parameters)
        return template('error', message='The experiment or parameter seem to be wrong!')


def word_finder(amount):
    with open('assets/txt/found_words.txt') as f:
        the_words = random.sample(list(f), amount)
        return [word[0:8] for word in the_words]


def frange(x, y, jump):
    while x <= y:
        yield x
        x += jump


@app.route('/<exp>/<para>')
@app.route('/<exp>/<para>/')
@app.route('/<exp>/<para>/index')
def show_startpage(exp, para):
    if check_url(exp, para) != None: return check_url(exp, para)

    values = {
        'headline': 'Open a new tab or window',
        'somethingToSay': '''
        <p>This study needs to run in a seperate window or tab of your browser!<br>
        Please click this link to <a href="./ethics" target="_blank">open the study in a new tab or window</a> of your browser.
        </p>
        <script src="/assets/js/os_checker.js"></script>
    '''
    }
    return template('generic', values)


@app.route('/<exp>/<para>/ethics')
def show_ethics(exp, para):
    print('Ethics')
    if check_url(exp, para) != None: return check_url(exp, para)
    values = {
        'study_duration': end_links[exp][para]['duration'],
    }
    return template('ethics', values)


@app.route('/<exp>/<para>/user')
def collect_user_data(exp, para):
    if check_url(exp, para) != None: return check_url(exp, para)
    return template('user')


@app.route('/<exp>/<para>/intro', method='POST')
def login(exp, para):
    if check_url(exp, para) != None: return check_url(exp, para)

    user_key = str(uuid.uuid4())
    user_data = {data: request.forms.get(data) for data in user_data_list}
    user_data['user_key'] = user_key
    user_data['browser'] = str(httpagentparser.simple_detect(user_data['browser']))
    user_data['totalStartTime'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
    user_data['experimentID'] = exp
    user_data['parameter'] = para

    values = {
        'para': para,
        'exp': exp,
    }
    if exp in ['scale', 'JND']:
        values['para_info'] = parameters[para]['info']

    if '_' in para:
        user_data['para1'] = values['para1'] = para.split('_')[0]
        user_data['para2'] = values['para2'] = para.split('_')[1]

    response.set_cookie('omnomnom', user_key)
    db.store_row(user_data, 'users')

    if exp == 'scale':
        values['axis_para'] = (parameters[para]["minimum"], parameters[para]["maximum"])
        return template('intro_s', values)
    elif exp == 'JND':
        values['axis_para'] = (parameters[para]["minimum"], parameters[para]["maximum"])
        return template('intro_j', values)
    elif exp == 'tm':
        return template('intro_tm', values)
    elif exp == 'field':
        return template('intro_f', values)


@app.route('/<exp>/<para>/tryajax', method='POST')
def ajax_scale_intro(exp, para):
    the_words = word_finder(3)

    new_trial_values = {
        'A': the_words[0],
        'B': the_words[1],
        'C': the_words[2],
    }

    return new_trial_values


# ----------------------------------------------------
# Trial amount and other settings for the experiments

# scale 
segments = 8
repetitions = 9

segment_w = (100. / segments)
trials = [[0, 0, 1 * round(segment_w * seg, 3), 100] for seg in range(segments + 1)]
trials_total = trials * repetitions

# JND 
JND_rounds = 35

field_combo_sets = [['slant', 'weight'], ['slant', 'xHeight'], ['slant', 'width'], ['weight', 'xHeight'],
                    ['weight', 'width'], ['width', 'xHeight']]
field_combos = {}

for p1, p2 in field_combo_sets:
    values1 = [parameters[p1]['s_min'] + (parameters[p1]['s_max'] - parameters[p1]['s_min']) / 4 * i for i in range(5)]
    values2 = [parameters[p2]['s_min'] + (parameters[p2]['s_max'] - parameters[p2]['s_min']) / 4 * i for i in range(5)]
    field_combos['%s_%s' % (p1, p2)] = [[val1, val2] for val2 in values2 for val1 in values1]


# -----------------------------
# these are the SCALE funcions

@app.route('/scale/<para>/round1')
def study(para):
    print('Study')
    if check_url('scale', para) != None: return check_url('scale', para)

    username = request.get_cookie("omnomnom")
    trials_total_ = trials_total[:]
    random.shuffle(trials_total_)

    if not username:
        print
        'Cookie was not present.'
        return template('error', message='Hmmm, seems like you did not supply a prolific ID?')

    values = {
        'exp': 'scale',
        'para': para,
        'round': 1,
        'axis_para': (parameters[para]["minimum"], parameters[para]["maximum"]),
        'decimals': parameters[para]["decimals"],
        'axis_segment': (0, 100),
        'knob': random.randint(0, 100),
        'test_word': word_finder(3),
        'trials_amount': len(trials_total_),
        'trials': trials,
        'allTrials': {t: {'seg': trial[0], 'lower': trial[1], 'step_value': trial[2], 'upper': trial[3]} for t, trial in
                      enumerate(trials_total_)},
        'collect': {0: {(t[2] * 1): [] for t in trials}},
        'increments': parameters[para]["step_unit"],
    }

    return template('scale', values)


@app.route('/scale/<para>/ajax', method='POST')
def ajax_scale_trial(para):
    the_words = word_finder(3)
    store_trial_data = {}

    trialCount = None

    try:
        trialCount = request.json['trialCount']
        store_trial_data['user_key'] = request.get_cookie("omnomnom")
        store_trial_data['browser_width'] = request.json['width']
        store_trial_data['browser_height'] = request.json['height']
        store_trial_data['startTime'] = request.json['startTime']
        store_trial_data['endTime'] = request.json['endTime']
        store_trial_data['timeDelta'] = request.json['timeDelta']
        store_trial_data['test_trial'] = None
        store_trial_data['roundCount'] = request.json['roundCount']
        store_trial_data['trialCount'] = request.json['trialCount']
        store_trial_data['segment'] = request.json['segment']
        store_trial_data['sameWord'] = False
        store_trial_data['wordLeft'] = request.json['wordLeft']
        store_trial_data['wordRight'] = request.json['wordRight']
        store_trial_data['wordCenter'] = request.json['wordCenter']
        store_trial_data['fontLeft'] = request.json['fontLeft'].strip('\'\"')
        store_trial_data['fontRight'] = request.json['fontRight'].strip('\'\"')
        store_trial_data['fontCenter'] = request.json['fontCenter'].strip('\'\"')
        store_trial_data['fontLoaded'] = request.json['fontLoaded']
        store_trial_data['parameterLeft'] = request.json['parameterLeft']
        store_trial_data['parameterRight'] = request.json['parameterRight']
        store_trial_data['parameterCenter'] = request.json['parameterCenter']
        store_trial_data['parameterKnob'] = request.json['parameterKnob']
        store_trial_data['parameterAnswer'] = request.json['parameterAnswer']
        store_trial_data['valueLeft'] = request.json['valueLeft']
        store_trial_data['valueRight'] = request.json['valueRight']
        store_trial_data['valueCenter'] = request.json['valueCenter']  # trials_total[int(trialCount)-1][2]
        store_trial_data['valueKnob'] = request.json['valueKnob']
        store_trial_data['valueAnswer'] = request.json['valueAnswer']
        store_trial_data['valueKnobMapped'] = request.json['valueKnobMapped']
        store_trial_data['valueAnswerMapped'] = request.json['valueAnswerMapped']
        store_trial_data['choice'] = None
        store_trial_data['interactionCount'] = request.json['interactionCount']
        store_trial_data['keyboard'] = request.json['keyboard']
        store_trial_data['dragged'] = request.json['dragged']
        # store_trial_data['conditionA'] = None
        # store_trial_data['conditionB'] = None


        new_trial_values = {
            'A': the_words[0],
            'B': the_words[1],
            'C': the_words[2],
            'trial_counter': trialCount + 1
        }

        condition = {'user_key': request.get_cookie("omnomnom"), 'roundCount': request.json['roundCount']}

        try:
            highest_trial = db.get_highest_value('trialCount', 'trials', condition)[0]
        except IndexError as error:
            print
            'Checking for the highest stored value seems to wrong.'

        if trialCount > highest_trial or highest_trial == 'null':
            try:
                db.store_row(store_trial_data, 'trials')
                # print 'Data stored for %d. trial in %s study.' % (request.json['trialCount'], para)
            except Exception as e:
                print
                'Storing the row does not seem to work.'


        elif trialCount - 1 == highest_trial:
            print
            'Previous trial was sent. Trialcount received: %d. Trialcount expected: %d ' % (
            trialCount - 1, highest_trial + 1)
            # do not save new data            
        else:
            print('Something went really wrong. It expected trial: ' + str(
                highest_trial + 1) + ' and we received: ' + str(trialCount - 1))
            values = {
                'headline': 'Feedback and comments',
                'somethingToSay': '''
                            <form action="./finish" method='post'>
                            <p>If you have some comments, question, remarks or other feedback please use the form below.</p>
                            <label for="comment"></label><br>
                            <textarea style='width: 100%' name='comment' maxlength='2000' placeholder='2000 characters Maximum' rows="15" id="comment"></textarea>
                            <input type="submit" value="N E X T">
                        </form>
                        '''
            }
            return template('generic', values)

        return new_trial_values

    except Exception as e:
        print
        "Error when posting data on scale experiment: {}".format(e)
        return template('error', message='Oh, there seems to be a problem with the POSTed data!')


# -----------------------------
# Here start the JND functions


@app.route('/JND/<para>/round1')
def study(para):
    if check_url('JND', para) != None: return check_url('JND', para)

    username = request.get_cookie("omnomnom")
    if not username: return template('error', message='Hmmm, seems like you did not supply a prolific ID?')

    all_user_JNDs[username] = [
        StaircaseProcedure([parameters[para]["minimum"], parameters[para]["maximum"]], parameters[para]["JND_step"],
                           0.00, -1),
        StaircaseProcedure([parameters[para]["minimum"], parameters[para]["maximum"]], parameters[para]["JND_step"],
                           0.25, -1),
        StaircaseProcedure([parameters[para]["minimum"], parameters[para]["maximum"]], parameters[para]["JND_step"],
                           0.25, +1),
        StaircaseProcedure([parameters[para]["minimum"], parameters[para]["maximum"]], parameters[para]["JND_step"],
                           0.50, -1),
        StaircaseProcedure([parameters[para]["minimum"], parameters[para]["maximum"]], parameters[para]["JND_step"],
                           0.50, +1),
        StaircaseProcedure([parameters[para]["minimum"], parameters[para]["maximum"]], parameters[para]["JND_step"],
                           0.75, -1),
        StaircaseProcedure([parameters[para]["minimum"], parameters[para]["maximum"]], parameters[para]["JND_step"],
                           0.75, +1),
        StaircaseProcedure([parameters[para]["minimum"], parameters[para]["maximum"]], parameters[para]["JND_step"],
                           1.00, +1)
    ]

    values = {
        'exp': 'JND',
        'para': para,
        'test_word': word_finder(3),
        'trials_amount': JND_rounds * len(all_user_JNDs[username]),
        'sp_0': '{:.{prec}f}'.format(all_user_JNDs[username][0].getNextParameterValue(),
                                     prec=parameters[para]["decimals"]),
        'tp_0': '{:.{prec}f}'.format(all_user_JNDs[username][0].getAssumedTestPoint(),
                                     prec=parameters[para]["decimals"]),
    }

    return template('JND', values)


@app.route('/JND/<para>/ajax', method='POST')
def ajax_JND_trial(para):
    # start_time = time.time()

    username = request.get_cookie("omnomnom")
    the_words = word_finder(3)
    store_trial_data = {}
    trialCount = None
    JND_points_amount = len(all_user_JNDs[username])

    try:
        trialCount = request.json['trialCount']
        c = int(trialCount) % JND_points_amount
        store_trial_data['user_key'] = request.get_cookie("omnomnom")
        store_trial_data['browser_width'] = request.json['width']
        store_trial_data['browser_height'] = request.json['height']
        store_trial_data['startTime'] = request.json['startTime']
        store_trial_data['endTime'] = request.json['endTime']
        store_trial_data['timeDelta'] = request.json['timeDelta']
        store_trial_data['test_trial'] = None
        store_trial_data['roundCount'] = int(trialCount / JND_points_amount) + 1
        store_trial_data['trialCount'] = trialCount
        store_trial_data['JNDModulo'] = c
        store_trial_data['sameWord'] = False
        store_trial_data['wordLeft'] = request.json['wordLeft']
        store_trial_data['wordRight'] = request.json['wordRight']
        store_trial_data['choice'] = request.json['choice']
        store_trial_data['fontLeft'] = request.json['fontLeft'].strip('\'\"')
        store_trial_data['fontRight'] = request.json['fontRight'].strip('\'\"')
        store_trial_data['parameterLeft'] = request.json['fontLeft'].strip('\'\"').split('_')[1]
        store_trial_data['parameterRight'] = request.json['fontRight'].strip('\'\"').split('_')[1]

        if (float(store_trial_data['parameterLeft']) > float(store_trial_data['parameterRight'])):
            if store_trial_data['choice'] == 'A':
                if para in ['aperture', 'slantL']:
                    # print 'left bigger and A -> false'
                    all_user_JNDs[username][c].update(0)
                else:
                    # print 'left bigger and A -> true'
                    all_user_JNDs[username][c].update(1)
            elif store_trial_data['choice'] == 'B':
                if para in ['aperture', 'slantL']:
                    # print 'left bigger and A -> false'
                    all_user_JNDs[username][c].update(1)
                else:
                    # print 'left bigger and B -> false'
                    all_user_JNDs[username][c].update(0)
        elif (float(store_trial_data['parameterLeft']) < float(store_trial_data['parameterRight'])):
            if store_trial_data['choice'] == 'B':
                if para in ['aperture', 'slantL']:
                    # print 'left bigger and A -> false'
                    all_user_JNDs[username][c].update(0)
                else:
                    # print 'right bigger and B -> true'
                    all_user_JNDs[username][c].update(1)
            elif store_trial_data['choice'] == 'A':
                if para in ['aperture', 'slantL']:
                    # print 'left bigger and A -> true'
                    all_user_JNDs[username][c].update(1)
                else:
                    # print 'right bigger and A -> false'
                    all_user_JNDs[username][c].update(0)
        else:
            print
            'Aaaahh! JND does not seem to chatch the parameter comparison!'

        store_trial_data['JND_parameterPoint'] = all_user_JNDs[username][c].originalParameterPoint
        store_trial_data['JND_direction'] = all_user_JNDs[username][c].originalDirection
        store_trial_data['fontLoaded'] = request.json['fontLoaded']
        store_trial_data['interactionCount'] = request.json['interactionCount']
        store_trial_data['keyboard'] = request.json['keyboard']
        store_trial_data['dragged'] = request.json['dragged']
        store_trial_data['threshold'] = str(all_user_JNDs[username][c].getActualLambda()[0])
        store_trial_data['thresholdNorm'] = str(all_user_JNDs[username][c].getEstimatedNormalisedLambda()[0])
        store_trial_data['slope'] = str(all_user_JNDs[username][c].getActualLambda()[1])
        store_trial_data['slopeNorm'] = str(all_user_JNDs[username][c].getEstimatedNormalisedLambda()[1])

        # print 'All: ' + str([(stp.originalParameterPoint, stp.originalDirection) for stp in all_user_JNDs[username]])
        # print 'Current: ' + str([c, all_user_JNDs[username][c].originalParameterPoint, store_trial_data['JND_parameterPoint'], all_user_JNDs[username][c].originalDirection])

        try:
            db.store_row(store_trial_data, 'trials')
            # print 'Data stored for %d. trial in %s study.' % (request.json['trialCount'], para)
        except Exception as e:
            print
            'Storing the row does not seem to work.'

        c_new = int(trialCount + 1) % JND_points_amount

        if c == 7:
            random.shuffle(all_user_JNDs[username])

        new_trial_values = {
            'modulo': c,
            'A': the_words[0],
            'B': the_words[1],
            'sp': '{:.{prec}f}'.format(all_user_JNDs[username][c_new].getNextParameterValue(),
                                       prec=parameters[para]["decimals"]),
            'tp': '{:.{prec}f}'.format(all_user_JNDs[username][c_new].getAssumedTestPoint(),
                                       prec=parameters[para]["decimals"]),
        }

        if int(trialCount) + 1 == (JND_rounds * JND_points_amount):
            new_trial_values['trialsEnd'] = 'trialsEnd'

        # print("--- %s seconds ---" % (time.time() - start_time))
        return new_trial_values

    except Exception as e:
        print
        "Error when posting data on JND experiment: {}".format(e)
        return template('error', message='Oh, there seems to be a problem with the POSTed data!')


# ----------------------------------------------
# Here start the tm (triplet matching) functions
# ----------------------------------------------


@app.route('/tm/<para>/round1')
def study(para):
    if check_url('tm', para) != None: return check_url('tm', para)

    username = request.get_cookie("omnomnom")

    if not username:
        return template('error', message='Hmmm, seems like you did not supply a prolific ID?')

    paras = para.split('_')

    tm_sections1 = [parameters[paras[0]]['s_min'], parameters[paras[0]]['math_middle'], parameters[paras[0]]['s_max']]
    tm_sections2 = [parameters[paras[1]]['s_min'], parameters[paras[1]]['math_middle'], parameters[paras[1]]['s_max']]
    combis = [[i, j] for i in tm_sections1 for j in tm_sections2]

    tm_trials = []

    for combi in combis:
        round_combis = list(combis)
        round_combis.remove(combi)
        tm_trials += [list(cc) + [combi] for cc in combinations(round_combis, 2)]

    # tm_trials =  [list(combi) for combi in combinations(combis, 3)]
    # tm_trials =  [ [combi[0], combi[1], combi[2] ] if random.random() > 0.5  else [combi[1], combi[0], combi[2]] for combi in combinations(combis, 3) ]

    control_trials = [[combis[i], combis[(i + 4) % len(combis)], combis[i]] if random.random() > 0.5 else [
        combis[(i + 4) % len(combis)], combis[i], combis[i]] for i in range(len(combis))]
    all_trials = control_trials + tm_trials

    random.shuffle(all_trials)

    values = {
        'exp': 'tm',
        'para_comb': para,
        'paras': paras,
        'round': 1,
        'test_word': word_finder(3),
        'trials_amount': len(all_trials),
        'allTrials': all_trials
    }

    if all_trials[0][0][0] == all_trials[0][2][0] and all_trials[0][0][1] == all_trials[0][2][1]:
        values['test_word'] = [values['test_word'][0], values['test_word'][1], values['test_word'][0]]
    elif all_trials[0][1][0] == all_trials[0][2][0] and all_trials[0][1][1] == all_trials[0][2][1]:
        values['test_word'] = [values['test_word'][1], values['test_word'][0], values['test_word'][0]]

    return template('tm', values)


@app.route('/tm/<para>/ajax', method='POST')
def ajax_tm_trial(para):
    the_words = word_finder(3)
    store_trial_data = {}
    trialCount = None

    try:
        trialCount = request.json['trialCount']
        store_trial_data['user_key'] = request.get_cookie("omnomnom")
        store_trial_data['browser_width'] = request.json['width']
        store_trial_data['browser_height'] = request.json['height']
        store_trial_data['startTime'] = request.json['startTime']
        store_trial_data['endTime'] = request.json['endTime']
        store_trial_data['timeDelta'] = request.json['timeDelta']
        store_trial_data['test_trial'] = None
        store_trial_data['roundCount'] = 1
        store_trial_data['trialCount'] = request.json['trialCount']
        store_trial_data['sameWord'] = False
        store_trial_data['wordLeft'] = request.json['wordLeft']
        store_trial_data['wordRight'] = request.json['wordRight']
        store_trial_data['wordCenter'] = request.json['wordCenter']
        store_trial_data['fontLeft'] = request.json['fontLeft'].strip('\'\"')
        store_trial_data['fontRight'] = request.json['fontRight'].strip('\'\"')
        store_trial_data['fontCenter'] = request.json['fontCenter'].strip('\'\"')
        store_trial_data['fontLoaded'] = request.json['fontLoaded']
        store_trial_data['para1Left'] = request.json['para1Left']
        store_trial_data['para2Left'] = request.json['para2Left']
        store_trial_data['para1Right'] = request.json['para1Right']
        store_trial_data['para2Right'] = request.json['para2Right']
        store_trial_data['para1Center'] = request.json['para1Center']
        store_trial_data['para2Center'] = request.json['para2Center']
        store_trial_data['choice'] = request.json['choice']
        store_trial_data['interactionCount'] = request.json['interactionCount']
        store_trial_data['keyboard'] = request.json['keyboard']
        store_trial_data['dragged'] = request.json['dragged']
        store_trial_data['conditionA'] = None
        store_trial_data['conditionB'] = None

        # print 'Data received for %d. trial in %s study.' % (request.json['trialCount'], para)
        # identifiers_date = datetime.now()
        # identifiers_cookie = request.get_cookie("omnomnom")[-5:]
        # identifiers = '[ %s - %s ]' % (str(identifiers_date), str(identifiers_cookie))

        new_trial_values = {
            'A': the_words[0],
            'B': the_words[1],
            'C': the_words[2],
            'trial_counter': trialCount + 1
        }

        condition = {'user_key': request.get_cookie("omnomnom")}

        # before_time = datetime.now()
        # print identifiers + 'Timestamp from before the db gets queried:', str(before_time)

        try:
            highest_trial = db.get_highest_value('trialCount', 'trials', condition)[0]
            # print db.get_highest_value('trialCount', 'trials', condition)
            # print type(db.get_highest_value('trialCount', 'trials', condition)[0])

        except IndexError as error:
            print
            'Checking for the highest stored value seems to wrong.'
            # print identifiers + 'What get hightest value returned: ', db.get_highest_value('trialCount', 'trials', condition)
            # print identifiers + "Error: {}".format(error)

        # print identifiers + 'Timedelta for the db query: ', str(datetime.now() - before_time)


        # if trialCount > highest_trial or highest_trial == 'null':
        try:
            db.store_row(store_trial_data, 'trials')
            # print 'Data stored for %d. trial in %s study.' % (request.json['trialCount'], para)
        except Exception as e:
            print
            'Storing the row does not seem to work.'
            print
            store_trial_data

        # del(trialCount)

        return new_trial_values


    except Exception as e:
        print
        "Error when posting data on Exp 3: {}".format(e)
        return template('error', message='Oh, there seems to be a problem with the POSTed data!')


# ----------------------------------------------
# Here start the field functions
# ----------------------------------------------

field_para_combos_css = {
    'weight_width': ['weight_30_width_0.8', 'weight_62_width_0.8', 'weight_95_width_0.8', 'weight_127_width_0.8',
                     'weight_160_width_0.8', 'weight_30_width_0.975', 'weight_62_width_0.975', 'weight_95_width_0.975',
                     'weight_127_width_0.975', 'weight_160_width_0.975', 'weight_30_width_1.15', 'weight_62_width_1.15',
                     'weight_95_width_1.15', 'weight_127_width_1.15', 'weight_160_width_1.15', 'weight_30_width_1.325',
                     'weight_62_width_1.325', 'weight_95_width_1.325', 'weight_127_width_1.325',
                     'weight_160_width_1.325', 'weight_30_width_1.5', 'weight_62_width_1.5', 'weight_95_width_1.5',
                     'weight_127_width_1.5', 'weight_160_width_1.5'],
    'slant_xHeight': ['slant_-20_xHeight_360', 'slant_-10_xHeight_360', 'slant_0_xHeight_360', 'slant_10_xHeight_360',
                      'slant_20_xHeight_360', 'slant_-20_xHeight_420', 'slant_-10_xHeight_420', 'slant_0_xHeight_420',
                      'slant_10_xHeight_420', 'slant_20_xHeight_420', 'slant_-20_xHeight_480', 'slant_-10_xHeight_480',
                      'slant_0_xHeight_480', 'slant_10_xHeight_480', 'slant_20_xHeight_480', 'slant_-20_xHeight_540',
                      'slant_-10_xHeight_540', 'slant_0_xHeight_540', 'slant_10_xHeight_540', 'slant_20_xHeight_540',
                      'slant_-20_xHeight_600', 'slant_-10_xHeight_600', 'slant_0_xHeight_600', 'slant_10_xHeight_600',
                      'slant_20_xHeight_600'],
    'slant_width': ['slant_-20_width_0.8', 'slant_-10_width_0.8', 'slant_0_width_0.8', 'slant_10_width_0.8',
                    'slant_20_width_0.8', 'slant_-20_width_0.975', 'slant_-10_width_0.975', 'slant_0_width_0.975',
                    'slant_10_width_0.975', 'slant_20_width_0.975', 'slant_-20_width_1.15', 'slant_-10_width_1.15',
                    'slant_0_width_1.15', 'slant_10_width_1.15', 'slant_20_width_1.15', 'slant_-20_width_1.325',
                    'slant_-10_width_1.325', 'slant_0_width_1.325', 'slant_10_width_1.325', 'slant_20_width_1.325',
                    'slant_-20_width_1.5', 'slant_-10_width_1.5', 'slant_0_width_1.5', 'slant_10_width_1.5',
                    'slant_20_width_1.5'],
    'slant_weight': ['slant_-20_weight_30', 'slant_-10_weight_30', 'slant_0_weight_30', 'slant_10_weight_30',
                     'slant_20_weight_30', 'slant_-20_weight_62', 'slant_-10_weight_62', 'slant_0_weight_62',
                     'slant_10_weight_62', 'slant_20_weight_62', 'slant_-20_weight_95', 'slant_-10_weight_95',
                     'slant_0_weight_95', 'slant_10_weight_95', 'slant_20_weight_95', 'slant_-20_weight_127',
                     'slant_-10_weight_127', 'slant_0_weight_127', 'slant_10_weight_127', 'slant_20_weight_127',
                     'slant_-20_weight_160', 'slant_-10_weight_160', 'slant_0_weight_160', 'slant_10_weight_160',
                     'slant_20_weight_160'],
    'width_xHeight': ['width_0.8_xHeight_360', 'width_0.975_xHeight_360', 'width_1.15_xHeight_360',
                      'width_1.325_xHeight_360', 'width_1.5_xHeight_360', 'width_0.8_xHeight_420',
                      'width_0.975_xHeight_420', 'width_1.15_xHeight_420', 'width_1.325_xHeight_420',
                      'width_1.5_xHeight_420', 'width_0.8_xHeight_480', 'width_0.975_xHeight_480',
                      'width_1.15_xHeight_480', 'width_1.325_xHeight_480', 'width_1.5_xHeight_480',
                      'width_0.8_xHeight_540', 'width_0.975_xHeight_540', 'width_1.15_xHeight_540',
                      'width_1.325_xHeight_540', 'width_1.5_xHeight_540', 'width_0.8_xHeight_600',
                      'width_0.975_xHeight_600', 'width_1.15_xHeight_600', 'width_1.325_xHeight_600',
                      'width_1.5_xHeight_600'],
    'weight_xHeight': ['weight_30_xHeight_360', 'weight_62_xHeight_360', 'weight_95_xHeight_360',
                       'weight_127_xHeight_360', 'weight_160_xHeight_360', 'weight_30_xHeight_420',
                       'weight_62_xHeight_420', 'weight_95_xHeight_420', 'weight_127_xHeight_420',
                       'weight_160_xHeight_420', 'weight_30_xHeight_480', 'weight_62_xHeight_480',
                       'weight_95_xHeight_480', 'weight_127_xHeight_480', 'weight_160_xHeight_480',
                       'weight_30_xHeight_540', 'weight_62_xHeight_540', 'weight_95_xHeight_540',
                       'weight_127_xHeight_540', 'weight_160_xHeight_540', 'weight_30_xHeight_600',
                       'weight_62_xHeight_600', 'weight_95_xHeight_600', 'weight_127_xHeight_600',
                       'weight_160_xHeight_600']
    }
field_para_levels = {'weight': ["30", "62", "95", "127", "160"], 'width': ["0.8", "0.975", "1.15", "1.325", "1.5"],
                     'slant': ["-20", "-10", "0", "10", "20"], 'xHeight': ["360", "420", "480", "540", "600"]}


@app.route('/field/<para>/round1')
def study(para):
    if check_url('field', para) != None: return check_url('field', para)

    username = request.get_cookie("omnomnom")
    if not username: return template('error', message='Hmmm, seems like you did not supply a prolific ID?')

    paras = para.split('_')
    para_values = field_para_combos_css[para][:]
    final_para_values = para_values * 2  # each word appears twice in a given trial
    random.shuffle(final_para_values)

    block_num = 3  # number of repetitions (each one set of trials per block)

    ref_order = field_para_combos_css[para][:]
    random.shuffle(ref_order)

    # shuffle the order for each block internally
    final_ref_order = []
    for _ in range(block_num):
        random.shuffle(ref_order)
        final_ref_order += ref_order

    values = {
        'exp': 'field',
        'para': para,
        'para1': paras[0],
        'para2': paras[1],
        'round': 1,  ####index of para ,
        'all_words': word_finder(len(final_para_values) + 1),  # display words + reference word
        'para_values': final_para_values,
        'ref_order': final_ref_order,
        'trials_amount': len(final_ref_order),
    }

    return template('field', values)


@app.route('/field/<para>/ajax', method='POST')
def ajax_field_trial(para):
    store_trial_data = {}
    trialCount = None

    try:
        trialCount = request.json['trialCount']

        store_trial_data['user_key'] = request.get_cookie("omnomnom")
        store_trial_data['browser_width'] = request.json['width']
        store_trial_data['browser_height'] = request.json['height']
        store_trial_data['startTime'] = request.json['startTime']
        store_trial_data['endTime'] = request.json['endTime']
        store_trial_data['timeDelta'] = request.json['timeDelta']
        store_trial_data['test_trial'] = None
        store_trial_data['roundCount'] = 1
        store_trial_data['trialCount'] = request.json['trialCount']

        store_trial_data['interactionCount'] = request.json['interactionCount']

        # store_trial_data['sectionOrder'] =

        # Getting the parameter names (from para)
        para1name = para.split("_")[0]
        para2name = para.split("_")[1]

        store_trial_data['parameter1name'] = para1name
        store_trial_data['parameter2name'] = para2name

        # **** Reference word        
        store_trial_data['wordRef'] = request.json['wordRef']
        fontRef = request.json['fontRef'].strip('"\'')
        store_trial_data['fontRef'] = fontRef

        # positions and size
        store_trial_data['wordPosXRef'] = request.json['wordPosXRef']
        store_trial_data['wordPosYRef'] = request.json['wordPosYRef']
        store_trial_data['wordWidthRef'] = request.json['wordWidthRef']
        store_trial_data['wordHeightRef'] = request.json['wordHeightRef']

        # Getting the parameter values
        # First, extract the parameter values
        para1valueRef = fontRef.split("_")[1]
        para2valueRef = fontRef.split("_")[3]

        # find which index a parameter value belongs to
        para1indexRef = field_para_levels[para1name].index(para1valueRef)
        para2indexRef = field_para_levels[para2name].index(para2valueRef)

        store_trial_data['parameterValue1Ref'] = para1valueRef
        store_trial_data['parameterValue2Ref'] = para2valueRef

        store_trial_data['parameterLevel1Ref'] = para1indexRef
        store_trial_data['parameterLevel2Ref'] = para2indexRef

        # **** Word A        
        store_trial_data['wordA'] = request.json['wordA']
        fontA = request.json['fontA'].strip('"\'')
        store_trial_data['fontA'] = fontA

        # positions and size
        store_trial_data['wordPosXA'] = request.json['wordPosXA']
        store_trial_data['wordPosYA'] = request.json['wordPosYA']
        store_trial_data['wordWidthA'] = request.json['wordWidthA']
        store_trial_data['wordHeightA'] = request.json['wordHeightA']

        # Times and orders
        store_trial_data['timeSelectA'] = request.json['timeSelectA']
        store_trial_data['wordNumberA'] = int(request.json['wordNumberA'].split('_')[1])
        # Getting the parameter values

        # First, extract the parameter values
        para1valueA = fontA.split("_")[1]
        para2valueA = fontA.split("_")[3]

        # find which index a parameter value belongs to
        para1indexA = field_para_levels[para1name].index(para1valueA)
        para2indexA = field_para_levels[para2name].index(para2valueA)

        store_trial_data['parameterValue1A'] = para1valueA
        store_trial_data['parameterValue2A'] = para2valueA

        store_trial_data['parameterLevel1A'] = para1indexA
        store_trial_data['parameterLevel2A'] = para2indexA

        # finally, calculating the distances

        store_trial_data['parameterDist1A'] = para1indexA - para1indexRef
        store_trial_data['parameterDist2A'] = para2indexA - para2indexRef

        # **** Word B
        store_trial_data['wordB'] = request.json['wordB']
        fontB = request.json['fontB'].strip('"\'')
        store_trial_data['fontB'] = fontB

        # positions and size
        store_trial_data['wordPosXB'] = request.json['wordPosXB']
        store_trial_data['wordPosYB'] = request.json['wordPosYB']
        store_trial_data['wordWidthB'] = request.json['wordWidthB']
        store_trial_data['wordHeightB'] = request.json['wordHeightB']

        # Times and orders
        store_trial_data['timeSelectB'] = request.json['timeSelectB']
        store_trial_data['wordNumberB'] = int(request.json['wordNumberB'].split('_')[1])

        # Getting the parameter values

        # First, extract the parameter values
        para1valueB = fontB.split("_")[1]
        para2valueB = fontB.split("_")[3]

        # find which index a parameter value belongs to
        para1indexB = field_para_levels[para1name].index(para1valueB)
        para2indexB = field_para_levels[para2name].index(para2valueB)

        store_trial_data['parameterValue1B'] = para1valueB
        store_trial_data['parameterValue2B'] = para2valueB

        store_trial_data['parameterLevel1B'] = para1indexB
        store_trial_data['parameterLevel2B'] = para2indexB

        # finally, calculating the distances

        store_trial_data['parameterDist1B'] = para1indexB - para1indexRef
        store_trial_data['parameterDist2B'] = para2indexB - para2indexRef

        # **** Word C        
        store_trial_data['wordC'] = request.json['wordC']
        fontC = request.json['fontC'].strip('"\'')
        store_trial_data['fontC'] = fontC

        # positions and size
        store_trial_data['wordPosXC'] = request.json['wordPosXC']
        store_trial_data['wordPosYC'] = request.json['wordPosYC']
        store_trial_data['wordWidthC'] = request.json['wordWidthC']
        store_trial_data['wordHeightC'] = request.json['wordHeightC']

        # Times and orders
        store_trial_data['timeSelectC'] = request.json['timeSelectC']
        store_trial_data['wordNumberC'] = int(request.json['wordNumberC'].split('_')[1])

        # Getting the parameter values

        # First, extract the parameter values
        para1valueC = fontC.split("_")[1]
        para2valueC = fontC.split("_")[3]

        # find which index a parameter value belongs to
        para1indexC = field_para_levels[para1name].index(para1valueC)
        para2indexC = field_para_levels[para2name].index(para2valueC)

        store_trial_data['parameterValue1C'] = para1valueC
        store_trial_data['parameterValue2C'] = para2valueC

        store_trial_data['parameterLevel1C'] = para1indexC
        store_trial_data['parameterLevel2C'] = para2indexC

        # finally, calculating the distances

        store_trial_data['parameterDist1C'] = para1indexC - para1indexRef
        store_trial_data['parameterDist2C'] = para2indexC - para2indexRef

        the_words = word_finder(51)

        new_trial_values = {
            'all_words': word_finder(51),
            'trial_counter': trialCount + 1
        }

        condition = {'user_key': request.get_cookie("omnomnom")}

        try:
            highest_trial = db.get_highest_value('trialCount', 'trials_field', condition)[0]
        except IndexError as error:
            print
            'Checking for the highest stored value seems to go  wrong.'

        try:
            db.store_row(store_trial_data, 'trials_field')
        except Exception as e:
            print
            'Storing the row does not seem to work.'
            print
            store_trial_data

        return new_trial_values


    except Exception as e:
        print
        "Error when posting data on Exp 3"
        traceback.print_exc()
        return template('error', message='Oh, there seems to be a problem with the POSTed data on the field!')


# -----------------------------
# These are the bits at the end

@app.route('/<exp>/<para>/feedback', method='POST')
def feedback_page(exp, para):
    if check_url(exp, para) != None: return check_url(exp, para)

    username = request.get_cookie("omnomnom")
    if not username:
        return template('error', message='Hmmm, seems like you did not supply a prolific ID?')

    if para == 'scale':
        prev_sections = json.loads(request.forms.get('sorted_diffs'))
        prev_answers = json.loads(request.forms.get('answers_av'))

        store_trial_data = {
            'answer_diff_round2': ";".join(
                ["low:%s,up:%s,diff:%s" % (t['lower'], t['upper'], t['diff']) for t in prev_sections]),
            'answer_av_round2': ','.join(["%s:%s" % (k, v) for k, v in prev_answers.items()])
        }
        condition = {'user_key': username}
        db.store_cell(store_trial_data, 'users', condition)

    if para == 'JND':
        del (all_user_JNDs[username])

    values = {
        'headline': 'Feedback and comments',
        'somethingToSay': '''
    <form action="./finish" method='post'>
        <p>If you have some comments, question, remarks or other feedback please use the form below.</p>
        <label for="comment"></label><br>
        <textarea style='width: 100%' name='comment' maxlength='2000' placeholder='2000 characters Maximum' rows="15" id="comment"></textarea>
        <input type="submit" value="N E X T">
    </form>
    '''
    }
    return template('generic', values)


@app.route('/<exp>/<para>/finish', method='POST')
def finish_page(exp, para):
    if check_url(exp, para) != None: return check_url(exp, para)
    comment_data = {
        'user_key': request.get_cookie("omnomnom"),
        'comment': request.forms.get('comment')
    }

    db.store_row(comment_data, 'comments')
    response.delete_cookie('omnomnom')

    end_link = end_links[exp][para]['link']
    return template('finish', end_link=end_link)


# @app.route('/show_data')
# def show_data():
#     output = template('show_data', rows = db.show_all())
#     return output


@app.route('/<exp>/<para>/dwld_data')
def dwld_data(exp, para):
    if check_url(exp, para) != None: return check_url(exp, para)

    dest = StringIO.StringIO()
    writer = csv.writer(dest, quoting=csv.QUOTE_NONNUMERIC)
    condition = {'parameter': para}
    writer.writerows(db.show_all(condition))
    dest.seek(0)
    response.headers['Content-Type'] = 'text/csv'
    response.headers['Content-Disposition'] = 'attachment; filename=export-data-%s-%s.csv' % (
    para, date.today().strftime('%y%m%d'))
    return dest.read()


# @app.route('/clear_data')
# def clear_data(): db.clear_tables()


@app.error(403)
def mistake403(code):
    return template('error', message='There seems to be a mistake with this url!')


@app.error(404)
def mistake404(code):
    return template('error', message='404 - Whoops, this page or file does not seem to exist!')


@app.error(405)
def mistake405(code):
    return template('error', message='405 - Whoops, this url or file does not seem to exist!')


@error(500)
def mistake500(code):
    return template('error', message='Whoops, this page does not seem to exist!')


debug(True)

if __name__ == '__main__':
    debug(True)
    app.run(
        reloader=True,
        host='0.0.0.0',
        port=8888)
