<?php

/**
 * Copyright Maarch since 2008 under licence GPLv3.
 * See LICENCE.txt file at the root folder for more details.
 * This file is part of Maarch software.
 */

/**
 * @brief Folder Controller
 *
 * @author dev@maarch.org
 */

namespace Folder\controllers;

use Attachment\models\AttachmentModel;
use Basket\models\BasketModel;
use Entity\models\EntityModel;
use Folder\models\EntityFolderModel;
use Folder\models\FolderModel;
use Folder\models\ResourceFolderModel;
use Folder\models\UserPinnedFolderModel;
use Group\controllers\PrivilegeController;
use History\controllers\HistoryController;
use Resource\controllers\ResController;
use Resource\controllers\ResourceListController;
use Resource\models\ResModel;
use Resource\models\ResourceListModel;
use Resource\models\UserFollowedResourceModel;
use Respect\Validation\Validator;
use Slim\Http\Request;
use Slim\Http\Response;
use SrcCore\controllers\PreparedClauseController;
use SrcCore\models\DatabaseModel;
use SrcCore\models\ValidatorModel;
use User\models\UserEntityModel;
use User\models\UserModel;

class FolderController
{
    public function get(Request $request, Response $response)
    {
        $folders = FolderController::getScopeFolders(['login' => $GLOBALS['login']]);

        $userEntities = EntityModel::getWithUserEntities(['select'  => ['entities.id'], 'where' => ['user_id = ?'], 'data' => [$GLOBALS['id']]]);

        $userEntities = array_column($userEntities, 'id');
        if (empty($userEntities)) {
            $userEntities = 0;
        }

        $foldersWithResources = FolderModel::getWithEntitiesAndResources([
            'select'   => ['COUNT(DISTINCT resources_folders.res_id)', 'resources_folders.folder_id'],
            'where'    => ['(entities_folders.entity_id in (?) OR folders.user_id = ? OR keyword = ?)'],
            'data'     => [$userEntities, $GLOBALS['id'], 'ALL_ENTITIES'],
            'groupBy'  => ['resources_folders.folder_id']
        ]);

        $tree = [];
        foreach ($folders as $folder) {
            $key = array_keys(array_column($foldersWithResources, 'folder_id'), $folder['id']);
            $count = 0;
            if (isset($key[0])) {
                $count = $foldersWithResources[$key[0]]['count'];
            }

            $isPinned = !empty(
                UserPinnedFolderModel::get([
                    'where' => ['folder_id = ?', 'user_id = ?'],
                    'data'  => [$folder['id'], $GLOBALS['id']]
                ])
            );

            $folderScope = FolderController::getScopeFolders(['login' => $GLOBALS['login'], 'folderId' => $folder['id'], 'edition' => true]);

            $insert = [
                'name'           => $folder['label'],
                'id'             => $folder['id'],
                'label'          => $folder['label'],
                'public'         => $folder['public'],
                'user_id'        => $folder['user_id'],
                'parent_id'      => $folder['parent_id'],
                'level'          => $folder['level'],
                'countResources' => $count,
                'pinned'         => $isPinned,
                'canEdit'        => !empty($folderScope)
            ];
            if ($folder['level'] == 0) {
                array_splice($tree, 0, 0, [$insert]);
            } else {
                $found = false;
                foreach ($tree as $key => $branch) {
                    if ($branch['id'] == $folder['parent_id']) {
                        array_splice($tree, $key + 1, 0, [$insert]);
                        $found = true;
                        break;
                    }
                }
                if (!$found) {
                    $insert['level'] = 0;
                    $insert['parent_id'] = null;
                    $tree[] = $insert;
                }
            }
        }

        return $response->withJson(['folders' => $tree]);
    }

    public function getById(Request $request, Response $response, array $args)
    {
        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

        $folder = FolderController::getScopeFolders(['login' => $GLOBALS['login'], 'folderId' => $args['id']]);
        if (empty($folder[0])) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder not found or out of your perimeter']);
        }

        $folder = $folder[0];
        $ownerInfo = UserModel::getById(['select' => ['firstname', 'lastname'], 'id' => $folder['user_id']]);
        $folder['ownerDisplayName'] = $ownerInfo['firstname'] . ' ' . $ownerInfo['lastname'];

        $userEntities = EntityModel::getWithUserEntities([
            'select' => ['id'],
            'where'  => ['user_id = ?'],
            'data'   => [$GLOBALS['id']]
        ]);
        $userEntities = array_column($userEntities, 'id');

        $folder['sharing']['entities'] = [];
        if ($folder['public']) {
            $entitiesFolder = EntityFolderModel::getEntitiesByFolderId(['folder_id' => $args['id'], 'select' => ['entities_folders.entity_id', 'entities_folders.edition', 'entities.entity_label']]);
            $canDeleteWithChildren = FolderController::areChildrenInPerimeter(['folderId' => $args['id'], 'entities' => $userEntities]);
            foreach ($entitiesFolder as $value) {
                $canDelete = $canDeleteWithChildren && $value['edition'] == true;
                $folder['sharing']['entities'][] = ['entity_id' => $value['entity_id'], 'edition' => $value['edition'], 'canDelete' => $canDelete, 'label' => $value['entity_label']];
            }

            $keywordsFolder = EntityFolderModel::getKeywordsByFolderId(['folder_id' => $args['id'], 'select' => ['edition', 'keyword']]);
            foreach ($keywordsFolder as $value) {
                $canDelete = $canDeleteWithChildren && $value['edition'] == true;
                $folder['sharing']['entities'][] = ['keyword' => $value['keyword'], 'edition' => $value['edition'], 'canDelete' => $canDelete];
            }
        }

