from numpy import linspace, zeros, exp
import matplotlib.pyplot as plt

def ode_FE(f, U_0, dt, T):
    N_t = int(round(float(T)/dt))
    u = zeros(N_t+1)
    t = linspace(0, N_t*dt, len(u))
    u[0] = U_0
    for n in range(N_t):
        u[n+1] = u[n] + dt*f(u[n], t[n])
    return u, t

def demo_population_growth():
    """Test case: u'=r*u, u(0)=100."""
    def f(u, t):
        return 0.1*u

    u, t = ode_FE(f=f, U_0=100, dt=0.5, T=20)
    plt.plot(t, u, t, 100*exp(0.1*t))
    plt.show()

if __name__ == '__main__':
    demo_population_growth()


from libs import *
from model import resnet
from model import resnet_1d
from model import seresnet_1d

from utils import ECG_DB
from utils import train
from utils import validation
from utils import static
from model import seresnet

from model import resnet_orignal
from model import AlexNet
from model import SqueezeNet

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('--data-dir', type=str, default='data/chapman/CPSC/', help='Directory for data dir')
    parser.add_argument('--leads', type=str, default='all', help='ECG leads to use')
    parser.add_argument('--seed', type=int, default=42, help='Seed to split data') #42
    parser.add_argument('--num-classes', type=int, default=int, help='Num of diagnostic classes')
    parser.add_argument('--lr', '--learning-rate', type=float, default=0.00009, help='Learning rate') #0.0001
    parser.add_argument('--batch-size', type=int, default=256, help='Batch size')
    parser.add_argument('--num-workers', type=int, default=4, help='Num of workers to load data')
    parser.add_argument('--phase', type=str, default='train', help='Phase: train or test')
    parser.add_argument('--epochs', type=int, default=50, help='Training epochs')
    parser.add_argument('--resume', default=False, action='store_true', help='Resume')
    parser.add_argument('--use-gpu', default=[1,2], action='store_true', help='Use GPU')
    parser.add_argument('--path', type=str, default="/home/ubuntu/hai.lv/IECG_new/ecg_2seconds/result/paper/") #Fix to your path to save model

    return parser.parse_args()

if __name__== "__main__":
    args = parse_args()
    args.best_metric = 0
    if args.use_gpu and torch.cuda.is_available():
        device = torch.device('cuda:1') # Change to your suitable GPU device
    data_dir = os.path.normpath(args.data_dir)

    label_csv = os.path.join('data/labels_6.csv') # Chang to your prefered label file (which inlude 6 classes)
    # The above label file has been divided into 10 folds, each fold has 5000 samples

    train_folds=[ 9 , 2 , 6 , 1 , 8,  3 ,10 , 5]
    val_folds=[4,7]

    train_dataset = ECG_DB.ECG_DB('train', data_dir, label_csv, train_folds)
    train_loader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True, num_workers=args.num_workers, pin_memory=True)

    val_dataset = ECG_DB.ECG_DB('val', data_dir, label_csv, val_folds)
    val_loader = DataLoader(val_dataset, batch_size=args.batch_size, shuffle=False, num_workers=args.num_workers, pin_memory=True)

    dic_model = {'resnet18.ptl': resnet_1d.ResNet18(num_classes=6).to(device),
                'resnet34.ptl': resnet_1d.ResNet34(num_classes=6).to(device),
                'SqueezeNet.ptl': SqueezeNet.SqueezeNet().to(device),
                'AlexNet.ptl': AlexNet.AlexNet().to(device),
                'resnet152.ptl': resnet_orignal.ResNet152(num_classes=6).to(device),
                'se_resnet18.ptl': seresnet_1d.se_resnet18(num_classes=6).to(device),
                'se_resnet34.ptl': seresnet_1d.se_resnet34(num_classes=6).to(device),
                'se_resnet152.ptl': seresnet.se_resnet152().to(device)}
    # Choose model

    bestmodel = 'se_resnet34.ptl'
    net = dic_model[bestmodel]

    optimizer = torch.optim.Adam(net.parameters(), lr=args.lr,betas=(0.1, 0.999)) # Define your optimizer (Adam with beta1=0.1, beta2=0.999)
    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer,T_max=40)
    # Define learning rate scheduler - dcay (CosineAnnealingLR with T_max=40)
    # This means that the learning rate will decrease from the initial value to zero over 40 epochs.
    criterion = nn.BCEWithLogitsLoss()

    print(bestmodel)
    if args.resume:
        net.load_state_dict(torch.load(args.path, map_location=device))

    for epoch in range(args.epochs):
        # Train and validation from utils
        train.train(train_loader, net, args, criterion, epoch, scheduler, optimizer, device)
        validation.validation(val_loader, net, args, criterion, device, bestmodel)


""" Core classes. """

from sklearn.externals.joblib import Parallel, delayed
from sklearn.model_selection import cross_val_score, cross_val_predict
from sklearn.model_selection import GridSearchCV
from sklearn.metrics.scorer import check_scoring
from sklearn.pipeline import Pipeline
from sklearn.base import TransformerMixin, BaseEstimator

from collections import OrderedDict
from itertools import product
from pandas import DataFrame
from numpy import mean, std, hstack, zeros
from time import time

import os

class Pipeliner(object):
    """

    Parameters:
    -----------
    steps : list
        List of (name, transforms) tuples

    eval_cv : int, cross-validation generator or an iterable, optional
        Determines the evaluation cross-validation splitting strategy.
        Possible inputs for cv are:
            - None, to use the default 3-fold cross validation,
            - integer, to specify the number of folds in a `(Stratified)KFold`,
            - An object to be used as cross-validation generator.
            - An iterable yielding train, test splits.

        For integer/None inputs, if the estimator is a classifier and ``y`` is
        either binary or multiclass, :class:`StratifiedKFold` is used. In all
        other cases, :class:`KFold` is used.

        Refer :ref:`User Guide <cross_validation>` for the various
        cross-validation strategies that can be used here.

    grid_cv : int, cross-validation generator or an iterable, optional

    param_grid :

    banned_combos :

    Attributes:
    -----------

    Examples:
    ---------

    """
    def __init__(self, steps, eval_cv, grid_cv, param_grid=dict(),
            banned_combos=list()):
        steps = OrderedDict(steps)
        columns = list(steps)
        for column in columns:
            steps[column] = OrderedDict(steps[column])

        def accept_from_banned_combos(row_keys, banned_combo):
            if set(banned_combo) - set(row_keys) == set():
                return False
            else:
                return True

        column_keys = [list(steps[column]) for column in columns]
        plan_rows = list()
        for row_keys in product(*column_keys):
            accept = list()
            for bnnd_cmb in banned_combos:
                accept += [accept_from_banned_combos(row_keys, bnnd_cmb)]

            if all(accept):
                row_of_plan = OrderedDict()
                for column, row_key in zip(columns, row_keys):
                    row_of_plan[column] = row_key
                plan_rows.append(row_of_plan)

        self.plan_table = DataFrame().from_dict(plan_rows)[columns]
        self.named_steps = steps
        self.eval_cv = eval_cv
        self.grid_cv = grid_cv
        self.param_grid = param_grid
        self._cached_data = OrderedDict()
        self.best_params = dict()
        self.scores = dict()

    def dump(self, path_to_file):
        """
        Write a representation of a Pipeliner's class to a specified file.

        Parameters:
        -----------
        path_to_file : string
            A path to which you want to save a class.

        """
        with open(path_to_file, 'wb') as f:
            for attr in sorted(self.__dict__.keys()):
                dump(getattr(self, attr), f)

    def load(self, path_to_file):
        """
        Load a representation of a Pipeliner's class from a specified file.

        Parameters:
        -----------
        path_to_file : string
            A path from which you want to load a class.

        """
        with open(path_to_file, 'rb') as f:
            for attr in sorted(self.__dict__.keys()):
                setattr(self, attr, load(f))

    def transform_with_caching(self, data, row_keys):
        """
        Sequentially apply a list of transforms to an input data.

        Parameters:
        -----------
        data : any
            A data, to which you want to apply transformations.

        row_keys : list
            Keys of transformations.

        Returns:
        --------
        transformed_data : any
            A data transformed with all specified transformations.

        """
        columns = list(self.plan_table.columns[:len(row_keys)])

        def remove_unmatched_caching_data(row_keys):
            cached_keys = list(self._cached_data)
            unmatched_caching_keys = cached_keys.copy()
            for row_key, cached_key in zip(row_keys, cached_keys):
                if not row_key == cached_key:
                    break
                unmatched_caching_keys.remove(row_key)

            for unmatched_caching_key in unmatched_caching_keys:
                del self._cached_data[unmatched_caching_key]

        def transform_data_from_last_cached(row_keys, columns):
            prev_key = list(self._cached_data)[-1]
            for row_key, column in zip(row_keys, columns):
                transformer = self.named_steps[column][row_key]
                data = self._cached_data[prev_key]
                self._cached_data[row_key] = transformer.fit_transform(data)
                prev_key = row_key

        if 'init' not in self._cached_data:
            self._cached_data['init'] = data
            transform_data_from_last_cached(row_keys, columns)
        else:
            row_keys = ['init'] + row_keys
            columns = ['init'] + columns
            remove_unmatched_caching_data(row_keys)
            cached_keys = list(self._cached_data)
            cached_keys_length = len(cached_keys)
            for i in range(cached_keys_length):
                del row_keys[0]
                del columns[0]
            transform_data_from_last_cached(row_keys, columns)

        last_cached_key = list(self._cached_data)[-1]

        return self._cached_data[last_cached_key]

    def get_grid_search_results(self, X, y, row_keys, scoring):
        """
        Give results with tuned parameters of a specified pipeline using grid search.

        Parameters:
        -----------
        X : array-like, shape (n_samples, n_features)
            Training vectors, where n_samples is the number of samples and n_features is the number of features.
            For kernel=”precomputed”, the expected shape of X is (n_samples, n_samples).

        y : array-like, shape (n_samples,)
            Target values (class labels in classification, real numbers in regression)

        row_keys : list
            Keys of transformations.

        scoring : list or str
            Name of a scoring method.
            If None, the score method of the estimator is used.

        Returns:
        --------
        results : dict
            Results dictionary.
        """
        classifier_key = row_keys[-1]
        if classifier_key in self.param_grid:
            columns = list(self.plan_table.columns)[-len(row_keys):]

            steps = list()
            for row_key, column in zip(row_keys, columns):
                steps.append((row_key, self.named_steps[column][row_key]))

            param_grid = dict()
            for key, value in self.param_grid[classifier_key].items():
                param_grid['{}__{}'.format(classifier_key, key)] = value

            self.asdf = param_grid
            self.asdfasdf = self.param_grid[classifier_key]

            grid_clf = GridSearchCV(estimator=Pipeline(steps),
                                    param_grid=param_grid,
                                    scoring=scoring,
                                    n_jobs=-1,
                                    cv=self.grid_cv)
            grid_clf.fit(X, y)

            best_params = dict()
            classifier_key_len = len(classifier_key)
            for key, value in grid_clf.best_params_.items():
                key = key[classifier_key_len + 2:]
                best_params[key] = value
            param_key = ''.join(row_keys) + str(scoring)
            self.best_params[param_key] = best_params

            results = dict()
            for i, params in enumerate(grid_clf.cv_results_['params']):
                if params == grid_clf.best_params_:

                    k = 'grid_{}_mean'.format(scoring)
                    results[k] = grid_clf.cv_results_['mean_test_score'][i]

                    k = 'grid_{}_std'.format(scoring)
                    results[k] = grid_clf.cv_results_['std_test_score'][i]

                    k = 'grid_{}_best_params'.format(scoring)
                    results[k] = str(best_params)

            return results
        else:
            param_key = ''.join(row_keys) + str(scoring)
            self.best_params[param_key] = dict()
            results = dict()
            results['grid_{}_mean'.format(scoring)] = 'NaN'
            results['grid_{}_std'.format(scoring)] = 'NaN'
            results['grid_{}_best_params'.format(scoring)] = 'NaN'
            return results

    def get_scores(self, X, y, row_keys, scoring, collect_n=None):
        """

        Parameters:
        -----------

        X :

        y :

        row_keys :

        scoring :

        collect_n :

        Returns:
        --------

        """
        columns = list(self.plan_table.columns)[-len(row_keys):]
        param_key = ''.join(row_keys) + str(scoring)

        steps = list()
        for row_key, column in zip(row_keys, columns):
            steps.append((row_key, self.named_steps[column][row_key]))

        steps[-1][1].set_params(**self.best_params[param_key])

        if not collect_n:
            scores = cross_val_score(Pipeline(steps), X, y,
                                     scoring=scoring,
                                     cv=self.eval_cv,
                                     n_jobs=-1)
        else:
            init_random_state = self.eval_cv.random_state
            scores = list()
            for i in range(collect_n):
                fold_prediction = cross_val_predict(Pipeline(steps), X, y,
                                                     cv=self.eval_cv,
                                                     n_jobs=-1)
                metric = check_scoring(steps[-1][1],
                                    scoring=scoring).__dict__['_score_func']
                scores.append(metric(y, fold_prediction))
                self.eval_cv.random_state += 1

            self.eval_cv.random_state = init_random_state
        return scores

    def get_results(self, data, caching_steps=list(), scoring='accuracy',
            results_file='results.csv', logs_file='results.log', collect_n=None):
        """

        Parameters:
        -----------
        data :

        caching_steps :

        scoring :

        results_file :

        logs_file :

        collect_n :

        Returns:
        --------

        """
        if type(scoring) == str:
            scoring = [scoring]

        columns = list(self.plan_table.columns)
        without_caching = [step for step in columns
                                if step not in caching_steps]

        for metric in scoring:
            grid_steps = ['grid_{}_mean'.format(metric),
                          'grid_{}_std'.format(metric),
                          'grid_{}_best_params'.format(metric)]

            eval_steps = ['eval_{}_mean'.format(metric),
                          'eval_{}_std'.format(metric),
                          'eval_{}_scores'.format(metric)]

            columns += grid_steps + eval_steps

        results = DataFrame(columns=columns)

        try:
            os.remove(results_file)
            print('Removed previous results file -- {}.'.format(results_file))
        except:
            print('No previous results found.')
        if results_file != None:
            results.to_csv(results_file)

        columns = list(self.plan_table.columns)
        results[columns] = self.plan_table

        with open(logs_file, 'w+') as logs:
            N = len(self.plan_table.index)
            for idx in self.plan_table.index:
                print('Line: {}/{}'.format(idx + 1, N))
                logs.write('Line: {}/{}\n'.format(idx + 1, N))
                logs.write('{}\n'.format(str(self.plan_table.loc[idx])))
                row = self.plan_table.loc[idx]
                caching_keys = list(row[caching_steps].values)

                time_point = time()
                X_featured, y = self.transform_with_caching(data, caching_keys)
                spent_time = round(time() - time_point, 3)
                logs.write('Got Features: {} sec\n'.format(spent_time))

                for metric in scoring:
                    logs.write('Scoring: {}\n'.format(metric))
                    ml_keys = list(row[without_caching].values)
                    time_point = time()
                    grid_res = self.get_grid_search_results(X_featured, y,
                                                            ml_keys,
                                                            metric)
                    spent_time = round(time() - time_point, 3)
                    logs.write('Grid Search: {} sec\n'.format(spent_time))
                    logs.write('Grid Search Results: {}\n'.format(grid_res))

                    for key, value in grid_res.items():
                        results.loc[idx][key] = value

                    time_point = time()
                    scores = self.get_scores(X_featured, y,
                                             ml_keys,
                                             metric,
                                             collect_n)
                    spent_time = round(time() - time_point, 3)
                    logs.write('Got Scores: {} sec\n'.format(spent_time))

                    mean_key = 'eval_{}_mean'.format(metric)
                    scores_mean = mean(scores)
                    results.loc[idx][mean_key] = scores_mean
                    logs.write('Scores mean: {}\n'.format(scores_mean))

                    std_key = 'eval_{}_std'.format(metric)
                    scores_std = std(scores)
                    results.loc[idx][std_key] = scores_std
                    logs.write('Scores std: {}\n'.format(scores_std))

                    scores_key = 'eval_{}_scores'.format(metric)
                    results.loc[idx][scores_key] = str(scores)
                    logs.write('Scores: {}\n\n'.format(str(scores)))
                results.loc[[idx]].to_csv(results_file, header=False, mode='a+')

        return results

