# -*- coding: utf-8 -*-
####################################################################
#
# © 2019-Today Somko Consulting (<https://www.somko.be>)
#
# License OPL-1 or later (https://www.odoo.com/documentation/user/11.0/legal/licenses/licenses.html)
#
####################################################################
import re

from odoo import models, fields, api, _
from odoo.exceptions import ValidationError

class OocMessageModel(models.Model):
    _name = 'ooc.message.model'
    _description = "Odoo Outlook Connector Message Model"

    name = fields.Char(string="Display Name", required=True, translate=True, help="This is the name of the model that the user will see in Outlook")
    model_id = fields.Many2one('ir.model', string='Model', required=True, ondelete='cascade', help="The Odoo model to which you want to be able to attach emails")
    model = fields.Char(related='model_id.model', store=True, readonly=False)

    auto_select = fields.Boolean(string="Auto Select Item", help="Automatically select a match in the Outlook Add-In, based on a domain filter", default=False)
    filter_regex = fields.Char(string="Filter Regex", help="For example: (SL.?\d{4}(.?\d{3})?)")
    filter_domain = fields.Char(string="Filter Domain", help="For example: [['name', 'ilike', match[0].replace(' ', '-')]]")

    auto_filter = fields.Boolean(string="Auto Filter Item", help="Automatically proposes matches in the Outlook Add-In, based on a domain filter. If no matches are found, then outlook will propose all records", default=False)
    select_domain = fields.Char(string="Select Domain", help="For example: ['|', '|', '|', ['partner_id.email', '=',  sender_email], ['email_from','=', sender_email], ['partner_id.name', '=',  sender_name], ['contact_name', '=', sender_name]]")

    default = fields.Boolean(string="Selected by Default", help="This model is the default selected object in the Outlook Add-In", default=False)

    # ------------
    #  Constraint
    # ------------
    @api.constrains('default')
    def constraint_default(self):
        if self.default and self.search_count([('default', '=', True)]) > 1:
            raise ValidationError(_('Can only set one model as default!'))

    # ---------
    #  Helpers
    # ---------
    @api.model
    def get_allowed_models(self):
        sr_models = self.search_read()
        allowed_models = []

        for sr_m in sr_models:
            try:
                self.env[sr_m['model']].check_access_rights('read')

                allowed_models.append({'name': sr_m['name'], 'model': sr_m['model'], 'default': sr_m['default']})
            except:
                pass

        return allowed_models

    @api.model
    def get_allowed_items(self, params):
        fields = ['id', 'display_name', 'name']
        is_project = False
        domain = [('name', 'ilike', params['search'])]
        message_obj = self.search([('model', '=', params['model'])], limit=1)

        if params['model'] == 'project.project':
            fields.append('privacy_visibility')
            fields.append('message_is_follower')
            is_project = True

        if len(self.env['ir.model.fields'].search([('model', '=', params['model']), ('name', '=', 'display_name'), ('store', '=', True)])):
            domain = [('display_name', 'ilike', params['search'])]

        self.search([('model', '=', params['model'])], limit=1)

        sr_items = []

        # Filter based on user email, etc.
        if message_obj.auto_filter:
            sender_domain = eval(message_obj.select_domain, {
                'sender_name': params['send_name'],
                'sender_email': params['send_mail'],
            }) + domain
            sr_items = self.env[params['model']].search_read(sender_domain , fields, limit=10)

        # Fallback to default search if no matches have been found
        if not sr_items:
            sr_items = self.env[params['model']].search_read(domain, fields, limit=10)

        # Add autoselect item
        auto_select = self.get_auto_select_item(params)
        sr_items += self.env[params['model']].search_read([('id', '=', auto_select.id)], fields)

        allowed_items = []

        for sr_i in sr_items:
            if is_project:
                # if sr_i['privacy_visibility'] != 'employees':
                if not sr_i['message_is_follower']:
                    continue

                del sr_i['privacy_visibility']
                del sr_i['message_is_follower']

            if sr_i['id'] == auto_select.id:
                sr_i['default'] = True

            allowed_items.append(sr_i)

        return allowed_items

    @api.model
    def get_auto_select_item(self, params):
        message_obj = self.search([('model', '=', params['model'])], limit=1)
        model_obj = self.env[params['model']]

        if message_obj.auto_select and 'title' in params and params['title']:
            matches = re.findall(message_obj.filter_regex, params['title'])

            if matches:
                # Match gets used in eval, maybe make this save
                match = matches[0]
                results = model_obj.search(eval(message_obj.filter_domain))

                if len(results) == 1:
                    return results

        return model_obj


import operator
from abc import abstractmethod, ABC
from typing import Optional, Callable

import tensorflow as tf
from tensorflow.keras import activations
from tensorflow.keras.layers import BatchNormalization, Layer
from tensorflow.keras.regularizers import Regularizer

from ..layers import op_dim_selector

class Identity(Layer):
    def __init__(self, name='identity', **kwargs):
        '''
        Simply adds an identity connection.
        '''
        super().__init__(name=name, **kwargs)

    def call(self, inputs, training=None, mask=None):
        return tf.identity(inputs)

# TODO: deprecated in previous versions, even if it should work fine for 2D. Right now it needs a refactor to adapt to different input dims.
# class IdentityReshaper(Layer):
#     def __init__(self, filters, input_filters: int, strides: 'tuple[int, ...]', name='identity_reshaper', **kwargs):
#         '''
#         Identity alternative when the tensor shape between input and output differs.
#         IdentityReshaper can apply a stride without doing any operation,
#         also adapting depth by replicating multiple times the tensor and concatenating the replicas on depth axis.
#         '''
#         super().__init__(name=name, **kwargs)
#         self.filters = filters
#         self.input_filters = input_filters
#         self.strides = strides
#         self.no_stride = self.strides[0] == 1 and self.strides[1] == 1
#
#         self.replication_factor = math.ceil(filters / input_filters)
#
#     def call(self, inputs, training=None, mask=None):
#         # TODO: it seems that ::1 slice is bugged, if stride is used when not needed the model has random output. TF bug?
#         input_stride = inputs if self.no_stride else inputs[:, ::self.strides[0], ::self.strides[1], :]
#         return tf.tile(input_stride, [1, 1, 1, self.replication_factor])[:, :, :, :self.filters]
#
#     def get_config(self):
#         config = super().get_config()
#         config.update({
#             'filters': self.filters,
#             'input_filters': self.input_filters,
#             'strides': self.strides
#         })
#         return config

class ConvBatchActivation(Layer, ABC):
    ''' Abstract utility class used as baseline for any {Convolution operator - Batch Normalization - Activation} layer. '''
    def __init__(self, filters: int, kernel: 'tuple[int, ...]', strides: 'tuple[int, ...]', dilation_rate: int = 1,
                 weight_reg: Optional[Regularizer] = None, activation_f: Callable = activations.swish, name='abstract', **kwargs):
        super().__init__(name=name, **kwargs)
        self.filters = filters
        self.kernel = kernel
        self.strides = strides
        self.dilation_rate = dilation_rate
        self.weight_reg = weight_reg
        self.activation_f = activation_f

        if any(map(lambda x: x > 1, self.strides)) and dilation_rate > 1:
            # TensorFlow does not support using strides and dilation at same time. Strides are set to 1 to avoid problems.
            # Pass the layer to DilatedConvBatchActivationPooling class to adapt spatial resolution in reduction cells.
            self.strides = tuple([1] * len(self.strides))

        self.bn = BatchNormalization()

        # concrete implementations must use a valid convolutional Keras layer / TF operation
        self.op = self._build_convolutional_layer()

    @abstractmethod
    def _build_convolutional_layer(self) -> Layer:
        ''' Build the convolutional operator. '''
        raise NotImplementedError()

    def call(self, inputs, training=None, mask=None):
        x = self.op(inputs)
        x = self.bn(x, training=training)
        return self.activation_f(x)

    def get_config(self):
        config = super().get_config()
        config.update({
            'filters': self.filters,
            'kernel': self.kernel,
            'strides': self.strides,
            'dilation_rate': self.dilation_rate,
            'weight_reg': self.weight_reg,
            'activation_f': self.activation_f
        })
        return config

class DilatedConvBatchActivationPooling(Layer):
    def __init__(self, conv_batch_act_layer: ConvBatchActivation, **kwargs):
        '''
        Utility class to adapt dilated convolution layers to spatial resolution changes (like in reduction cell), since TensorFlow doesn't
        support the usage of both striding and dilation in the same operator (to avoid discarding values completely).
        Takes as parameter a ConvBatchActivation layer with dilation > 1 and stride 1, then adapts the spatial dim with a max pooling.
        '''
        super().__init__(name=f'{conv_batch_act_layer.name}_pooled', **kwargs)
        self.conv_batch_act_layer = conv_batch_act_layer

        # build size and stride of pooling operator
        pool_size = tuple([2] * len(self.conv_batch_act_layer.kernel))
        self.pool = Pooling('max', size=pool_size, strides=pool_size)

    def call(self, inputs, training=None, mask=None):
        x = self.conv_batch_act_layer(inputs)
        return self.pool(x, training=training)

    def get_config(self):
        config = super().get_config()
        config.update({
            'conv_batch_act_layer': self.conv_batch_act_layer,
        })
        return config

class SeparableConvolution(ConvBatchActivation):
    ''' Constructs a {Separable Convolution - Batch Normalization - Activation} layer. '''
    def _build_convolutional_layer(self) -> Layer:
        dconv = op_dim_selector['dconv'][len(self.kernel)]
        return dconv(self.filters, self.kernel, strides=self.strides, dilation_rate=self.dilation_rate, padding='same',
                     depthwise_initializer='he_uniform', pointwise_initializer='he_uniform',
                     depthwise_regularizer=self.weight_reg, pointwise_regularizer=self.weight_reg)

class Convolution(ConvBatchActivation):
    ''' Constructs a {Spatial Convolution - Batch Normalization - Activation} layer. '''
    def _build_convolutional_layer(self) -> Layer:
        conv = op_dim_selector['conv'][len(self.kernel)]
        return conv(self.filters, self.kernel, strides=self.strides, dilation_rate=self.dilation_rate, padding='same',
                    kernel_initializer='he_uniform', kernel_regularizer=self.weight_reg)

class TransposeConvolution(ConvBatchActivation):
    ''' Constructs a {Transpose Convolution - Batch Normalization - Activation} layer. '''
    def _build_convolutional_layer(self) -> Layer:
        tconv = op_dim_selector['tconv'][len(self.kernel)]
        return tconv(self.filters, self.kernel, self.strides, dilation_rate=self.dilation_rate, padding='same',
                     kernel_initializer='he_uniform', kernel_regularizer=self.weight_reg)

class TransposeConvolutionStack(Layer):
    def __init__(self, filters: int, kernel: 'tuple[int, ...]', strides: 'tuple[int, ...]',
                 weight_reg: Optional[Regularizer] = None, activation_f: Callable = activations.swish, name='tconv', **kwargs):
        '''
        Constructs a Transpose Convolution - Convolution layer. Batch Normalization and Relu are applied on both.
        '''
        super().__init__(name=name, **kwargs)
        self.filters = filters
        self.kernel = kernel
        self.strides = strides
        self.weight_reg = weight_reg
        self.activation_f = activation_f

        transpose_stride = tuple([2] * len(strides))
        conv_strides = tuple(map(operator.mul, transpose_stride, strides))

        self.transposeConv = TransposeConvolution(filters, kernel, transpose_stride, 1, weight_reg, activation_f=activation_f)
        self.conv = Convolution(filters, kernel, conv_strides, 1, weight_reg, activation_f=activation_f)

    def call(self, inputs, training=None, mask=None):
        x = self.transposeConv(inputs)
        return self.conv(x)

    def get_config(self):
        config = super().get_config()
        config.update({
            'filters': self.filters,
            'kernel': self.kernel,
            'strides': self.strides,
            'weight_reg': self.weight_reg,
            'activation_f': self.activation_f
        })
        return config