        $folder['pinned'] = !empty(
            UserPinnedFolderModel::get([
                'where' => ['folder_id = ?', 'user_id = ?'],
                'data'  => [$folder['id'], $GLOBALS['id']]
            ])
        );

        return $response->withJson(['folder' => $folder]);
    }

    public function create(Request $request, Response $response)
    {
        $data = $request->getParsedBody();

        if (!Validator::stringType()->notEmpty()->validate($data['label'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body label is empty or not a string']);
        }
        if (!empty($data['parent_id']) && !Validator::intval()->validate($data['parent_id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body parent_id is not an integer']);
        }

        if (empty($data['parent_id'])) {
            $data['parent_id'] = null;
            $owner  = $GLOBALS['id'];
            $public = false;
            $level  = 0;
        } else {
            $folder = FolderController::getScopeFolders(['login' => $GLOBALS['login'], 'folderId' => $data['parent_id'], 'edition' => true]);
            if (empty($folder[0])) {
                return $response->withStatus(400)->withJson(['errors' => 'Parent Folder not found or out of your perimeter']);
            }
            $owner  = $folder[0]['user_id'];
            $public = $folder[0]['public'];
            $level  = $folder[0]['level'] + 1;
        }

        $id = FolderModel::create([
            'label'     => $data['label'],
            'public'    => $public,
            'user_id'   => $owner,
            'parent_id' => $data['parent_id'],
            'level'     => $level
        ]);

        if (!empty($data['parent_id'])) {
            $parentSharing = EntityFolderModel::get([
                'select' => ['entity_id', 'edition', 'keyword'],
                'where'  => ['folder_id = ?'],
                'data'   => [$data['parent_id']]
            ]);

            foreach ($parentSharing as $sharing) {
                EntityFolderModel::create([
                    'folder_id' => $id,
                    'entity_id' => $sharing['entity_id'],
                    'edition'   => $sharing['edition'],
                    'keyword'   => $sharing['keyword']
                ]);
            }
        }

        UserPinnedFolderModel::create([
            'folder_id' => $id,
            'user_id'   => $GLOBALS['id']
        ]);

        if ($public && !empty($data['parent_id'])) {
            $entitiesSharing = EntityFolderModel::getEntitiesByFolderId(['folder_id' => $data['parent_id'], 'select' => ['entities.id', 'entities_folders.edition']]);
            foreach ($entitiesSharing as $entity) {
                EntityFolderModel::create([
                    'folder_id' => $id,
                    'entity_id' => $entity['id'],
                    'edition'   => $entity['edition'],
                ]);
            }
        }

        HistoryController::add([
            'tableName' => 'folders',
            'recordId'  => $id,
            'eventType' => 'ADD',
            'info'      => _FOLDER_CREATION . " : {$data['label']}",
            'moduleId'  => 'folder',
            'eventId'   => 'folderCreation',
        ]);

        return $response->withJson(['folder' => $id]);
    }

    public function update(Request $request, Response $response, array $args)
    {
        $data = $request->getParams();

        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Query id is empty or not an integer']);
        }
        if (!Validator::stringType()->notEmpty()->validate($data['label'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body label is empty or not a string']);
        }
        if (!empty($data['parent_id']) && !Validator::intval()->validate($data['parent_id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body parent_id is not an integer']);
        }
        if ($data['parent_id'] == $args['id']) {
            return $response->withStatus(400)->withJson(['errors' => 'Parent_id and id can not be the same']);
        }
        if (!empty($data['parent_id']) && FolderController::isParentFolder(['parent_id' => $data['parent_id'], 'id' => $args['id']])) {
            return $response->withStatus(400)->withJson(['errors' => 'parent_id does not exist or Id is a parent of parent_id']);
        }

        $folder = FolderController::getScopeFolders(['login' => $GLOBALS['login'], 'folderId' => $args['id'], 'edition' => true]);
        if (empty($folder[0])) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder not found or out of your perimeter']);
        }

        $folderOwner = $folder[0]['user_id'];
        if (empty($data['parent_id'])) {
            $data['parent_id'] = null;
            $level = 0;
        } else {
            $folderParent = FolderController::getScopeFolders(['login' => $GLOBALS['login'], 'folderId' => $data['parent_id'], 'edition' => true]);
            if (empty($folderParent[0])) {
                return $response->withStatus(400)->withJson(['errors' => 'Parent Folder not found or out of your perimeter']);
            }
            $level = $folderParent[0]['level'] + 1;
            $folderOwner = $folderParent[0]['user_id'];
        }

        if ($folder[0]['parent_id'] != $data['parent_id']) {
            $userEntities = EntityModel::getWithUserEntities([
                'select' => ['id'],
                'where'  => ['user_id = ?'],
                'data'   => [$GLOBALS['id']]
            ]);
            $userEntities = array_column($userEntities, 'id');

            $childrenInPerimeter = FolderController::areChildrenInPerimeter(['folderId' => $args['id'], 'entities' => $userEntities]);
            if (!$childrenInPerimeter && $folder[0]['user_id'] != $GLOBALS['id']) {
                return $response->withStatus(400)->withJson(['errors' => 'Cannot move folder because at least one folder is out of your perimeter']);
            }

            if (!empty($data['parent_id'])) {
                $parentEntities = EntityFolderModel::get([
                    'select' => ['entity_id', 'edition'],
                    'where'  => ['folder_id = ?', 'entity_id is not null'],
                    'data'   => [$data['parent_id']]
                ]);
                $entities = [];
                foreach ($parentEntities as $entity) {
                    $entities[] = ['entity_id' => $entity['entity_id'], 'edition' => $entity['edition']];
                }

                $keywordsList = EntityFolderModel::get([
                    'select'  => ['keyword', 'edition'],
                    'where'   => ['folder_id in (?)', 'keyword is not null'],
                    'data'    => [[$data['parent_id'], $args['id']]],
                    'groupBy' => ['keyword', 'edition'],
                    'orderBy' => ['edition DESC']
                ]);
                $keywords = [];
                foreach ($keywordsList as $keyword) {
                    $keywords[] = ['keyword' => $keyword['keyword'], 'edition' => $keyword['edition']];
                }

                DatabaseModel::beginTransaction();
                $sharing = FolderController::folderSharing([
                    'folderId' => $args['id'],
                    'public'   => true,
                    'remove'   => [],
                    'add'      => $entities,
                    'keywords' => $keywords
                ]);
                if (!$sharing) {
                    DatabaseModel::rollbackTransaction();
                    return $response->withStatus(400)->withJson(['errors' => 'Cannot share/unshare folder because at least one folder is out of your perimeter']);
                }
                DatabaseModel::commitTransaction();
            }

            FolderModel::update([
                'set'   => [
                    'parent_id' => $data['parent_id'],
                    'level'     => $level,
                    'user_id'   => $folderOwner
                ],
                'where' => ['id = ?'],
                'data'  => [$args['id']]
            ]);
        }

        FolderController::updateChildren($args['id'], $level, $folderOwner);

        FolderModel::update([
            'set'   => [
                'label' => $data['label']
            ],
            'where' => ['id = ?'],
            'data'  => [$args['id']]
        ]);

        HistoryController::add([
            'tableName' => 'folders',
            'recordId'  => $args['id'],
            'eventType' => 'UP',
            'info'      => _FOLDER_MODIFICATION . " : {$data['label']}",
            'moduleId'  => 'folder',
            'eventId'   => 'folderModification',
        ]);

        return $response->withStatus(200);
    }

    public function sharing(Request $request, Response $response, array $args)
    {
        $data = $request->getParams();

        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Query id is empty or not an integer']);
        }
        if (!Validator::boolType()->validate($data['public'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body public is empty or not a boolean']);
        }
        if ($data['public'] && !isset($data['sharing']['entities'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body sharing/entities does not exists']);
        }

        $keywords = [];
        $entities = [];
        foreach ($data['sharing']['entities'] as $item) {
            if (isset($item['entity_id'])) {
                $entities[] = $item;
            } elseif (isset($item['keyword'])) {
                $keywords[] = $item;
            }
        }

        $entitiesBefore = EntityFolderModel::getEntitiesByFolderId(['select' => ['entities_folders.entity_id, edition'], 'folder_id' => $args['id']]);
        $entitiesAfter = $entities;

        $entitiesToRemove = array_udiff($entitiesBefore, $entitiesAfter, function ($a, $b) {
            if ($a['entity_id'] == $b['entity_id'] && $a['edition'] != $b['edition']) {
                return 1;
            }
            if ($a["entity_id"] < $b["entity_id"]) {
                return -1;
            }
            if ($a["entity_id"] > $b["entity_id"]) {
                return 1;
            }
            return 0;
        });

        $entitiesToAdd = array_udiff($entitiesAfter, $entitiesBefore, function ($a, $b) {
            if ($a['entity_id'] == $b['entity_id'] && $a['edition'] != $b['edition']) {
                return 1;
            }
            if ($a["entity_id"] < $b["entity_id"]) {
                return -1;
            }
            if ($a["entity_id"] > $b["entity_id"]) {
                return 1;
            }
            return 0;
        });

        DatabaseModel::beginTransaction();
        $sharing = FolderController::folderSharing([
            'folderId' => $args['id'],
            'public'   => $data['public'],
            'remove'   => $entitiesToRemove,
            'add'      => $entitiesToAdd,
            'keywords' => $keywords
        ]);
        if (!$sharing) {
            DatabaseModel::rollbackTransaction();
            return $response->withStatus(400)->withJson(['errors' => 'Cannot share/unshare folder because at least one folder is out of your perimeter']);
        }
        DatabaseModel::commitTransaction();

        $folder = FolderModel::getById(['select' => ['label'], 'id' => $args['id']]);

        HistoryController::add([
            'tableName' => 'folders',
            'recordId'  => $args['id'],
            'eventType' => 'UP',
            'info'      => _FOLDER_SHARING_MODIFICATION . " : {$folder['label']}",
            'moduleId'  => 'folder',
            'eventId'   => 'folderModification',
        ]);

        return $response->withStatus(204);
    }

    public function folderSharing($args = [])
    {
        $folder = FolderController::getScopeFolders(['login' => $GLOBALS['login'], 'folderId' => $args['folderId'], 'edition' => true]);
        if (empty($folder[0])) {
            return false;
        }
        $entitiesToRemove = array_column($args['remove'], 'entity_id');

        FolderModel::update([
            'set'   => [
                'public' => empty($args['public']) ? 'false' : 'true',
            ],
            'where' => ['id = ?'],
            'data'  => [$args['folderId']]
        ]);

        $entitiesToAdd = array_column($args['add'], 'entity_id');
        if (!empty($entitiesToAdd)) {
            $alreadyPresentEntities = EntityFolderModel::get(['select' => ['entity_id'], 'where' => ['folder_id = ?', 'entity_id in (?)'], 'data' => [$args['folderId'], $entitiesToAdd]]);
            $alreadyPresentEntities = array_column($alreadyPresentEntities, 'entity_id');
            $entitiesToRemove = array_merge($entitiesToRemove, $alreadyPresentEntities);
        }
        if (!empty($entitiesToRemove)) {
            EntityFolderModel::delete(['where' => ['entity_id in (?)', 'folder_id = ?'], 'data' => [$entitiesToRemove, $args['folderId']]]);
        }
        if (!empty($args['add'])) {
            foreach ($args['add'] as $entity) {
                EntityFolderModel::create([
                    'folder_id' => $args['folderId'],
                    'entity_id' => $entity['entity_id'],
                    'edition'   => $entity['edition']
                ]);
            }
        }

        if (!empty($args['keywords'])) {
            $folderKeywords = EntityFolderModel::get([
                'select' => ['id'],
                'where'  => ['folder_id = ?', 'entity_id is null', 'keyword is not null'],
                'data'   => [$args['folderId']]
            ]);
            $folderKeywords = array_column($folderKeywords, 'id');

            if (!empty($folderKeywords)) {
                EntityFolderModel::delete([
                    'where' => ['id in (?)'],
                    'data'  => [$folderKeywords]
                ]);
            }

            foreach ($args['keywords'] as $keyword) {
                EntityFolderModel::create([
                    'folder_id' => $args['folderId'],
                    'entity_id' => null,
                    'edition'   => $keyword['edition'],
                    'keyword'   => $keyword['keyword']
                ]);
            }
        } else {
            EntityFolderModel::delete([
                'where' => ['folder_id = ?', 'entity_id is null', 'keyword is not null'],
                'data'  => [$args['folderId']]
            ]);
        }

        $entitiesOfFolder = EntityFolderModel::getEntitiesByFolderId([
            'select'    => ['entities.entity_id'],
            'folder_id' => $args['folderId']
        ]);
        $entitiesOfFolder = array_column($entitiesOfFolder, 'entity_id');

        $users = UserPinnedFolderModel::get([
            'select' => ['user_id'],
            'where'  => ['folder_id = ?'],
            'data'   => [$args['folderId']]
        ]);

        if (!empty($users) && empty($entitiesOfFolder)) {
            UserPinnedFolderModel::delete([
                'where' => ['folder_id = ?', 'user_id != ?'],
                'data'  => [$args['folderId'], $folder[0]['user_id']]
            ]);
        } else {
            foreach ($users as $user) {
                if ($user['user_id'] != $folder[0]['user_id']) {
                    $inEntities = UserEntityModel::getWithUsers([
                        'select' => ['users.id'],
                        'where'  => ['users.id = ?', 'entity_id in (?)'],
                        'data'   => [$user['user_id'], $entitiesOfFolder]
                    ]);
                    if (empty($inEntities)) {
                        UserPinnedFolderModel::delete([
                            'where' => ['folder_id = ?', 'user_id = ?'],
                            'data'  => [$args['folderId'], $user['user_id']]
                        ]);
                    }
                }
            }
        }

        $folderChild = FolderModel::getChild(['id' => $args['folderId'], 'select' => ['id']]);
        if (!empty($folderChild)) {
            foreach ($folderChild as $child) {
                FolderController::folderSharing([
                    'folderId' => $child['id'],
                    'public'   => $args['public'],
                    'remove'   => $args['remove'],
                    'add'      => $args['add'],
                    'keywords' => $args['keywords']
                ]);
            }
        }

        return true;
    }

    public function delete(Request $request, Response $response, array $aArgs)
    {
        if (!Validator::numeric()->notEmpty()->validate($aArgs['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Query id is empty or not an integer']);
        }
        $userEntities = EntityModel::getWithUserEntities([
            'select' => ['id'],
            'where'  => ['user_id = ?'],
            'data'   => [$GLOBALS['id']]
        ]);
        $userEntities = array_column($userEntities, 'id');

        $canDelete = FolderController::areChildrenInPerimeter(['folderId' => $aArgs['id'], 'entities' => $userEntities]);

        if (!$canDelete) {
            return $response->withStatus(400)->withJson(['errors' => 'Cannot delete because at least one folder is out of your perimeter']);
        }

        $folder = FolderController::getScopeFolders(['login' => $GLOBALS['login'], 'folderId' => $aArgs['id'], 'edition' => true]);
        
        DatabaseModel::beginTransaction();
        $deletion = FolderController::folderDeletion(['folderId' => $aArgs['id']]);
        if (!$deletion) {
            DatabaseModel::rollbackTransaction();
            return $response->withStatus(400)->withJson(['errors' => 'Cannot delete because at least one folder is out of your perimeter']);
        }
        DatabaseModel::commitTransaction();

        HistoryController::add([
            'tableName' => 'folder',
            'recordId'  => $aArgs['id'],
            'eventType' => 'DEL',
            'info'      => _FOLDER_SUPPRESSION . " : {$folder[0]['label']}",
            'moduleId'  => 'folder',
            'eventId'   => 'folderSuppression',
        ]);

        return $response->withStatus(204);
    }

    public static function folderDeletion(array $args = [])
    {
        $folder = FolderController::getScopeFolders(['login' => $GLOBALS['login'], 'folderId' => $args['folderId'], 'edition' => true]);
        if (empty($folder[0])) {
            return false;
        }

        FolderModel::delete(['where' => ['id = ?'], 'data' => [$args['folderId']]]);
        EntityFolderModel::deleteByFolderId(['folder_id' => $args['folderId']]);
        ResourceFolderModel::delete(['where' => ['folder_id = ?'], 'data' => [$args['folderId']]]);
        UserPinnedFolderModel::delete([ 'where' => ['folder_id = ?'], 'data'  => [$args['folderId']] ]);

        $folderChild = FolderModel::getChild(['id' => $args['folderId'], 'select' => ['id']]);
        if (!empty($folderChild)) {
            foreach ($folderChild as $child) {
                $deletion = FolderController::folderDeletion(['folderId' => $child['id']]);
                if (!$deletion) {
                    return false;
                }
            }
        }
        return true;
    }

    public static function areChildrenInPerimeter(array $args = [])
    {
        ValidatorModel::notEmpty($args, ['folderId', 'entities']);
        ValidatorModel::intVal($args, ['folderId']);
        ValidatorModel::arrayType($args, ['entities']);

        $folder = FolderController::getScopeFolders(['login' => $GLOBALS['login'], 'folderId' => $args['folderId'], 'edition' => true]);
        if (empty($folder[0])) {
            return false;
        }

        $folder = $folder[0];

        // All sub-folders of a folders have the same owner user -> if user is owner of folder, all children are in perimeter
        if ($folder['user_id'] == $GLOBALS['id']) {
            return true;
        }

        $children = FolderModel::getWithEntities([
            'select' => ['distinct (folders.id)', 'edition', 'user_id', 'keyword', 'entity_id', 'parent_id'],
            'where'  => ['parent_id = ?', '(entity_id in (?) OR entity_id is null)'],
            'data'   => [$args['folderId'], $args['entities']]
        ]);

        $allEntitiesCanDelete = true;
        foreach ($children as $key => $child) {
            if (!($child['keyword'] == 'ALL_ENTITIES' && $child['edition'] == true)) {
                $allEntitiesCanDelete = false;
                break;
            }
        }


        if (!empty($children)) {
            foreach ($children as $child) {
                if (($child['edition'] == null || $child['edition'] == false) && (!$allEntitiesCanDelete)) {
                    return false;
                }
                if (!FolderController::areChildrenInPerimeter(['folderId' => $child['id'], 'entities' => $args['entities']])) {
                    return false;
                }
            }
        }
        return true;
    }

    public function getResourcesById(Request $request, Response $response, array $args)
    {
        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

        if (!FolderController::hasFolders(['folders' => [$args['id']], 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder out of perimeter']);
        }

        $foldersResources = ResourceFolderModel::get(['select' => ['res_id'], 'where' => ['folder_id = ?'], 'data' => [$args['id']]]);
        $foldersResources = array_column($foldersResources, 'res_id');

        $formattedResources = [];
        $allResources = [];
        $count = 0;
        if (!empty($foldersResources)) {
            $queryParams = $request->getQueryParams();
            $queryParams['offset'] = (empty($queryParams['offset']) || !is_numeric($queryParams['offset']) ? 0 : (int)$queryParams['offset']);
            $queryParams['limit'] = (empty($queryParams['limit']) || !is_numeric($queryParams['limit']) ? 10 : (int)$queryParams['limit']);

            $allQueryData = ResourceListController::getResourcesListQueryData(['data' => $queryParams]);
            if (!empty($allQueryData['order'])) {
                $data['order'] = $allQueryData['order'];
            }

            $rawResources = ResourceListModel::getOnView([
                'select'    => ['res_id'],
                'table'     => $allQueryData['table'],
                'leftJoin'  => $allQueryData['leftJoin'],
                'where'     => array_merge(['res_id in (?)'], $allQueryData['where']),
                'data'      => array_merge([$foldersResources], $allQueryData['queryData']),
                'orderBy'   => empty($data['order']) ? ['creation_date'] : [$data['order']]
            ]);

            $resIds = ResourceListController::getIdsWithOffsetAndLimit(['resources' => $rawResources, 'offset' => $queryParams['offset'], 'limit' => $queryParams['limit']]);

            $allResources = array_column($rawResources, 'res_id');

            $formattedResources = [];
            if (!empty($resIds)) {
                $attachments = AttachmentModel::get([
                    'select'    => ['COUNT(res_id)', 'res_id_master'],
                    'where'     => ['res_id_master in (?)', 'status not in (?)', '((status = ? AND typist = ?) OR status != ?)'],
                    'data'      => [$resIds, ['DEL', 'OBS'], 'TMP', $GLOBALS['id'], 'TMP'],
                    'groupBy'   => ['res_id_master']
                ]);

                $select = [
                    'res_letterbox.res_id', 'res_letterbox.subject', 'res_letterbox.barcode', 'res_letterbox.alt_identifier',
                    'status.label_status AS "status.label_status"', 'status.img_filename AS "status.img_filename"', 'priorities.color AS "priorities.color"',
                    'res_letterbox.filename as res_filename'
                ];
                $tableFunction = ['status', 'priorities'];
                $leftJoinFunction = ['res_letterbox.status = status.id', 'res_letterbox.priority = priorities.id'];

                $order = 'CASE res_letterbox.res_id ';
                foreach ($resIds as $key => $resId) {
                    $order .= "WHEN {$resId} THEN {$key} ";
                }
                $order .= 'END';

                $resources = ResourceListModel::getOnResource([
                    'select'    => $select,
                    'table'     => $tableFunction,
                    'leftJoin'  => $leftJoinFunction,
                    'where'     => ['res_letterbox.res_id in (?)'],
                    'data'      => [$resIds],
                    'orderBy'   => [$order]
                ]);

                $followedResources = UserFollowedResourceModel::get(['select' => ['res_id'], 'where' => ['user_id = ?'], 'data' => [$GLOBALS['id']]]);
                $followedResources = array_column($followedResources, 'res_id');

                $formattedResources = ResourceListController::getFormattedResources([
                    'resources'     => $resources,
                    'userId'        => $GLOBALS['id'],
                    'attachments'   => $attachments,
                    'checkLocked'   => false,
                    'trackedMails'  => $followedResources,
                    'listDisplay'   => ['folders']
                ]);

                $folderPrivilege = PrivilegeController::hasPrivilege(['privilegeId' => 'include_folders_and_followed_resources_perimeter', 'userId' => $GLOBALS['id']]);
                foreach ($formattedResources as $key => $formattedResource) {
                    if ($folderPrivilege) {
                        $formattedResources[$key]['allowed'] = true;
                    } else {
                        $formattedResources[$key]['allowed'] = ResController::hasRightByResId(['resId' => [$formattedResource['resId']], 'userId' => $GLOBALS['id']]);
                    }
                }
            }

            $count = count($rawResources);
        }

        return $response->withJson(['resources' => $formattedResources, 'countResources' => $count, 'allResources' => $allResources]);
    }

    public function addResourcesById(Request $request, Response $response, array $args)
    {
        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

        $body = $request->getParsedBody();
        if (!Validator::arrayType()->notEmpty()->validate($body['resources'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body resources is empty or not an array']);
        }

        if (!FolderController::hasFolders(['folders' => [$args['id']], 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder out of perimeter']);
        }

        $foldersResources = ResourceFolderModel::get(['select' => ['res_id'], 'where' => ['folder_id = ?'], 'data' => [$args['id']]]);
        $foldersResources = array_column($foldersResources, 'res_id');

        $resourcesToClassify = array_diff($body['resources'], $foldersResources);
        if (empty($resourcesToClassify)) {
            return $response->withJson(['countResources' => count($foldersResources)]);
        }

        if (!ResController::hasRightByResId(['resId' => $resourcesToClassify, 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(400)->withJson(['errors' => 'Resources out of perimeter']);
        }

        foreach ($resourcesToClassify as $value) {
            ResourceFolderModel::create(['folder_id' => $args['id'], 'res_id' => $value]);
        }

        $folders             = FolderModel::getById(['select' => ['label'], 'id' => $args['id']]);
        $resourcesInfo       = ResModel::get(['select' => ['alt_identifier'], 'where' => ['res_id in (?)'], 'data' => [$resourcesToClassify]]);
        $resourcesIdentifier = array_column($resourcesInfo, 'alt_identifier');

        foreach ($resourcesToClassify as $resource) {
            HistoryController::add([
                'tableName' => 'res_letterbox',
                'recordId'  => $resource,
                'eventType' => 'UP',
                'info'      => _ADDED_TO_FOLDER . " \"" . $folders['label'] . "\"",
                'moduleId'  => 'resource',
                'eventId'   => 'resourceModification',
            ]);
        }

        HistoryController::add([
            'tableName' => 'resources_folders',
            'recordId'  => $args['id'],
            'eventType' => 'ADD',
            'info'      => _FOLDER_RESOURCES_ADDED . " : " . implode(", ", $resourcesIdentifier) . " " . _FOLDER_TO_FOLDER . " \"" . $folders['label'] . "\"",
            'moduleId'  => 'folder',
            'eventId'   => 'folderResourceAdded',
        ]);

        return $response->withJson(['countResources' => count($foldersResources) + count($resourcesToClassify)]);
    }

    public function removeResourcesById(Request $request, Response $response, array $args)
    {
        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

        if (!FolderController::hasFolders(['folders' => [$args['id']], 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder out of perimeter']);
        }

        $foldersResources = ResourceFolderModel::get(['select' => ['res_id'], 'where' => ['folder_id = ?'], 'data' => [$args['id']]]);
        $foldersResources = array_column($foldersResources, 'res_id');

        $body = $request->getParsedBody();
        if (!Validator::arrayType()->notEmpty()->validate($body['resources'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body resources is empty or not an array']);
        }

        $resourcesToUnclassify = array_intersect($foldersResources, $body['resources']);
        if (empty($resourcesToUnclassify)) {
            return $response->withJson(['countResources' => count($foldersResources)]);
        }

        $folder = FolderModel::getById(['select' => ['label', 'public', 'user_id'], 'id' => $args['id']]);
        if ($folder['public'] || $folder['user_id'] != $GLOBALS['id']) {
            if (!ResController::hasRightByResId(['resId' => $resourcesToUnclassify, 'userId' => $GLOBALS['id']])) {
                return $response->withStatus(400)->withJson(['errors' => 'Resources out of perimeter']);
            }
        }

        foreach ($resourcesToUnclassify as $value) {
            ResourceFolderModel::delete(['where' => ['folder_id = ?', 'res_id = ?'], 'data' => [$args['id'], $value]]);
        }

        $resourcesInfo       = ResModel::get(['select' => ['alt_identifier'], 'where' => ['res_id in (?)'], 'data' => [$resourcesToUnclassify]]);
        $resourcesIdentifier = array_column($resourcesInfo, 'alt_identifier');

        foreach ($resourcesToUnclassify as $resource) {
            HistoryController::add([
                'tableName' => 'res_letterbox',
                'recordId'  => $resource,
                'eventType' => 'UP',
                'info'      => _REMOVED_TO_FOLDER . " \"" . $folder['label'] . "\"",
                'moduleId'  => 'resource',
                'eventId'   => 'resourceModification',
            ]);
        }

        HistoryController::add([
            'tableName' => 'resources_folders',
            'recordId'  => $args['id'],
            'eventType' => 'DEL',
            'info'      => _FOLDER_RESOURCES_REMOVED . " : " . implode(", ", $resourcesIdentifier) . " " . _FOLDER_TO_FOLDER . " \"" . $folder['label'] . "\"",
            'moduleId'  => 'folder',
            'eventId'   => 'folderResourceRemoved',
        ]);

        return $response->withJson(['countResources' => count($foldersResources) - count($resourcesToUnclassify)]);
    }

    public function getBasketsFromFolder(Request $request, Response $response, array $args)
    {
        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

        if (!FolderController::hasFolders(['folders' => [$args['id']], 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(403)->withJson(['errors' => 'Folder out of perimeter']);
        }

        $foldersResource = ResourceFolderModel::get(['select' => [1], 'where' => ['folder_id = ?', 'res_id = ?'], 'data' => [$args['id'], $args['resId']]]);
        if (empty($foldersResource)) {
            return $response->withStatus(403)->withJson(['errors' => 'Resource out of perimeter']);
        }

        $baskets = BasketModel::getWithPreferences([
            'select'  => ['baskets.id', 'baskets.basket_name', 'baskets.basket_clause', 'users_baskets_preferences.group_serial_id', 'usergroups.group_desc'],
            'where'   => ['users_baskets_preferences.user_serial_id = ?'],
            'data'    => [$GLOBALS['id']],
            'orderBy' => ['baskets.basket_name']
        ]);
        $groupsBaskets = [];
        $inCheckedBaskets = [];
        $outCheckedBaskets = [];
        foreach ($baskets as $basket) {
            if (in_array($basket['id'], $outCheckedBaskets)) {
                continue;
            } else {
                if (!in_array($basket['id'], $inCheckedBaskets)) {
                    $preparedClause = PreparedClauseController::getPreparedClause(['clause' => $basket['basket_clause'], 'login' => $GLOBALS['login']]);
                    $resource = ResModel::getOnView(['select' => [1], 'where' => ['res_id = ?', "({$preparedClause})"], 'data' => [$args['resId']]]);
                    if (empty($resource)) {
                        $outCheckedBaskets[] = $basket['id'];
                        continue;
                    }
                }
                $inCheckedBaskets[] = $basket['id'];
                $groupsBaskets[] = ['groupId' => $basket['group_serial_id'], 'groupName' => $basket['group_desc'], 'basketId' => $basket['id'], 'basketName' => $basket['basket_name']];
            }
        }

        return $response->withJson(['groupsBaskets' => $groupsBaskets]);
    }

    public function getFilters(Request $request, Response $response, array $args)
    {
        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

        if (!FolderController::hasFolders(['folders' => [$args['id']], 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder out of perimeter']);
        }

        $foldersResources = ResourceFolderModel::get(['select' => ['res_id'], 'where' => ['folder_id = ?'], 'data' => [$args['id']]]);
        $foldersResources = array_column($foldersResources, 'res_id');

        if (empty($foldersResources)) {
            return $response->withJson([
                'entities'         => [],
                'priorities'       => [],
                'categories'       => [],
                'statuses'         => [],
                'entitiesChildren' => [],
                'doctypes'         => [],
                'folders'          => []
            ]);
        }

        $where = ['(res_id in (?))'];
        $queryData = [$foldersResources];
        $queryParams = $request->getQueryParams();

        $filters = ResourceListController::getFormattedFilters(['where' => $where, 'queryData' => $queryData, 'queryParams' => $queryParams]);

        return $response->withJson($filters);
    }

    // login (string) : Login of user connected
    // folderId (integer) : Check specific folder
    // edition (boolean) : whether user can edit or not
    public static function getScopeFolders(array $args)
    {
        $login = $args['login'];

        $user = UserModel::getByLogin(['login' => $login, 'select' => ['id']]);
        $userEntities = EntityModel::getWithUserEntities(['select'  => ['entities.id'], 'where' => ['user_id = ?'], 'data' => [$user['id']]]);

        $userEntities = array_column($userEntities, 'id');
        if (empty($userEntities)) {
            $userEntities = [0];
        }

        $args['edition'] = $args['edition'] ?? false;
        if ($args['edition']) {
            $edition = [1];
        } else {
            $edition = [0, 1, null];
        }

        $where = ['keyword = ?', 'entities_folders.edition in (?)'];
        $data = ['ALL_ENTITIES', $edition];

        if (!empty($args['folderId'])) {
            $where[] = 'folder_id = ?';
            $data[]  = $args['folderId'];
        }

        $folderKeywords = EntityFolderModel::get([
            'select' => ['folder_id'],
            'where'  => $where,
            'data'   => $data
        ]);
        $folderKeywords = array_column($folderKeywords, 'folder_id');
        if (empty($folderKeywords)) {
            $folderKeywords = [0];
        }

        $where = ['(user_id = ? OR (entity_id in (?) AND entities_folders.edition in (?)) OR folders.id in (?))'];
        $data = [$user['id'], $userEntities, $edition, $folderKeywords];

        if (!empty($args['folderId'])) {
            $where[] = 'folders.id = ?';
            $data[]  = $args['folderId'];
        }

        $folders = FolderModel::getWithEntities([
            'select'    => ['distinct (folders.id)', 'folders.*'],
            'where'     => $where,
            'data'      => $data,
            'orderBy'   => ['level', 'label desc']
        ]);

        return $folders;
    }

    public function pinFolder(Request $request, Response $response, array $args)
    {
        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id not found or is not an integer']);
        }

        if (!FolderController::hasFolders(['folders' => [$args['id']], 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder not found or out of your perimeter']);
        }

        $alreadyPinned = UserPinnedFolderModel::get([
            'select'    => ['folder_id', 'user_id'],
            'where'     => ['folder_id = ?', 'user_id = ?'],
            'data'      => [$args['id'], $GLOBALS['id']]
        ]);

        if (!empty($alreadyPinned)) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder is already pinned']);
        }

        UserPinnedFolderModel::create([
            'folder_id' => $args['id'],
            'user_id'   => $GLOBALS['id']
        ]);

        return $response->withStatus(204);
    }

    public function getPinnedFolders(Request $request, Response $response)
    {
        $folders = UserPinnedFolderModel::getById(['user_id' => $GLOBALS['id']]);
        if (empty($folders)) {
            return $response->withJson(['folders' => []]);
        }

        $foldersIds = array_column($folders, 'id');
        $foldersWithResources = FolderModel::getWithEntitiesAndResources([
            'select'   => ['COUNT(DISTINCT resources_folders.res_id)', 'resources_folders.folder_id'],
            'where'    => ['folders.id in (?)'],
            'data'     => [$foldersIds],
            'groupBy'  => ['resources_folders.folder_id']
        ]);

        $pinnedFolders = [];

        foreach ($folders as $folder) {
            $key = array_keys(array_column($foldersWithResources, 'folder_id'), $folder['id']);
            $count = 0;
            if (isset($key[0])) {
                $count = $foldersWithResources[$key[0]]['count'];
            }
            $pinnedFolders[] = [
                'name'       => $folder['label'],
                'id'         => $folder['id'],
                'label'      => $folder['label'],
                'public'     => $folder['public'],
                'user_id'    => $folder['user_id'],
                'parent_id'  => $folder['parent_id'],
                'level'      => $folder['level'],
                'countResources' => $count
            ];
        }

        return $response->withJson(['folders' => $pinnedFolders]);
    }

    public function unpinFolder(Request $request, Response $response, array $args)
    {
        if (!Validator::numeric()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id not found or is not an integer']);
        }

        if (!FolderController::hasFolders(['folders' => [$args['id']], 'userId' => $GLOBALS['id']])) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder not found or out of your perimeter']);
        }

        $alreadyPinned = UserPinnedFolderModel::get([
            'select'    => ['folder_id', 'user_id'],
            'where'     => ['folder_id = ?', 'user_id = ?'],
            'data'      => [$args['id'], $GLOBALS['id']]
        ]);

        if (empty($alreadyPinned)) {
            return $response->withStatus(400)->withJson(['errors' => 'Folder is not pinned']);
        }

        UserPinnedFolderModel::delete([
            'where' => ['folder_id = ?', 'user_id = ?'],
            'data'  => [$args['id'], $GLOBALS['id']]
        ]);

        return $response->withStatus(204);
    }

    public static function hasFolders(array $args)
    {
        ValidatorModel::notEmpty($args, ['folders', 'userId']);
        ValidatorModel::arrayType($args, ['folders']);
        ValidatorModel::intVal($args, ['userId']);

        $entities = UserModel::getEntitiesById(['id' => $args['userId'], 'select' => ['entities.id']]);
        $entities = array_column($entities, 'id');

        if (empty($entities)) {
            $entities = [0];
        }

        $folders = FolderModel::getWithEntities([
            'select'   => ['count(distinct folders.id)'],
            'where'    => ['folders.id in (?)', "(user_id = ? OR entity_id in (?) OR keyword = 'ALL_ENTITIES')"],
            'data'     => [$args['folders'], $args['userId'], $entities]
        ]);

        if ($folders[0]['count'] != count($args['folders'])) {
            return false;
        }

        return true;
    }

    private static function isParentFolder(array $args)
    {
        $parentInfo = FolderModel::getById(['id' => $args['parent_id'], 'select' => ['folders.id', 'parent_id']]);
        if (empty($parentInfo) || $parentInfo['id'] == $args['id']) {
            return true;
        } elseif (!empty($parentInfo['parent_id'])) {
            return FolderController::isParentFolder(['parent_id' => $parentInfo['parent_id'], 'id' => $args['id']]);
        }
        return false;
    }

    private static function updateChildren($parentId, $levelParent, $folderOwner)
    {
        $folderChild = FolderModel::getChild(['id' => $parentId]);
        if (!empty($folderChild)) {
            $level = $levelParent + 1;
            foreach ($folderChild as $child) {
                FolderController::updateChildren($child['id'], $level, $folderOwner);
            }

            $idsChildren = array_column($folderChild, 'id');

            FolderModel::update([
                'set' => [
                    'level' => $level,
                    'user_id' => $folderOwner
                ],
                'where' => ['id in (?)'],
                'data' => [$idsChildren]
            ]);
        }
    }
}