class Transformer(TransformerMixin, BaseEstimator):
    """

    Parameters:
    -----------
    func :

    params :

    collect :

    Attributes:
    -----------

    """
    def __init__(self, func, params=None, collect=None):
        self.func = func
        self.params = params
        self.collect = collect

    def fit(self, data):
        """

        Parameters:
        -----------
        data :

        Returns:
        --------

        """
        return self

    def transform(self, data):
        """

        Parameters:
        -----------
        data :

        Returns:
        --------
        """
        if type(data) == dict:
            data = data.copy()

        if self.params:
            self.params['data'] = data
            result = self.func(**self.params)
        else:
            result = self.func(data)

        if self.collect:
            y = result['y']
            X = result[self.collect[0]]

            for key in self.collect[1:]:
                X = hstack((X, result[key]))
            return X, y

        return result

__all__ = ['Transformer', 'Pipeliner']


# coding: utf-8

import unittest
import httpretty
import json
from backlog.base import BacklogAPI

API_ENDPOINT = "https://{space}.backlog.jp/api/v2/{uri}"

class TestGit(unittest.TestCase):
    def setUp(self):
        self.api = BacklogAPI("space", "api_key")
        self.space = "space"
        self.api_key = "api_key"

    #
    # For Git Repository
    #
    @httpretty.activate
    def test_list_git_repositories(self):
        _project_id_or_key = 151
        _uri = "projects/{projectIdOrKey}/git/repositories".format(projectIdOrKey=_project_id_or_key)

        expects = [
            {
                "id": 1,
                "projectId": _project_id_or_key,
                "name": "app",
                "description": "",
                "hookUrl": None,
                "httpUrl": "https://xx.backlog.jp/git/BLG/app.git",
                "sshUrl": "xx@xx.git.backlog.jp:/BLG/app.git",
                "displayOrder": 0,
                "pushedAt": None,
                "createdUser": {
                    "id": 1,
                    "userId": "admin",
                    "name": "admin",
                    "roleType": 1,
                    "lang": "ja",
                    "mailAddress": "eguchi@nulab.example"
                },
                "created": "2013-05-30T09:11:36Z",
                "updatedUser": {
                    "id": 1,
                    "userId": "admin",
                    "name": "admin",
                    "roleType": 1,
                    "lang": "ja",
                    "mailAddress": "eguchi@nulab.example"
                },
                "updated": "2013-05-30T09:11:36Z"
            }
        ]

        httpretty.register_uri(
            httpretty.GET,
            API_ENDPOINT.format(space=self.space, uri=_uri),
            body=json.dumps(expects),
            status=200
        )

        resp = self.api.git.list_git_repositories(projectIdOrKey=_project_id_or_key)

        self.assertEqual(expects, resp)

    @httpretty.activate
    def test_get_git_repositories(self):
        pass

    #
    # For Pull Request
    #
    @httpretty.activate
    def test_list_pull_requests(self):
        _project_id_or_key = 3
        _repo_id_or_name = 5
        _uri = "projects/{projectIdOrKey}/git/repositories/{repoIdOrName}/pullRequests".format(
            projectIdOrKey=_project_id_or_key,
            repoIdOrName=_repo_id_or_name
        )

        expects = [
            {
                "id": 2,
                "projectId": 3,
                "repositoryId": 5,
                "number": 1,
                "summary": "test",
                "description": "test data",
                "base": "master",
                "branch": "develop",
                "status": {
                    "id": 1,
                    "name": "Open"
                },
                "assignee": {
                    "id": 5,
                    "userId": "testuser2",
                    "name": "testuser2",
                    "roleType": 1,
                    "lang": None,
                    "mailAddress": "testuser2@nulab.test"
                },
                "issue": {
                    "id": 31
                },
                "baseCommit": None,
                "branchCommit": None,
                "closeAt": None,
                "mergeAt": None,
                "createdUser": {
                    "id": 1,
                    "userId": "admin",
                    "name": "admin",
                    "roleType": 1,
                    "lang": "ja",
                    "mailAddress": "eguchi@nulab.example"
                },
                "created": "2015-04-23T03:04:14Z",
                "updatedUser": {
                    "id": 1,
                    "userId": "admin",
                    "name": "admin",
                    "roleType": 1,
                    "lang": "ja",
                    "mailAddress": "eguchi@nulab.example"
                },
                "updated": "2015-04-23T03:04:14Z"
            }
        ]

        httpretty.register_uri(
            httpretty.GET,
            API_ENDPOINT.format(space=self.space, uri=_uri),
            body=json.dumps(expects),
            status=200
        )

        resp = self.api.git.list_pull_requests(
            projectIdOrKey=_project_id_or_key,
            repoIdOrName=_repo_id_or_name
        )

        self.assertEqual(expects, resp)

    @httpretty.activate
    def test_count_pull_requests(self):
        pass

    @httpretty.activate
    def test_add_pull_request(self):
        _project_id_or_key = 3
        _repository_id_or_key = 5
        _uri = "projects/{projectIdOrKey}/git/repositories/{repoIdOrName}/pullRequests".format(
            projectIdOrKey=_project_id_or_key,
            repoIdOrName=_repository_id_or_key
        )

        expects = {
            "id": 2,
            "projectId": _project_id_or_key,
            "repositoryId": _repository_id_or_key,
            "number": 1,
            "summary": "test",
            "description": "test data",
            "base": "master",
            "branch": "develop",
            "status": {
                "id": 1,
                "name": "Open"
            },
            "assignee": {
                "id": 5,
                "userId": "testuser2",
                "name": "testuser2",
                "roleType": 1,
                "lang": None,
                "mailAddress": "testuser2@nulab.test"
            },
            "issue": {
                "id": 1,
                "projectId": 1,
                "issueKey": "BLG-1",
                "keyId": 1,
                "issueType": {
                    "id": 2,
                    "projectId": 1,
                    "name": "タスク",
                    "color": "#7ea800",
                    "displayOrder": 0
                },
                "summary": "first issue",
                "description": "",
                "resolutions": None,
                "priority": {
                    "id": 3,
                    "name": "中"
                },
                "status": {
                    "id": 1,
                    "name": "未対応"
                },
                "assignee": {
                    "id": 2,
                    "name": "eguchi",
                    "roleType": 2,
                    "lang": None,
                    "mailAddress": "eguchi@nulab.example"
                },
                "category": [],
                "versions": [],
                "milestone": [
                    {
                        "id": 30,
                        "projectId": 1,
                        "name": "wait for release",
                        "description": "",
                        "startDate": None,
                        "releaseDueDate": None,
                        "archived": False
                    }
                ],
                "startDate": None,
                "dueDate": None,
                "estimatedHours": None,
                "actualHours": None,
                "parentIssueId": None,
                "createdUser": {
                    "id": 1,
                    "userId": "admin",
                    "name": "admin",
                    "roleType": 1,
                    "lang": "ja",
                    "mailAddress": "eguchi@nulab.example"
                },
                "created": "2012-07-23T06:10:15Z",
                "updatedUser": {
                    "id": 1,
                    "userId": "admin",
                    "name": "admin",
                    "roleType": 1,
                    "lang": "ja",
                    "mailAddress": "eguchi@nulab.example"
                },
                "updated": "2013-02-07T08:09:49Z",
                "customFields": [],
                "attachments": [
                    {
                        "id": 1,
                        "name": "IMGP0088.JPG",
                        "size": 85079
                    }
                ],
                "sharedFiles": [
                    {
                        "id": 454403,
                        "type": "file",
                        "dir": "/ユーザアイコン/",
                        "name": "01_サラリーマン.png",
                        "size": 2735,
                        "createdUser": {
                            "id": 5686,
                            "userId": "takada",
                            "name": "takada",
                            "roleType": 2,
                            "lang": "ja",
                            "mailAddress": "takada@nulab.example"
                        },
                        "created": "2009-02-27T03:26:15Z",
                        "updatedUser": {
                            "id": 5686,
                            "userId": "takada",
                            "name": "takada",
                            "roleType": 2,
                            "lang": "ja",
                            "mailAddress": "takada@nulab.example"
                        },
                        "updated": "2009-03-03T16:57:47Z"
                    }
                ],
                "stars": [
                    {
                        "id": 10,
                        "comment": None,
                        "url": "https://xx.backlog.jp/view/BLG-1",
                        "title": "[BLG-1] first issue | 課題の表示 - Backlog",
                        "presenter": {
                            "id": 2,
                            "userId": "eguchi",
                            "name": "eguchi",
                            "roleType": 2,
                            "lang": "ja",
                            "mailAddress": "eguchi@nulab.example"
                        },
                        "created": "2013-07-08T10:24:28Z"
                    }
                ]
            },
            "baseCommit": None,
            "branchCommit": None,
            "closeAt": None,
            "mergeAt": None,
            "createdUser": {
                "id": 1,
                "userId": "admin",
                "name": "admin",
                "roleType": 1,
                "lang": "ja",
                "mailAddress": "eguchi@nulab.example"
            },
            "created": "2015-04-23T03:04:14Z",
            "updatedUser": {
                "id": 1,
                "userId": "admin",
                "name": "admin",
                "roleType": 1,
                "lang": "ja",
                "mailAddress": "eguchi@nulab.example"
            },
            "updated": "2015-04-23T03:04:14Z"
        }

        httpretty.register_uri(
            httpretty.POST,
            API_ENDPOINT.format(space=self.space, uri=_uri),
            body=json.dumps(expects),
            status=200
        )

        resp = self.api.git.add_pull_request(
            projectIdOrKey=_project_id_or_key,
            repoIdOrName=_repository_id_or_key)

        self.assertEqual(expects, resp)

    @httpretty.activate
    def test_get_pull_requests(self):
        pass

    @httpretty.activate
    def test_update_pull_requests(self):
        pass

    @httpretty.activate
    def test_get_pull_request_comment(self):
        pass

    @httpretty.activate
    def test_add_pull_request_comment(self):
        _project_id_or_key = 3
        _repository_id_or_key = 5
        _number = 1
        _content = "test",
        _notified_user_ids = []
        _uri = "projects/{projectIdOrKey}/git/repositories/{repoIdOrName}/pullRequests/{number}/comments".format(
            projectIdOrKey=_project_id_or_key,
            repoIdOrName=_repository_id_or_key,
            number=_number
        )

        expects = {
            "id": 35,
            "content": "from api",
            "changeLog": [
                {
                    "field": "dependentIssue",
                    "newValue": "GIT-3",
                    "originalValue": None
                }
            ],
            "createdUser": {
                "id": 1,
                "userId": "admin",
                "name": "admin",
                "roleType": 1,
                "lang": "ja",
                "mailAddress": "eguchi@nulab.example"
            },
            "created": "2015-05-14T01:53:38Z",
            "updated": "2015-05-14T01:53:38Z",
            "stars": [],
            "notifications": []
        }

        httpretty.register_uri(
            httpretty.POST,
            API_ENDPOINT.format(space=self.space, uri=_uri),
            body=json.dumps(expects),
            status=200
        )

        resp = self.api.git.add_pull_request_comment(
            projectIdOrKey=_project_id_or_key,
            repoIdOrName=_repository_id_or_key,
            number=_number,
            content=_content,
            notifiedUserIds=_notified_user_ids
        )

        self.assertEqual(expects, resp)

    @httpretty.activate
    def test_count_pull_request_comments(self):
        pass

    @httpretty.activate
    def test_update_pull_request_comment(self):
        pass

    @httpretty.activate
    def test_list_pull_request_attachments(self):
        pass

    @httpretty.activate
    def test_get_pull_request_attachment(self):
        pass

    @httpretty.activate
    def test_delelte_pull_request_attachment(self):
        pass