class StackedConvolution(Layer):
    def __init__(self, filter_list: 'list[int]', kernel_list: 'list[tuple]', stride_list: 'list[tuple]', weight_reg: Optional[Regularizer] = None,
                 activation_f: Callable = activations.swish, name='stack_conv', **kwargs):
        '''
        Constructs a stack of Convolution blocks that are chained together.
        '''
        super().__init__(name=name, **kwargs)
        self.filter_list = filter_list
        self.kernel_list = kernel_list
        self.stride_list = stride_list
        self.weight_reg = weight_reg
        self.activation_f = activation_f

        assert len(filter_list) == len(kernel_list) and len(kernel_list) == len(stride_list), "List lengths must match"

        self.convs = [Convolution(f, k, s, weight_reg=weight_reg, activation_f=activation_f)
                      for (f, k, s) in zip(filter_list, kernel_list, stride_list)]

    def call(self, inputs, training=None, mask=None):
        x = inputs

        for conv in self.convs:
            x = conv(x, training=training)

        return x

    def get_config(self):
        config = super().get_config()
        config.update({
            'filter_list': self.filter_list,
            'kernel_list': self.kernel_list,
            'stride_list': self.stride_list,
            'weight_reg': self.weight_reg,
            'activation_f': self.activation_f
        })
        return config

class Pooling(Layer):
    def __init__(self, pool_type: str, size: 'tuple[int, ...]', strides: 'tuple[int, ...]', name='pool', **kwargs):
        '''
        Constructs a standard pooling layer (average or max).
        '''
        super().__init__(name=name, **kwargs)
        self.pool_type = pool_type
        self.size = size
        self.strides = strides

        pool_layer = op_dim_selector[f'{pool_type}_pool'][len(size)]
        self.pool = pool_layer(size, strides, padding='same')

    def call(self, inputs, training=None, mask=None):
        return self.pool(inputs)

    def get_config(self):
        config = super().get_config()
        config.update({
            'pool_type': self.pool_type,
            'size': self.size,
            'strides': self.strides
        })
        return config

class PoolingConv(Layer):
    def __init__(self, pool_layer: Pooling, filters: int, weight_reg: Optional[Regularizer] = None,
                 activation_f: Callable = activations.swish, **kwargs):
        ''' Adds a pointwise convolution (using Convolution class) to a Pooling layer, to adapt the output depth to filters size. '''
        super().__init__(name=f'{pool_layer.name}_pw', **kwargs)
        self.pool_layer = pool_layer
        self.filters = filters
        self.weight_reg = weight_reg
        self.activation_f = activation_f

        ones = tuple([1] * len(self.pool_layer.size))
        self.pointwise_conv = Convolution(filters, kernel=ones, strides=ones, weight_reg=weight_reg, activation_f=activation_f)

    def call(self, inputs, training=None, mask=None):
        x = self.pool_layer(inputs)
        return self.pointwise_conv(x)

    def get_config(self):
        config = super().get_config()
        config.update({
            'pool_layer': self.pool_layer,
            'filters': self.filters,
            'weight_reg': self.weight_reg,
            'activation_f': self.activation_f
        })
        return config


import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.ensemble import RandomForestRegressor
from sklearn.svm import SVR
from preprocess import load, preprocess
from config import config_rf, config_svr, config_xgb
from xgboost import XGBRegressor
from save import save_model
import joblib

def train_rf(name, X_train, y_train):
    #случайный лес
    model = RandomForestRegressor(**config_rf(name), n_jobs=-1)
    model.fit(X_train, y_train)
    return model

def train_lr(X_train, y_train):
    #линейная регрессия
    model = LinearRegression()
    model.fit(X_train, y_train)
    return model

def train_polynom(X_train, y_train):
    #полином 2 степени
    pol = PolynomialFeatures(degree=2)
    X_train_pol = pol.fit_transform(X_train)
    model = LinearRegression()
    model.fit(X_train_pol, y_train)
    return model

def train_svr(name, X_train, y_train):
    #support vector regressor
    y_train = y_train * 1000
    model = SVR(**config_svr(name))
    model.fit(X_train, y_train)
    return model

def train_xgboost(name, X_train, y_train):
    model = XGBRegressor(**config_xgb(name))
    model.fit(X_train, y_train)
    return model

def train_test_split(name, df):
    crypto = df[df["Name"] == name]
    crypto = crypto.drop(["timestamp", "Name"], axis=1)
    X = crypto.drop("Target", axis=1)
    y = crypto["Target"]
    X_train, y_train = X[:int(X.shape[0] * 0.8)], y[:int(X.shape[0] * 0.8)]
    X_valid, y_valid = X[int(X.shape[0] * 0.8):], y[int(X.shape[0] * 0.8):]
    return X_train, X_valid, y_train, y_valid

def main():
    #проверка, что все работает
    df, asset_details = load("train.csv", "asset_details.csv")
    df_without_null_scaled = preprocess(df, asset_details)
    X_train, X_valid, y_train, y_valid = train_test_split("Bitcoin", df_without_null_scaled)
    crypto = train_rf("Bitcoin", X_train, y_train)
    save_model("Bitcoin", crypto)
    print(joblib.load("saved_models/Bitcoin_rf").predict(X_valid))

if __name__ == "__main__":
    main()


from flask import Flask, request, Blueprint
from library.ap.controller import applicant
from extension import db, ma
from model import Applicant, results
import os

def create_db(app):
    if not os.path.exists("appli/appli.db"):
        db.create_all(app=app)
        print("Tao DB!")

def create_app(config_file="config.py"):
    app = Flask(__name__)
    db.init_app(app)
    ma.init_app(app)
    app.config.from_pyfile(config_file)
    create_db(app)
    app.register_blueprint(applicant)
    #app.register_blueprint(borrow)
    return app


"""This module contains utilities to handle atlases in Clinica.

An atlas is currently defined by its name and a set of labels in a template space.

This current implementation has some drawbacks:
- Atlas is misleading: it is only a set of labels in a template space
- This implementation can not handle case where parcellation is in several template space
(e.g. `MNI152NLin2009cAsym` and `MNI152NLin6Asym`, see TemplateFlow for example)

Either a refactoring of this module or the use of an external API
(e.g. TemplateFlow - https://www.templateflow.org/) needs to be considered.
"""

import abc

T1_VOLUME_ATLASES = [
    "AAL2",
    "AICHA",
    "Hammers",
    "LPBA40",
    "Neuromorphometrics",
]

PET_VOLUME_ATLASES = [
    "AAL2",
    "AICHA",
    "Hammers",
    "LPBA40",
    "Neuromorphometrics",
]

DWI_DTI_ATLASES = [
    "JHUDTI81",
    "JHUTract0",
    "JHUTract25",
]

VOLUME_ATLASES = list(set(T1_VOLUME_ATLASES + PET_VOLUME_ATLASES + DWI_DTI_ATLASES))

class AtlasAbstract:
    """Abstract class for Atlas handling.

    Naming convention for children classes of AtlasAbstract:
    <name_atlas>[<resolution>][<map>]
    """

    __metaclass__ = abc.ABCMeta

    @staticmethod
    @abc.abstractmethod
    def get_name_atlas():
        """Return the name of the atlas (as defined in BIDS/CAPS specifications)."""

    def get_spatial_resolution(self):
        """Return the spatial resolution of the atlas (in format "XxXxX" e.g. 1.5x1.5x1.5)."""
        import nibabel as nib

        img_labels = nib.load(self.get_atlas_labels())
        voxels_labels = img_labels.header.get_zooms()
        # Will display integers without decimals
        if int(voxels_labels[0]) == voxels_labels[0]:
            s_x = str(int(voxels_labels[0]))
        else:
            s_x = str(voxels_labels[0])
        if int(voxels_labels[1]) == voxels_labels[1]:
            s_y = str(int(voxels_labels[1]))
        else:
            s_y = str(voxels_labels[1])
        if int(voxels_labels[2]) == voxels_labels[2]:
            s_z = str(int(voxels_labels[2]))
        else:
            s_z = str(voxels_labels[2])

        return f"{s_x}x{s_y}x{s_z}"

    @staticmethod
    @abc.abstractmethod
    def get_atlas_labels():
        """Return the image with the different labels/ROIs."""

    @staticmethod
    @abc.abstractmethod
    def get_tsv_roi():
        """Return the TSV file containing the ROI (regions of interest) of the atlas."""

    def get_index(self):
        import nibabel as nib
        import numpy as np

        img_labels = nib.load(self.get_atlas_labels())
        img_labels = img_labels.get_fdata(dtype="float32")
        labels = list(set(img_labels.ravel()))
        index_vector = np.zeros(len(labels))
        for index, n in enumerate(labels):
            index_vector[index] = index
        return index_vector

class JHUDTI811mm(AtlasAbstract):
    def __init__(self):
        AtlasAbstract.__init__(self)

    @staticmethod
    def get_name_atlas():
        return "JHUDTI81"

    @staticmethod
    def get_atlas_labels():
        import os

        import nipype.interfaces.fsl as fsl

        from .check_dependency import check_environment_variable
        from .inputs import _sha256

        fsl_dir = check_environment_variable("FSLDIR", "FSL")
        atlas_labels = os.path.join(
            fsl_dir, "data", "atlases", "JHU", "JHU-ICBM-labels-1mm.nii.gz"
        )

        # Adding checksum for updated file with version 6.0.5 of fsl
        fsl_atlas_checksums = {
            "old": "fac584ec75ff2a8631710d3345df96733ed87d9bde3387f5b462f8d22914ed69",
            "new": "3c3f5d2f1250a3df60982acff35a75b99fd549a05d5f8124a63f78221aa0ec16",
        }

        if ["5", "0", "5"] <= fsl.Info.version().split(".") < ["6", "0", "5"]:
            expected_checksum = fsl_atlas_checksums["old"]
        else:
            expected_checksum = fsl_atlas_checksums["new"]

        if _sha256(atlas_labels) != expected_checksum:
            raise IOError(
                f"{atlas_labels} has an SHA256 checksum ({_sha256(atlas_labels)}) "
                f"differing from expected ({expected_checksum}), "
                f"file may be corrupted and changed with newer version of FSL."
            )
        return atlas_labels

    @staticmethod
    def get_tsv_roi():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-JHUDTI81_dseg.tsv",
        )

class JHUTracts01mm(AtlasAbstract):
    def __init__(self):
        AtlasAbstract.__init__(self)

    @staticmethod
    def get_name_atlas():
        return "JHUTracts0"

    @staticmethod
    def get_atlas_labels():
        import os

        from .check_dependency import check_environment_variable
        from .inputs import _sha256

        fsl_dir = check_environment_variable("FSLDIR", "FSL")
        atlas_labels = os.path.join(
            fsl_dir, "data", "atlases", "JHU", "JHU-ICBM-tracts-maxprob-thr0-1mm.nii.gz"
        )
        expected_checksum = (
            "eb1de9413a46b02d2b5c7b77852097c6f42c8a5d55a5dbdef949c2e63b95354e"
        )
        if _sha256(atlas_labels) != expected_checksum:
            raise IOError(
                f"{atlas_labels} has an SHA256 checksum ({_sha256(atlas_labels)}) "
                f"differing from expected ({expected_checksum}), "
                f"file may be corrupted and changed with newer version of FSL."
            )
        return atlas_labels

    @staticmethod
    def get_tsv_roi():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-JHUTract_dseg.tsv",
        )

