(res) => {
                    const body = res.body;
                    assert.strictEqual(body.userid, studentId);
                    assert.strictEqual(body.classid, classid);
                    assert.strictEqual(body.type, projectDetails.type);
                    assert.strictEqual(body.name, projectDetails.name);
                    assert.strictEqual(body.isCrowdSourced, false);
                    assert.deepStrictEqual(body.labels, ['_background_noise_']);
                }


() => {
            const projectDetails = {
                name : uuid(),
                type : 'text',
                language : 'it',
                isCrowdSourced : true,
            };
            const studentId = uuid();

            const url = '/api/classes/' + TESTCLASS + '/students/' + studentId + '/projects';

            nextAuth0UserId = studentId;
            nextAuth0UserTenant = TESTCLASS;

            return request(testServer)
                .post(url)
                .send(projectDetails)
                .expect('Content-Type', /json/)
                .expect(httpstatus.FORBIDDEN)
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error : 'Only teachers or group leaders can create crowd-sourced projects',
                    });
                });
        }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error : 'Only teachers or group leaders can create crowd-sourced projects',
                    });
                }


() => {
            const projectDetails = {
                name : uuid(),
                type : 'text',
                language : 'it',
                isCrowdSourced : true,
            };
            const studentId = uuid();

            const url = '/api/classes/' + TESTCLASS + '/students/' + studentId + '/projects';

            nextAuth0UserId = studentId;
            nextAuth0UserTenant = TESTCLASS;
            nextAuth0UserRole = 'supervisor';

            return request(testServer)
                .post(url)
                .send(projectDetails)
                .expect('Content-Type', /json/)
                .expect(httpstatus.CREATED)
                .then((res) => {
                    const body = res.body;
                    assert.strictEqual(body.userid, studentId);
                    assert.strictEqual(body.classid, TESTCLASS);
                    assert.strictEqual(body.type, projectDetails.type);
                    assert.strictEqual(body.name, projectDetails.name);
                    assert.strictEqual(body.language, projectDetails.language);
                    assert.strictEqual(body.isCrowdSourced, true);
                    assert(body.id);

                    return request(testServer)
                        .get(url + '/' + body.id)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.strictEqual(body.userid, studentId);
                    assert.strictEqual(body.classid, TESTCLASS);
                    assert.strictEqual(body.type, projectDetails.type);
                    assert.strictEqual(body.name, projectDetails.name);
                    assert.strictEqual(body.language, projectDetails.language);
                    assert.strictEqual(body.isCrowdSourced, true);
                });
        }


(res) => {
                    const body = res.body;
                    assert.strictEqual(body.userid, studentId);
                    assert.strictEqual(body.classid, TESTCLASS);
                    assert.strictEqual(body.type, projectDetails.type);
                    assert.strictEqual(body.name, projectDetails.name);
                    assert.strictEqual(body.language, projectDetails.language);
                    assert.strictEqual(body.isCrowdSourced, true);
                    assert(body.id);

                    return request(testServer)
                        .get(url + '/' + body.id)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


(res) => {
                    const body = res.body;
                    assert.strictEqual(body.userid, studentId);
                    assert.strictEqual(body.classid, TESTCLASS);
                    assert.strictEqual(body.type, projectDetails.type);
                    assert.strictEqual(body.name, projectDetails.name);
                    assert.strictEqual(body.language, projectDetails.language);
                    assert.strictEqual(body.isCrowdSourced, true);
                }


() => {

        it('should cope with an empty list', () => {
            const classid = uuid();
            const studentid = uuid();
            nextAuth0UserId = studentid;
            nextAuth0UserTenant = classid;
            return request(testServer)
                .get('/api/classes/' + classid + '/students/' + studentid + '/projects')
                .expect('Content-Type', /json/)
                .expect(httpstatus.OK)
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, 0);
                });
        });

        it('should return projects for a user', () => {
            const studentId = uuid();

            let count = 0;

            const url = '/api/classes/' + TESTCLASS + '/students/' + studentId + '/projects';

            nextAuth0UserId = studentId;
            nextAuth0UserTenant = TESTCLASS;

            return request(testServer)
                .post(url)
                .send({ name : uuid(), type : 'text', language : 'en' })
                .expect('Content-Type', /json/)
                .expect(httpstatus.CREATED)
                .then(() => {
                    return request(testServer)
                        .get(url)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert(body.length > 0);
                    count = body.length;

                    return request(testServer)
                        .post(url)
                        .send({ name : uuid(), type : 'text', language : 'en' })
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.CREATED);
                })
                .then(() => {
                    return request(testServer)
                        .get(url)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, count + 1);
                });
        });

        function testCreateProject(classid: string, user: string, crowd: boolean): Promise<any> {
            return request(testServer)
                    .post('/api/classes/' + classid + '/students/' + user + '/projects')
                    .send({ name : uuid(), type : 'text', language : 'en', isCrowdSourced : crowd })
                    .expect('Content-Type', /json/)
                    .expect(httpstatus.CREATED)
                    .then((res) => {
                        return res.body;
                    });
        }
        function testGetProjects(classid: string, user: string): Promise<any> {
            return request(testServer)
                    .get('/api/classes/' + classid + '/students/' + user + '/projects')
                    .expect('Content-Type', /json/)
                    .expect(httpstatus.OK)
                    .then((res) => {
                        return res.body;
                    });
        }
        function testSetUser(classid: string, user: string) {
            nextAuth0UserTenant = classid;
            nextAuth0UserId = user;
            nextAuth0UserRole = 'student';
        }
        function testSetTeacher(classid: string, user: string) {
            testSetUser(classid, user);
            nextAuth0UserRole = 'supervisor';
        }

        function compareProjects(objA: any, objB: any) {
            if (objA.id > objB.id) {
                return 1;
            }
            if (objA.id < objB.id) {
                return -1;
            }
            return 0;
        }

        async function testVerifyAll(
            classid: string,
            studentA: string, studentB: string, teacherId: string,
            expected: any)
        {
            expected.studentA.sort(compareProjects);
            expected.studentB.sort(compareProjects);
            expected.teacher.sort(compareProjects);

            testSetUser(classid, studentA);
            let projects = await testGetProjects(classid, studentA);
            projects.sort(compareProjects);
            assert.deepStrictEqual(projects, expected.studentA);

            testSetUser(classid, studentB);
            projects = await testGetProjects(classid, studentB);
            projects.sort(compareProjects);
            assert.deepStrictEqual(projects, expected.studentB);

            testSetTeacher(classid, teacherId);
            projects = await testGetProjects(classid, teacherId);
            projects.sort(compareProjects);
            assert.deepStrictEqual(projects, expected.teacher);
        }

        it('should return crowd-sourced projects', () => {
            const classId = uuid();

            const studentA = uuid();
            const studentB = uuid();
            const teacherId = uuid();

            const expectedStudentA: any[] = [];
            const expectedStudentB: any[] = [];
            const expectedTeacher: any[] = [];

            const expected = {
                studentA : expectedStudentA,
                studentB : expectedStudentB,
                teacher : expectedTeacher,
            };

            return testVerifyAll(classId, studentA, studentB, teacherId, expected)
                .then(() => {
                    return testCreateProject(classId, studentA, false);
                })
                .then((newproj) => {
                    expected.studentA.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return testCreateProject(classId, studentA, false);
                })
                .then((newproj) => {
                    expected.studentA.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return testCreateProject(classId, teacherId, true);
                })
                .then((newproj) => {
                    expected.studentA.push(newproj);
                    expected.studentB.push(newproj);
                    expected.teacher.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return testCreateProject(classId, studentB, false);
                })
                .then((newproj) => {
                    expected.studentB.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return testCreateProject(classId, teacherId, false);
                })
                .then((newproj) => {
                    expected.teacher.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return store.deleteProjectsByClassId(classId);
                });
        });

    }


() => {
            const classid = uuid();
            const studentid = uuid();
            nextAuth0UserId = studentid;
            nextAuth0UserTenant = classid;
            return request(testServer)
                .get('/api/classes/' + classid + '/students/' + studentid + '/projects')
                .expect('Content-Type', /json/)
                .expect(httpstatus.OK)
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, 0);
                });
        }