if __name__ == "__main__":
    unittest.main(warnings='ignore')


# Program: Translator
# Name: Gurtej Virdi
# Date: Nov 28, 2020

# Create a dictionary in which the keys and values were swapped
def reverseDictionary(dictionary):
    return {v: k for k, v in dictionary.items()}

# Dictionaries
EtoF_nouns = {'bread': 'pain', 'wine': 'vin', 'friends': 'amis'}
EtoF_adjectives = {'red': 'rouge'}
EtoF_verbs = {'eat': 'mange', 'drink': 'bois', 'love': 'amour'}
EtoF = {'with': 'avec', 'and': 'et',
        'as': 'comme', 'i': 'je', 'his': 'son', 'that': 'que', 'he': 'il', 'was': 'Ã©tait', 'for': 'pour', 'on': 'sur',
        'are': 'sont', 'with': 'avec', 'they': 'ils', 'be': 'Ãªtre', 'at': 'Ã\xa0', 'one': 'un', 'have': 'avoir',
        'this': 'ce', 'from': 'Ã\xa0 partir de', 'by': 'par', 'hot': 'chaud', 'word': 'mot', 'but': 'mais',
        'what': 'que', 'some': 'certains', 'is': 'est', 'it': 'il', 'you': 'vous', 'or': 'ou', 'had': 'eu',
        'the': 'la', 'of': 'du', 'to': 'Ã\xa0', 'and': 'et', 'a': 'un', 'in': 'dans', 'we': 'nous', 'can': 'boÃ®te',
        'out': 'dehors', 'other': 'autre', 'were': 'Ã©taient', 'which': 'qui', 'do': 'faire', 'their': 'leur',
        'time': 'temps', 'if': 'si', 'will': 'volontÃ©', 'how': 'comment', 'said': 'dit', 'an': 'un', 'each': 'chaque',
        'tell': 'dire', 'does': 'ne', 'set': 'ensemble', 'three': 'trois', 'want': 'vouloir', 'air': 'air',
        'well': 'bien', 'also': 'aussi', 'play': 'jouer', 'small': 'petit', 'end': 'fin', 'put': 'mettre',
        'home': 'maison', 'read': 'lire', 'hand': 'main', 'port': 'port', 'large': 'grand', 'spell': 'Ã©peler',
        'add': 'ajouter', 'even': 'mÃªme', 'land': 'terre', 'here': 'ici', 'must': 'il faut', 'big': 'grand',
        'high': 'haut', 'such': 'tel', 'follow': 'suivre', 'act': 'acte', 'why': 'pourquoi', 'ask': 'interroger',
        'men': 'hommes', 'change': 'changement', 'went': 'est allÃ©', 'light': 'lumiÃ¨re', 'kind': 'genre',
        'off': 'de', 'need': 'besoin', 'house': 'maison', 'picture': 'image', 'try': 'essayer', 'us': 'nous',
        'again': 'encore', 'animal': 'animal', 'point': 'point', 'mother': 'mÃ¨re', 'world': 'monde',
        'near': 'prÃ¨s de', 'build': 'construire', 'self': 'soi', 'earth': 'terre', 'father': 'pÃ¨re', 'any': 'tout',
        'new': 'nouveau', 'work': 'travail', 'part': 'partie', 'take': 'prendre', 'get': 'obtenir', 'place': 'lieu',
        'made': 'fabriquÃ©', 'live': 'vivre', 'where': 'oÃ¹', 'after': 'aprÃ¨s', 'back': 'arriÃ¨re', 'little': 'peu',
        'only': 'seulement', 'round': 'tour', 'man': 'homme', 'year': 'annÃ©e', 'came': 'est venu', 'show': 'montrer',
        'every': 'tous', 'good': 'bon', 'me': 'moi', 'give': 'donner', 'our': 'notre', 'under': 'sous', 'name': 'nom',
        'very': 'trÃ¨s', 'through': 'par', 'just': 'juste', 'form': 'forme', 'sentence': 'phrase', 'great': 'grand',
        'think': 'penser', 'say': 'dire', 'help': 'aider', 'low': 'faible', 'line': 'ligne', 'differ': 'diffÃ©rer',
        'turn': 'tour', 'cause': 'la cause', 'much': 'beaucoup', 'mean': 'signifier', 'before': 'avant',
        'move': 'dÃ©mÃ©nagement', 'right': 'droit', 'boy': 'garÃ§on', 'old': 'vieux', 'too': 'trop', 'same': 'mÃªme',
        'she': 'elle', 'all': 'tous', 'there': 'lÃ\xa0', 'when': 'quand', 'up': 'jusquâ€™Ã\xa0', 'use': 'utiliser',
        'your': 'votre', 'way': 'maniÃ¨re', 'about': 'sur', 'many': 'beaucoup', 'then': 'puis', 'them': 'les',
        'write': 'Ã©crire', 'would': 'voudrais', 'like': 'comme', 'so': 'si', 'these': 'ces', 'her': 'son',
        'long': 'long', 'make': 'faire', 'thing': 'chose', 'see': 'voir', 'him': 'lui', 'two': 'deux', 'has': 'a',
        'look': 'regarder', 'more': 'plus', 'day': 'jour', 'could': 'pourrait', 'go': 'aller', 'come': 'venir',
        'did': 'fait', 'number': 'nombre', 'sound': 'son', 'no': 'aucun', 'most': 'plus', 'people': 'personnes',
        'my': 'ma', 'over': 'sur', 'know': 'savoir', 'water': 'eau', 'than': 'que', 'call': 'appel',
        'first': 'premiÃ¨re', 'who': 'qui', 'may': 'peut', 'down': 'vers le bas', 'side': 'cÃ´tÃ©', 'been': 'Ã©tÃ©',
        'now': 'maintenant', 'find': 'trouver', 'head': 'tÃªte', 'stand': 'supporter', 'own': 'propre', 'page': 'page',
        'should': 'devrait', 'country': 'pays', 'found': 'trouvÃ©', 'answer': 'rÃ©ponse', 'school': 'Ã©cole',
        'grow': 'croÃ®tre', 'study': 'Ã©tude', 'still': 'encore', 'learn': 'apprendre', 'plant': 'usine',
        'cover': 'couvercle', 'food': 'nourriture', 'sun': 'soleil', 'four': 'quatre', 'between': 'entre',
        'state': 'Ã©tat', 'keep': 'garder', 'eye': 'Å“il', 'never': 'jamais', 'last': 'dernier', 'let': 'laisser',
        'thought': 'pensÃ©e', 'city': 'ville', 'tree': 'arbre', 'cross': 'traverser', 'farm': 'ferme', 'hard': 'dur',
        'start': 'dÃ©but', 'might': 'puissance', 'story': 'histoire', 'saw': 'scie', 'far': 'loin', 'sea': 'mer',
        'draw': 'tirer', 'left': 'gauche', 'late': 'tard', 'run': 'courir', 'donâ€™t': 'needs a context',
        'while': 'tandis que', 'press': 'presse', 'close': 'proche', 'night': 'nuit', 'real': 'rÃ©el', 'life': 'vie',
        'few': 'peu', 'north': 'nord', 'book': 'livre', 'carry': 'porter', 'took': 'a pris', 'science': 'science',
        'eat': 'manger', 'room': 'chambre', 'friend': 'ami', 'began': 'a commencÃ©', 'idea': 'idÃ©e',
        'fish': 'poisson', 'mountain': 'montagne', 'stop': 'ArrÃªtez', 'once': 'une fois', 'base': 'base',
        'hear': 'entendre', 'horse': 'cheval', 'cut': 'coupe', 'sure': 'sÃ»r', 'watch': 'regarder', 'color': 'couleur',
        'face': 'face', 'wood': 'bois', 'main': 'principal', 'open': 'ouvert', 'seem': 'paraÃ®tre',
        'together': 'ensemble', 'next': 'suivant', 'white': 'blanc', 'children': 'enfants', 'begin': 'commencer',
        'got': 'eu', 'walk': 'marcher', 'example': 'exemple', 'ease': 'facilitÃ©', 'paper': 'papier',
        'group': 'groupe', 'always': 'toujours', 'music': 'musique', 'those': 'ceux', 'both': 'tous les deux',
        'mark': 'marque', 'often': 'souvent', 'letter': 'lettre', 'until': 'jusquâ€™Ã\xa0 ce que', 'mile': 'mile',
        'river': 'riviÃ¨re', 'car': 'voiture', 'feet': 'pieds', 'care': 'soins', 'second': 'deuxiÃ¨me',
        'enough': 'assez', 'plain': 'plaine', 'girl': 'fille', 'usual': 'habituel', 'young': 'jeune', 'ready': 'prÃªt',
        'above': 'au-dessus', 'ever': 'jamais', 'red': 'rouge', 'list': 'liste', 'though': 'bien que',
        'feel': 'sentir', 'talk': 'parler', 'bird': 'oiseau', 'soon': 'bientÃ´t', 'body': 'corps', 'dog': 'chien',
        'family': 'famille', 'direct': 'direct', 'pose': 'pose', 'leave': 'laisser', 'song': 'chanson',
        'measure': 'mesurer', 'door': 'porte', 'product': 'produit', 'black': 'noir', 'short': 'court',
        'numeral': 'chiffre', 'class': 'classe', 'wind': 'vent', 'question': 'question', 'happen': 'arriver',
        'complete': 'complÃ¨te', 'ship': 'navire', 'area': 'zone', 'half': 'moitiÃ©', 'rock': 'rock', 'order': 'ordre',
        'fire': 'feu', 'south': 'sud', 'problem': 'problÃ¨me', 'piece': 'piÃ¨ce', 'told': 'dit', 'knew': 'savait',
        'pass': 'passer', 'since': 'depuis', 'top': 'haut', 'whole': 'ensemble', 'king': 'roi', 'street': 'rue',
        'inch': 'pouce', 'multiply': 'multiplier', 'nothing': 'rien', 'course': 'cours', 'stay': 'rester',
        'wheel': 'roue', 'full': 'plein', 'force': 'force', 'blue': 'bleu', 'object': 'objet', 'decide': 'dÃ©cider',
        'surface': 'surface', 'deep': 'profond', 'moon': 'lune', 'island': 'Ã®le', 'foot': 'pied',
        'system': 'systÃ¨me', 'busy': 'occupÃ©', 'test': 'test', 'record': 'record', 'boat': 'bateau',
        'common': 'commun', 'gold': 'or', 'possible': 'possible', 'plane': 'plan', 'stead': 'place', 'dry': 'sec',
        'wonder': 'se demander', 'laugh': 'rire', 'thousand': 'mille', 'ago': 'il ya', 'ran': 'ran',
        'check': 'vÃ©rifier', 'game': 'jeu', 'shape': 'forme', 'equate': 'assimiler', 'miss': 'manquer',
        'brought': 'apportÃ©', 'heat': 'chaleur', 'snow': 'neige', 'tire': 'pneu', 'bring': 'apporter', 'yes': 'oui',
        'distant': 'lointain', 'fill': 'remplir', 'east': 'est', 'paint': 'peindre', 'language': 'langue',
        'among': 'entre', 'unit': 'unitÃ©', 'power': 'puissance', 'town': 'ville', 'fine': 'fin', 'certain': 'certain',
        'fly': 'voler', 'fall': 'tomber', 'lead': 'conduire', 'cry': 'cri', 'dark': 'sombre', 'machine': 'machine',
        'note': 'Note', 'wait': 'patienter', 'plan': 'plan', 'figure': 'figure', 'star': 'Ã©toile', 'box': 'boÃ®te',
        'noun': 'nom', 'field': 'domaine', 'rest': 'reste', 'correct': 'correct', 'able': 'capable', 'pound': 'livre',
        'done': 'TerminÃ©', 'beauty': 'beautÃ©', 'drive': 'entraÃ®nement', 'stood': 'rÃ©sistÃ©', 'contain': 'contenir',
        'front': 'avant', 'teach': 'enseigner', 'week': 'semaine', 'final': 'finale', 'gave': 'donnÃ©',
        'green': 'vert', 'oh': 'oh', 'quick': 'rapide', 'develop': 'dÃ©velopper', 'ocean': 'ocÃ©an', 'warm': 'chaud',
        'free': 'gratuit', 'minute': 'minute', 'strong': 'fort', 'special': 'spÃ©cial', 'mind': 'esprit',
        'behind': 'derriÃ¨re', 'clear': 'clair', 'tail': 'queue', 'produce': 'produire', 'fact': 'fait',
        'space': 'espace', 'heard': 'entendu', 'best': 'meilleur', 'hour': 'heure', 'better': 'mieux', 'TRUE': 'vrai',
        'during': 'pendant', 'hundred': 'cent', 'five': 'cinq', 'remember': 'rappeler', 'step': 'Ã©tape',
        'early': 'tÃ´t', 'hold': 'tenir', 'west': 'ouest', 'ground': 'sol', 'interest': 'intÃ©rÃªt',
        'reach': 'atteindre', 'fast': 'rapide', 'verb': 'verbe', 'sing': 'chanter', 'listen': 'Ã©couter', 'six': 'six',
        'table': 'table', 'travel': 'Voyage', 'less': 'moins', 'morning': 'matin', 'ten': 'dix', 'simple': 'simple',
        'several': 'plusieurs', 'vowel': 'voyelle', 'toward': 'vers', 'war': 'guerre', 'lay': 'poser',
        'against': 'contre', 'pattern': 'modÃ¨le', 'slow': 'lent', 'center': 'centre', 'love': 'amour',
        'person': 'personne', 'money': 'argent', 'serve': 'servir', 'appear': 'apparaÃ®tre', 'road': 'route',
        'map': 'carte', 'rain': 'pluie', 'rule': 'rÃ¨gle', 'govern': 'gouverner', 'pull': 'tirer', 'cold': 'froid',
        'notice': 'avis', 'voice': 'voix', 'energy': 'Ã©nergie', 'hunt': 'chasse', 'probable': 'probable',
        'bed': 'lit', 'brother': 'frÃ¨re', 'egg': 'Å“uf', 'ride': 'tour', 'cell': 'cellule', 'believe': 'croire',
        'perhaps': 'peut-Ãªtre', 'pick': 'choisir', 'sudden': 'soudain', 'count': 'compter', 'square': 'carrÃ©',
        'reason': 'raison', 'length': 'longueur', 'represent': 'reprÃ©senter', 'art': 'art', 'subject': 'sujet',
        'region': 'rÃ©gion', 'size': 'taille', 'vary': 'varier', 'settle': 'rÃ©gler', 'speak': 'parler',
        'weight': 'poids', 'general': 'gÃ©nÃ©ral', 'ice': 'glace', 'matter': 'question', 'circle': 'cercle',
        'pair': 'paire', 'include': 'inclure', 'divide': 'fracture', 'syllable': 'syllabe', 'felt': 'feutre',
        'grand': 'grandiose', 'ball': 'balle', 'yet': 'encore', 'wave': 'vague', 'drop': 'tomber', 'heart': 'cÅ“ur',
        'am': 'h', 'present': 'prÃ©sent', 'heavy': 'lourd', 'dance': 'danse', 'engine': 'moteur',
        'position': 'position', 'arm': 'bras', 'wide': 'large', 'sail': 'voile', 'material': 'matÃ©riel',
        'fraction': 'fraction', 'forest': 'forÃªt', 'sit': 'sâ€™asseoir', 'race': 'course', 'window': 'fenÃªtre',
        'store': 'magasin', 'summer': 'Ã©tÃ©', 'train': 'train', 'sleep': 'sommeil', 'prove': 'prouver',
        'lone': 'seul', 'leg': 'jambe', 'exercise': 'exercice', 'wall': 'mur', 'catch': 'capture', 'mount': 'monture',
        'wish': 'souhaiter', 'sky': 'ciel', 'board': 'conseil', 'joy': 'joie', 'winter': 'hiver', 'sat': 'sat',
        'written': 'Ã©crit', 'wild': 'sauvage', 'instrument': 'instrument', 'kept': 'conservÃ©', 'glass': 'verre',
        'grass': 'herbe', 'cow': 'vache', 'job': 'emploi', 'edge': 'bord', 'sign': 'signe', 'visit': 'visite',
        'past': 'passÃ©', 'soft': 'doux', 'fun': 'amusement', 'bright': 'clair', 'gas': 'gaz', 'weather': 'temps',
        'month': 'mois', 'million': 'million', 'bear': 'porter', 'finish': 'finition', 'happy': 'heureux',
        'hope': 'espoir', 'flower': 'fleur', 'clothe': 'vÃªtir', 'strange': 'Ã©trange', 'gone': 'disparu',
        'trade': 'commerce', 'melody': 'mÃ©lodie', 'trip': 'voyage', 'office': 'bureau', 'receive': 'recevoir',
        'row': 'rangÃ©e', 'mouth': 'bouche', 'exact': 'exact', 'symbol': 'symbole', 'die': 'mourir', 'least': 'moins',
        'trouble': 'difficultÃ©', 'shout': 'cri', 'except': 'sauf', 'wrote': 'Ã©crit', 'seed': 'semence',
        'tone': 'ton', 'join': 'joindre', 'suggest': 'suggÃ©rer', 'clean': 'propre', 'break': 'pause', 'lady': 'dame',
        'yard': 'cour', 'rise': 'augmenter', 'bad': 'mauvais', 'blow': 'coup', 'oil': 'huile', 'blood': 'sang',
        'touch': 'toucher', 'grew': 'a augmentÃ©', 'cent': 'cent', 'mix': 'mÃ©langer', 'team': 'Ã©quipe',
        'wire': 'fil', 'cost': 'coÃ»t', 'lost': 'perdu', 'brown': 'brun', 'wear': 'porter', 'garden': 'jardin',
        'equal': 'Ã©gal', 'sent': 'expÃ©diÃ©', 'choose': 'choisir', 'fell': 'est tombÃ©', 'fit': 'sâ€™adapter',
        'flow': 'dÃ©bit', 'fair': 'juste', 'bank': 'banque', 'collect': 'recueillir', 'save': 'sauver',
        'control': 'contrÃ´le', 'decimal': 'dÃ©cimal', 'ear': 'oreille', 'else': 'autre', 'quite': 'tout Ã\xa0 fait',
        'broke': 'cassÃ©', 'case': 'cas', 'middle': 'milieu', 'kill': 'tuer', 'son': 'fils', 'lake': 'lac',
        'moment': 'moment', 'scale': 'Ã©chelle', 'loud': 'fort', 'spring': 'printemps', 'observe': 'observer',
        'child': 'enfant', 'straight': 'droit', 'consonant': 'consonne', 'nation': 'nation',
        'dictionary': 'dictionnaire', 'milk': 'lait', 'speed': 'vitesse', 'method': 'mÃ©thode', 'organ': 'organe',
        'pay': 'payer', 'age': 'Ã¢ge', 'section': 'section', 'dress': 'robe', 'cloud': 'nuage', 'surprise': 'surprise',
        'quiet': 'calme', 'stone': 'pierre', 'tiny': 'minuscule', 'climb': 'montÃ©e', 'cool': 'frais',
        'design': 'conception', 'poor': 'pauvres', 'lot': 'lot', 'experiment': 'expÃ©rience', 'bottom': 'bas',
        'key': 'clÃ©', 'iron': 'fer', 'single': 'unique', 'stick': 'bÃ¢ton', 'flat': 'plat', 'twenty': 'vingt',
        'skin': 'peau', 'smile': 'sourire', 'crease': 'pli', 'hole': 'trou', 'jump': 'sauter', 'baby': 'bÃ©bÃ©',
        'eight': 'huit', 'village': 'village', 'meet': 'se rencontrent', 'root': 'racine', 'buy': 'acheter',
        'raise': 'augmenter', 'solve': 'rÃ©soudre', 'metal': 'mÃ©tal', 'whether': 'si', 'push': 'pousser',
        'seven': 'sept', 'paragraph': 'paragraphe', 'third': 'troisiÃ¨me', 'shall': 'doit', 'held': 'en attente',
        'hair': 'cheveux', 'describe': 'dÃ©crire', 'cook': 'cuisinier', 'floor': 'Ã©tage', 'either': 'chaque',
        'result': 'rÃ©sultat', 'burn': 'brÃ»ler', 'hill': 'colline', 'safe': 'coffre-fort', 'cat': 'chat',
        'century': 'siÃ¨cle', 'consider': 'envisager', 'type': 'type', 'law': 'droit', 'bit': 'peu', 'coast': 'cÃ´te',
        'copy': 'copie', 'phrase': 'phrase', 'silent': 'silencieux', 'tall': 'haut', 'sand': 'sable', 'soil': 'sol',
        'roll': 'rouleau', 'temperature': 'tempÃ©rature', 'finger': 'doigt', 'industry': 'industrie',
        'value': 'valeur', 'fight': 'lutte', 'lie': 'mensonge', 'beat': 'battre', 'excite': 'exciter',
        'natural': 'naturel', 'view': 'vue', 'sense': 'sens', 'capital': 'capital', 'wonâ€™t': 'ne sera pas',
        'chair': 'chaise', 'danger': 'danger', 'fruit': 'fruit', 'rich': 'riche', 'thick': 'Ã©pais',
        'soldier': 'soldat', 'process': 'processus', 'operate': 'fonctionner', 'practice': 'pratique',
        'separate': 'sÃ©parÃ©', 'difficult': 'difficile', 'doctor': 'mÃ©decin', 'please': 'sâ€™il vous plaÃ®t',
        'protect': 'protÃ©ger', 'noon': 'midi', 'crop': 'rÃ©colte', 'modern': 'moderne', 'element': 'Ã©lÃ©ment',
        'hit': 'frapper', 'student': 'Ã©tudiant', 'corner': 'coin', 'party': 'partie', 'supply': 'alimentation',
        'whose': 'dont', 'locate': 'localiser', 'ring': 'anneau', 'character': 'caractÃ¨re', 'insect': 'insecte',
        'caught': 'pris', 'period': 'pÃ©riode', 'indicate': 'indiquer', 'radio': 'radio', 'spoke': 'rayon',
        'atom': 'atome', 'human': 'humain', 'history': 'histoire', 'effect': 'effet', 'electric': 'Ã©lectrique',
        'expect': 'attendre', 'bone': 'os', 'rail': 'rail', 'imagine': 'imaginer', 'provide': 'fournir',
        'agree': 'se mettre dâ€™accord', 'thus': 'ainsi', 'gentle': 'doux', 'woman': 'femme', 'captain': 'capitaine',
        'guess': 'deviner', 'necessary': 'nÃ©cessaire', 'sharp': 'net', 'wing': 'aile', 'create': 'crÃ©er',
        'neighbor': 'voisin', 'wash': 'lavage', 'bat': 'chauve-souris', 'rather': 'plutÃ´t', 'crowd': 'foule',
        'corn': 'blÃ©', 'compare': 'comparer', 'poem': 'poÃ¨me', 'string': 'chaÃ®ne', 'bell': 'cloche',
        'depend': 'dÃ©pendre', 'meat': 'viande', 'rub': 'rub', 'tube': 'tube', 'famous': 'cÃ©lÃ¨bre',
        'dollar': 'dollar', 'stream': 'courant', 'fear': 'peur', 'sight': 'vue', 'thin': 'mince',
        'triangle': 'triangle', 'planet': 'planÃ¨te', 'hurry': 'se dÃ©pÃªcher', 'chief': 'chef', 'colony': 'colonie',
        'clock': 'horloge', 'mine': 'mine', 'tie': 'lien', 'enter': 'entrer', 'major': 'majeur', 'fresh': 'frais',
        'search': 'recherche', 'send': 'envoyer', 'yellow': 'jaune', 'gun': 'pistolet', 'allow': 'permettre',
        'print': 'impression', 'dead': 'mort', 'spot': 'place', 'desert': 'dÃ©sert', 'suit': 'costume',
        'current': 'courant', 'lift': 'ascenseur', 'rose': 'rose', 'arrive': 'arriver', 'master': 'maÃ®tre',
        'track': 'piste', 'parent': 'mÃ¨re', 'shore': 'rivage', 'division': 'division', 'sheet': 'feuille',
        'substance': 'substance', 'favor': 'favoriser', 'connect': 'relier', 'post': 'poste', 'spend': 'passer',
        'chord': 'corde', 'fat': 'graisse', 'glad': 'heureux', 'original': 'original', 'share': 'part',
        'station': 'station', 'dad': 'papa', 'bread': 'pain', 'charge': 'charger', 'proper': 'propre', 'bar': 'bar',
        'offer': 'proposition', 'segment': 'segment', 'slave': 'esclave', 'duck': 'canard', 'instant': 'instant',
        'market': 'marchÃ©', 'degree': 'degrÃ©', 'populate': 'peupler', 'chick': 'poussin', 'dear': 'cher',
        'enemy': 'ennemi', 'reply': 'rÃ©pondre', 'drink': 'boisson', 'occur': 'se produire', 'support': 'support',
        'speech': 'discours', 'nature': 'nature', 'range': 'gamme', 'steam': 'vapeur', 'motion': 'mouvement',
        'path': 'chemin', 'liquid': 'liquide', 'log': 'enregistrer', 'meant': 'signifiait', 'quotient': 'quotient',
        'teeth': 'dents', 'shell': 'coquille', 'neck': 'cou', 'oxygen': 'oxygÃ¨ne', 'sugar': 'sucre',
        'death': 'dÃ©cÃ¨s', 'pretty': 'assez', 'skill': 'compÃ©tence', 'women': 'femmes', 'season': 'saison',
        'solution': 'solution', 'magnet': 'aimant', 'silver': 'argent', 'thank': 'merci', 'branch': 'branche',
        'match': 'rencontre', 'suffix': 'suffixe', 'especially': 'particuliÃ¨rement', 'fig': 'figue', 'afraid': 'peur',
        'huge': 'Ã©norme', 'sister': 'sÅ“ur', 'steel': 'acier', 'discuss': 'discuter', 'forward': 'avant',
        'similar': 'similaire', 'guide': 'guider', 'experience': 'expÃ©rience', 'score': 'score', 'apple': 'pomme',
        'bought': 'achetÃ©', 'led': 'LED', 'pitch': 'pas', 'coat': 'manteau', 'mass': 'masse', 'card': 'carte',
        'band': 'bande', 'rope': 'corde', 'slip': 'glissement', 'win': 'gagner', 'dream': 'rÃªver',
        'evening': 'soirÃ©e', 'condition': 'condition', 'feed': 'alimentation', 'tool': 'outil', 'total': 'total',
        'basic': 'de base', 'smell': 'odeur', 'valley': 'vallÃ©e', 'nor': 'ni', 'double': 'double', 'seat': 'siÃ¨ge',
        'continue': 'continuer', 'block': 'bloc', 'chart': 'graphique', 'hat': 'chapeau', 'sell': 'vendre',
        'success': 'succÃ¨s', 'company': 'entreprise', 'subtract': 'soustraire', 'event': 'Ã©vÃ©nement',
        'particular': 'particulier', 'deal': 'accord', 'swim': 'baignade', 'term': 'terme', 'opposite': 'opposÃ©',
        'wife': 'femme', 'shoe': 'chaussure', 'shoulder': 'Ã©paule', 'spread': 'propagation', 'arrange': 'organiser',
        'camp': 'camp', 'invent': 'inventer', 'cotton': 'coton', 'born': 'nÃ©', 'determine': 'dÃ©terminer',
        'quart': 'litre', 'nine': 'neuf', 'truck': 'camion', 'noise': 'bruit', 'level': 'niveau', 'chance': 'chance',
        'gather': 'recueillir', 'shop': 'boutique', 'stretch': 'tronÃ§on', 'throw': 'jeter', 'shine': 'Ã©clat',
        'property': 'propriÃ©tÃ©', 'column': 'colonne', 'molecule': 'molÃ©cule', 'select': 'sÃ©lectionner',
        'wrong': 'mal', 'gray': 'gris', 'repeat': 'rÃ©pÃ©tition', 'require': 'exiger', 'broad': 'large',
        'prepare': 'prÃ©parer', 'salt': 'sel', 'nose': 'nez', 'plural': 'pluriel', 'anger': 'colÃ¨re',
        'claim': 'revendication', 'continent': 'continent'}