class JHUTracts251mm(AtlasAbstract):
    def __init__(self):
        AtlasAbstract.__init__(self)

    @staticmethod
    def get_name_atlas():
        return "JHUTracts25"

    @staticmethod
    def get_atlas_labels():
        import os

        from .check_dependency import check_environment_variable
        from .inputs import _sha256

        fsl_dir = check_environment_variable("FSLDIR", "FSL")
        atlas_labels = os.path.join(
            fsl_dir,
            "data",
            "atlases",
            "JHU",
            "JHU-ICBM-tracts-maxprob-thr25-1mm.nii.gz",
        )
        expected_checksum = (
            "7cd85fa2be1918fc83173e9bc0746031fd4c08d70d6c81b7b9224b5d3da6d8a6"
        )
        if _sha256(atlas_labels) != expected_checksum:
            raise IOError(
                f"{atlas_labels} has an SHA256 checksum ({_sha256(atlas_labels)}) "
                f"differing from expected ({expected_checksum}), "
                f"file may be corrupted and changed with newer version of FSL."
            )
        return atlas_labels

    @staticmethod
    def get_tsv_roi():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-JHUTract_dseg.tsv",
        )

class JHUTracts501mm(AtlasAbstract):
    def __init__(self):
        AtlasAbstract.__init__(self)

    @staticmethod
    def get_name_atlas():
        return "JHUTracts50"

    @staticmethod
    def get_atlas_labels():
        import os

        from .check_dependency import check_environment_variable
        from .inputs import _sha256

        fsl_dir = check_environment_variable("FSLDIR", "FSL")
        atlas_labels = os.path.join(
            fsl_dir,
            "data",
            "atlases",
            "JHU",
            "JHU-ICBM-tracts-maxprob-thr50-1mm.nii.gz",
        )
        expected_checksum = (
            "20ff0216d770686838de26393c0bdac38c8104760631a1a2b5f518bc0bbb470a"
        )
        if _sha256(atlas_labels) != expected_checksum:
            raise IOError(
                f"{atlas_labels} has an SHA256 checksum ({_sha256(atlas_labels)}) "
                f"differing from expected ({expected_checksum}), "
                f"file may be corrupted and changed with newer version of FSL."
            )
        return atlas_labels

    @staticmethod
    def get_tsv_roi():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-JHUTract_dseg.tsv",
        )

class AAL2(AtlasAbstract):
    def __init__(self):
        AtlasAbstract.__init__(self)

    @staticmethod
    def get_name_atlas():
        return "AAL2"

    @staticmethod
    def get_atlas_labels():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-AAL2_dseg.nii.gz",
        )

    @staticmethod
    def get_tsv_roi():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-AAL2_dseg.tsv",
        )

class Hammers(AtlasAbstract):
    def __init__(self):
        AtlasAbstract.__init__(self)

    @staticmethod
    def get_name_atlas():
        return "Hammers"

    @staticmethod
    def get_atlas_labels():
        from clinica.utils.inputs import RemoteFileStructure, get_file_from_server

        hammers_parc = RemoteFileStructure(
            filename="atlas-Hammers_dseg.nii.gz",
            url="https://aramislab.paris.inria.fr/files/software/cat12/CAT12-Atlases/",
            checksum="c034a7bce2dcab390a0b72f4e7d04769eb3fe5b990d0e18d89b0ce73339a5376",
        )
        return get_file_from_server(hammers_parc)

    @staticmethod
    def get_tsv_roi():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-Hammers_dseg.tsv",
        )

class LPBA40(AtlasAbstract):
    def __init__(self):
        AtlasAbstract.__init__(self)

    @staticmethod
    def get_name_atlas():
        return "LPBA40"

    @staticmethod
    def get_atlas_labels():
        from clinica.utils.inputs import RemoteFileStructure, get_file_from_server

        lpba40_parc = RemoteFileStructure(
            filename="atlas-LPBA40_dseg.nii.gz",
            url="https://aramislab.paris.inria.fr/files/software/cat12/CAT12-Atlases/",
            checksum="20826b572bbbdbcdbf28bbd3801dc0c2fed28d1e54bc4fd5027e64ccc6d50374",
        )
        return get_file_from_server(lpba40_parc)

    @staticmethod
    def get_tsv_roi():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-LPBA40_dseg.tsv",
        )

class AICHA(AtlasAbstract):
    def __init__(self):
        AtlasAbstract.__init__(self)

    @staticmethod
    def get_name_atlas():
        return "AICHA"

    @staticmethod
    def get_atlas_labels():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-AICHA_dseg.nii.gz",
        )

    @staticmethod
    def get_tsv_roi():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-AICHA_dseg.tsv",
        )

class Neuromorphometrics(AtlasAbstract):
    def __init__(self):
        AtlasAbstract.__init__(self)

    @staticmethod
    def get_name_atlas():
        return "Neuromorphometrics"

    @staticmethod
    def get_atlas_labels():
        from clinica.utils.inputs import RemoteFileStructure, get_file_from_server

        neuromorphometrics_parc = RemoteFileStructure(
            filename="atlas-Neuromorphometrics_dseg.nii.gz",
            url="https://aramislab.paris.inria.fr/files/software/cat12/CAT12-Atlases/",
            checksum="19a50136cd2f8a14357a19ad8a1dc4a2ecb6beb3fc16cb5441f4f2ebaf64a9a5",
        )
        return get_file_from_server(neuromorphometrics_parc)

    @staticmethod
    def get_tsv_roi():
        from os.path import join, realpath, split

        return join(
            split(realpath(__file__))[0],
            "..",
            "resources",
            "atlases",
            "atlas-Neuromorphometrics_dseg.tsv",
        )


import sys

from PyQt4.QtCore import Qt, QTime, QTimer
from PyQt4.QtGui import QApplication, QLabel, QPixmap

class Principal(QLabel):
    def __init__(self, parent=None):
        QLabel.__init__(self, parent)
        self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter);
        self.setStyleSheet("QLabel {font-size : 200px; color : black; background-image: url('background.bmp');}")
        self.setText(str(self.countdown))

def main():
    app = QApplication(sys.argv)
    widget = Principal()
    widget.show()
    app.exec_()

if __name__ == '__main__':
    main()


from datetime import date, datetime, timedelta
from unittest.mock import Mock, call, patch

from django.contrib.auth.models import Group
from django.core import mail
from django.core.cache import caches
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
from django_webtest import WebTest
from model_bakery import baker

from evap.evaluation.models import (
    Contribution,
    Course,
    CourseType,
    EmailTemplate,
    Evaluation,
    NotArchiveable,
    Question,
    Questionnaire,
    QuestionType,
    RatingAnswerCounter,
    Semester,
    TextAnswer,
    UserProfile,
)
from evap.evaluation.tests.tools import (
    let_user_vote_for_evaluation,
    make_contributor,
    make_editor,
    make_rating_answer_counters,
)
from evap.grades.models import GradeDocument
from evap.results.tools import cache_results, calculate_average_distribution
from evap.results.views import get_evaluation_result_template_fragment_cache_key

class TestSemester(WebTest):
    def test_can_be_deleted_by_manager(self):
        semester = baker.make(Semester)
        self.assertTrue(semester.can_be_deleted_by_manager)

        semester.is_active = True
        self.assertFalse(semester.can_be_deleted_by_manager)
        semester.is_active = False

        voter = baker.make(UserProfile)
        baker.make(Evaluation, course__semester=semester, state=Evaluation.State.PUBLISHED, voters=[voter])
        self.assertFalse(semester.can_be_deleted_by_manager)

        semester.archive()
        self.assertFalse(semester.can_be_deleted_by_manager)

        semester.delete_grade_documents()
        self.assertFalse(semester.can_be_deleted_by_manager)

        semester.archive_results()
        self.assertTrue(semester.can_be_deleted_by_manager)

class TestQuestionnaire(WebTest):
    def test_can_be_deleted_by_manager(self):
        questionnaire = baker.make(Questionnaire)
        self.assertTrue(questionnaire.can_be_deleted_by_manager)

        baker.make(Contribution, questionnaires=[questionnaire])
        self.assertFalse(questionnaire.can_be_deleted_by_manager)