(res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, 0);
                }


() => {
            const studentId = uuid();

            let count = 0;

            const url = '/api/classes/' + TESTCLASS + '/students/' + studentId + '/projects';

            nextAuth0UserId = studentId;
            nextAuth0UserTenant = TESTCLASS;

            return request(testServer)
                .post(url)
                .send({ name : uuid(), type : 'text', language : 'en' })
                .expect('Content-Type', /json/)
                .expect(httpstatus.CREATED)
                .then(() => {
                    return request(testServer)
                        .get(url)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert(body.length > 0);
                    count = body.length;

                    return request(testServer)
                        .post(url)
                        .send({ name : uuid(), type : 'text', language : 'en' })
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.CREATED);
                })
                .then(() => {
                    return request(testServer)
                        .get(url)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, count + 1);
                });
        }


() => {
                    return request(testServer)
                        .get(url)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


(res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert(body.length > 0);
                    count = body.length;

                    return request(testServer)
                        .post(url)
                        .send({ name : uuid(), type : 'text', language : 'en' })
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.CREATED);
                }


(res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, count + 1);
                }


(res) => {
                        return res.body;
                    }


() => {
            const classId = uuid();

            const studentA = uuid();
            const studentB = uuid();
            const teacherId = uuid();

            const expectedStudentA: any[] = [];
            const expectedStudentB: any[] = [];
            const expectedTeacher: any[] = [];

            const expected = {
                studentA : expectedStudentA,
                studentB : expectedStudentB,
                teacher : expectedTeacher,
            };

            return testVerifyAll(classId, studentA, studentB, teacherId, expected)
                .then(() => {
                    return testCreateProject(classId, studentA, false);
                })
                .then((newproj) => {
                    expected.studentA.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return testCreateProject(classId, studentA, false);
                })
                .then((newproj) => {
                    expected.studentA.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return testCreateProject(classId, teacherId, true);
                })
                .then((newproj) => {
                    expected.studentA.push(newproj);
                    expected.studentB.push(newproj);
                    expected.teacher.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return testCreateProject(classId, studentB, false);
                })
                .then((newproj) => {
                    expected.studentB.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return testCreateProject(classId, teacherId, false);
                })
                .then((newproj) => {
                    expected.teacher.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                })
                .then(() => {
                    return store.deleteProjectsByClassId(classId);
                });
        }


() => {
                    return testCreateProject(classId, studentA, false);
                }


(newproj) => {
                    expected.studentA.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                }


() => {
                    return testCreateProject(classId, teacherId, true);
                }


(newproj) => {
                    expected.studentA.push(newproj);
                    expected.studentB.push(newproj);
                    expected.teacher.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                }


() => {
                    return testCreateProject(classId, studentB, false);
                }


(newproj) => {
                    expected.studentB.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                }


() => {
                    return testCreateProject(classId, teacherId, false);
                }


(newproj) => {
                    expected.teacher.push(newproj);
                    return testVerifyAll(classId, studentA, studentB, teacherId, expected);
                }


() => {
                    return store.deleteProjectsByClassId(classId);
                }


() => {

        it('should cope with an empty list', () => {
            const classid = uuid();
            nextAuth0UserRole = 'supervisor';
            nextAuth0UserTenant = classid;
            return request(testServer)
                .get('/api/classes/' + classid + '/projects')
                .expect('Content-Type', /json/)
                .expect(httpstatus.OK)
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, 0);
                });
        });

        it('should return projects for a class', () => {
            const studentA = uuid();
            const studentB = uuid();

            let count: number;

            nextAuth0UserId = 'teacher';
            nextAuth0UserRole = 'supervisor';
            nextAuth0UserTenant = TESTCLASS;
            return request(testServer)
                .get('/api/classes/' + TESTCLASS + '/projects')
                .expect('Content-Type', /json/)
                .expect(httpstatus.OK)
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    count = body.length;

                    return request(testServer)
                        .post('/api/classes/' + TESTCLASS + '/students/' + studentA + '/projects')
                        .send({ name : uuid(), type : 'text', language : 'en' })
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.CREATED);
                })
                .then(() => {
                    return request(testServer)
                        .get('/api/classes/' + TESTCLASS + '/projects')
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, count + 1);

                    count = body.length;

                    return request(testServer)
                        .post('/api/classes/' + TESTCLASS + '/students/' + studentB + '/projects')
                        .send({ name : uuid(), type : 'text', language : 'en' })
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.CREATED);
                })
                .then(() => {
                    return request(testServer)
                        .get('/api/classes/' + TESTCLASS + '/projects')
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, count + 1);
                });
        });

    }


() => {
            const classid = uuid();
            nextAuth0UserRole = 'supervisor';
            nextAuth0UserTenant = classid;
            return request(testServer)
                .get('/api/classes/' + classid + '/projects')
                .expect('Content-Type', /json/)
                .expect(httpstatus.OK)
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, 0);
                });
        }


() => {
            const studentA = uuid();
            const studentB = uuid();

            let count: number;

            nextAuth0UserId = 'teacher';
            nextAuth0UserRole = 'supervisor';
            nextAuth0UserTenant = TESTCLASS;
            return request(testServer)
                .get('/api/classes/' + TESTCLASS + '/projects')
                .expect('Content-Type', /json/)
                .expect(httpstatus.OK)
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    count = body.length;

                    return request(testServer)
                        .post('/api/classes/' + TESTCLASS + '/students/' + studentA + '/projects')
                        .send({ name : uuid(), type : 'text', language : 'en' })
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.CREATED);
                })
                .then(() => {
                    return request(testServer)
                        .get('/api/classes/' + TESTCLASS + '/projects')
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, count + 1);

                    count = body.length;

                    return request(testServer)
                        .post('/api/classes/' + TESTCLASS + '/students/' + studentB + '/projects')
                        .send({ name : uuid(), type : 'text', language : 'en' })
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.CREATED);
                })
                .then(() => {
                    return request(testServer)
                        .get('/api/classes/' + TESTCLASS + '/projects')
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, count + 1);
                });
        }


(res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    count = body.length;

                    return request(testServer)
                        .post('/api/classes/' + TESTCLASS + '/students/' + studentA + '/projects')
                        .send({ name : uuid(), type : 'text', language : 'en' })
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.CREATED);
                }


() => {
                    return request(testServer)
                        .get('/api/classes/' + TESTCLASS + '/projects')
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


(res) => {
                    const body = res.body;
                    assert(Array.isArray(body));
                    assert.strictEqual(body.length, count + 1);

                    count = body.length;

                    return request(testServer)
                        .post('/api/classes/' + TESTCLASS + '/students/' + studentB + '/projects')
                        .send({ name : uuid(), type : 'text', language : 'en' })
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.CREATED);
                }


() => {
            const projectDetails = { type : 'text', name : uuid(), language : 'en' };
            const studentId = uuid();
            let projectId: string;

            const url = '/api/classes/' + TESTCLASS + '/students/' + studentId + '/projects';

            nextAuth0UserId = studentId;
            nextAuth0UserTenant = TESTCLASS;
            nextAuth0UserRole = 'student';

            return request(testServer)
                .post(url)
                .send(projectDetails)
                .expect('Content-Type', /json/)
                .expect(httpstatus.CREATED)
                .then((res) => {
                    const body = res.body;
                    projectId = body.id;

                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, []);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then(() => {
                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel' ]);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'different',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then(() => {
                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel', 'different' ]);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then(() => {
                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel', 'different' ]);
                });
        }


(res) => {
                    const body = res.body;
                    projectId = body.id;

                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, []);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


() => {
                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel' ]);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'different',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel', 'different' ]);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel', 'different' ]);
                }


() => {
            const projectDetails = { type : 'text', name : uuid(), language : 'en' };
            const studentId = uuid();
            let projectId: string;

            const url = '/api/classes/' + TESTCLASS + '/students/' + studentId + '/projects';

            nextAuth0UserId = studentId;
            nextAuth0UserTenant = TESTCLASS;
            nextAuth0UserRole = 'student';

            return request(testServer)
                .post(url)
                .send(projectDetails)
                .expect('Content-Type', /json/)
                .expect(httpstatus.CREATED)
                .then((res) => {
                    const body = res.body;
                    projectId = body.id;

                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, []);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then(() => {
                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel' ]);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'remove',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then(() => {
                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, []);
                });
        }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel' ]);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'remove',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, []);
                }