EtoF.update(dict(list(EtoF_verbs.items()) + list(EtoF_adjectives.items()) + list(EtoF_nouns.items())))
FtoE_nouns = reverseDictionary(EtoF_nouns)
FtoE_adjectives = reverseDictionary(EtoF_adjectives)
FtoE_verbs = reverseDictionary(EtoF_verbs)
FtoE = reverseDictionary(EtoF)
dicts = {'English to French': EtoF, 'French to English': FtoE}

# Translate word
def translateWord(word, dictionary):
    if word.lower() in dictionary.keys():
        if word.lower() == 'je':
            return dictionary[word.lower()].capitalize()
        else:
            return dictionary[word.lower()]
    elif word != '':
        return '"' + word + '"'
    return word

# Fix j'
def fix_j_apostrophe(s, d):
    if d == EtoF:
        if ',' in s:
            return fix_j_apostrophe(s.split(',', maxsplit=1)[0], EtoF) + ', ' + fix_j_apostrophe(
                s.split(',', maxsplit=1)[1], EtoF)
        elif '.' in s:
            return fix_j_apostrophe(s.split('.', maxsplit=1)[0], EtoF) + '. ' + fix_j_apostrophe(
                s.split('.', maxsplit=1)[1], EtoF)
        elif ';' in s:
            return fix_j_apostrophe(s.split(';', maxsplit=1)[0], EtoF) + '; ' + fix_j_apostrophe(
                s.split(';', maxsplit=1)[1], EtoF)
        elif '!' in s:
            return fix_j_apostrophe(s.split('!', maxsplit=1)[0], EtoF) + '! ' + fix_j_apostrophe(
                s.split('!', maxsplit=1)[1], EtoF)
        elif '?' in s:
            return fix_j_apostrophe(s.split('?', maxsplit=1)[0], EtoF) + '? ' + fix_j_apostrophe(
                s.split('?', maxsplit=1)[1], EtoF)
        else:
            s = s.split()
            for i in range(len(s) - 1):
                if s[i].lower() == 'je' and s[i + 1].lower()[0] in 'aeiou' and s[i + 1].lower() in FtoE_verbs.keys():
                    s = s[:i] + ['j\'' + s[i + 1]] + s[i + 2:]
            return ' '.join(s)
    elif d == FtoE:
        if 'j\'' in s:
            s = s.replace('j\'', 'je ')
        if 'J\'' in s:
            s = s.replace('J\'', 'je ')
        return s