@override_settings(EVALUATION_END_OFFSET_HOURS=0)
class TestEvaluations(WebTest):
    def test_approved_to_in_evaluation(self):
        evaluation = baker.make(Evaluation, state=Evaluation.State.APPROVED, vote_start_datetime=datetime.now())

        with patch("evap.evaluation.models.EmailTemplate") as mock:
            mock.EVALUATION_STARTED = EmailTemplate.EVALUATION_STARTED
            mock.Recipients.ALL_PARTICIPANTS = EmailTemplate.Recipients.ALL_PARTICIPANTS
            mock.objects.get.return_value = mock
            Evaluation.update_evaluations()

        self.assertEqual(mock.objects.get.call_args_list[0][1]["name"], EmailTemplate.EVALUATION_STARTED)
        mock.send_to_users_in_evaluations.assert_called_once_with(
            [evaluation], [EmailTemplate.Recipients.ALL_PARTICIPANTS], use_cc=False, request=None
        )

        evaluation = Evaluation.objects.get(pk=evaluation.pk)
        self.assertEqual(evaluation.state, Evaluation.State.IN_EVALUATION)

    def test_in_evaluation_to_evaluated(self):
        evaluation = baker.make(
            Evaluation,
            state=Evaluation.State.IN_EVALUATION,
            vote_start_datetime=datetime.now() - timedelta(days=2),
            vote_end_date=date.today() - timedelta(days=1),
        )

        with patch("evap.evaluation.models.Evaluation.is_fully_reviewed") as mock:
            mock.__get__ = Mock(return_value=False)
            Evaluation.update_evaluations()

        evaluation = Evaluation.objects.get(pk=evaluation.pk)
        self.assertEqual(evaluation.state, Evaluation.State.EVALUATED)

    def test_in_evaluation_to_reviewed(self):
        # Evaluation is "fully reviewed" as no open text answers are present by default.
        evaluation = baker.make(
            Evaluation,
            state=Evaluation.State.IN_EVALUATION,
            vote_start_datetime=datetime.now() - timedelta(days=2),
            vote_end_date=date.today() - timedelta(days=1),
        )

        Evaluation.update_evaluations()

        evaluation = Evaluation.objects.get(pk=evaluation.pk)
        self.assertEqual(evaluation.state, Evaluation.State.REVIEWED)

    def test_in_evaluation_to_published(self):
        # Evaluation is "fully reviewed" and not graded, thus gets published immediately.
        course = baker.make(Course)
        evaluation = baker.make(
            Evaluation,
            course=course,
            state=Evaluation.State.IN_EVALUATION,
            vote_start_datetime=datetime.now() - timedelta(days=2),
            vote_end_date=date.today() - timedelta(days=1),
            wait_for_grade_upload_before_publishing=False,
        )

        with patch(
            "evap.evaluation.models.EmailTemplate.send_participant_publish_notifications"
        ) as participant_mock, patch(
            "evap.evaluation.models.EmailTemplate.send_contributor_publish_notifications"
        ) as contributor_mock:
            Evaluation.update_evaluations()

        participant_mock.assert_called_once_with([evaluation])
        contributor_mock.assert_called_once_with([evaluation])

        evaluation = Evaluation.objects.get(pk=evaluation.pk)
        self.assertEqual(evaluation.state, Evaluation.State.PUBLISHED)

    @override_settings(EVALUATION_END_WARNING_PERIOD=24)
    def test_ends_soon(self):
        evaluation = baker.make(
            Evaluation,
            vote_start_datetime=datetime.now() - timedelta(days=2),
            vote_end_date=date.today() + timedelta(hours=24),
        )

        self.assertFalse(evaluation.ends_soon)

        evaluation.vote_end_date = date.today()
        self.assertTrue(evaluation.ends_soon)

        evaluation.vote_end_date = date.today() - timedelta(hours=48)
        self.assertFalse(evaluation.ends_soon)

    @override_settings(EVALUATION_END_WARNING_PERIOD=24, EVALUATION_END_OFFSET_HOURS=24)
    def test_ends_soon_with_offset(self):
        evaluation = baker.make(
            Evaluation, vote_start_datetime=datetime.now() - timedelta(days=2), vote_end_date=date.today()
        )

        self.assertFalse(evaluation.ends_soon)

        evaluation.vote_end_date = date.today() - timedelta(hours=24)
        self.assertTrue(evaluation.ends_soon)

        evaluation.vote_end_date = date.today() - timedelta(hours=72)
        self.assertFalse(evaluation.ends_soon)

    def test_evaluation_ended(self):
        # Evaluation is out of evaluation period.
        course_1 = baker.make(Course)
        course_2 = baker.make(Course)
        baker.make(
            Evaluation,
            course=course_1,
            state=Evaluation.State.IN_EVALUATION,
            vote_start_datetime=datetime.now() - timedelta(days=2),
            vote_end_date=date.today() - timedelta(days=1),
            wait_for_grade_upload_before_publishing=False,
        )
        # This evaluation is not.
        baker.make(
            Evaluation,
            course=course_2,
            state=Evaluation.State.IN_EVALUATION,
            vote_start_datetime=datetime.now() - timedelta(days=2),
            vote_end_date=date.today(),
            wait_for_grade_upload_before_publishing=False,
        )

        with patch("evap.evaluation.models.Evaluation.end_evaluation") as mock:
            Evaluation.update_evaluations()

        self.assertEqual(mock.call_count, 1)

    def test_approved_to_in_evaluation_sends_emails(self):
        """Regression test for #945"""
        participant = baker.make(UserProfile, email="foo@example.com")
        evaluation = baker.make(
            Evaluation, state=Evaluation.State.APPROVED, vote_start_datetime=datetime.now(), participants=[participant]
        )

        Evaluation.update_evaluations()

        evaluation = Evaluation.objects.get(pk=evaluation.pk)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(evaluation.state, Evaluation.State.IN_EVALUATION)

    def test_has_enough_questionnaires(self):
        # manually circumvent Evaluation's save() method to have a Evaluation without a general contribution
        # the semester must be specified because of https://github.com/vandersonmota/model_bakery/issues/258
        course = baker.make(Course, semester=baker.make(Semester), type=baker.make(CourseType))
        Evaluation.objects.bulk_create([baker.prepare(Evaluation, course=course)])
        evaluation = Evaluation.objects.get()
        self.assertEqual(evaluation.contributions.count(), 0)
        self.assertFalse(evaluation.general_contribution_has_questionnaires)
        self.assertFalse(evaluation.all_contributions_have_questionnaires)

        editor_contribution = baker.make(
            Contribution,
            evaluation=evaluation,
            contributor=baker.make(UserProfile),
            role=Contribution.Role.EDITOR,
            textanswer_visibility=Contribution.TextAnswerVisibility.GENERAL_TEXTANSWERS,
        )
        evaluation = Evaluation.objects.get()
        self.assertFalse(evaluation.general_contribution_has_questionnaires)
        self.assertFalse(evaluation.all_contributions_have_questionnaires)

        general_contribution = baker.make(Contribution, evaluation=evaluation, contributor=None)
        evaluation = Evaluation.objects.get()
        self.assertFalse(evaluation.general_contribution_has_questionnaires)
        self.assertFalse(evaluation.all_contributions_have_questionnaires)

        questionnaire = baker.make(Questionnaire)
        general_contribution.questionnaires.add(questionnaire)
        self.assertTrue(evaluation.general_contribution_has_questionnaires)
        self.assertFalse(evaluation.all_contributions_have_questionnaires)

        editor_contribution.questionnaires.add(questionnaire)
        self.assertTrue(evaluation.general_contribution_has_questionnaires)
        self.assertTrue(evaluation.all_contributions_have_questionnaires)

    def test_single_result_can_be_deleted_only_in_reviewed(self):
        responsible = baker.make(UserProfile)
        evaluation = baker.make(Evaluation, is_single_result=True)
        contribution = baker.make(
            Contribution,
            evaluation=evaluation,
            contributor=responsible,
            questionnaires=[Questionnaire.single_result_questionnaire()],
            role=Contribution.Role.EDITOR,
            textanswer_visibility=Contribution.TextAnswerVisibility.GENERAL_TEXTANSWERS,
        )
        make_rating_answer_counters(Questionnaire.single_result_questionnaire().questions.first(), contribution)
        evaluation.skip_review_single_result()
        evaluation.publish()
        evaluation.save()

        self.assertTrue(Evaluation.objects.filter(pk=evaluation.pk).exists())
        self.assertFalse(evaluation.can_be_deleted_by_manager)

        evaluation.unpublish()
        self.assertTrue(evaluation.can_be_deleted_by_manager)

        RatingAnswerCounter.objects.filter(contribution__evaluation=evaluation).delete()
        evaluation.delete()
        self.assertFalse(Evaluation.objects.filter(pk=evaluation.pk).exists())

    @staticmethod
    def test_single_result_can_be_published():
        """Regression test for #1238"""
        responsible = baker.make(UserProfile)
        single_result = baker.make(Evaluation, is_single_result=True, _participant_count=5, _voter_count=5)
        contribution = baker.make(
            Contribution,
            evaluation=single_result,
            contributor=responsible,
            questionnaires=[Questionnaire.single_result_questionnaire()],
            role=Contribution.Role.EDITOR,
            textanswer_visibility=Contribution.TextAnswerVisibility.GENERAL_TEXTANSWERS,
        )
        make_rating_answer_counters(Questionnaire.single_result_questionnaire().questions.first(), contribution)

        single_result.skip_review_single_result()
        single_result.publish()  # used to crash

    def test_second_vote_sets_can_publish_text_results_to_true(self):
        student1 = baker.make(UserProfile, email="student1@institution.example.com")
        student2 = baker.make(UserProfile, email="student2@example.com")
        evaluation = baker.make(
            Evaluation, participants=[student1, student2], voters=[student1], state=Evaluation.State.IN_EVALUATION
        )
        evaluation.save()
        top_general_questionnaire = baker.make(Questionnaire, type=Questionnaire.Type.TOP)
        baker.make(Question, questionnaire=top_general_questionnaire, type=QuestionType.POSITIVE_LIKERT)
        evaluation.general_contribution.questionnaires.set([top_general_questionnaire])

        self.assertFalse(evaluation.can_publish_text_results)

        let_user_vote_for_evaluation(student2, evaluation)
        evaluation = Evaluation.objects.get(pk=evaluation.pk)

        self.assertTrue(evaluation.can_publish_text_results)

    def test_textanswers_get_deleted_if_they_cannot_be_published(self):
        student = baker.make(UserProfile)
        evaluation = baker.make(
            Evaluation,
            state=Evaluation.State.REVIEWED,
            participants=[student],
            voters=[student],
            can_publish_text_results=False,
        )
        questionnaire = baker.make(Questionnaire, type=Questionnaire.Type.TOP)
        question = baker.make(Question, type=QuestionType.TEXT, questionnaire=questionnaire)
        evaluation.general_contribution.questionnaires.set([questionnaire])
        baker.make(TextAnswer, question=question, contribution=evaluation.general_contribution)

        self.assertEqual(evaluation.textanswer_set.count(), 1)
        evaluation.publish()
        self.assertEqual(evaluation.textanswer_set.count(), 0)

    def test_textanswers_do_not_get_deleted_if_they_can_be_published(self):
        student = baker.make(UserProfile)
        student2 = baker.make(UserProfile)
        evaluation = baker.make(
            Evaluation,
            state=Evaluation.State.REVIEWED,
            participants=[student, student2],
            voters=[student, student2],
            can_publish_text_results=True,
        )
        questionnaire = baker.make(Questionnaire, type=Questionnaire.Type.TOP)
        question = baker.make(Question, type=QuestionType.TEXT, questionnaire=questionnaire)
        evaluation.general_contribution.questionnaires.set([questionnaire])
        baker.make(TextAnswer, question=question, contribution=evaluation.general_contribution)

        self.assertEqual(evaluation.textanswer_set.count(), 1)
        evaluation.publish()
        self.assertEqual(evaluation.textanswer_set.count(), 1)

    def test_textanswers_to_delete_get_deleted_on_publish(self):
        student = baker.make(UserProfile)
        student2 = baker.make(UserProfile)
        evaluation = baker.make(
            Evaluation,
            state=Evaluation.State.REVIEWED,
            participants=[student, student2],
            voters=[student, student2],
            can_publish_text_results=True,
        )
        questionnaire = baker.make(Questionnaire, type=Questionnaire.Type.TOP)
        question = baker.make(Question, type=QuestionType.TEXT, questionnaire=questionnaire)
        evaluation.general_contribution.questionnaires.set([questionnaire])
        baker.make(
            TextAnswer,
            question=question,
            contribution=evaluation.general_contribution,
            answer=iter(["deleted", "public", "private"]),
            review_decision=iter(
                [
                    TextAnswer.ReviewDecision.DELETED,
                    TextAnswer.ReviewDecision.PUBLIC,
                    TextAnswer.ReviewDecision.PRIVATE,
                ]
            ),
            _quantity=3,
            _bulk_create=True,
        )

        self.assertEqual(evaluation.textanswer_set.count(), 3)
        evaluation.publish()
        self.assertEqual(evaluation.textanswer_set.count(), 2)
        self.assertFalse(TextAnswer.objects.filter(answer="deleted").exists())

    def test_original_textanswers_get_deleted_on_publish(self):
        student = baker.make(UserProfile)
        student2 = baker.make(UserProfile)
        evaluation = baker.make(
            Evaluation,
            state=Evaluation.State.REVIEWED,
            participants=[student, student2],
            voters=[student, student2],
            can_publish_text_results=True,
        )
        questionnaire = baker.make(Questionnaire, type=Questionnaire.Type.TOP)
        question = baker.make(Question, type=QuestionType.TEXT, questionnaire=questionnaire)
        evaluation.general_contribution.questionnaires.set([questionnaire])
        baker.make(
            TextAnswer,
            question=question,
            contribution=evaluation.general_contribution,
            answer="published answer",
            original_answer="original answer",
            review_decision=TextAnswer.ReviewDecision.PUBLIC,
        )

        self.assertEqual(evaluation.textanswer_set.count(), 1)
        self.assertFalse(TextAnswer.objects.get().original_answer is None)
        evaluation.publish()
        self.assertEqual(evaluation.textanswer_set.count(), 1)
        self.assertTrue(TextAnswer.objects.get().original_answer is None)

    def test_publishing_and_unpublishing_effect_on_template_cache(self):
        student = baker.make(UserProfile)
        evaluation = baker.make(
            Evaluation,
            state=Evaluation.State.REVIEWED,
            participants=[student],
            voters=[student],
            can_publish_text_results=True,
        )

        self.assertIsNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "en", True))
        )
        self.assertIsNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "en", False))
        )
        self.assertIsNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "de", True))
        )
        self.assertIsNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "de", False))
        )

        evaluation.publish()
        evaluation.save()

        self.assertIsNotNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "en", True))
        )
        self.assertIsNotNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "en", False))
        )
        self.assertIsNotNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "de", True))
        )
        self.assertIsNotNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "de", False))
        )

        evaluation.unpublish()
        evaluation.save()

        self.assertIsNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "en", True))
        )
        self.assertIsNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "en", False))
        )
        self.assertIsNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "de", True))
        )
        self.assertIsNone(
            caches["results"].get(get_evaluation_result_template_fragment_cache_key(evaluation.id, "de", False))
        )

    def assert_textanswer_review_state(
        self,
        evaluation,
        expected_default_value,
        expected_value_with_gets_no_grade_documents,
        expected_value_with_wait_for_grade_upload_before_publishing,
        expected_value_after_grade_upload,
    ):
        self.assertEqual(evaluation.textanswer_review_state, expected_default_value)

        evaluation.course.gets_no_grade_documents = True
        self.assertEqual(evaluation.textanswer_review_state, expected_value_with_gets_no_grade_documents)
        evaluation.course.gets_no_grade_documents = False

        evaluation.wait_for_grade_upload_before_publishing = True
        self.assertEqual(
            evaluation.textanswer_review_state, expected_value_with_wait_for_grade_upload_before_publishing
        )

        grade_document = baker.make(GradeDocument, type=GradeDocument.Type.FINAL_GRADES, course=evaluation.course)
        self.assertEqual(evaluation.textanswer_review_state, expected_value_after_grade_upload)
        grade_document.delete()

        evaluation.wait_for_grade_upload_before_publishing = False

    def test_textanswer_review_state(self):
        evaluation = baker.make(
            Evaluation,
            state=Evaluation.State.IN_EVALUATION,
            can_publish_text_results=True,
            wait_for_grade_upload_before_publishing=False,
        )

        self.assert_textanswer_review_state(
            evaluation,
            evaluation.TextAnswerReviewState.NO_TEXTANSWERS,
            evaluation.TextAnswerReviewState.NO_TEXTANSWERS,
            evaluation.TextAnswerReviewState.NO_TEXTANSWERS,
            evaluation.TextAnswerReviewState.NO_TEXTANSWERS,
        )

        textanswer = baker.make(TextAnswer, contribution=evaluation.general_contribution)
        del evaluation.num_textanswers  # reset cached_property cache

        # text_answer_review_state should be NO_REVIEW_NEEDED as long as we are still in_evaluation
        self.assert_textanswer_review_state(
            evaluation,
            evaluation.TextAnswerReviewState.NO_REVIEW_NEEDED,
            evaluation.TextAnswerReviewState.NO_REVIEW_NEEDED,
            evaluation.TextAnswerReviewState.NO_REVIEW_NEEDED,
            evaluation.TextAnswerReviewState.NO_REVIEW_NEEDED,
        )

        evaluation.end_evaluation()
        evaluation.save()

        self.assert_textanswer_review_state(
            evaluation,
            evaluation.TextAnswerReviewState.REVIEW_URGENT,
            evaluation.TextAnswerReviewState.REVIEW_URGENT,  # course has `gets_no_grade_documents`
            evaluation.TextAnswerReviewState.REVIEW_NEEDED,  # still waiting for grades
            evaluation.TextAnswerReviewState.REVIEW_URGENT,  # grades were uploaded
        )

        textanswer.review_decision = TextAnswer.ReviewDecision.PUBLIC
        textanswer.save()
        del evaluation.num_reviewed_textanswers  # reset cached_property cache

        self.assert_textanswer_review_state(
            evaluation,
            evaluation.TextAnswerReviewState.REVIEWED,
            evaluation.TextAnswerReviewState.REVIEWED,
            evaluation.TextAnswerReviewState.REVIEWED,
            evaluation.TextAnswerReviewState.REVIEWED,
        )