async () => {
            const studentId = uuid();
            let projectId: string = '';
            const label = 'newlabel';
            const imageIds = [ uuid(), uuid(), uuid() ];

            const url = '/api/classes/' + TESTCLASS + '/students/' + studentId + '/projects';

            await store.deleteAllPendingJobs();

            const project = await store.storeProject(studentId, TESTCLASS, 'images', uuid(), 'en', [], false);
            projectId = project.id;

            nextAuth0UserId = studentId;
            nextAuth0UserTenant = TESTCLASS;
            nextAuth0UserRole = 'student';

            await request(testServer)
                .patch(url + '/' + projectId)
                .send([{
                    path : '/labels', op : 'add',
                    value : label,
                }])
                .expect('Content-Type', /json/)
                .expect(httpstatus.OK);

            await store.storeImageTraining(
                projectId,
                '/api/classes/' + TESTCLASS +
                    '/students/' + studentId +
                    '/projects/' + projectId +
                    '/images/' + imageIds[0],
                label,
                true,
                imageIds[0]);
            await store.storeImageTraining(
                projectId,
                '/api/classes/' + TESTCLASS +
                    '/students/' + studentId +
                    '/projects/' + projectId +
                    '/images/' + imageIds[1],
                label,
                true,
                imageIds[1]);
            await store.storeImageTraining(
                projectId,
                '/api/classes/' + TESTCLASS +
                    '/students/' + studentId +
                    '/projects/' + projectId +
                    '/images/' + imageIds[2],
                label,
                true,
                imageIds[2]);

            await store.storeImageTraining(
                projectId,
                'http://existingimage.com/image.png',
                label,
                false);

            await request(testServer)
                .patch(url + '/' + projectId)
                .send([{
                    path : '/labels', op : 'remove',
                    value : label,
                }])
                .expect('Content-Type', /json/)
                .expect(httpstatus.OK);

            let jobCount = 0;
            let job = await store.getNextPendingJob();
            while (job) {
                assert.strictEqual(job.jobtype, 1);
                const jobdata: Types.ImageSpec = job.jobdata as Types.ImageSpec;
                assert.strictEqual(jobdata.projectid, projectId);
                assert.strictEqual(jobdata.userid, studentId);
                assert.strictEqual(jobdata.classid, TESTCLASS);
                assert(imageIds.includes(jobdata.imageid));

                jobCount += 1;

                await store.deletePendingJob(job);
                job = await store.getNextPendingJob();
            }

            assert.strictEqual(jobCount, 3);

            const count = await store.countTraining('images', projectId);
            assert.strictEqual(count, 0);
        }


() => {
            const projectDetails = { type : 'text', name : uuid(), language : 'en'  };
            const studentId = uuid();
            let projectId: string;

            const url = '/api/classes/' + TESTCLASS + '/students/' + studentId + '/projects';

            nextAuth0UserId = studentId;
            nextAuth0UserTenant = TESTCLASS;
            nextAuth0UserRole = 'student';

            return request(testServer)
                .post(url)
                .send(projectDetails)
                .expect('Content-Type', /json/)
                .expect(httpstatus.CREATED)
                .then((res) => {
                    const body = res.body;
                    projectId = body.id;

                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, []);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then(() => {
                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel' ]);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'replace',
                            value : [ 'apple', 'banana', 'tomato' ],
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then(() => {
                    return request(testServer)
                        .get(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'apple', 'banana', 'tomato' ]);
                });
        }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'newlabel' ]);

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'replace',
                            value : [ 'apple', 'banana', 'tomato' ],
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.OK);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body.labels, [ 'apple', 'banana', 'tomato' ]);
                }


() => {
            const projectDetails = { type : 'text', name : uuid(), language : 'en'  };
            const studentId = uuid();
            let projectId: string;

            const url = '/api/classes/' + TESTCLASS + '/students/' + studentId + '/projects';

            nextAuth0UserId = studentId;
            nextAuth0UserRole = 'student';
            nextAuth0UserTenant = TESTCLASS;

            return request(testServer)
                .post(url)
                .send(projectDetails)
                .expect('Content-Type', /json/)
                .expect(httpstatus.CREATED)
                .then((res) => {
                    const body = res.body;
                    projectId = body.id;

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH body should be an array',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send({})
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH body should be an array',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Only individual PATCH requests are supported',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Only modifications to project labels are supported',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels' }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH requests must include an op',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels', op : 'INVALID', value : [ 'BAD' ] }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Invalid PATCH op',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels', op : 'add' }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH requests must include a value',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels', op : 'add', value : [ 'BAD' ] }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH requests to add or remove a label should specify a string',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels', op : 'add', value : ' ' }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Cannot add an empty label',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : randomstring.generate({ length : 100 }),
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Label exceeds max length',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'replace',
                            value : 'should be an array',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH requests to replace labels should specify an array',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'replace',
                            value : [ 'test', randomstring.generate({ length : 100 }) ],
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                })
                .then((res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Label exceeds max length',
                    });

                    return request(testServer)
                        .patch('/api/classes/' + TESTCLASS + '/students/' + 'different' + '/projects' + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.FORBIDDEN);
                });
        }


(res) => {
                    const body = res.body;
                    projectId = body.id;

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH body should be an array',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send({})
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH body should be an array',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Only individual PATCH requests are supported',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Only modifications to project labels are supported',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels' }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH requests must include an op',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels', op : 'INVALID', value : [ 'BAD' ] }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Invalid PATCH op',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels', op : 'add' }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH requests must include a value',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels', op : 'add', value : [ 'BAD' ] }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH requests to add or remove a label should specify a string',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{ path : '/labels', op : 'add', value : ' ' }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Cannot add an empty label',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : randomstring.generate({ length : 100 }),
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Label exceeds max length',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'replace',
                            value : 'should be an array',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'PATCH requests to replace labels should specify an array',
                    });

                    return request(testServer)
                        .patch(url + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'replace',
                            value : [ 'test', randomstring.generate({ length : 100 }) ],
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.BAD_REQUEST);
                }


(res) => {
                    const body = res.body;
                    assert.deepStrictEqual(body, {
                        error: 'Label exceeds max length',
                    });

                    return request(testServer)
                        .patch('/api/classes/' + TESTCLASS + '/students/' + 'different' + '/projects' + '/' + projectId)
                        .send([{
                            path : '/labels', op : 'add',
                            value : 'newlabel',
                        }])
                        .expect('Content-Type', /json/)
                        .expect(httpstatus.FORBIDDEN);
                }


(_tab, index) => index > this.maxNumberOfTabs - 2


tab => !tab.hide


visibleTab => visibleTab.id === tab.id


tabChild => tabChild.active && tabChild.id !== tab.id


tabChild => {
      if (!tabChild.disabled && !tabChild.hide) {
        tabChild.active = true;
        return true;
      }
    }


/**
 * @docsExtends PoTabsBaseComponent
 *
 * @example
 *
 * <example name="po-tabs-basic" title="PO Tabs Basic">
 *  <file name="sample-po-tabs-basic/sample-po-tabs-basic.component.html"> </file>
 *  <file name="sample-po-tabs-basic/sample-po-tabs-basic.component.ts"> </file>
 * </example>
 *
 * <example name="po-tabs-labs" title="PO Tabs Labs">
 *  <file name="sample-po-tabs-labs/sample-po-tabs-labs.component.html"> </file>
 *  <file name="sample-po-tabs-labs/sample-po-tabs-labs.component.ts"> </file>
 * </example>
 *
 * <example name="po-tabs-travel" title="PO Tabs - Travel">
 *  <file name="sample-po-tabs-travel/sample-po-tabs-travel.component.html"> </file>
 *  <file name="sample-po-tabs-travel/sample-po-tabs-travel.component.ts"> </file>
 * </example>
 *
 * <example name="po-tabs-business-conf" title="PO Tabs - Business Conference">
 *  <file name="sample-po-tabs-business-conf/sample-po-tabs-business-conf.component.html"> </file>
 *  <file name="sample-po-tabs-business-conf/sample-po-tabs-business-conf.component.ts"> </file>
 * </example>
 */