# Fix adjacent words
def fix_adjacent_words(s, d):
    if d == FtoE:
        if ',' in s:
            return fix_adjacent_words(s.split(',', maxsplit=1)[0], FtoE) + ', ' + fix_adjacent_words(
                s.split(',', maxsplit=1)[1], FtoE)
        elif '.' in s:
            return fix_adjacent_words(s.split('.', maxsplit=1)[0], FtoE) + '. ' + fix_adjacent_words(
                s.split('.', maxsplit=1)[1], FtoE)
        elif ';' in s:
            return fix_adjacent_words(s.split(';', maxsplit=1)[0], FtoE) + '; ' + fix_adjacent_words(
                s.split(';', maxsplit=1)[1], FtoE)
        elif '!' in s:
            return fix_adjacent_words(s.split('!', maxsplit=1)[0], FtoE) + '! ' + fix_adjacent_words(
                s.split('!', maxsplit=1)[1], FtoE)
        elif '?' in s:
            return fix_adjacent_words(s.split('?', maxsplit=1)[0], FtoE) + '? ' + fix_adjacent_words(
                s.split('?', maxsplit=1)[1], FtoE)
        else:
            s = s.split()
            for i in range(len(s) - 1):
                if s[i].lower() in EtoF_nouns.keys() and s[i + 1].lower() in EtoF_adjectives.keys():
                    s[i], s[i + 1] = s[i + 1], s[i]
            return ' '.join(s)
    elif d == EtoF:
        if ',' in s:
            return fix_adjacent_words(s.split(',', maxsplit=1)[0], EtoF) + ', ' + fix_adjacent_words(
                s.split(',', maxsplit=1)[1], EtoF)
        elif '.' in s:
            return fix_adjacent_words(s.split('.', maxsplit=1)[0], EtoF) + '. ' + fix_adjacent_words(
                s.split('.', maxsplit=1)[1], EtoF)
        elif ';' in s:
            return fix_adjacent_words(s.split(';', maxsplit=1)[0], EtoF) + '; ' + fix_adjacent_words(
                s.split(';', maxsplit=1)[1], EtoF)
        elif '!' in s:
            return fix_adjacent_words(s.split('!', maxsplit=1)[0], EtoF) + '! ' + fix_adjacent_words(
                s.split('!', maxsplit=1)[1], EtoF)
        elif '?' in s:
            return fix_adjacent_words(s.split('?', maxsplit=1)[0], EtoF) + '? ' + fix_adjacent_words(
                s.split('?', maxsplit=1)[1], EtoF)
        else:
            s = s.split()
            for i in range(len(s) - 1):
                if s[i].lower() in FtoE_adjectives.keys() and s[i + 1].lower() in FtoE_nouns.keys():
                    s[i], s[i + 1] = s[i + 1], s[i]
            return ' '.join(s)

