from PySide2 import QtSql as qts
from PySide2 import QtWidgets as qtw
from PySide2 import QtCore as qtc

import sys
import os


# noinspection PyTypeChecker
class Database(qtc.QObject):
    __instance = None

    @staticmethod
    def get_instance(mainWindow):
        if Database.__instance is None:
            Database(mainWindow)
        return Database.__instance

    def get_db(self):
        return self.db

    def create_db(self):
        if not self.db.open():
            self.show_error(
                self.tr('Error'),
                'open db: ' + self.db.lastError().text()
            )
            sys.exit(1)
        all_sqls = open('db/database.sql', encoding='utf8').read().split(';')
        for sql in all_sqls:
            query = qts.QSqlQuery(self.db)
            query.prepare(sql)
            if not query.exec_():
                self.show_error(
                    self.tr('Error'),
                    'creating db: ' + self.db.lastError().text()
                )
                sys.exit(1)

    def __init__(self, mainWindow):
        super().__init__()
        if Database.__instance is not None:
            raise Exception("This class is a singleton!")
        self.mainWindow = mainWindow
        self.db = qts.QSqlDatabase.addDatabase('QSQLITE')
        self.db.setDatabaseName('db/minimizer.db')
        if not os.path.exists('db/minimizer.db'):
            self.create_db()
        else:
            if not self.db.open():
                self.show_error(
                    self.tr('DB Connection Error'),
                    self.tr('Could not open database file {}').format(self.db.lastError().text())
                )
                sys.exit(1)
        required_tables = {'trials', 'discarded_identifiers', 'treatments', 'factors', 'levels', 'subjects',
                           'subject_levels', 'preloads'}
        missing_tables = required_tables - set(self.db.tables())
        if missing_tables:
            self.show_error(
                self.tr('DB Integrity Error'),
                self.tr('Missing tables, please repair DB {}').format(missing_tables)
            )
            sys.exit(1)
        if not self.checkForeignKeys():
            sys.exit(1)
        Database.__instance = self

    def checkForeignKeys(self):
        query = qts.QSqlQuery(self.db)
        query.prepare('PRAGMA foreign_keys;')
        if not query.exec_():
            self.show_query_error(query)
        res = query.next()
        if not res:
            self.show_query_error(query)
        if query.value(0):
            return True
        query.prepare('PRAGMA foreign_keys = ON;')
        if not query.exec_():
            self.show_query_error(query)
        query.prepare('PRAGMA foreign_keys;')
        query.exec_()
        if query.next():
            return query.value(0) > 0
        else:
            return False

    def get_treatment_title(self, treatment_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT title FROM treatments WHERE id = :treatment_id')
        query.bindValue(':treatment_id', treatment_id)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        return query.value('title')

    def update_subject_treatment_id(self, subject_id, treatment_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('''UPDATE subjects set treatment_id = :treatment_id, 
        modified = CURRENT_TIMESTAMP WHERE id = :subject_id''')
        query.bindValue(':treatment_id', treatment_id)
        query.bindValue(':subject_id', subject_id)
        if not query.exec_():
            self.show_query_error(query)

    def get_subject_treatment_id(self, subject_id):
        query = qts.QSqlQuery(self.db)
        query.prepare("SELECT treatment_id FROM subjects WHERE id = :subject_id")
        query.bindValue(':subject_id', subject_id)
        if not query.exec_():
            self.show_query_error(query)
        if not query.next():
            return None
        return query.value('treatment_id')

    def get_subject(self, subject_id):
        query = qts.QSqlQuery(self.db)
        query.prepare("SELECT * FROM subjects WHERE id = :subject_id")
        query.bindValue(':subject_id', subject_id)
        if not query.exec_():
            self.show_query_error(query)
        if not query.next():
            return None
        return query

    def get_subjects(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('''SELECT id, treatment_id, identifier_value, enrolled, 
        modified FROM subjects WHERE trial_id = :trial_id''')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        subjects = []
        while query.next():
            subject = [
                query.value('id'),
                query.value('treatment_id'),
                query.value('identifier_value'),
                query.value('enrolled'),
                query.value('modified')
            ]
            subjects.append(subject)
        return subjects

    def get_subject_level_titles(self, subject_levels):
        query = qts.QSqlQuery(self.db)
        level_title = []
        for factor_id, level_id in subject_levels:
            query.prepare('SELECT title FROM levels WHERE id = :level_id')
            query.bindValue(':level_id', level_id)
            if not query.exec_():
                self.show_query_error(query)
            if not query.next():
                return False
            level_title.append(query.value('title'))
        return level_title

    def get_subject_levels(self, subject_id):
        query = qts.QSqlQuery(self.db)
        query.prepare("SELECT factor_id, level_id FROM subject_levels WHERE subject_id = :subject_id ORDER BY id")
        query.bindValue(':subject_id', subject_id)
        if not query.exec_():
            self.show_query_error(query)
        subject_levels = []
        while query.next():
            subject_level = [query.value('factor_id'), query.value('level_id')]
            subject_levels.append(subject_level)
        return subject_levels

    def get_freq(self, trial_id):
        subjects = self.get_subjects(trial_id)
        freq = self.get_empty_freq(trial_id)
        for subject in subjects:
            subject_levels = self.get_subject_levels(subject[0])
            for subject_level in subject_levels:
                key = (subject[1], subject_level[0], subject_level[1])
                freq[key] += 1
        return freq

    def get_empty_freq(self, trial_id):
        freq = {}
        treatments = self.read_treatments(trial_id)
        factors = self.read_factors(trial_id)
        if not treatments or not factors:
            return
        for treatment in treatments:
            for factor in factors:
                levels = self.factor_levels(factor[0])
                for level in levels:
                    key = (treatment[0], factor[0], level[0])
                    if key in freq:
                        continue
                    freq[key] = 0
        return freq

    def get_preload(self, trial_id):
        query = qts.QSqlQuery(self.db)
        preload = self.get_empty_freq(trial_id)
        query.prepare("SELECT treatment_id, factor_id, level_id, count FROM preloads WHERE trial_id = :trial_id")
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        rows = []
        while query.next():
            row = [query.value('treatment_id'), query.value('factor_id'), query.value('level_id'), query.value('count')]
            rows.append(row)
        for row in rows:
            key = (row[0], row[1], row[2])
            preload[key] = row[3]
        return preload

    def get_preload_with_freq(self, trial_id):
        freq = self.get_freq(trial_id)
        preload = self.get_preload(trial_id)
        if freq is None or preload is None:
            return None
        preload_with_freq = {}
        for key in freq:
            preload_with_freq[key] = freq[key] + preload[key]
        return preload_with_freq

    def insert_level(self, trial_id, factor_id, title):
        query = qts.QSqlQuery(self.db)
        query.prepare('INSERT INTO levels (trial_id, factor_id, title)  VALUES (:trial_id, :factor_id, :title)')
        query.bindValue(':trial_id', trial_id)
        query.bindValue(':factor_id', factor_id)
        query.bindValue(':title', title)
        if not query.exec_():
            self.show_query_error(query)
        return query.lastInsertId()

    def insert_factor(self, trial_id, title):
        query = qts.QSqlQuery(self.db)
        query.prepare('INSERT INTO factors (trial_id, title)  VALUES (:trial_id, :title)')
        query.bindValue(':title', title)
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        return query.lastInsertId()

    def insert_treatment(self, trial_id, title):
        query = qts.QSqlQuery(self.db)
        query.prepare('INSERT INTO treatments (trial_id, title)  VALUES (:trial_id, :title)')
        query.bindValue(':title', title)
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        return query.lastInsertId()

    def insert_trial(self, title):
        query = qts.QSqlQuery(self.db)
        query.prepare('INSERT INTO trials (title)  VALUES (:title)')
        query.bindValue(':title', title)
        if not query.exec_():
            self.show_query_error(query)
        return query.lastInsertId()

    def delete_level(self, level_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('DELETE FROM levels WHERE id = :id')
        query.bindValue(':id', level_id)
        if not query.exec_():
            self.show_query_error(query)

    def delete_treatment(self, treatment_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('DELETE FROM treatments WHERE id = :id')
        query.bindValue(':id', treatment_id)
        if not query.exec_():
            self.show_query_error(query)

    def delete_factor(self, factor_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('DELETE FROM factors WHERE id = :id')
        query.bindValue(':id', factor_id)
        if not query.exec_():
            self.show_query_error(query)

    def delete_trial(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('DELETE FROM trials WHERE id = :id')
        query.bindValue(':id', trial_id)
        if not query.exec_():
            self.show_query_error(query)

    def get_title(self, row_id, table):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT title FROM {} WHERE id = :row_id'.format(table))
        query.bindValue(':row_id', row_id)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        return query.value('title')

    def update_level(self, level_id, title):
        query = qts.QSqlQuery(self.db)
        query.prepare('UPDATE levels set title = :title WHERE id = :id')
        query.bindValue(':title', title)
        query.bindValue(':id', level_id)
        if not query.exec_():
            if self.show_query_error(query):
                return self.get_title(level_id, 'levels')

    def update_factor(self, factor_id, column, value):
        query = qts.QSqlQuery(self.db)
        query.prepare('UPDATE factors set {} = :value WHERE id = :id'.format(column))
        query.bindValue(':value', value)
        query.bindValue(':id', factor_id)
        if not query.exec_():
            if self.show_query_error(query):
                return self.get_title(factor_id, 'factors')

    def update_treatment(self, treatment_id, column, value):
        query = qts.QSqlQuery(self.db)
        query.prepare('UPDATE treatments set {} = :value WHERE id = :id'.format(column))
        query.bindValue(':value', value)
        query.bindValue(':id', treatment_id)
        if not query.exec_():
            if self.show_query_error(query):
                return self.get_title(treatment_id, 'treatments')
        else:
            print(query.result())


    def update_trial(self, trial_id, column, value):
        query = qts.QSqlQuery(self.db)
        query.prepare('UPDATE trials SET {} = :value, modified = CURRENT_TIMESTAMP WHERE id = :id'.format(column))
        query.bindValue(':value', value)
        query.bindValue(':id', trial_id)
        if not query.exec_():
            if self.show_query_error(query):
                return self.get_title(trial_id, 'trials')

    def load_levels(self, factor_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT * FROM levels WHERE factor_id = :factor_id ORDER BY id')
        query.bindValue(':factor_id', factor_id)
        if not query.exec_():
            self.show_query_error(query)
        return query

    def load_treatments(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT * FROM treatments WHERE trial_id = :trial_id ORDER BY id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        return query

    def load_factors(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT * FROM factors WHERE trial_id = :trial_id ORDER BY id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        return query

    def get_levels(self, factor_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT title FROM levels WHERE factor_id = :factor_id ORDER BY id')
        query.bindValue(':factor_id', factor_id)
        if not query.exec_():
            qtw.QMessageBox.critical(
                self.parent(),
                self.tr('DB read Error'),
                query.lastError().text()
            )
            return
        levels = []
        while query.next():
            levels.append(query.value('title'))
        return ', '.join(levels)

    def get_used_identifiers(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT identifier_value FROM subjects WHERE trial_id = :trial_id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        ret = []
        while query.next():
            ret.append(query.value('identifier_value'))
        return ret

    def get_discarded_identifiers(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT identifier FROM discarded_identifiers WHERE trial_id = :trial_id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        ret = []
        while query.next():
            ret.append(query.value('identifier'))
        return ret

    def get_count_discarded_identifiers(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT COUNT(*) FROM discarded_identifiers WHERE trial_id = :trial_id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        return query.value(0)

    def get_num_treatments(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT COUNT(*) FROM treatments WHERE trial_id = :trial_id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        return query.value(0)

    def get_factor_level_indices(self, factor_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT COUNT(*) FROM levels WHERE factor_id = :factor_id')
        query.bindValue(':factor_id', factor_id)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        return list(range(query.value(0)))

    def get_factor_weights(self, trial_id):
        factors = self.read_factors(trial_id)
        if not factors:
            return
        return [factor[2] for factor in factors]

    def get_factors_level_indices(self, trial_id):
        factors = self.read_factors(trial_id)
        if not factors:
            return
        fli = []
        for factor in factors:
            fli.append(self.get_factor_level_indices(factor[0]))
        return fli

    def get_allocation_ratios(self, trial_id):
        treatments = self.read_treatments(trial_id)
        if not treatments:
            return False
        return [treatment[2] for treatment in treatments]

    def get_treatments_id_dict(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT id from treatments WHERE trial_id = :trial_id ORDER BY id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        rows = []
        while query.next():
            rows.append(query.value('id'))
        return {row: i for i, row in enumerate(rows)}

    def get_allocations(self, trial_id):
        treatment_dict = self.get_treatments_id_dict(trial_id)
        allocations = []
        subjects = self.get_subjects(trial_id)
        for subject in subjects:
            allocation = {'allocation': treatment_dict[subject[1]], 'levels': [], 'UI': subject[2]}
            levels = self.get_subject_levels(subject[0])
            for level in levels:
                level_dict = self.get_factor_level_dict(level[0])
                allocation['levels'].append(level_dict[level[1]])
            allocations.append(allocation)
        return allocations

    def get_min_allocation_ratio_group_index(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT id FROM treatments WHERE trial_id = :trial_id AND ratio = (SELECT min(ratio) FROM treatments);')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        result = query.value('id')
        treatment_dict = self.get_treatments_id_dict(trial_id)
        return treatment_dict[result]

    def get_factor_level_dict(self, factor_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT id FROM levels WHERE factor_id = :factor_id ORDER BY id')
        query.bindValue(':factor_id', factor_id)
        if not query.exec_():
            self.show_query_error(query)
        rows = []
        while query.next():
            rows.append(query.value('id'))
        return {row: i for i, row in enumerate(rows)}

    def update_trial_setting(self, trial_id, ui):
        title = ui.trialTitleLineEdit.text()
        if len(title.strip()) == 0:
            return
        code = ui.trialCodeLineEdit.text()
        prob_method = ui.trialProbMethodComboBox.currentIndex()
        base_prob = ui.trialBaseProbabilitySlider.value() / 100.0
        dist_method = ui.trialDistanceMethodComboBox.currentIndex()
        identifier_type = ui.trialIdentifierTypeComboBox.currentIndex()
        identifier_order = ui.trialIentifierOrderComboBox.currentIndex()
        identifier_length = ui.trialIdentifierLengthSpinBox.value()
        recycle_ids = 1 if ui.trialRecycleIdsCheckBox.isChecked() else 0
        new_subject_random = 1 if ui.trialNewSubjectRandomCheckBox.isChecked() else 0
        arms_weight = ui.trialArmsWeightDoubleSlider.value()
        query = qts.QSqlQuery(self.db)
        query.prepare('UPDATE trials set '
                      'title = :title, '
                      'code = :code, '
                      'modified = CURRENT_TIMESTAMP, '
                      'prob_method = :prob_method, '
                      'base_prob = :base_prob, '
                      'dist_method = :dist_method, '
                      'identifier_type = :identifier_type, '
                      'identifier_order = :identifier_order, '
                      'identifier_length = :identifier_length, '
                      'recycle_ids = :recycle_ids, '
                      'new_subject_random = :new_subject_random, '
                      'arms_weight = :arms_weight '
                      'WHERE id = :id'
                      )
        query.bindValue(':title', title)
        query.bindValue(':code', code)
        query.bindValue(':prob_method', prob_method)
        query.bindValue(':base_prob', base_prob)
        query.bindValue(':dist_method', dist_method)
        query.bindValue(':identifier_type', identifier_type)
        query.bindValue(':identifier_order', identifier_order)
        query.bindValue(':identifier_length', identifier_length)
        query.bindValue(':recycle_ids', recycle_ids)
        query.bindValue(':new_subject_random', new_subject_random)
        query.bindValue(':arms_weight', arms_weight)
        query.bindValue(':id', trial_id)
        if not query.exec_():
            self.show_query_error(query)

    def get_trial_setting(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT * FROM trials WHERE id = :id')
        query.bindValue(':id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        if not query.next():
            return False
        return query

    def clear_trial_setting(self, ui):
        ui.trialCreatedValueLabel.setText('')
        ui.trialModifiedValueLabel.setText('')
        ui.trialTitleLineEdit.setText('')
        ui.trialCodeLineEdit.setText('')
        ui.trialProbMethodComboBox.setCurrentIndex(0)
        ui.trialBaseProbabilitySlider.setValue(0.7 * 100)
        ui.trialDistanceMethodComboBox.setCurrentIndex(0)
        ui.trialIdentifierTypeComboBox.setCurrentIndex(0)
        ui.trialIentifierOrderComboBox.setCurrentIndex(0)
        ui.trialIdentifierLengthSpinBox.setValue(3)
        ui.trialRecycleIdsCheckBox.setChecked(False)
        ui.trialNewSubjectRandomCheckBox.setChecked(False)
        ui.trialArmsWeightDoubleSlider.setValue(1.0)

    def map_trial_setting(self, query, ui):
        ui.trialCreatedValueLabel.setText(query.value('created'))
        ui.trialModifiedValueLabel.setText(query.value('modified'))
        ui.trialTitleLineEdit.setText(query.value('title'))
        ui.trialCodeLineEdit.setText(query.value('code'))
        ui.trialProbMethodComboBox.setCurrentIndex(query.value('prob_method'))
        ui.trialBaseProbabilitySlider.setValue(query.value('base_prob') * 100)
        ui.trialDistanceMethodComboBox.setCurrentIndex(query.value('dist_method'))
        ui.trialIdentifierTypeComboBox.setCurrentIndex(query.value('identifier_type'))
        ui.trialIentifierOrderComboBox.setCurrentIndex(query.value('identifier_order'))
        ui.trialIdentifierLengthSpinBox.setValue(query.value('identifier_length'))
        ui.trialRecycleIdsCheckBox.setChecked(query.value('recycle_ids') != 0)
        ui.trialNewSubjectRandomCheckBox.setChecked(query.value('new_subject_random') != 0)
        ui.trialArmsWeightDoubleSlider.setValue(query.value('arms_weight'))

    def load_trial_setting(self, trial_id, ui):
        self.clear_trial_setting(ui)
        query = self.get_trial_setting(trial_id)
        if not query:
            return
        self.map_trial_setting(query, ui)

    def read_factors(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT id, title, weight FROM factors WHERE trial_id = :trial_id ORDER BY id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        factors = []
        while query.next():
            factor = [query.value('id'), query.value('title'), query.value('weight')]
            factors.append(factor)
        return factors

    def read_treatments(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT id, title, ratio FROM treatments WHERE trial_id = :trial_id ORDER BY id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        treatments = []
        while query.next():
            treatment = [query.value('id'), query.value('title'), query.value('ratio')]
            treatments.append(treatment)
        return treatments

    def get_factor_level(self, trial_id):
        factors = self.read_factors(trial_id)
        if not factors:
            return
        fl = []
        for factor in factors:
            query = qts.QSqlQuery(self.db)
            query.prepare('SELECT id, title FROM levels WHERE factor_id = :factor_id ORDER BY id')
            query.bindValue(':factor_id', factor[0])
            query.exec_()
            levels = []
            while query.next():
                levels.append([query.value('id'), query.value('title')])
            fl.append(levels)
        return fl

    def factor_levels(self, factor_id):
        query = qts.QSqlQuery(self.db)
        query.prepare("SELECT id, title FROM levels WHERE factor_id = :factor_id ORDER BY id")
        query.bindValue(':factor_id', factor_id)
        query.exec_()
        levels = []
        while query.next():
            levels.append([query.value('id'), query.value('title')])
        return levels

    def insert_subject(self, identifier_value, treatment_id, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('''INSERT INTO subjects (trial_id, identifier_value, treatment_id) 
        VALUES (:trial_id, :identifier_value, :treatment_id)''')
        query.bindValue(':trial_id', trial_id)
        query.bindValue(':identifier_value', identifier_value)
        query.bindValue(':treatment_id', treatment_id)
        if not query.exec_():
            self.show_query_error(query)
        return query.lastInsertId()

    def insert_subject_levels(self, trial_id, subject_id, subject_levels):
        query = qts.QSqlQuery(self.db)
        for factor_id, level_id in subject_levels:
            query.prepare('''INSERT INTO subject_levels (trial_id, subject_id, factor_id, level_id)
        VALUES (:trial_id, :subject_id, :factor_id, :level_id);''')
            query.bindValue(':trial_id', trial_id)
            query.bindValue(':subject_id', subject_id)
            query.bindValue(':factor_id', factor_id)
            query.bindValue(':level_id', level_id)
            if not query.exec_():
                self.show_query_error(query)
        return self.get_subject_level_titles(subject_levels)

    def clear_preload(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('DELETE FROM preloads WHERE trial_id = :trial_id')
        query.bindValue(':trial_id', trial_id)
        query.exec_()

    def delete_subject_levels(self, subject_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('DELETE FROM subject_levels WHERE subject_id = :subject_id')
        query.bindValue(':subject_id', subject_id)
        if not query.exec_():
            self.show_query_error(query)

    def delete_subject(self, subject_id, id_value=None, trial_id=None):
        query = qts.QSqlQuery(self.db)
        query.prepare('DELETE FROM subjects WHERE id = :subject_id;')
        query.bindValue(':subject_id', subject_id)
        if not query.exec_():
            self.show_query_error(query)
        if id_value is None:
            return
        query.prepare('''INSERT INTO discarded_identifiers (trial_id, identifier)
        VALUES (:trial_id, :identifier)''')
        query.bindValue(':trial_id', trial_id)
        query.bindValue(':identifier', id_value)
        if not query.exec_():
            self.show_query_error(query)

    def delete_subjects(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('DELETE FROM subjects WHERE trial_id = :trial_id;')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        query.prepare('DELETE FROM discarded_identifiers WHERE trial_id = :trial_id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)

    def save_preload(self, trial_id, preload):
        query = qts.QSqlQuery(self.db)
        for key in preload:
            t, f, lv = key
            c = preload[key]
            query.prepare('''INSERT INTO preloads (trial_id, treatment_id, factor_id, level_id, count) 
                        VALUES (:trial_id, :treatment_id, :factor_id, :level_id, :count)''')
            query.bindValue(':trial_id', trial_id)
            query.bindValue(':treatment_id', t)
            query.bindValue(':factor_id', f)
            query.bindValue(':level_id', lv)
            query.bindValue(':count', c)
            query.exec_()

    def show_error(self, title, text):
        qtw.QMessageBox.critical(self.mainWindow, title, text)

    def show_query_error(self, query):
        qtw.QMessageBox.critical(
            self.mainWindow,
            self.tr('DB Error'),
            query.lastError().text()
        )
        fatal = 'UNIQUE constraint failed' not in query.lastError().text()
        if fatal:
            sys.exit(1)
            return False
        else:
            return True

    def has_preload(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT COUNT(*) FROM preloads WHERE trial_id = :trial_id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        if not query.next():
            return False
        return query.value(0) > 0

    def get_subject_count(self, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT COUNT(*) FROM subjects WHERE trial_id = :trial_id')
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        if not query.next():
            return False
        return query.value(0)

    def has_subject(self, trial_id):
        subject_count = self.get_subject_count(trial_id)
        if not subject_count:
            return False
        return subject_count > 0

    def get_first_trial_setting(self):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT min(id) FROM trials')
        if not query.exec_():
            self.show_query_error(query)
        if not query.next():
            return False
        trial_id = query.value(0)
        return self.get_trial_setting(trial_id)

    def trial_title_exists(self, title):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT COUNT(*) FROM trials WHERE title = :title')
        query.bindValue(':title', title)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        return query.value(0) > 0

    def treatment_title_exists(self, title, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT COUNT(*) FROM treatments WHERE title = :title and trial_id = :trial_id')
        query.bindValue(':title', title)
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        return query.value(0) > 0

    def factor_title_exists(self, title, trial_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT COUNT(*) FROM factors WHERE title = :title and trial_id = :trial_id')
        query.bindValue(':title', title)
        query.bindValue(':trial_id', trial_id)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        return query.value(0) > 0

    def level_title_exists(self, title, factor_id):
        query = qts.QSqlQuery(self.db)
        query.prepare('SELECT COUNT(*) FROM levels WHERE title = :title and factor_id = :factor_id')
        query.bindValue(':title', title)
        query.bindValue(':factor_id', factor_id)
        if not query.exec_():
            self.show_query_error(query)
        query.next()
        return query.value(0) > 0