@Component({
  selector: 'po-tabs',
  templateUrl: './po-tabs.component.html'
})
export class PoTabsComponent extends PoTabsBaseComponent {
  maxNumberOfTabs = poTabsMaxNumberOfTabs;

  private previousActiveTab: PoTabComponent;

  // Tabs utilizados no ng-content
  @ContentChildren(PoTabComponent) tabs: QueryList<PoTabComponent>;

  @ViewChild('tabDropdown', { static: true }) tabDropdown: PoTabDropdownComponent;

  constructor(private changeDetector: ChangeDetectorRef) {
    super();
  }

  get isMobileDevice() {
    return isMobile();
  }

  get isShowTabDropdown() {
    return !this.isMobileDevice && this.visibleTabs.length > this.maxNumberOfTabs;
  }

  // tabs que serão apresentadas na aba "Mais"
  get overflowedTabs() {
    return this.visibleTabs.filter((_tab, index) => index > this.maxNumberOfTabs - 2);
  }

  get visibleTabs() {
    return this.tabs.filter(tab => !tab.hide);
  }

  closePopover(): void {
    const containsPopoverVisible = this.tabDropdown && this.tabDropdown.popover && !this.tabDropdown.popover.isHidden;

    if (containsPopoverVisible) {
      this.tabDropdown.popover.close();
    }
  }

  isVisibleTab(tab) {
    if (this.isMobileDevice) {
      return true;
    }

    const visibleTabIndex = this.visibleTabs.findIndex(visibleTab => visibleTab.id === tab.id);

    return this.visibleTabs.length <= this.maxNumberOfTabs || visibleTabIndex < this.maxNumberOfTabs - 1;
  }

  // Função disparada quando alguma tab ficar ativa
  onTabActive(tab) {
    this.previousActiveTab = this.tabs.find(tabChild => tabChild.active && tabChild.id !== tab.id);

    this.deactivateTab();
  }

  // funcao será disparada quando mudar o estado do poTab para desabilitado ou escondido.
  onTabChangeState(tab: PoTabComponent) {
    if (tab.active) {
      tab.active = false;

      this.activeDistinctTab();

      this.changeDetector.detectChanges();
    }
  }

  // selectiona a aba informada por parametro, caso houver click faz a emição do evento.
  selectedTab(tab: PoTabComponent) {
    tab.active = true;

    if (tab.click) {
      tab.click.emit(tab);
    }

    this.changeDetector.detectChanges();
  }

  trackByFn(_i, tab: PoTabComponent) {
    return tab.id;
  }

  // ativa a previousActiveTab ou primeira tab encontrada.
  private activeDistinctTab() {
    if (this.previousActiveTab) {
      this.previousActiveTab.active = true;
    } else {
      this.activeFirstTab();
    }
  }

  // Ativa a primeira Tab que não estiver desabilitada ou escondida.
  private activeFirstTab() {
    this.tabs.some(tabChild => {
      if (!tabChild.disabled && !tabChild.hide) {
        tabChild.active = true;
        return true;
      }
    });
  }

  // desativa previousActiveTab e dispara a detecção de mudança.
  private deactivateTab() {
    if (this.previousActiveTab) {
      this.previousActiveTab.active = false;

      this.changeDetector.detectChanges();
    }
  }
}


closePopover(): void {
    const containsPopoverVisible = this.tabDropdown && this.tabDropdown.popover && !this.tabDropdown.popover.isHidden;

    if (containsPopoverVisible) {
      this.tabDropdown.popover.close();
    }
  }


isVisibleTab(tab) {
    if (this.isMobileDevice) {
      return true;
    }

    const visibleTabIndex = this.visibleTabs.findIndex(visibleTab => visibleTab.id === tab.id);

    return this.visibleTabs.length <= this.maxNumberOfTabs || visibleTabIndex < this.maxNumberOfTabs - 1;
  }


// Função disparada quando alguma tab ficar ativa
onTabActive(tab) {
    this.previousActiveTab = this.tabs.find(tabChild => tabChild.active && tabChild.id !== tab.id);

    this.deactivateTab();
  }


// funcao será disparada quando mudar o estado do poTab para desabilitado ou escondido.
onTabChangeState(tab: PoTabComponent) {
    if (tab.active) {
      tab.active = false;

      this.activeDistinctTab();

      this.changeDetector.detectChanges();
    }
  }


// selectiona a aba informada por parametro, caso houver click faz a emição do evento.
selectedTab(tab: PoTabComponent) {
    tab.active = true;

    if (tab.click) {
      tab.click.emit(tab);
    }

    this.changeDetector.detectChanges();
  }


trackByFn(_i, tab: PoTabComponent) {
    return tab.id;
  }


// ativa a previousActiveTab ou primeira tab encontrada.
private activeDistinctTab() {
    if (this.previousActiveTab) {
      this.previousActiveTab.active = true;
    } else {
      this.activeFirstTab();
    }
  }


// Ativa a primeira Tab que não estiver desabilitada ou escondida.
private activeFirstTab() {
    this.tabs.some(tabChild => {
      if (!tabChild.disabled && !tabChild.hide) {
        tabChild.active = true;
        return true;
      }
    });
  }


// desativa previousActiveTab e dispara a detecção de mudança.
private deactivateTab() {
    if (this.previousActiveTab) {
      this.previousActiveTab.active = false;

      this.changeDetector.detectChanges();
    }
  }


export interface UserDocument extends Document {
    _id: string;
    badges: DevBadge[];
    banned: boolean;
    lastVotedAt: Date;
    premium: boolean;
    role: UserRole;
}


export enum DevBadge {
    BetaTester = 'BETA_TESTER',
    OG = 'OG',
    Viewer = 'VIEWER'
}


export type UserRole = '' | 'reviewer' | 'admin';


// Get or create chat
export function findOrCreateChat(id: number) {
  return ChatModel.findOneAndUpdate(
    { id },
    {},
    {
      upsert: true,
      new: true,
    }
  )
}


export async function findOnlyChat(id: number) {
  return await ChatModel.findOne({ id })
}


export async function findAllChats() {
  return await ChatModel.find({})
}


export async function countChats() {
  return await ChatModel.countDocuments({})
}


@modelOptions({ schemaOptions: { timestamps: true } })
export class Chat {
  @prop({ required: true, index: true, unique: true })
  id!: number

  @prop({ required: true, default: 'en' })
  language!: string

  @prop({ required: true, default: 0.65 })
  toxic_thresh!: number

  @prop({ required: true, default: 0.7 })
  profan_thresh!: number

  @prop({ required: true, default: 0.7 })
  identity_thresh!: number

  @prop({ required: true, default: 0.6 })
  insult_thresh!: number

  @prop({ required: false, default: [] })
  moderators!: Array<number>

  @prop({ required: true, default: true })
  interactive!: boolean

  @prop({ required: true, default: {} })
  triggers!: { [key: string]: number }

  @prop({ required: true, default: {} })
  ignored_triggers!: { [key: string]: number }
}


() => {
      return {
        ...extra,
        error: 'PermissionsError',
        message,
        type: permissionErrorType
      }
    }


export default class PermissionsError extends OrionError {
  constructor(permissionErrorType, extra: any = {}) {
    // Calling parent constructor of base Error class.
    const message =
      extra.message || `Client is not allowed to perform this action [${permissionErrorType}]`

    super(message)
    Error.captureStackTrace(this, this.constructor)

    this.isOrionError = true
    this.isPermissionsError = true
    this.code = 'PermissionsError'
    this.extra = extra

    this.getInfo = () => {
      return {
        ...extra,
        error: 'PermissionsError',
        message,
        type: permissionErrorType
      }
    }
  }
}