# Capitalize first word in sentence
def capitalize_str(s):
    if '.' in s:
        return capitalize_str(s.split('.', maxsplit=1)[0]) + '. ' + capitalize_str(s.split('.', maxsplit=1)[1])
    elif '!' in s:
        return capitalize_str(s.split('!', maxsplit=1)[0]) + '! ' + capitalize_str(s.split('!', maxsplit=1)[1])
    elif '?' in s:
        return capitalize_str(s.split('?', maxsplit=1)[0]) + '? ' + capitalize_str(s.split('?', maxsplit=1)[1])
    else:
        s = s.split()
        return ' '.join([s[i].capitalize().strip() if i == 0 else s[i].strip() for i in range(len(s))])

# Translate sentences
def translate(phrase, dicts, direction):
    UCletters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZÂÇÆÉÈÊËÏÎÔŒÙÛÜŸ'
    LCletters = 'abcdefghijklmnopqrstuvwxyzàâçæéèêëïîôœùûüÿ'
    letters = UCletters + LCletters
    dictionary = dicts[direction]
    if dictionary == FtoE:
        phrase = fix_j_apostrophe(phrase, dictionary)
    translation = ''
    word = ''
    for character in phrase:
        if character in letters:
            word = word + character
        else:
            translation = translation + translateWord(word, dictionary) + character
            word = ''
    translation = translation + translateWord(word, dictionary)
    if dictionary == EtoF:
        translation = fix_j_apostrophe(translation, dictionary)
    translation = fix_adjacent_words(translation, dictionary)
    translation = capitalize_str(translation)
    return translation

# Ask to run again
def ask_again():
    return input('Would you like to run this program again? ')[0].lower() == 'y'

# Main
if __name__ == '__main__':
    print('Python Translator')
    print('Vocabulary of', len(EtoF), 'words')
    print('Domain - most commonly used English words')
    while True:
        print('Please select operation -')
        print('1. English to French')
        print('2. French to English')
        select = int(input('Select operations form 1, 2: '))
        if select == 1:
            print('--------------------------------------')
            user_input = input('Input: ')
            output = translate(user_input, dicts, 'English to French')
            print('Output:', output)
            f = open("output.txt", "a")
            f.write('Input: ' + user_input + '\n')
            f.write('Output: ' + output + '\n')
            f.close()
            print('--------------------------------------')
        elif select == 2:
            print('--------------------------------------')
            user_input = input('Input: ')
            output = translate(user_input, dicts, 'French to English')
            print('Output:', output)
            f = open("output.txt", "a")
            f.write('Input: ' + user_input + '\n')
            f.write('Output: ' + output + '\n')
            f.close()
            print('--------------------------------------')
        else:
            print('Invalid input')
        if not ask_again():
            exit()


from django import forms

from .models import *

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = ('question_text',)
        labels = {
           'question_text': 'Текст вопроса:',
           }

    def __init__(self, *args, **kwargs):
        super(QuestionForm, self).__init__(*args, **kwargs)
        self.fields['question_text'].widget.attrs.update({'class' : 'form-control'})

class QuestionGroupForm(forms.ModelForm):
    class Meta:
        model = Question_group
        fields = ('title',)
        labels = {
           'title': 'Название опроса:',
           }

    def __init__(self, *args, **kwargs):
        super(QuestionGroupForm, self).__init__(*args, **kwargs)
        self.fields['title'].widget.attrs.update({'class' : 'form-control'})

class ChoiceForm(forms.ModelForm):
    class Meta:
        model = Choice
        fields = ('choice_text',)
        labels = {
           'choice_text': 'Ответ на вопрос:',
           }

    def __init__(self, *args, **kwargs):
        super(ChoiceForm, self).__init__(*args, **kwargs)
        self.fields['choice_text'].widget.attrs.update({'class' : 'form-control'})


# union means all value of 2 sets expects duplicate

# s1 = {1,2,3,4}
# s2 = {2,3,5}
# s3 = s1.union(s2)
# print(s3)

# intersection means all common value of 2 sets

# s1 = {1,2,3,4}
# s2 = {2,3,5}
# s3 = s1.intersection(s2)
# print(s3)

# symmetric_difference means all value of 2sets expect common value
s1 = {1,2,3,4}
s2 = {2,3,7}
s2.clear()
print(s2)


from __future__ import annotations
from typing import Optional, Union
import json

from WxDataGrid import GridDataSource, GridDataRowClass, ColDefinition, ColDefinitionsList
from HelpersPackage import RemoveAccents
from FanzineIssueSpecPackage import FanzineDateRange
from Log import Log

####################################################################################
class Con(GridDataRowClass):
    def __init__(self):
        self._name: str=""                  # Name including number designation
        self._locale: str=""                # Name of locale where the con was held
        self._dates: Optional[FanzineDateRange]=None      # Date range of the con
        self._gohs: str=""                  # A list of the con's GoHs
        self._URL: str=""                   # The URL of the individual con page, if any

    def Signature(self) -> int:        # Con(GridDataRowClass)
        return hash(self._name)+hash(self._locale)+hash(self._dates)+hash(self._gohs)+hash(self._URL)

    # Serialize and deserialize
    def ToJson(self) -> str:        # Con(GridDataRowClass)
        d={"ver": 3,
           "_name": self._name,
           "_locale": self._locale,
           "_dates": str(self._dates),
           "_URL": self._URL,
           "_gohs": self._gohs}
        return json.dumps(d)

    def FromJson(self, val: str) -> Con:        # Con(GridDataRowClass)
        d=json.loads(val)
        self._name=RemoveAccents(d["_name"])
        self._locale=d["_locale"]
        self._gohs=d["_gohs"]
        self._dates=FanzineDateRange().Match(d["_dates"])
        if "_URL" in d.keys():
            self._URL=d["_URL"]
        else:
            self._URL=""
        return self

    @property
    def Name(self) -> str:        # Con(GridDataRowClass)
        return self._name
    @Name.setter
    def Name(self, val: str):
        self._name=RemoveAccents(val)

    @property
    def GoHs(self) -> str:        # Con(GridDataRowClass)
        return self._gohs
    @GoHs.setter
    def GoHs(self, val: str):
        self._gohs=val

    @property
    def Locale(self) -> str:        # Con(GridDataRowClass)
        return self._locale
    @Locale.setter
    def Locale(self, val: str):
        self._locale=val

    @property
    def Dates(self) -> Optional[FanzineDateRange]:        # Con(GridDataRowClass)
        return self._dates
    @Dates.setter
    def Dates(self, val: FanzineDateRange):
        self._dates=val

    @property
    def URL(self) -> str:        # Con(GridDataRowClass)
        return self._URL
    @URL.setter
    def URL(self, val: str) -> None:
        self._URL=val

    # Get or set a value by name or column number
    #def GetVal(self, name: Union[str, int]) -> Union[str, int, FanzineDateRange]:
    def __getitem__(self, index: Union[str, int, slice]) -> Union[str, int, FanzineDateRange]:        # Con(GridDataRowClass)
        # (Could use return eval("self."+name))
        if index == "Name" or index == 0:
            return self.Name
        if index == "Dates" or index == 1:
            return self.Dates
        if index == "Locale" or index == 2:
            return self.Locale
        if index == "GoHs" or index == 3:
            return self.GoHs
        if index != 4:  # Index == 4 is normal, being the terminator of an iteration through the columns
            Log(f"Con(GridDataRowClass).__getitem__({index}) does not exist")
        raise IndexError

    #def SetVal(self, nameOrCol: Union[str, int], val: Union[str, int, FanzineDateRange]) -> None:
    def __setitem__(self, index: Union[str, int, slice], value: Union[str, int, FanzineDateRange]) -> None:        # Con(GridDataRowClass)
        # (Could use return eval("self."+name))
        if index == "Name" or index == 0:
            self.Name=value
            return
        if index == "Dates" or index == 1:
            self.Dates=FanzineDateRange().Match(value)
            return
        if index == "Locale" or index == 2:
            self.Locale=value
            return
        if index == "GoHs" or index == 3:
            self.GoHs=value
            return
        Log(f"Con(GridDataRowClass).__putitem__({index}) does not exist")
        raise IndexError

    # def IsEmptyRow(self) -> bool:  # Con(GridDataRowClass)
    #     return self._name != "" or self._locale != "" or self._dates.IsEmpty() != "" or self._gohs != "" or self._URL != ""

    def IsEmptyRow(self) -> bool:        # Con(GridDataRowClass)
        return (self._name or self._name == "") and (self._locale or self._locale == "") and (self._dates is None or type(self._dates) is str  or self._dates.IsEmpty())  and (self._gohs or self._gohs == "") and (self._URL or self._URL == "")