class TestCourse(TestCase):
    def test_can_be_deleted_by_manager(self):
        course = baker.make(Course)
        evaluation = baker.make(Evaluation, course=course)
        self.assertFalse(course.can_be_deleted_by_manager)

        evaluation.delete()
        self.assertTrue(course.can_be_deleted_by_manager)

    def test_responsibles_names(self):
        # last names required for sorting
        user1 = baker.make(UserProfile, last_name="Doe")
        user2 = baker.make(UserProfile, last_name="Meyer")
        course = baker.make(Course, responsibles=[user1, user2])
        self.assertEqual(course.responsibles_names, f"{user1.full_name}, {user2.full_name}")

class TestUserProfile(TestCase):
    def test_is_student(self):
        some_user = baker.make(UserProfile)
        self.assertFalse(some_user.is_student)

        student = baker.make(UserProfile, evaluations_participating_in=[baker.make(Evaluation)])
        self.assertTrue(student.is_student)

        contributor = baker.make(UserProfile, contributions=[baker.make(Contribution)])
        self.assertFalse(contributor.is_student)

        semester_contributed_to = baker.make(Semester, created_at=date.today())
        semester_participated_in = baker.make(Semester, created_at=date.today())
        course_contributed_to = baker.make(Course, semester=semester_contributed_to)
        course_participated_in = baker.make(Course, semester=semester_participated_in)
        evaluation_contributed_to = baker.make(Evaluation, course=course_contributed_to)
        evaluation_participated_in = baker.make(Evaluation, course=course_participated_in)
        contribution = baker.make(Contribution, evaluation=evaluation_contributed_to)
        user = baker.make(
            UserProfile, contributions=[contribution], evaluations_participating_in=[evaluation_participated_in]
        )

        self.assertTrue(user.is_student)

        semester_contributed_to.created_at = date.today() - timedelta(days=1)
        semester_contributed_to.save()

        # invalidate cached_property
        del user.is_student
        self.assertTrue(user.is_student)

        semester_participated_in.created_at = date.today() - timedelta(days=2)
        semester_participated_in.save()

        # invalidate cached_property
        del user.is_student
        self.assertFalse(user.is_student)

    def test_can_be_deleted_by_manager(self):
        user = baker.make(UserProfile)
        baker.make(Evaluation, participants=[user], state=Evaluation.State.NEW)
        self.assertFalse(user.can_be_deleted_by_manager)

        user2 = baker.make(UserProfile)
        baker.make(Evaluation, participants=[user2], state=Evaluation.State.IN_EVALUATION)
        self.assertFalse(user2.can_be_deleted_by_manager)

        contributor = baker.make(UserProfile)
        baker.make(Contribution, contributor=contributor)
        self.assertFalse(contributor.can_be_deleted_by_manager)

        proxy_user = baker.make(UserProfile, is_proxy_user=True)
        self.assertFalse(proxy_user.can_be_deleted_by_manager)

    def test_inactive_users_hidden(self):
        active_user = baker.make(UserProfile)
        baker.make(UserProfile, is_active=False)

        self.assertEqual(list(UserProfile.objects.exclude(is_active=False)), [active_user])

    def test_inactive_users_shown(self):
        active_user = baker.make(UserProfile)
        inactive_user = baker.make(UserProfile, is_active=False)

        user_list = list(UserProfile.objects.all())
        self.assertIn(active_user, user_list)
        self.assertIn(inactive_user, user_list)

    def test_can_be_marked_inactive_by_manager(self):
        user = baker.make(UserProfile)
        evaluation = baker.make(Evaluation, state=Evaluation.State.NEW)
        self.assertTrue(user.can_be_marked_inactive_by_manager)
        evaluation.participants.set([user])
        evaluation.save()
        self.assertFalse(user.can_be_marked_inactive_by_manager)

        contributor = baker.make(UserProfile)
        baker.make(Contribution, contributor=contributor)
        self.assertFalse(contributor.can_be_marked_inactive_by_manager)

        reviewer = baker.make(UserProfile, groups=[Group.objects.get(name="Reviewer")])
        self.assertFalse(reviewer.can_be_marked_inactive_by_manager)

        grade_publisher = baker.make(UserProfile, groups=[Group.objects.get(name="Grade publisher")])
        self.assertFalse(grade_publisher.can_be_marked_inactive_by_manager)

        super_user = baker.make(UserProfile, is_superuser=True)
        self.assertFalse(super_user.can_be_marked_inactive_by_manager)

        proxy_user = baker.make(UserProfile, is_proxy_user=True)
        self.assertFalse(proxy_user.can_be_marked_inactive_by_manager)

    @override_settings(INSTITUTION_EMAIL_REPLACEMENTS=[("example.com", "institution.com")])
    def test_email_domain_replacement(self):
        user = baker.make(UserProfile, email="test@example.com")
        self.assertEqual(user.email, "test@institution.com")

    def test_get_sorted_due_evaluations(self):
        student = baker.make(UserProfile, email="student@example.com")
        course = baker.make(Course)

        evaluations = baker.make(
            Evaluation,
            course=course,
            name_en=iter(["C", "B", "A"]),
            name_de=iter(["C", "B", "A"]),
            vote_end_date=iter([date.today(), date.today(), date.today() + timedelta(days=1)]),
            state=Evaluation.State.IN_EVALUATION,
            participants=[student],
            _quantity=3,
        )

        sorted_evaluations = student.get_sorted_due_evaluations()
        self.assertEqual(sorted_evaluations, [(evaluations[1], 0), (evaluations[0], 0), (evaluations[2], 1)])

    def test_correct_sorting(self):
        baker.make(
            UserProfile,
            last_name=iter(["Y", "x", "", ""]),
            first_name_given=iter(["x", "x", "a", ""]),
            email=iter(["3xy@example.com", "4xx@example.com", "2a@example.com", "1unnamed@example.com"]),
            _quantity=4,
            _bulk_create=True,
        )
        email_list = [user.email for user in UserProfile.objects.all()]
        self.assertEqual(email_list, ["4xx@example.com", "3xy@example.com", "2a@example.com", "1unnamed@example.com"])

class ParticipationArchivingTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.semester = baker.make(Semester)
        cls.evaluation = baker.make(
            Evaluation, state=Evaluation.State.PUBLISHED, course=baker.make(Course, semester=cls.semester)
        )
        cls.evaluation.general_contribution.questionnaires.set([baker.make(Questionnaire)])

        users = baker.make(UserProfile, _bulk_create=True, _quantity=3)
        cls.evaluation.participants.set(users)
        cls.evaluation.voters.set(users[:2])

    def refresh_evaluation(self):
        """refresh_from_db does not work with evaluations"""
        self.evaluation = self.semester.evaluations.first()

    def test_counts_dont_change(self):
        """
        Asserts that evaluation.num_voters evaluation.num_participants don't change after archiving.
        """
        voter_count = self.evaluation.num_voters
        participant_count = self.evaluation.num_participants

        self.semester.archive()
        self.refresh_evaluation()

        self.assertEqual(voter_count, self.evaluation.num_voters)
        self.assertEqual(participant_count, self.evaluation.num_participants)

    def test_participants_do_not_loose_evaluations(self):
        """
        Asserts that participants still participate in their evaluations after the participations get archived.
        """
        some_participant = self.evaluation.participants.first()

        self.semester.archive()

        self.assertEqual(list(some_participant.evaluations_participating_in.all()), [self.evaluation])

    def test_participations_are_archived(self):
        """
        Tests whether participations_are_archived returns True on semesters and evaluations with archived participations.
        """
        self.assertFalse(self.evaluation.participations_are_archived)

        self.semester.archive()
        self.refresh_evaluation()

        self.assertTrue(self.evaluation.participations_are_archived)

    def test_archiving_participations_does_not_change_results(self):
        cache_results(self.evaluation)
        distribution = calculate_average_distribution(self.evaluation)

        self.semester.archive()
        self.refresh_evaluation()
        caches["results"].clear()

        cache_results(self.evaluation)
        new_distribution = calculate_average_distribution(self.evaluation)
        self.assertEqual(new_distribution, distribution)

    def test_archiving_participations_twice_raises_exception(self):
        self.semester.archive()
        with self.assertRaises(NotArchiveable):
            self.semester.archive()
        with self.assertRaises(NotArchiveable):
            self.semester.courses.first().evaluations.first()._archive()

    def test_evaluation_participations_are_not_archived_if_participant_count_is_set(self):
        evaluation = baker.make(Evaluation, state=Evaluation.State.PUBLISHED, _participant_count=1, _voter_count=1)
        self.assertFalse(evaluation.participations_are_archived)
        self.assertTrue(evaluation.participations_can_be_archived)

    def test_archiving_participations_doesnt_change_single_results_participant_count(self):
        responsible = baker.make(UserProfile)
        evaluation = baker.make(
            Evaluation, state=Evaluation.State.PUBLISHED, is_single_result=True, _participant_count=5, _voter_count=5
        )
        contribution = baker.make(
            Contribution,
            evaluation=evaluation,
            contributor=responsible,
            role=Contribution.Role.EDITOR,
            textanswer_visibility=Contribution.TextAnswerVisibility.GENERAL_TEXTANSWERS,
        )
        contribution.questionnaires.add(Questionnaire.single_result_questionnaire())

        evaluation.course.semester.archive()
        evaluation = Evaluation.objects.get(pk=evaluation.pk)
        self.assertEqual(evaluation._participant_count, 5)
        self.assertEqual(evaluation._voter_count, 5)

class TestLoginUrlEmail(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.other_user = baker.make(UserProfile, email="other@extern.com")
        cls.user = baker.make(UserProfile, email="extern@extern.com")
        cls.user.ensure_valid_login_key()

        cls.evaluation = baker.make(Evaluation)
        baker.make(
            Contribution,
            evaluation=cls.evaluation,
            contributor=cls.user,
            role=Contribution.Role.EDITOR,
            textanswer_visibility=Contribution.TextAnswerVisibility.GENERAL_TEXTANSWERS,
        )

        cls.template = baker.make(EmailTemplate, plain_content="{{ login_url }}")

        EmailTemplate.objects.filter(name="Login Key Created").update(plain_content="{{ user.login_url }}")

    @override_settings(PAGE_URL="https://example.com")
    def test_no_login_url_when_delegates_in_cc(self):
        self.user.delegates.add(self.other_user)
        self.template.send_to_users_in_evaluations(
            [self.evaluation], EmailTemplate.Recipients.CONTRIBUTORS, use_cc=True, request=None
        )
        self.assertEqual(len(mail.outbox), 2)
        self.assertEqual(mail.outbox[0].body, "")  # message does not contain the login url
        self.assertEqual(mail.outbox[1].body, self.user.login_url)  # separate email with login url was sent
        self.assertEqual(len(mail.outbox[1].cc), 0)
        self.assertEqual(mail.outbox[1].to, [self.user.email])

    def test_no_login_url_when_cc_users_in_cc(self):
        self.user.cc_users.add(self.other_user)
        self.template.send_to_users_in_evaluations(
            [self.evaluation], [EmailTemplate.Recipients.CONTRIBUTORS], use_cc=True, request=None
        )
        self.assertEqual(len(mail.outbox), 2)
        self.assertEqual(mail.outbox[0].body, "")  # message does not contain the login url
        self.assertEqual(mail.outbox[1].body, self.user.login_url)  # separate email with login url was sent
        self.assertEqual(len(mail.outbox[1].cc), 0)
        self.assertEqual(mail.outbox[1].to, [self.user.email])

    def test_login_url_when_nobody_in_cc(self):
        # message is not sent to others in cc
        self.template.send_to_users_in_evaluations(
            [self.evaluation], [EmailTemplate.Recipients.CONTRIBUTORS], use_cc=True, request=None
        )
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].body, self.user.login_url)  # message does contain the login url

    def test_login_url_when_use_cc_is_false(self):
        # message is not sent to others in cc
        self.user.delegates.add(self.other_user)
        self.template.send_to_users_in_evaluations(
            [self.evaluation], [EmailTemplate.Recipients.CONTRIBUTORS], use_cc=False, request=None
        )
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].body, self.user.login_url)  # message does contain the login url