(props)=> {
  const {result, bemBlocks} = props
  return (
    <div className={bemBlocks.item().mix(bemBlocks.container("item"))}>
      {result._source.name}
    </div>


export class App extends React.Component<any, any> {

  searchkit:SearchkitManager

  constructor() {
    super()
    const host = "http://demo.searchkit.co/api/taxonomy"
    this.searchkit = new SearchkitManager(host)
  }

  render(){ return (

    <SearchkitProvider searchkit={this.searchkit}>

Searchkit Acme co

          <SearchBox
            translations={{"searchbox.placeholder":"search regions"}}
            queryOptions={


render(){ return (

    <SearchkitProvider searchkit={this.searchkit}>

Searchkit Acme co

          <SearchBox
            translations={{"searchbox.placeholder":"search regions"}}


export function scrollStrategyFactory(
  overlay: Overlay
): RepositionScrollStrategy {
  return overlay.scrollStrategies.reposition();
}


() => {
        this.onViewExtentReset.emit();
        this.state = InteractionState.NONE;
        this.changeDetector.markForCheck();
      }


(event) => {
        const newState = this.shouldPan(event);
        if (newState !== this.specialKeyPressed) {
          this.specialKeyPressed = newState;
          this.changeDetector.markForCheck();
        }
      }


(event) => {
        const prevState = this.state;
        const nextState = this.shouldPan(event)
          ? InteractionState.PANNING
          : InteractionState.DRAG_ZOOMING;

        // Override the dragStartCoord and zoomBox only when started to zoom.
        // For instance, if you press left button then right, drag zoom should start at
        // the left button down so the second mousedown is ignored.
        if (
          prevState === InteractionState.NONE &&
          nextState === InteractionState.DRAG_ZOOMING
        ) {
          this.dragStartCoord = {x: event.offsetX, y: event.offsetY};
          this.zoomBoxInUiCoordinate = {
            x: event.offsetX,
            width: 0,
            y: event.offsetY,
            height: 0,
          };
        }

        if (prevState !== nextState) {
          this.state = nextState;
          this.changeDetector.markForCheck();
        }
      }


(event) => {
        const leftClicked =
          (event.buttons & MouseEventButtons.LEFT) === MouseEventButtons.LEFT;
        this.dragStartCoord = null;

        const zoomBox = this.zoomBoxInUiCoordinate;
        if (
          !leftClicked &&
          this.state === InteractionState.DRAG_ZOOMING &&
          zoomBox.width > 0 &&
          zoomBox.height > 0
        ) {
          const xMin = this.getDataX(zoomBox.x);
          const xMax = this.getDataX(zoomBox.x + zoomBox.width);
          const yMin = this.getDataY(zoomBox.y + zoomBox.height);
          const yMax = this.getDataY(zoomBox.y);

          this.onViewExtentChange.emit({
            dataExtent: {
              x: [xMin, xMax],
              y: [yMin, yMax],
            },
          });
        }
        if (this.state !== InteractionState.NONE) {
          this.state = InteractionState.NONE;
          this.changeDetector.markForCheck();
        }
      }


(event) => {
        this.isCursorInside = true;
        this.updateTooltip(event);
        this.changeDetector.markForCheck();
      }


(event) => {
        this.dragStartCoord = null;
        this.isCursorInside = false;
        this.updateTooltip(event);
        this.state = InteractionState.NONE;
        this.changeDetector.markForCheck();
      }


(event) => {
        switch (this.state) {
          case InteractionState.SCROLL_ZOOMING: {
            this.state = InteractionState.NONE;
            this.updateTooltip(event);
            this.changeDetector.markForCheck();
            break;
          }
          case InteractionState.NONE:
            this.updateTooltip(event);
            this.changeDetector.markForCheck();
            break;
          case InteractionState.PANNING: {
            const deltaX = -event.movementX;
            const deltaY = -event.movementY;
            const {width: domWidth, height: domHeight} = this.domDim;
            const xMin = this.getDataX(deltaX);
            const xMax = this.getDataX(domWidth + deltaX);
            const yMin = this.getDataY(domHeight + deltaY);
            const yMax = this.getDataY(deltaY);
            this.onViewExtentChange.emit({
              dataExtent: {
                x: [xMin, xMax],
                y: [yMin, yMax],
              },
            });
            break;
          }
          case InteractionState.DRAG_ZOOMING:
            {
              if (!this.dragStartCoord) {
                break;
              }
              const xs = [this.dragStartCoord.x, event.offsetX];
              const ys = [this.dragStartCoord.y, event.offsetY];
              this.zoomBoxInUiCoordinate = {
                x: Math.min(...xs),
                width: Math.max(...xs) - Math.min(...xs),
                y: Math.min(...ys),
                height: Math.max(...ys) - Math.min(...ys),
              };
            }
            this.changeDetector.markForCheck();
            break;
        }
      }


(event: WheelEvent) => {
          const shouldZoom = !event.ctrlKey && !event.shiftKey && event.altKey;
          this.showZoomInstruction = !shouldZoom;
          this.changeDetector.markForCheck();

          if (shouldZoom) {
            event.preventDefault();
            return of(event);
          }
          return timer(3000).pipe(
            tap(() => {
              this.showZoomInstruction = false;
              this.changeDetector.markForCheck();
            }),
            map(() => null)
          );
        }


() => {
              this.showZoomInstruction = false;
              this.changeDetector.markForCheck();
            }


(eventOrNull) => Boolean(eventOrNull)


(eventOrNull) => {
        const event = eventOrNull!;
        this.onViewExtentChange.emit({
          dataExtent: getProposedViewExtentOnZoom(
            event,
            this.viewExtent,
            this.domDim,
            SCROLL_ZOOM_SPEED_FACTOR,
            this.xScale,
            this.yScale
          ),
        });

        if (this.state !== InteractionState.SCROLL_ZOOMING) {
          this.state = InteractionState.SCROLL_ZOOMING;
          this.changeDetector.markForCheck();
        }
      }


(seriesDatum) => {
            return {
              seriesDatum: seriesDatum,
              metadata: this.seriesMetadataMap[seriesDatum.id],
            };
          }


({metadata}) => {
            return metadata && metadata.visible && !Boolean(metadata.aux);
          }


({seriesDatum, metadata}) => {
            const index = findClosestIndex(seriesDatum.points, cursorLoc.x);
            return {
              id: seriesDatum.id,
              closestPointIndex: index,
              point: seriesDatum.points[index],
              metadata,
            };
          }


(tooltipDatumOrNull) => tooltipDatumOrNull


export interface TooltipDatum<
  Metadata extends DataSeriesMetadata = DataSeriesMetadata,
  PointDatum extends Point = Point
> {
  id: string;
  metadata: Metadata;
  closestPointIndex: number;
  point: PointDatum;
}


export interface TooltipTemplateContext {
  cursorLocationInDataCoord: {x: number; y: number};
  data: TooltipDatum[];
}


enum InteractionState {
  NONE,
  DRAG_ZOOMING,
  SCROLL_ZOOMING,
  PANNING,
}


export type TooltipTemplate = TemplateRef<TooltipTemplateContext>;


ngOnChanges() {
    this.updateCursoredDataAndTooltipVisibility();
  }


ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }


private shouldPan(event: MouseEvent | KeyboardEvent): boolean {
    const specialKeyEngaged = event.shiftKey || event.altKey;

    if (event instanceof KeyboardEvent) {
      return specialKeyEngaged;
    }

    const leftClicked =
      (event.buttons & MouseEventButtons.LEFT) === MouseEventButtons.LEFT;
    const middleClicked =
      (event.buttons & MouseEventButtons.MIDDLE) === MouseEventButtons.MIDDLE;

    // Ignore right/forward/back clicks.
    if (!leftClicked && !middleClicked) return false;
    // At this point, either left or middle is clicked, but if both are clicked, left
    // takes precedence.
    return (middleClicked && !leftClicked) || specialKeyEngaged;
  }


trackBySeriesName(index: number, datum: TooltipDatum) {
    return datum.id;
  }


getDomX(uiCoord: number): number {
    return this.xScale.forward(
      this.viewExtent.x,
      getScaleRangeFromDomDim(this.domDim, 'x'),
      uiCoord
    );
  }


private getDataX(uiCoord: number): number {
    return this.xScale.reverse(
      this.viewExtent.x,
      getScaleRangeFromDomDim(this.domDim, 'x'),
      uiCoord
    );
  }


getDomY(uiCoord: number): number {
    return this.yScale.forward(
      this.viewExtent.y,
      getScaleRangeFromDomDim(this.domDim, 'y'),
      uiCoord
    );
  }


private getDataY(uiCoord: number): number {
    return this.yScale.reverse(
      this.viewExtent.y,
      getScaleRangeFromDomDim(this.domDim, 'y'),
      uiCoord
    );
  }


shouldRenderTooltipPoint(point: Point | null): boolean {
    return point !== null && !isNaN(point.x) && !isNaN(point.y);
  }


private updateTooltip(event: MouseEvent) {
    this.cursorLocationInDataCoord = {
      x: this.getDataX(event.offsetX),
      y: this.getDataY(event.offsetY),
    };
    this.updateCursoredDataAndTooltipVisibility();
  }


onTooltipDisplayDetached() {
    this.tooltipDisplayAttached = false;
  }


private updateCursoredDataAndTooltipVisibility() {
    const cursorLoc = this.cursorLocationInDataCoord;
    if (cursorLoc === null) {
      this.cursoredData = [];
      this.tooltipDisplayAttached = false;
      return;
    }

    this.cursoredData = this.isCursorInside
      ? (this.seriesData
          .map((seriesDatum) => {
            return {
              seriesDatum: seriesDatum,
              metadata: this.seriesMetadataMap[seriesDatum.id],
            };
          })
          .filter(({metadata}) => {
            return metadata && metadata.visible && !Boolean(metadata.aux);
          })
          .map(({seriesDatum, metadata}) => {
            const index = findClosestIndex(seriesDatum.points, cursorLoc.x);
            return {
              id: seriesDatum.id,
              closestPointIndex: index,
              point: seriesDatum.points[index],
              metadata,
            };
          })
          .filter((tooltipDatumOrNull) => tooltipDatumOrNull) as TooltipDatum[])
      : [];
    this.tooltipDisplayAttached = Boolean(this.cursoredData.length);
  }


(acc, [stateStr, priority]) => {
		const state = Number(stateStr) as TestRunState;
		acc[state] = { statusNode: true, state, priority };
		return acc;
	}


(a: TestRunState, b: TestRunState) => statePriority[b] - statePriority[a]


(a: TestRunState, b: TestRunState) => statePriority[a] > statePriority[b] ? a : b


s => Number(s) as TestRunState


export type TreeStateNode = { statusNode: true; state: TestRunState; priority: number };


function renderViewTree(container: HTMLElement): HTMLElement {
	const treeContainer = document.createElement('div');
	dom.addClass(treeContainer, 'debug-view-content');
	container.appendChild(treeContainer);
	return treeContainer;
}


(ariaLabel: string) => {
	return <tree.ITreeOptions> {
		indentPixels: 8,
		twistiePixels: 20,
		ariaLabel
	};
}


() => this.onFocusedStackFrameUpdated()


() => {
			collapseAction.enabled = this.debugService.getState() === debug.State.Running || this.debugService.getState() === debug.State.Stopped;
		}


() => {
			const stackFrame = this.debugService.getViewModel().getFocusedStackFrame();
			if (stackFrame) {
				return stackFrame.getScopes(this.debugService).then(scopes => {
					if (scopes.length > 0) {
						return this.tree.expand(scopes[0]);
					}
				});
			}
		}


scopes => {
					if (scopes.length > 0) {
						return this.tree.expand(scopes[0]);
					}
				}


(we) => {
			// only expand when a new watch expression is added.
			if (we instanceof model.Expression) {
				this.expand();
			}
		}


(we: model.Expression) => this.onWatchExpressionsUpdated(we)


(expression: debug.IExpression) => {
			if (!expression || !(expression instanceof model.Expression)) {
				return;
			}

			this.tree.refresh(expression, false).then(() => {
				this.tree.setHighlight(expression);

				const unbind = this.tree.addListener(events.EventType.HIGHLIGHT, (e: tree.IHighlightEvent) => {
					if (!e.highlight) {
						this.debugService.getViewModel().setSelectedExpression(null);
						this.tree.refresh(expression).done(null, errors.onUnexpectedError);
						unbind();
					}
				});
			}).done(null, errors.onUnexpectedError);
		}


() => {
				this.tree.setHighlight(expression);

				const unbind = this.tree.addListener(events.EventType.HIGHLIGHT, (e: tree.IHighlightEvent) => {
					if (!e.highlight) {
						this.debugService.getViewModel().setSelectedExpression(null);
						this.tree.refresh(expression).done(null, errors.onUnexpectedError);
						unbind();
					}
				});
			}


(e: tree.IHighlightEvent) => {
					if (!e.highlight) {
						this.debugService.getViewModel().setSelectedExpression(null);
						this.tree.refresh(expression).done(null, errors.onUnexpectedError);
						unbind();
					}
				}


() => {
			return we instanceof model.Expression ? this.tree.reveal(we): TPromise.as(true);
		}


(e: tree.ISelectionEvent) => {
			if (!e.selection.length) {
				return;
			}
			const element = e.selection[0];
			if (!(element instanceof model.StackFrame)) {
				return;
			}

			const stackFrame = <debug.IStackFrame> element;
			this.debugService.setFocusedStackFrameAndEvaluate(stackFrame);

			const isMouse = (e.payload.origin === 'mouse');
			let preserveFocus = isMouse;

			const originalEvent:KeyboardEvent|MouseEvent = e && e.payload && e.payload.originalEvent;
			if (originalEvent && isMouse && originalEvent.detail === 2) {
				preserveFocus = false;
				originalEvent.preventDefault();  // focus moves to editor, we need to prevent default
			}

			const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey));
			this.debugService.openOrRevealEditor(stackFrame.source, stackFrame.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
		}


() => {
			this.tree.refresh().done(null, errors.onUnexpectedError);
		}


() => {
			const focussedThread = this.debugService.getModel().getThreads()[this.debugService.getViewModel().getFocusedThreadId()];
			const previouslyHidden = this.messageBox.hidden;
			if (focussedThread && focussedThread.stoppedReason && focussedThread.stoppedReason !== 'step') {
				this.messageBox.textContent = nls.localize('debugStopped', "Paused on {0}.", focussedThread.stoppedReason);
				focussedThread.stoppedReason === 'exception' ? this.messageBox.classList.add('exception') : this.messageBox.classList.remove('exception');

				this.messageBox.hidden = false;
			} else {
				this.messageBox.hidden = true;
			}

			if (previouslyHidden !== this.messageBox.hidden) {
				this.layoutBody(this.fullSize);
			}
		}


() => {
			const focused = this.debugService.getViewModel().getFocusedStackFrame();
			if (focused) {
				const threads = this.debugService.getModel().getThreads();
				for (let ref in threads) {
					if (threads[ref].callStack.some(sf => sf === focused)) {
						this.tree.expand(threads[ref]);
					}
				}
				this.tree.setFocus(focused);
			}
		}


sf => sf === focused


() => this.onBreakpointsChange()


(e: tree.ISelectionEvent) => {
			if (!e.selection.length) {
				return;
			}
			const element = e.selection[0];
			if (!(element instanceof model.Breakpoint)) {
				return;
			}

			const breakpoint = <debug.IBreakpoint> element;
			if (!breakpoint.source.inMemory) {
				const isMouse = (e.payload.origin === 'mouse');
				let preserveFocus = isMouse;

				const originalEvent:KeyboardEvent|MouseEvent = e && e.payload && e.payload.originalEvent;
				if (originalEvent && isMouse && originalEvent.detail === 2) {
					preserveFocus = false;
					originalEvent.preventDefault();  // focus moves to editor, we need to prevent default
				}

				const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey));
				this.debugService.openOrRevealEditor(breakpoint.source, breakpoint.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
			}
		}


() => {
			this.onDebugServiceStateChange();
		}


v => this.splitView.addView(<any> v)


() => {
			return Promise.join(this.views.map((view) => view.setVisible(visible)));
		}


(view) => view.setVisible(visible)


a => {
				this.toDispose.push(a);
			}


v => v.shutdown()


class VariablesView extends viewlet.CollapsibleViewletView {

	private static MEMENTO = 'variablesview.memento';

	constructor(actionRunner: actions.IActionRunner, private settings: any,
		@IMessageService messageService: IMessageService,
		@IContextMenuService contextMenuService: IContextMenuService,
		@IDebugService private debugService: IDebugService,
		@IInstantiationService private instantiationService: IInstantiationService
	) {
		super(actionRunner, !!settings[VariablesView.MEMENTO], nls.localize('variablesSection', "Variables Section"), messageService, contextMenuService);
	}

	public renderHeader(container: HTMLElement): void {
		super.renderHeader(container);
		const titleDiv = $('div.title').appendTo(container);
		$('span').text(nls.localize('variables', "Variables")).appendTo(titleDiv);
	}

	public renderBody(container: HTMLElement): void {
		dom.addClass(container, 'debug-variables');
		this.treeContainer = renderViewTree(container);

		this.tree = new treeimpl.Tree(this.treeContainer, {
			dataSource: new viewer.VariablesDataSource(this.debugService),
			renderer: this.instantiationService.createInstance(viewer.VariablesRenderer),
			accessibilityProvider: new viewer.VariablesAccessibilityProvider(),
			controller: new viewer.BaseDebugController(this.debugService, this.contextMenuService, new viewer.VariablesActionProvider(this.instantiationService))
		}, debugTreeOptions(nls.localize('variablesAriaTreeLabel', "Debug Variables")));

		const viewModel = this.debugService.getViewModel();

		this.tree.setInput(viewModel);

		const collapseAction = this.instantiationService.createInstance(viewlet.CollapseAction, this.tree, false, 'explorer-action collapse-explorer');
		this.toolBar.setActions(actionbarregistry.prepareActions([collapseAction]))();

		this.toDispose.push(viewModel.addListener2(debug.ViewModelEvents.FOCUSED_STACK_FRAME_UPDATED, () => this.onFocusedStackFrameUpdated()));
		this.toDispose.push(this.debugService.addListener2(debug.ServiceEvents.STATE_CHANGED, () => {
			collapseAction.enabled = this.debugService.getState() === debug.State.Running || this.debugService.getState() === debug.State.Stopped;
		}));
	}

	private onFocusedStackFrameUpdated(): void {
		this.tree.refresh().then(() => {
			const stackFrame = this.debugService.getViewModel().getFocusedStackFrame();
			if (stackFrame) {
				return stackFrame.getScopes(this.debugService).then(scopes => {
					if (scopes.length > 0) {
						return this.tree.expand(scopes[0]);
					}
				});
			}
		}).done(null, errors.onUnexpectedError);
	}

	public shutdown(): void {
		this.settings[VariablesView.MEMENTO] = (this.state === splitview.CollapsibleState.COLLAPSED);
		super.shutdown();
	}
}


class WatchExpressionsView extends viewlet.CollapsibleViewletView {

	private static MEMENTO = 'watchexpressionsview.memento';

	constructor(actionRunner: actions.IActionRunner, private settings: any,
		@IMessageService messageService: IMessageService,
		@IContextMenuService contextMenuService: IContextMenuService,
		@IDebugService private debugService: IDebugService,
		@IInstantiationService private instantiationService: IInstantiationService
	) {
		super(actionRunner, !!settings[WatchExpressionsView.MEMENTO], nls.localize('expressionsSection', "Expressions Section"), messageService, contextMenuService);
		this.toDispose.push(this.debugService.getModel().addListener2(debug.ModelEvents.WATCH_EXPRESSIONS_UPDATED, (we) => {
			// only expand when a new watch expression is added.
			if (we instanceof model.Expression) {
				this.expand();
			}
		}));
	}

	public renderHeader(container: HTMLElement): void {
		super.renderHeader(container);
		const titleDiv = $('div.title').appendTo(container);
		$('span').text(nls.localize('watch', "Watch")).appendTo(titleDiv);
	}

	public renderBody(container: HTMLElement): void {
		dom.addClass(container, 'debug-watch');
		this.treeContainer = renderViewTree(container);

		const actionProvider = new viewer.WatchExpressionsActionProvider(this.instantiationService);
		this.tree = new treeimpl.Tree(this.treeContainer, {
			dataSource: new viewer.WatchExpressionsDataSource(this.debugService),
			renderer: this.instantiationService.createInstance(viewer.WatchExpressionsRenderer, actionProvider, this.actionRunner),
			accessibilityProvider: new viewer.WatchExpressionsAccessibilityProvider(),
			controller: new viewer.WatchExpressionsController(this.debugService, this.contextMenuService, actionProvider)
		}, debugTreeOptions(nls.localize('watchAriaTreeLabel', "Debug Watch Expressions")));

		this.tree.setInput(this.debugService.getModel());

		const addWatchExpressionAction = this.instantiationService.createInstance(debugactions.AddWatchExpressionAction, debugactions.AddWatchExpressionAction.ID, debugactions.AddWatchExpressionAction.LABEL);
		const collapseAction = this.instantiationService.createInstance(viewlet.CollapseAction, this.tree, false, 'explorer-action collapse-explorer');
		const removeAllWatchExpressionsAction = this.instantiationService.createInstance(debugactions.RemoveAllWatchExpressionsAction, debugactions.RemoveAllWatchExpressionsAction.ID, debugactions.RemoveAllWatchExpressionsAction.LABEL);
		this.toolBar.setActions(actionbarregistry.prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))();

		this.toDispose.push(this.debugService.getModel().addListener2(debug.ModelEvents.WATCH_EXPRESSIONS_UPDATED, (we: model.Expression) => this.onWatchExpressionsUpdated(we)));
		this.toDispose.push(this.debugService.getViewModel().addListener2(debug.ViewModelEvents.SELECTED_EXPRESSION_UPDATED, (expression: debug.IExpression) => {
			if (!expression || !(expression instanceof model.Expression)) {
				return;
			}

			this.tree.refresh(expression, false).then(() => {
				this.tree.setHighlight(expression);

				const unbind = this.tree.addListener(events.EventType.HIGHLIGHT, (e: tree.IHighlightEvent) => {
					if (!e.highlight) {
						this.debugService.getViewModel().setSelectedExpression(null);
						this.tree.refresh(expression).done(null, errors.onUnexpectedError);
						unbind();
					}
				});
			}).done(null, errors.onUnexpectedError);
		}));
	}

	private onWatchExpressionsUpdated(we: model.Expression): void {
		this.tree.refresh().done(() => {
			return we instanceof model.Expression ? this.tree.reveal(we): TPromise.as(true);
		}, errors.onUnexpectedError);
	}

	public shutdown(): void {
		this.settings[WatchExpressionsView.MEMENTO] = (this.state === splitview.CollapsibleState.COLLAPSED);
		super.shutdown();
	}
}


class CallStackView extends viewlet.CollapsibleViewletView {

	private static MEMENTO = 'callstackview.memento';
	private messageBox: HTMLDivElement;
	private fullSize: number;

	constructor(actionRunner: actions.IActionRunner, private settings: any,
		@IMessageService messageService: IMessageService,
		@IContextMenuService contextMenuService: IContextMenuService,
		@IDebugService private debugService: IDebugService,
		@IInstantiationService private instantiationService: IInstantiationService
	) {
		super(actionRunner, !!settings[CallStackView.MEMENTO], nls.localize('callstackSection', "Call Stack Section"), messageService, contextMenuService);
	}

	public renderHeader(container: HTMLElement): void {
		super.renderHeader(container);
		const titleDiv = $('div.title').appendTo(container);
		$('span').text(nls.localize('callStack', "Call Stack")).appendTo(titleDiv);
	}

	public renderBody(container: HTMLElement): void {
		dom.addClass(container, 'debug-call-stack');
		this.renderMessageBox(container);
		this.treeContainer = renderViewTree(container);

		this.tree = new treeimpl.Tree(this.treeContainer, {
			dataSource: new viewer.CallStackDataSource(),
			renderer: this.instantiationService.createInstance(viewer.CallStackRenderer),
			accessibilityProvider: this.instantiationService.createInstance(viewer.CallstackAccessibilityProvider)
		}, debugTreeOptions(nls.localize('callStackAriaLabel', "Debug Call Stack")));

		const debugModel = this.debugService.getModel();

		this.tree.setInput(debugModel);

		this.toDispose.push(this.tree.addListener2('selection', (e: tree.ISelectionEvent) => {
			if (!e.selection.length) {
				return;
			}
			const element = e.selection[0];
			if (!(element instanceof model.StackFrame)) {
				return;
			}

			const stackFrame = <debug.IStackFrame> element;
			this.debugService.setFocusedStackFrameAndEvaluate(stackFrame);

			const isMouse = (e.payload.origin === 'mouse');
			let preserveFocus = isMouse;

			const originalEvent:KeyboardEvent|MouseEvent = e && e.payload && e.payload.originalEvent;
			if (originalEvent && isMouse && originalEvent.detail === 2) {
				preserveFocus = false;
				originalEvent.preventDefault();  // focus moves to editor, we need to prevent default
			}

			const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey));
			this.debugService.openOrRevealEditor(stackFrame.source, stackFrame.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
		}));

		this.toDispose.push(debugModel.addListener2(debug.ModelEvents.CALLSTACK_UPDATED, () => {
			this.tree.refresh().done(null, errors.onUnexpectedError);
		}));
		this.toDispose.push(this.debugService.getViewModel().addListener2(debug.ViewModelEvents.FOCUSED_STACK_FRAME_UPDATED, () => {
			const focussedThread = this.debugService.getModel().getThreads()[this.debugService.getViewModel().getFocusedThreadId()];
			const previouslyHidden = this.messageBox.hidden;
			if (focussedThread && focussedThread.stoppedReason && focussedThread.stoppedReason !== 'step') {
				this.messageBox.textContent = nls.localize('debugStopped', "Paused on {0}.", focussedThread.stoppedReason);
				focussedThread.stoppedReason === 'exception' ? this.messageBox.classList.add('exception') : this.messageBox.classList.remove('exception');

				this.messageBox.hidden = false;
			} else {
				this.messageBox.hidden = true;
			}

			if (previouslyHidden !== this.messageBox.hidden) {
				this.layoutBody(this.fullSize);
			}
		}));

		this.toDispose.push(this.debugService.getViewModel().addListener2(debug.ViewModelEvents.FOCUSED_STACK_FRAME_UPDATED,() => {
			const focused = this.debugService.getViewModel().getFocusedStackFrame();
			if (focused) {
				const threads = this.debugService.getModel().getThreads();
				for (let ref in threads) {
					if (threads[ref].callStack.some(sf => sf === focused)) {
						this.tree.expand(threads[ref]);
					}
				}
				this.tree.setFocus(focused);
			}
		}));
	}

	public layoutBody(size: number): void {
		this.fullSize = size;
		const sizeWithRespectToMessageBox = this.messageBox && !this.messageBox.hidden ? size - 27 : size;
		super.layoutBody(sizeWithRespectToMessageBox);
	}

	private renderMessageBox(container: HTMLElement): void {
		this.messageBox = document.createElement('div');
		dom.addClass(this.messageBox, 'debug-message-box');
		this.messageBox.hidden = true;
		container.appendChild(this.messageBox);
	}

	public shutdown(): void {
		this.settings[CallStackView.MEMENTO] = (this.state === splitview.CollapsibleState.COLLAPSED);
		super.shutdown();
	}
}


class BreakpointsView extends viewlet.AdaptiveCollapsibleViewletView {

	private static MAX_VISIBLE_FILES = 9;
	private static MEMENTO = 'breakopintsview.memento';

	constructor(actionRunner: actions.IActionRunner, private settings: any,
		@IMessageService messageService: IMessageService,
		@IContextMenuService contextMenuService: IContextMenuService,
		@IDebugService private debugService: IDebugService,
		@IInstantiationService private instantiationService: IInstantiationService
	) {
		super(actionRunner, BreakpointsView.getExpandedBodySize(
			debugService.getModel().getBreakpoints().length + debugService.getModel().getFunctionBreakpoints().length + debugService.getModel().getExceptionBreakpoints().length),
			!!settings[BreakpointsView.MEMENTO], nls.localize('breakpointsSection', "Breakpoints Section"), messageService, contextMenuService);

		this.toDispose.push(this.debugService.getModel().addListener2(debug.ModelEvents.BREAKPOINTS_UPDATED,() => this.onBreakpointsChange()));
	}

	public renderHeader(container: HTMLElement): void {
		super.renderHeader(container);
		const titleDiv = $('div.title').appendTo(container);
		$('span').text(nls.localize('breakpoints', "Breakpoints")).appendTo(titleDiv);
	}

	public renderBody(container: HTMLElement): void {
		dom.addClass(container, 'debug-breakpoints');
		this.treeContainer = renderViewTree(container);
		const actionProvider = new viewer.BreakpointsActionProvider(this.instantiationService);

		this.tree = new treeimpl.Tree(this.treeContainer, {
			dataSource: new viewer.BreakpointsDataSource(),
			renderer: this.instantiationService.createInstance(viewer.BreakpointsRenderer, actionProvider, this.actionRunner),
			accessibilityProvider: this.instantiationService.createInstance(viewer.BreakpointsAccessibilityProvider),
			controller: new viewer.BreakpointsController(this.debugService, this.contextMenuService, actionProvider),
			sorter: {
				compare(tree: tree.ITree, element: any, otherElement: any): number {
					const first = <debug.IBreakpoint> element;
					const second = <debug.IBreakpoint> otherElement;
					if (first instanceof model.ExceptionBreakpoint) {
						return -1;
					}
					if (second instanceof model.ExceptionBreakpoint) {
						return 1;
					}
					if (first instanceof model.FunctionBreakpoint) {
						return -1;
					}
					if(second instanceof model.FunctionBreakpoint) {
						return 1;
					}

					if (first.source.uri.toString() !== second.source.uri.toString()) {
						return first.source.uri.toString().localeCompare(second.source.uri.toString());
					}

					return first.desiredLineNumber - second.desiredLineNumber;
				}
			}
		}, debugTreeOptions(nls.localize('breakpointsAriaTreeLabel', "Debug Breakpoints")));

		const debugModel = this.debugService.getModel();

		this.tree.setInput(debugModel);

		this.toDispose.push(this.tree.addListener2('selection', (e: tree.ISelectionEvent) => {
			if (!e.selection.length) {
				return;
			}
			const element = e.selection[0];
			if (!(element instanceof model.Breakpoint)) {
				return;
			}

			const breakpoint = <debug.IBreakpoint> element;
			if (!breakpoint.source.inMemory) {
				const isMouse = (e.payload.origin === 'mouse');
				let preserveFocus = isMouse;

				const originalEvent:KeyboardEvent|MouseEvent = e && e.payload && e.payload.originalEvent;
				if (originalEvent && isMouse && originalEvent.detail === 2) {
					preserveFocus = false;
					originalEvent.preventDefault();  // focus moves to editor, we need to prevent default
				}

				const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey));
				this.debugService.openOrRevealEditor(breakpoint.source, breakpoint.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
			}
		}));
	}

	public getActions(): actions.IAction[] {
		return [
			// this.instantiationService.createInstance(debugactions.AddFunctionBreakpointAction, debugactions.AddFunctionBreakpointAction.ID, debugactions.AddFunctionBreakpointAction.LABEL),
			this.instantiationService.createInstance(debugactions.ReapplyBreakpointsAction, debugactions.ReapplyBreakpointsAction.ID, debugactions.ReapplyBreakpointsAction.LABEL),
			this.instantiationService.createInstance(debugactions.ToggleBreakpointsActivatedAction, debugactions.ToggleBreakpointsActivatedAction.ID, debugactions.ToggleBreakpointsActivatedAction.LABEL),
			this.instantiationService.createInstance(debugactions.RemoveAllBreakpointsAction, debugactions.RemoveAllBreakpointsAction.ID, debugactions.RemoveAllBreakpointsAction.LABEL)
		];
	}

	private onBreakpointsChange(): void {
		const model = this.debugService.getModel();
		this.expandedBodySize = BreakpointsView.getExpandedBodySize(
			model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length);

		if (this.tree) {
			this.tree.refresh();
		}
	}

	private static getExpandedBodySize(length: number): number {
		return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22;
	}

	public shutdown(): void {
		this.settings[BreakpointsView.MEMENTO] = (this.state === splitview.CollapsibleState.COLLAPSED);
		super.shutdown();
	}
}