####################################################################################
class ConSeries(GridDataSource):

    def __init__(self):
        GridDataSource.__init__(self)
        self._colDefs: ColDefinitionsList=ColDefinitionsList([
            ColDefinition("Name", Type="url", Width=30, IsEditable="maybe"),
            ColDefinition("Dates", Type="date range", Width=30),
            ColDefinition("Locale", Width=30),
            ColDefinition("GoHs", Width=30),
        ])
        self._element=Con
        self._series: list[Con]=[]  # This supplies the Rows property that GridDataSource needs
        self._name: str=""

    # Serialize and deserialize
    def ToJson(self) -> str:        #  ConSeries(GridDataSource)
        d={"ver": 5,
           "_name": self._name}
        for i, s in enumerate(self._series):
            d[i]=s.ToJson()
        return json.dumps(d)

    def FromJson(self, val: str) -> ConSeries:        #  ConSeries(GridDataSource)
        d=json.loads(val)
        self._name=RemoveAccents(d["_name"])    # Clean out old accented entries
        self._series=[]
        i=0
        while str(i) in d.keys():       # This is because json merges 1 and "1" as the same. (It appears to be a bug.)
            self._series.append(Con().FromJson(d[str(i)]))
            i+=1

        return self

    def __getitem__(self, index) -> Con:        #  ConSeries(GridDataSource)
        assert index != -1
        return self._series[index]

    def __setitem__(self, index: int, val: Con):        #  ConSeries(GridDataSource)
        self._series[index]=val

    def Signature(self) -> int:        #  ConSeries(GridDataSource)
        return hash(self._name)+sum([x.Signature()*(i+1) for i, x in enumerate(self.Rows)])

    @property
    def NumRows(self) -> int:        #  ConSeries(GridDataSource)
        return len(self._series)

    @property
    def Rows(self) -> list[Con]:        #  ConSeries(GridDataSource)
        return self._series
    @Rows.setter
    def Rows(self, rows: list[Con]) -> None:
        self._series=rows

    @property
    def ColDefs(self) -> ColDefinitionsList:        #  ConSeries(GridDataSource)
        return self._colDefs

    #------------
    @property
    def Name(self) -> str:        #  ConSeries(GridDataSource)
        return self._name
    @Name.setter
    def Name(self, val: str) -> None:
        self._name=val

    def InsertEmptyRows(self, index: int, num: int=1) -> None:        #  ConSeries(GridDataSource)
        if num <= 0:
            return
        if index > len(self.Rows):
            index=len(self.Rows)
        self.Rows=self.Rows[:index]+[Con() for i in range(num)]+self.Rows[index:]


# Text Adventure Python Test
# by Tim Hicks 2022
# 6/24/22 - fixed up encounter/combat rounds

import time
import numpy as np
import sys
import random
from random import randint
import unicodedata

# delay printing
def delay_print(s):
    for c in s:
        sys.stdout.write(c)
        sys.stdout.flush()
        time.sleep(0.05)

# player class, contains health and damage
class Player:
    def __init__(self, name, healthPoints, attackPower, defense, xp, inventory, weapon, castLightFlag):
    # Attack = d6
    # Defense
    # Luck
    # Dodge
        self.name = name
        self.healthPoints = healthPoints
        self.attackPower = attackPower
        self.defense = defense
        self.xp = xp
        self.inventory = [] # lets make inventory 6 slots
        # weapons
        self.weapon = weapon
        #self.spells = [] # spell book
        self.castLightFlag = 0

    def attack(self, Target):
        #while (self.healthPoints > 0) and (Target.health > 0):
            # determine damage
        if self.weapon.damage == "d6":
            currentAttackRoll = Game.rollD6()
        elif self.weapon.damage == "d4":
            currentAttackRoll = Game.rollD4()
        delay_print(f"({self.name}) attack roll: {currentAttackRoll}")
        time.sleep(.3)
        if currentAttackRoll == 6:
            delay_print(f" -- Critical hit!")
            Target.healthPoints -= currentAttackRoll
            print (f"\n({Target.type}) health: {Target.healthPoints}")
        elif currentAttackRoll < 6 and currentAttackRoll > 0:
            delay_print(f"\n({Target.type}) defense: {Target.defense}")
            time.sleep(.3)
            totalDamage = currentAttackRoll - Target.defense
            if totalDamage > 0:
                Target.healthPoints -= totalDamage
                delay_print(f"\nDamage dealt to ({Target.type}): {totalDamage}!")
                time.sleep(.3)
                print (f"\n({Target.type}) health: {Target.healthPoints}")
            elif totalDamage <= 0:
                delay_print(f"\n({Target.type}) defended your attack! No damage!")
                time.sleep(.3)
        elif currentAttackRoll >= 0:
            delay_print(f"You missed!")

    def inventoryAdd(self, Target):
        # add item
        # check 6 item limit in inventory
        if len(self.inventory) <= 6:
            self.inventory.append(Target.type)
            print(f"\nYou picked up {Target.type}!")
        else:
            print(f"\nYou can't hold anymore!")

    def castLight(self):
        print(f"Casting [Light]!")
        Room.Light(self)
        self.castLightFlag += 1

    def equipWeapon(self, Target):
        # equip the weapon
        self.weapon = Target

    def lootMonster(self, Target):
        if len(self.inventory) <= 6:
            self.inventory.append(Target.lootDrop)
            print(f"You picked up {Target.lootDrop}!")
        else:
            print(f"\nYou can't hold anymore!")

    def checkInventory(self):
        # check inventory
        print(f"\n ====== INVENTORY ======")
        i = 0
        while i < len(self.inventory):
            #print(f"{len(self.inventory)}")
            print(f"{i+1}: {self.inventory[i]}")
            i += 1
        choice = input("Select number of item to use, or [I] to exit: ")
        if (choice == "1"):
            self.useHealthPotion(smHealthPotion)

    def checkSpellbook(self):
        print(f"\n ===== SPELLBOOK =====")
        print(f"\n [Light] - cast an intense burst of light, illuminating the area")
        #print(f"\n [Fear] - afflict the target with fear")

    def characterSheet(self):
        print (f"\n ====== CHARACTER SHEET ====== ")
        print(f"Name: {self.name}")
        print(f"Level 1 Adventurer")
        print(f"Current HP: {self.healthPoints}")
        print(f"Attack Power: d6")
        print(f"Defense: {self.defense}")
        print(f"Current XP: {self.xp} / 20")

    # def search(self):
        #!!!! search the room - should pass through room at later time
        # delay_print(f'\nThere is an item on the floor. Pick it up? [y/n]:')
        # choice = input("\n Selection: ")
        # if choice == "y":
            # self.inventoryAdd(smHealthPotion)

    #def moveForward(self):
        # !!!! room
        #delay_print(f"\nYou walk forward into the darkness...")
        #delay_print(f"\n 'RAAAAAHH...'")
        #time.sleep(0.75)
        #print("\nA goblin appears!")
        #Game.initEncounter(p1, Goblin)

    def gainXP(self,Target):
        self.xp += Target.xpOut
        print(f"\nYou gain {Target.xpOut} experience!")

    def useHealthPotion(self, Target):
        if p1.checkHealthUse() == 1:
            self.healthPoints += Target.healAmt
            print(f"\nYou consumed {Target.type}.")
            print(f"You healed for {Target.healAmt} health!")
            print(f"Your health is now {self.healthPoints}")
            self.inventory.remove(Target.type)
        elif p1.checkHealthUse() == 0:
            print(f"\nCan't use that item - you are at max health already!")

    def checkHealthUse(self):
        if (self.healthPoints < 12):
            return 1
        elif (self.healthPoints >= 12):
            return 0

    def checkIsDead(self):
         if self.healthPoints <= 0:
                print(f"\nYou died!")
                delay_print(f"\n ... ")
                time.sleep(1)
                Game.gameOver()

# monster class contains health, attack
class Monster:
    def __init__(self, type, healthPoints, attackPower, defense, xpOut, lootDrop, weapon, talk):
        self.type = type
        self.healthPoints = healthPoints
        self.attackPower = attackPower
        self.defense = defense
        self.xpOut = xpOut
        self.lootDrop = lootDrop
        self.weapon = weapon
        self.talk = talk

    def attack(self, Target):
       #while (self.healthPoints > 0) and (Target.health > 0):
            # determine damage
        if self.weapon.damage == "d6":
            currentAttackRoll = Game.rollD6()
        elif self.weapon.damage == "d4":
            currentAttackRoll = Game.rollD4()
        delay_print(f"({self.type}) attack roll: {currentAttackRoll}")
        time.sleep(.3)
        if currentAttackRoll == 4:
            delay_print(f"  -- Critical hit!")
            Target.healthPoints -= currentAttackRoll
            delay_print(f"\n({Target.name}) health: {Target.healthPoints}")
        elif currentAttackRoll < 4 and currentAttackRoll > 0:
            delay_print(f"\n({Target.name}) defense: {Target.defense}")
            time.sleep(.3)
            totalDamage = currentAttackRoll - Target.defense
            if totalDamage > 0:
                Target.healthPoints -= totalDamage
                delay_print(f"\nDamage dealt to ({Target.name}): {totalDamage}")
                time.sleep(.3)
                delay_print(f"\n({Target.name}) health: {Target.healthPoints}")
            elif totalDamage <= 0:
                delay_print(f"\n({Target.name}) defended ({self.type}) attack!")
                time.sleep(.3)
        elif currentAttackRoll >= 0:
            delay_print(f"\n{self.type} missed!")
            time.sleep(.3)

    def checkIsDead(self):
         if self.healthPoints <= 0:
                print(f"\n\n{self.type} has been slain!")
                delay_print(f"\n ... ")
                time.sleep(1)
                self.lootDrop(self)

    def lootDropFx():
        #lootDrop = ["{self.weapon}"]
        print(f"{self.type} dropped {self.weapon}")

class Item:
    def __init__(self, type, effect):
        self.type = type
        self.effect = effect

class Weapon:
    def __init__(self, type, description, damage, cost):
        self.type = type
        self.description = description
        self.damage = damage
        self.cost = cost

class potion:
    def __init__(self, type, description, healAmt):
        self.type = type
        self.description = description
        self.healAmt = healAmt
        # health potion not working yet