@override_settings(INSTITUTION_EMAIL_DOMAINS=["example.com"])
class TestEmailTemplate(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.user = baker.make(UserProfile, email="user@example.com")
        cls.additional_cc = baker.make(UserProfile, email="additional@example.com")
        cls.template = EmailTemplate.objects.get(name=EmailTemplate.EDITOR_REVIEW_NOTICE)

    @staticmethod
    def test_missing_email_address():
        """
        Tests send_to_user when the user has no email address.
        Regression test to https://github.com/e-valuation/EvaP/issues/825
        """
        user = baker.make(UserProfile, email=None)
        template = EmailTemplate.objects.get(name=EmailTemplate.STUDENT_REMINDER)
        template.send_to_user(user, {}, {}, False, None)

    def test_send_multi_alternatives_email(self):
        template = EmailTemplate(
            subject="Example", plain_content="Example plain content", html_content="<p>Example html content</p>"
        )
        template.send_to_user(self.user, {}, {}, False)
        self.assertEqual(len(mail.outbox), 1)
        self.assertTrue(isinstance(mail.outbox[0], mail.message.EmailMultiAlternatives))
        self.assertEqual(mail.outbox[0].body, "Example plain content")
        self.assertEqual(len(mail.outbox[0].alternatives), 1)
        self.assertEqual(mail.outbox[0].alternatives[0][1], "text/html")
        self.assertIn("<p>Example html content</p>", mail.outbox[0].alternatives[0][0])

    def test_plain_content_escaped_and_copied_on_empty_html_content(self):
        template = EmailTemplate(subject="Subject <>&", plain_content="A\nB <>& {{ some_var }}", html_content="")
        template.send_to_user(self.user, {}, {"some_var": "0 < 1"}, False)

        self.assertEqual(len(mail.outbox), 1)
        message = mail.outbox[0]
        self.assertEqual(message.alternatives[0][1], "text/html")
        html_content = message.alternatives[0][0]

        self.assertEqual("Subject <>&", message.subject)
        self.assertEqual("A\nB <>& 0 < 1", message.body)
        self.assertIn("A<br>B <>& 0 < 1\n", html_content)
        self.assertNotIn("<>&", html_content)

    def test_escaping_with_html_content(self):
        template = EmailTemplate(
            subject="Subject <>&",
            plain_content="A\nB <>& {{ some_var }}",
            html_content="Html content &<br/> {{ some_var }}",
        )
        template.send_to_user(self.user, {}, {"some_var": "0 < 1"}, False)

        self.assertEqual(len(mail.outbox), 1)
        message = mail.outbox[0]
        self.assertEqual(message.alternatives[0][1], "text/html")
        html_content = message.alternatives[0][0]

        self.assertEqual("Subject <>&", message.subject)
        self.assertEqual("A\nB <>& 0 < 1", message.body)
        self.assertIn("Html content &<br/> 0 < 1", html_content)
        self.assertNotIn("<>&", html_content)

    def test_put_delegates_in_cc(self):
        delegate_a = baker.make(UserProfile, email="delegate-a@example.com")
        delegate_b = baker.make(UserProfile, email="delegate-b@example.com")
        self.user.delegates.add(delegate_a, delegate_b)
        self.template.send_to_user(self.user, {}, {}, use_cc=True)

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(set(mail.outbox[0].cc), {delegate_a.email, delegate_b.email})

    def test_put_cc_users_in_cc(self):
        cc_a = baker.make(UserProfile, email="cc-a@example.com")
        cc_b = baker.make(UserProfile, email="cc-b@example.com")
        self.user.cc_users.add(cc_a, cc_b)
        self.template.send_to_user(self.user, {}, {}, use_cc=True)

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(set(mail.outbox[0].cc), {cc_a.email, cc_b.email})

    def test_put_additional_cc_users_in_cc(self):
        additional_cc_b = baker.make(UserProfile, email="additional-b@example.com")
        self.template.send_to_user(
            self.user, {}, {}, use_cc=True, additional_cc_users=[self.additional_cc, additional_cc_b]
        )

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(set(mail.outbox[0].cc), {self.additional_cc.email, additional_cc_b.email})

    def test_put_delegates_of_additional_cc_user_in_cc(self):
        additional_delegate_a = baker.make(UserProfile, email="additional-delegate-a@example.com")
        additional_delegate_b = baker.make(UserProfile, email="additional-delegate-b@example.com")
        self.additional_cc.delegates.add(additional_delegate_a, additional_delegate_b)
        self.template.send_to_user(self.user, {}, {}, use_cc=True, additional_cc_users=[self.additional_cc])

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(
            set(mail.outbox[0].cc), {self.additional_cc.email, additional_delegate_a.email, additional_delegate_b.email}
        )

    def test_cc_does_not_contain_duplicates(self):
        user_a = baker.make(UserProfile, email="a@example.com")
        user_b = baker.make(UserProfile, email="b@example.com")
        user_c = baker.make(UserProfile, email="c@example.com")
        self.user.delegates.add(user_a)
        self.user.cc_users.add(self.additional_cc, user_b)
        self.additional_cc.delegates.add(user_b, user_c)
        self.additional_cc.cc_users.add(user_a, user_c)
        self.template.send_to_user(self.user, {}, {}, use_cc=True, additional_cc_users=[self.additional_cc])

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(len(mail.outbox[0].cc), 4)
        self.assertEqual(set(mail.outbox[0].cc), {self.additional_cc.email, user_a.email, user_b.email, user_c.email})

    def test_disable_cc(self):
        delegate = baker.make(UserProfile, email="delegate@example.com")
        cc_user = baker.make(UserProfile, email="cc@example.com")
        self.user.delegates.add(delegate)
        self.user.cc_users.add(cc_user)
        self.additional_cc.delegates.add(delegate)
        self.additional_cc.cc_users.add(cc_user)
        self.template.send_to_user(self.user, {}, {}, use_cc=False, additional_cc_users=[self.additional_cc])

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(set(mail.outbox[0].cc), {self.additional_cc.email})

    @staticmethod
    def test_send_contributor_publish_notifications():
        responsible1 = baker.make(UserProfile)
        responsible2 = baker.make(UserProfile)
        # use is_single_result to get can_publish_average_grade to become true
        evaluation1 = baker.make(Evaluation, course__responsibles=[responsible1], is_single_result=True)
        evaluation2 = baker.make(Evaluation, course__responsibles=[responsible2])

        editor1 = baker.make(UserProfile)
        contributor1 = baker.make(UserProfile)

        contributor2 = baker.make(UserProfile)
        editor2 = baker.make(UserProfile)
        contributor_both = baker.make(UserProfile)

        # Contributions for evaluation1
        make_contributor(responsible1, evaluation1)
        make_contributor(contributor1, evaluation1)
        make_contributor(contributor_both, evaluation1)
        make_editor(editor1, evaluation1)

        # Contributions for evaluation2
        make_editor(editor2, evaluation2)
        contributor_both_contribution = make_contributor(contributor_both, evaluation2)
        contributor2_contribution = make_contributor(contributor2, evaluation2)

        baker.make(TextAnswer, contribution=contributor_both_contribution)
        baker.make(TextAnswer, contribution=contributor2_contribution)

        expected_calls = [
            # these 4 are included since they are contributors for evaluation1 which can publish the average grade
            call(responsible1, {}, {"user": responsible1, "evaluations": {evaluation1}}, use_cc=True),
            call(editor1, {}, {"user": editor1, "evaluations": {evaluation1}}, use_cc=True),
            call(contributor1, {}, {"user": contributor1, "evaluations": {evaluation1}}, use_cc=True),
            call(
                contributor_both, {}, {"user": contributor_both, "evaluations": {evaluation1, evaluation2}}, use_cc=True
            ),
            # contributor2 has textanswers, so they are notified
            call(contributor2, {}, {"user": contributor2, "evaluations": {evaluation2}}, use_cc=True),
        ]

        with patch("evap.evaluation.models.EmailTemplate.send_to_user") as send_to_user_mock:
            EmailTemplate.send_contributor_publish_notifications({evaluation1, evaluation2})
            # Assert that all expected publish notifications are sent to contributors.
            send_to_user_mock.assert_has_calls(expected_calls, any_order=True)

        # if general textanswers for an evaluation exist, all responsibles should also be notified
        baker.make(TextAnswer, contribution=evaluation2.general_contribution)
        expected_calls.append(call(responsible2, {}, {"user": responsible2, "evaluations": {evaluation2}}, use_cc=True))

        with patch("evap.evaluation.models.EmailTemplate.send_to_user") as send_to_user_mock:
            EmailTemplate.send_contributor_publish_notifications({evaluation1, evaluation2})
            send_to_user_mock.assert_has_calls(expected_calls, any_order=True)

class TestEmailRecipientList(TestCase):
    def test_recipient_list(self):
        evaluation = baker.make(Evaluation)
        responsible = baker.make(UserProfile)
        editor = baker.make(UserProfile)
        contributor = baker.make(UserProfile)
        evaluation.course.responsibles.set([responsible])
        baker.make(Contribution, evaluation=evaluation, contributor=editor, role=Contribution.Role.EDITOR)
        baker.make(Contribution, evaluation=evaluation, contributor=contributor)

        participant1 = baker.make(UserProfile, evaluations_participating_in=[evaluation])
        participant2 = baker.make(UserProfile, evaluations_participating_in=[evaluation])
        evaluation.voters.set([participant1])

        recipient_list = EmailTemplate.recipient_list_for_evaluation(evaluation, [], filter_users_in_cc=False)
        self.assertCountEqual(recipient_list, [])

        recipient_list = EmailTemplate.recipient_list_for_evaluation(
            evaluation, [EmailTemplate.Recipients.RESPONSIBLE], filter_users_in_cc=False
        )
        self.assertCountEqual(recipient_list, [responsible])

        recipient_list = EmailTemplate.recipient_list_for_evaluation(
            evaluation, [EmailTemplate.Recipients.EDITORS], filter_users_in_cc=False
        )
        self.assertCountEqual(recipient_list, [responsible, editor])

        recipient_list = EmailTemplate.recipient_list_for_evaluation(
            evaluation, [EmailTemplate.Recipients.CONTRIBUTORS], filter_users_in_cc=False
        )
        self.assertCountEqual(recipient_list, [responsible, editor, contributor])

        recipient_list = EmailTemplate.recipient_list_for_evaluation(
            evaluation, [EmailTemplate.Recipients.ALL_PARTICIPANTS], filter_users_in_cc=False
        )
        self.assertCountEqual(recipient_list, [participant1, participant2])

        recipient_list = EmailTemplate.recipient_list_for_evaluation(
            evaluation, [EmailTemplate.Recipients.DUE_PARTICIPANTS], filter_users_in_cc=False
        )
        self.assertCountEqual(recipient_list, [participant2])

    def test_recipient_list_filtering(self):
        evaluation = baker.make(Evaluation)

        contributor1 = baker.make(UserProfile)
        contributor2 = baker.make(UserProfile, delegates=[contributor1])

        baker.make(Contribution, evaluation=evaluation, contributor=contributor1)
        baker.make(Contribution, evaluation=evaluation, contributor=contributor2)

        # no-one should get filtered.
        recipient_list = EmailTemplate.recipient_list_for_evaluation(
            evaluation, [EmailTemplate.Recipients.CONTRIBUTORS], filter_users_in_cc=False
        )
        self.assertCountEqual(recipient_list, [contributor1, contributor2])

        # contributor1 is in cc of contributor2 and gets filtered.
        recipient_list = EmailTemplate.recipient_list_for_evaluation(
            evaluation, [EmailTemplate.Recipients.CONTRIBUTORS], filter_users_in_cc=True
        )
        self.assertCountEqual(recipient_list, [contributor2])

        contributor3 = baker.make(UserProfile, delegates=[contributor2])
        baker.make(Contribution, evaluation=evaluation, contributor=contributor3)

        # again, no-one should get filtered.
        recipient_list = EmailTemplate.recipient_list_for_evaluation(
            evaluation, [EmailTemplate.Recipients.CONTRIBUTORS], filter_users_in_cc=False
        )
        self.assertCountEqual(recipient_list, [contributor1, contributor2, contributor3])

        # contributor1 is in cc of contributor2 and gets filtered.
        # contributor2 is in cc of contributor3 but is not filtered since contributor1 wouldn't get an email at all then.
        recipient_list = EmailTemplate.recipient_list_for_evaluation(
            evaluation, [EmailTemplate.Recipients.CONTRIBUTORS], filter_users_in_cc=True
        )
        self.assertCountEqual(recipient_list, [contributor2, contributor3])

class QuestionnaireTests(TestCase):
    def test_locked_contributor_questionnaire(self):
        questionnaire = baker.prepare(Questionnaire, is_locked=True, type=Questionnaire.Type.CONTRIBUTOR)
        self.assertRaises(ValidationError, questionnaire.clean)


from group import *
# from .exception import *
from sport import *
from member import *
# from .dev import *

FRONT = f"""

                ##########################################################################################
                #                           Verkefni 6 í gagnaskipan, notkun ...                         #
                # -------------------------------------------------------------------------------------- #
                #                          Atli Þór & Wentzel Copyright 2019 (c)                         #
                ##########################################################################################

                """

FRONT += "                   -----------------VERY NICE TO SEE YOU-----------------\n\n"

class SportDatabase():
    def __init__(self):
        print(FRONT)
        print()
        self.s = Sports()
        self.m = Member()
        self.g = Group()

    def options(self):
        print()
        print("----NEW ITEMS----")
        print("[1] REGISTER NEW SPORT")
        print("[2] REGISTER A GROUP")
        print("[3] REGISTER NEW MEMBER")
        print("[4] SIGN MEMBER UP FOR A SPORT\n")
        print("----REMOVING ITEMS----")
        print("[5] REMOVE MEMBER FROM A SPORT")
        print("[6] REMOVE A SPORT FROM THE SYSTEM \n")
        print("----RETRIEVE DATA----")
        print("[7] LIST OF MEMBERS, PICK IN WHICH ORDER")  # order by name, age or sport
        print("[8] FIND A MEMBER")  # name, phone, email, age
        print("[9] SELECT A MEMBER AND GET ALL INFO, INCLUDING SPORTS")
        print("[10] SEE A LIST OF ALL SPORTS")
        print("[11] SELECT A SPORT AND SEE LIST OF USERS IN THAT SPORT")
        print()

    def do_again(self):
        '''Offers the user to do again or quit after each action'''

        x = input("You want to continue [Y/n]: ").upper()
        if x == "N":
            quit()
        elif x == "Y":
            self.main()
        else:
            print(f"{x} is not valid. Try again. ")
            self.do_again()

    def validate_input(self, option):
        '''Validates if input is an integer'''

        try:
            int(option)
            return option
        except ValueError:
            print("Input should be an integer")
            self.do_again()

    def main(self):
        self.options()
        option = str(input("ENTER YOUR CHOICE: \n"))
        option = self.validate_input(option)
        if option in "1,3,4,5,6,7,8,9,10,11":
            if option == "1":
                self.s.register_sport()
                self.do_again()

            elif option == "2":
                #TODO: Wentzel implementar
                pass

            elif option == "3":
                self.m.register_member()
                self.do_again()

            elif option == "4":
                #TODO implement
                self.s.sign_for_sport() #spurning hvort þetta ætti að vera s eða m
                self.do_again()

            elif option == "5":
                #TODO: implement
                self.s.remove_member_from_sport() #spurning hvort þetta ætti að vera s eða m
                self.do_again()

            elif option == "6":
                self.s.remove_sport()
                self.do_again()

            elif option == "7":
                #TODO: implement sorting by sports
                self.m.get_ordered_list() #komið með aldur og nafn
                self.do_again()

            elif option == "8":
                #temp
                self.m.find_member()

            elif option == "9":
                pass

            elif option == "10":
                print(self.s)
                self.do_again()

            elif option == "11":
                pass

            else:
                print("Invalid number man!")
                self.do_again()

        self.main()


# BOJ 4949번

import sys
input = sys.stdin.readline
answer = []

while ( True ) :
    stack = []
    check = False
    char_list = input().rstrip()

    if char_list[0] == '.' :
        break

    for char_data in char_list :
        if char_data == '.' :
            break
        elif char_data == '(' :
            stack.append( '(' )
        elif char_data == '[' :
            stack.append( '[' )
        elif char_data == ')' :
            if stack and stack[-1] == '(' :
                stack.pop()
            else :
                check = True
                answer.append('no')
                break
        elif char_data == ']' :
            if stack and stack[-1] == '[' :
                stack.pop()
            else :
                check = True
                answer.append('no')
                break

    if check :
            continue

    if stack :
        answer.append( 'no' )
        continue
    answer.append( 'yes' )

print( *answer )


"""
This script summarized DE results.
"""
import numpy as np
import pandas as pd
import session_info
from pyhere import here
from functools import lru_cache
from scipy.stats import mannwhitneyu

@lru_cache()
def get_annotation(feature, tissue):
    """
    Get the annotation file based on feature and tissue.
    """
    feat_lt = {"genes": "gene", "transcripts": "tx",
               "exons": "exon", "junctions": "jxn"}
    new_feature = feat_lt[feature]
    fn = here("input/counts/text_files_counts/_m",
              f"{tissue.lower()}/{new_feature}_annotation.txt")
    return pd.read_csv(fn, sep="\t")

@lru_cache()
def get_resid(feature, tissue):
    """
    Using feature and tissue to select residualized expression.
    """
    return pd.read_csv(here("interaction_sex_sz/interaction_model",
                            f"{tissue.lower()}/_m/{feature}",
                            "residualized_expression.tsv"),
                       sep="\t", index_col=0)\
             .transpose()

@lru_cache()
def get_pheno():
    return pd.read_csv(here("input/phenotypes/_m/phenotypes.csv"))\
             .loc[:, ["RNum", "Sex", "Dx", "Race", "Age"]]

@lru_cache()
def merge_resid(feature, tissue):
    return pd.merge(get_pheno(), get_resid(feature, tissue),
                    left_on="RNum", right_index=True)

@lru_cache()
def subset_resid(tissue, feature, sex):
    sex_dict = {"Male": "F", "Female": "M"}
    df = merge_resid(feature, tissue)
    ctl = df[(df["Dx"] == "Control") &
             (df["Sex"] == sex_dict[sex])].copy()
    sz = df[(df["Dx"] == "SCZD") &
            (df["Sex"] == sex_dict[sex])].copy()
    return ctl, sz

@lru_cache()
def get_de(feature, tissue, sex, fdr):
    """
    Using feature and tissue, select the correct DE results
    file and filter based on FDR (input).
    """
    df = pd.read_csv(here("interaction_sex_sz/by_sex_sz",
                          f"{tissue.lower()}/{sex.lower()}_analysis/_m",
                          f"{feature}/diffExpr_szVctl_full.txt"),
                     sep="\t", index_col=0)\
           .rename(columns={"gene_id":"gencodeID",
                            "gencodeGeneID": "gencodeID",
                            "gene_name": "Symbol"})
    df["ensemblID"] = df.gencodeID.str.replace("\\..*", "", regex=True)
    return df[(df["adj.P.Val"] < fdr)].copy()

@lru_cache()
def annotate_de(feature, tissue, sex, fdr):
    """
    Annotate DE features
    """
    cols = ["Feature", "seqnames", "gencodeID", "ensemblID",
            "Symbol", "logFC", "P.Value", "adj.P.Val", "SE"]
    return get_de(feature, tissue, sex, fdr)\
        .merge(get_annotation(feature, tissue),
               left_index=True, right_on="name")\
        .rename(columns={"name": "Feature"})\
        .loc[:, cols].sort_values("adj.P.Val")

@lru_cache()
def annotate_pvals(feature, tissue, sex, fdr):
    if fdr == 1:
        return annotate_de(feature, tissue, sex, fdr)
    else:
        df = annotate_de(feature, tissue, sex, fdr)
        ctl, sz = subset_resid(tissue, feature, sex)
        pval_df = []
        for feature_id in df.Feature:
            stat, pval = mannwhitneyu(ctl[feature_id], sz[feature_id])
            pval_df.append(pval)
        df["other_pval"] = pval_df
        return df

@lru_cache()
def extract_de(feature, tissue, fdr):
    ff = annotate_pvals(feature, tissue, "Female", fdr)
    mm = annotate_pvals(feature, tissue, "Male", fdr)
    ff["Sex"] = "Female"; mm["Sex"] = "Male"
    return ff, mm

@lru_cache()
def get_unique(feature, tissue, fdr):
    ff, mm = extract_de(feature, tissue, fdr)
    ff_genes = list(set(ff.Feature) - set(mm.Feature))
    mm_genes = list(set(mm.Feature) - set(ff.Feature))
    ff = ff[(ff["Feature"].isin(ff_genes))].copy()
    mm = mm[(mm["Feature"].isin(mm_genes))].copy()
    return ff, mm

@lru_cache()
def filter_pvals(feature, tissue, fdr):
    if fdr == 0.05:
        ff, mm = get_unique(feature, tissue, fdr)
        ff = ff[(ff["other_pval"] < fdr)].copy()
        mm = mm[(mm["other_pval"] < fdr)].copy()
    else:
        ff, mm = extract_de(feature, tissue, fdr)
    return pd.concat([ff, mm], axis=0)

@lru_cache()
def extract_features(tissue, fdr):
    # Extract DE from mash model
    genes = filter_pvals("genes", tissue, fdr)
    trans = filter_pvals("transcripts", tissue, fdr)
    exons = filter_pvals("exons", tissue, fdr)
    juncs = filter_pvals("junctions", tissue, fdr)
    genes["Type"] = "Gene"; trans["Type"] = "Transcript"
    exons["Type"] = "Exon"; juncs["Type"] = "Junction"
    return genes, trans, exons, juncs

def annotate_chrom(df):
    df.loc[:, "Chrom_Type"] = "Other"
    df.loc[df["seqnames"].isin(["chrX", "chrY"]), "Chrom_Type"] = "Allosome"
    df.loc[df["seqnames"].str.contains("chr\d+"), "Chrom_Type"] = "Autosome"
    df.loc[df["seqnames"] == "chrM", "Chrom_Type"] = "Mitochondria"
    return df

def get_DEGs_result_by_tissue(tissue, fdr=0.05):
    # Schizophrenia = 1; Control = -1
    genes, trans, exons, juncs = extract_features(tissue, fdr)
    df = pd.concat([genes, trans, exons, juncs])
    df.loc[:, "Direction"] = np.sign(df.logFC)
    df["Type"] = df.Type.astype("category")\
                   .cat.reorder_categories(["Gene", "Transcript",
                                            "Exon", "Junction"])
    df["Tissue"] = tissue
    return annotate_chrom(df)

def print_summary(tissue, fdr=0.05):
    df = get_DEGs_result_by_tissue(tissue, fdr)
    if tissue == "Caudate":
        w_mode = "w"
    else:
        w_mode = "a"
    statement = f"Significant DE (FDR < 0.05) in {tissue}"
    with open("summarize_results.log", mode=w_mode) as f:
        print(statement, file=f)
        for sex in ["Female", "Male"]:
            print(sex, file=f)
            genes = df[(df["Sex"] == sex) &
                       (df["Type"] == "Gene")].copy()
            trans = df[(df["Sex"] == sex) &
                       (df["Type"] == "Transcript")].copy()
            exons = df[(df["Sex"] == sex) &
                       (df["Type"] == "Exon")].copy()
            juncs = df[(df["Sex"] == sex) &
                       (df["Type"] == "Junction")].copy()
            for variable in ["Feature", "gencodeID"]:
                print(variable, file=f)
                gg = len(set(genes[variable]))
                tt = len(set(trans[variable]))
                ee = len(set(exons[variable]))
                jj = len(set(juncs[variable]))
                print(f"\nGene:\t\t{gg}\nTranscript:\t{tt}"+\
                      f"\nExon:\t\t{ee}\nJunction:\t{jj}\n", file=f)

def merge_data():
    bigdata1 = []; bigdata2 = [];
    for tissue in ["Caudate", "DLPFC", "Hippocampus"]:
        print_summary(tissue)
        data2 = get_DEGs_result_by_tissue(tissue, 0.05)
        data1 = get_DEGs_result_by_tissue(tissue, 1)
        bigdata1.append(data1); bigdata2.append(data2)
    return pd.concat(bigdata1), pd.concat(bigdata2)

def main():
    df1, df2 = merge_data()
    # Summary
    print("\nSummary:")
    gene = df2[(df2["Type"] == "Gene")].copy()
    ff = gene[(gene["Sex"] == "Female")].copy()
    mm = gene[(gene["Sex"] == "Male")].copy()
    print("Unique DEGs (Female): %d" %
          ff.drop_duplicates(subset="gencodeID").shape[0])
    print("Unique DEGs (Male): %d" %
          mm.drop_duplicates(subset="gencodeID").shape[0])
    print(gene.groupby(["Tissue", "Sex", "Direction"]).size())
    # Output
    ## Full data
    df1.sort_values(["Tissue", "Sex", "Type", "P.Value"])\
       .to_csv("differential_expression_schizophrenia_by_sex_4features.txt.gz",
               sep='\t', index=False)
    ## FDR significant
    df2.sort_values(["Tissue", "Sex", "Type", "P.Value"])\
       .to_csv("differential_expression_schizophrenia_by_sex_4features.sig.txt.gz",
               sep='\t', index=False)
    # Session infomation
    session_info.show()

if __name__ == '__main__':
    main()


# coding: utf8

import pytest
import os
import shutil

@pytest.fixture(params=[
    'group_image',
    'individual_image'
])
def cli_commands(request):

    if request.param == 'group_image':
        cnn_input = [
            'train',
            'image',
            'cnn',
            'data/dataset/random_example',
            't1-linear',
            'data/labels_list',
            'results',
            'Conv5_FC3',
            '--epochs', '1',
            '--n_splits', '2',
            '--split', '0'
        ]
        interpret_input = [
            'interpret',
            'group',
            'results',
            'group-test'
        ]

    elif request.param == 'individual_image':
        cnn_input = [
            'train',
            'image',
            'cnn',
            'data/dataset/random_example',
            't1-linear',
            'data/labels_list',
            'results',
            'Conv5_FC3',
            '--epochs', '1',
            '--n_splits', '2',
            '--split', '0'
        ]
        interpret_input = [
            'interpret',
            'individual',
            'results',
            'individual-test'
        ]
    else:
        raise NotImplementedError(
            "Test %s is not implemented." %
            request.param)

    return cnn_input, interpret_input

def test_interpret(cli_commands):
    cnn_input, interpret_input = cli_commands
    train_error = not os.system("clinicadl " + " ".join(cnn_input))
    interpret_error = not os.system("clinicadl " + " ".join(interpret_input))
    interpret_flag = os.path.exists(
        os.path.join("results", "fold-0", "gradients"))
    assert train_error
    assert interpret_error
    assert interpret_flag
    shutil.rmtree("results")


import os
from timer import timeit
from collections import defaultdict, deque

stardate = 14
dataname = f"dec{stardate}.txt"
# dataname = f"dec{stardate}_test.txt"
curdir = os.path.dirname(os.path.abspath(__file__))
filename = f'{curdir}\\{dataname}'
data = [_.strip() for _ in open(filename, 'r').readlines()]
if not data:
    raise FileNotFoundError(f"No data in {dataname}")

class Node():
    def __init__(self, value) -> None:
        self.value = value
        self.next = None

class LinkedList():
    def __init__(self) -> None:
        self.head = None
        self.tail = None
        self.length = 0

    def append(self, node: Node):
        if self.head is None:
            self.head = node
            self.tail = node
        else:
            self.tail.next = node
            self.tail = node
        self.length += 1

    def __str__(self) -> str:
        res = []
        n = self.head
        while n != self.tail.next:
            res.append(str(n.value))
            n = n.next

        return ", ".join(res)

def parse_data(data):
    rules = {}
    for p in data[2:]:
        from_, to = p.split(" -> ")
        rules[from_] = to

    initial_chain = LinkedList()
    [initial_chain.append(Node(c)) for c in data[0]]

    return initial_chain, rules

def make_gen(template, rules):
    current = template.head
    while current.next:
        pair = current.value + current.next.value
        if pair in rules:
            node = Node(rules[pair])
            node.next = current.next
            current.next = node
            template.length += 1
            current = node
        current = current.next

def get_counts(template):
    counts = defaultdict(int)
    c = template.head
    while c:
        counts[c.value] += 1
        c = c.next
    return counts

@timeit
def star1(template, rules, generations):
    for g in range(1, generations + 1):
        make_gen(template, rules)

    counts = get_counts(template)
    print("diff", g, max(counts.values()) - min(counts.values()))

@timeit
def star2(template, rules, generations):
    pairs = defaultdict(int)
    c = template.head

    counts = defaultdict(int)
    while c.next:
        pairs[c.value + c.next.value] += 1
        counts[c.value] += 1
        c = c.next
    counts[c.value] += 1

    for g in range(1, generations+1):
        nextgen = defaultdict(int)
        for p in pairs:
            counts[rules[p]] += pairs[p]
            nextgen[p[0] + rules[p]] += pairs[p]
            nextgen[rules[p] + p[1]] += pairs[p]
        pairs = {**nextgen}

    print("diff", g, max(counts.values()) - min(counts.values()))

# In retrospect, star1 could easily be solved by star2.
# Keeping star1 solution to show how the solution doesn't scale :)
star1(*parse_data(data), 10)
star2(*parse_data(data), 40)