#class Room:
#    def __init__(self, search, listen)
#        self.search = search
#        self.listen = listen
# game
class Game:

    def rollD4():
        return random.randint(1,4)

    def rollD6():
        return random.randint(1,6)

    def rollD8():
        print.random.randint(1,8)

    def rollD10():
        print.random.randint(1,10)

    #def startScreen(self):
    def intro(self):
        print(f"\n <intro skipped>")
        # print(f"\nWhat is your name, adventurer?")
        choice = input("Type your name: ")
        p1.name = choice.capitalize()

    def gameOver(self):
        choice = input(f"\n Gameover. Try again?")
        if (choice ==  "y") or (choice == "Y"):
            os.execl(sys.executable, sys.executable, *sys.argv)
        else:
            quit()

    def ui(self):
        print(f"\nWhat would you like to do?")
        print(f"[C]HARACTER SHEET | [I]NVENTORY | [S]PELLBOOK")
        print(f"Or type /command (type /help for list of commands)")
        choice = input("Type selection: ")
        if (choice == "c") or (choice == "C"):
            p1.characterSheet()
        elif (choice == "i") or (choice == "I"):
            p1.checkInventory()
        elif (choice == "s") or (choice == "S"):
            p1.checkSpellbook()
        elif (choice == "/command") or (choice == "/COMMAND"):
            print(f"Type /help for list of /commands")
        elif (choice == "/help") or (choice == "/HELP"):
            print(f"\nRecognized commands: ")
            #print(f"/forward - move forward")
            print(f"/search - search the area")
            print(f"/listen - listen to your surroundings for notable sounds")
            #print(f"/hide - hide behind cover or in the darkness")
            print(f"/cast - cast a spell from your spellbook")
            print(f"/talk <target> - attempt to talk to a target")
            print(f"/rest - rest for a few hours")
            print(f"/sit - sit down")
            #print(f"/shout - shout loudly")
            print(f"/dance - break out into dance <not recommended during dangerous encounters>")
            print(f"\n")
        elif (choice == "/search") or (choice == "/SEARCH"):
            Room.search(self)
        elif (choice == "/listen") or (choice == "/LISTEN"):
            Room.listen(self)
        elif (choice == "/sit") or (choice == "/SIT"):
            Room.sit(self)
        elif (choice == "/dance") or (choice == "/DANCE"):
            Room.dance(self)
        elif (choice == "/talk") or (choice == "/TALK"):
            Room.talk(self)
        elif (choice == "/rest") or (choice == "/REST"):
            Room.rest(self)
        elif (choice == "/cast") or (choice == "/CAST"):
            choice = input(f"Cast [Light]? [y/n]: ")
            if choice == "y":
                p1.castLight()
        #elif (choice == "h") or (choice == "H"):
        #    Game.helpManual(self)
        else:
            print(f"Invalid selection, try again")

    def initEncounter(self, Target):
        while (self.healthPoints > 0 and Target.healthPoints > 0):
            choice = input(f"\n[A]ttack or attempt to [R]un: ")
            if choice == "a" or choice =="A":
                Game.round(self, Target)
            choice = input(f"\n[A]ttack again, or attempt to [R]un: ")
            if choice == "a" or choice == "A":
                Game.round(self, Target)

    def round(self, Target):
        print("\n")
        self.attack(Target)
        # target is dead check
        # target health print
        time.sleep(.3)
        if (Target.healthPoints > 0):
            time.sleep(0.85)
            print(f"\n{Target.type} strikes back!\n")
            time.sleep(0.85)
            Target.attack(self)
            #self is dead check
            #get back to prompt
        elif (Target.healthPoints <= 0):
             delay_print(f"\n({Target.type}) has been slain!")
             Target.lootDropFx()

class Room:
    def search(self):
        if p1.castLightFlag < 1:
            delay_print(f"\nIt is too dark to search in here. Perhaps if there was a way to light the area ...")
        elif p1.castLightFlag >= 1:
            delay_print(f"\nIt is dark, but you recall when you cast list there was small stone was by your feet...")
            delay_print(f"\nPick up the stone? [y/n]: ")
        delay_print("\n")

    def listen(self):
        delay_print(f"\nYou listen closely - you can hear the shuffling and grumbling of a creature about 20 feet ahead ...")
        delay_print("\n")

    def sit(self):
        delay_print(f"\nYou sit down on the cold, gravel floor. It is slightly damp and not at all comfortable ...")
        delay_print("\n")

    def dance(self):
        delay_print(f"\nIn the darkness you do the 'Griddy'. Let's hope the shuffling of your clothes doesn't attract any near by creatures ...")
        delay_print("\n")

    def talk(self):
        if p1.castLightFlag < 1:
            delay_print(f"\nYou are alone - you have nobody to talk to ...")
        elif p1.castLightFlag >= 1:
            delay_print(f"\nDo you want to try talking to the goblin in the distance? [y/n]: ")
        delay_print("\n")

    def rest(self):
        delay_print(f"\nYou lay on the cold, damp gravel floor and close your eyes but you can't seem to rest.. It's just not the ideal spot.")
        delay_print("\n")

    def Light(self):
        if p1.castLightFlag < 1:
            delay_print(f"\nAs the room illuminates you see clearly that you are indeed in a dank cave.")
            delay_print(f"\nThe walls surround you closely,")
            delay_print(f"\nand down a narrow passage ahead you see the back of a goblin, shuffling ...")
            delay_print(f"\nThe burst of light startles the goblin - ")
            delay_print(f"\nit is alert now, but does not know where the light came from. The goblin stands in place, on guard.")
            delay_print(f"\nYou also notice a small stone by your feet")
            delay_print(f"\nThe light flickers out, darkness returns ...")
            delay_print(f"\n")
        elif p1.castLightFlag >= 1:
            print(f"\nAre you sure you want to cast light again?")

if __name__ == '__main__':
    p1 = Player("", 12, randint(1,6), 2, 0, [0], "", 0)
    #room1 = Room("The room is too dark to see. Need a way to light the area", "You can hear a creature moving about 20 feet ahead of you.")
    roughDagger = Weapon("Rough Dagger", "A rough dagger of PRIMITIVE quality.", "d4", 1)
    ironSword = Weapon("Iron Sword", "An iron sword of ROUGH quality.", "d6", 2)
    Goblin = Monster("Goblin", 8, randint(1,4), 1, 5, "Small Health Potion", roughDagger, "The goblin shrieks at you. Seems he does not want to talk.")
    #healthPotion = Item("Health Potion", "Heals the user for +5 health.")
    smHealthPotion = potion("Small Health Potion", "Heals user for +5 health.", 5)

    Game.intro(p1)
    # initialize p1 with starting weapon
    #p1.inventoryAdd(ironSword)
    #p1.equipWeapon(ironSword)
    while (1):
        #Game.startScreen(p1)
        Game.ui(p1)

    # delay_print("\nOh shit, now a dragon ...")
    # time.sleep(1)
    # choice = input("\nShoul you attack? [y/n] ")
    # if choice == "y":
        # Game.encounter(p1, Dragon)

    # print("\nGoblin attacks you back!")
    # time.sleep(1)
    # Goblin.attack(p1)

    # choice = input("\nAttack again [y/n]? ")
    # if choice == "y":
        # p1.attack(Goblin)


from multiprocessing import Queue
from typing import Callable, list

def task_wrapper(pid:int, function:Callable, batch:list, queue:Queue, *args, **kwargs):
    """
    Wrapper to add progress bar update

    Parameters:
    -----------
    pid : int
        Process id
    function : Callable
        Function to apply to each element in batch
    batch : list
        List of elements to apply function to
    queue : multiprocessing.Queue
        Queue to send progress updates
    *args, **kwargs :
        Arguments to pass to function
    """
    result = []
    for example in batch:
        result.append(function(example, *args, **kwargs))
        queue.put(f'update{pid}')
    return result


from concurrent.futures import process
import os
import sys
import time
import pprint
import signal
import pathlib
import shutil
import itertools
import subprocess
import randomfiletree

from file_generator.generate_random_file_tree import generate_random_files
from .test_utils.file_utils import get_all_files_in_directory_recursively
from .test_utils.metrics import compute_ransomware_encryption_metrics

pp = pprint.PrettyPrinter(indent=2)
FILE_PATH = os.path.abspath(os.path.dirname(__file__))
DETECTOR_TO_TEST = "SensoryDetector"
detector_proc = None
ransomware_proc = None

def cleanup_test_folder():
    try:
        shutil.rmtree(f"{FILE_PATH}/test_folder")
    except OSError:
        print("(test) Test folder doesn't exist, skipping clean up of test folder")

def cleanup_sigint(sig, frame):
    print("(test) Cleaning up SIGINT")
    os.system("sudo pkill -9 -f SensoryDetector")  # just to make sure the process is killed
    os.system("sudo pkill -9 -f raasnet_payload")
    sys.exit(0)

# register signal handler
signal.signal(signal.SIGINT, cleanup_sigint)

def test():
    """
    This script will be responsible to benchmarking our program (Rescate-tance) detector VS RAASNET
    """

    """0. Clean up test folder"""
    cleanup_test_folder()

    try:
        subprocess.check_output(["pip3", "install", "-r", "requirements.txt"])
    except Exception:
        print("(test) Can't install dependencies")

    """1. Create a bunch of dummy folders and dummy files"""
    print("(test) Creating dummy files")
    randomfiletree.core.iterative_gaussian_tree(
        f"{FILE_PATH}/test_folder", nfiles=10, nfolders=5, maxdepth=3, repeat=3, payload=generate_random_files
    )

    """2. Keep track of original files that were created in test_folder"""
    original_files = get_all_files_in_directory_recursively(f"{FILE_PATH}/test_folder")

    """3. Run the detector"""
    os.chdir(f"{FILE_PATH}/../")
    detector_proc = subprocess.Popen(["sudo", "python3", "main.py", "-d", DETECTOR_TO_TEST], stdout=subprocess.PIPE)

    print("(test) Sleeping a bit for ransomware detector to start up!")
    time.sleep(5)

    """4. Run the ransomware"""
    ransomware_proc = subprocess.Popen(["sudo", "python3", "tests/ransomware/raasnet_payload.py"], shell=False)

    """5. Wait for ransomware to be killed, or after a certain time then just kill everything"""
    print("(test) Sleeping a bit for ransomware to be detected")
    time.sleep(10)
    if ransomware_proc.poll() is None:
        print("(test) Ransomware detector was not able to kill RAASNET :(. Stopping")
    else:
        print("(test) Ransomware detector succesfully stopped RAASNET!")

    detector_proc.kill()
    ransomware_proc.kill()
    os.system("sudo pkill -9 -f SensoryDetector")  # just to make sure the process is killed

    """6: Get files after ransomware hit and check percentage encrypted"""
    after_ransomware_files = get_all_files_in_directory_recursively(f"{FILE_PATH}/test_folder")
    metrics = compute_ransomware_encryption_metrics(original_files, after_ransomware_files)

    print("------- Test Results ---------")
    pp.pprint(metrics)
    print("------------------------------")

    """7. Cleanup test folder again"""
    cleanup_test_folder()


import threading
import socket
import queue

SERVER_HOST = "127.0.0.1"
SERVER_PORT = 7777

packets_q = queue.Queue()
clients = []

def client_handle(client):
  try:
    clients.append(client)
    while True:
      full_data = b""
      error = False
      while True:
        chunk = client.recv(1)
        if len(chunk) <= 0:
          error = True
          break
        full_data += chunk
        if full_data.endswith(b"</data>"): break
      if error: break
      packets_q.put(full_data)
  except Exception:
    return
  finally:
    clients.remove(client)

def broadcast():
  while True:
    packet = packets_q.get()
    current_clients = clients.copy()
    for client in current_clients:
      try:
        client.send(packet)
      except Exception as e:
        print(e)
        continue

def main():
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  sock.bind((SERVER_HOST, SERVER_PORT))
  sock.listen()
  print(f"Listening to {SERVER_HOST}:{SERVER_PORT}...")
  threading.Thread(target=broadcast, daemon=True).start()

  while True:
    try:
      client, client_address = sock.accept()
      threading.Thread(target=client_handle, args=[client], daemon=True).start()
    except KeyboardInterrupt:
      return

if __name__ == "__main__":
  main()