/* BdfServer - Copyright (c) 2015-2023 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package fr.exemole.bdfserver.tools.roles;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.managers.PermissionManager;
import fr.exemole.bdfserver.api.namespaces.BdfUserSpace;
import fr.exemole.bdfserver.api.roles.FichePermission;
import fr.exemole.bdfserver.api.roles.Permission;
import fr.exemole.bdfserver.api.roles.Role;
import fr.exemole.bdfserver.api.roles.RoleDef;
import fr.exemole.bdfserver.api.users.BdfUser;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.permission.PermissionSummary;
import net.fichotheque.sphere.Redacteur;
import net.fichotheque.tools.permission.PermissionUtils;
import net.fichotheque.utils.SphereUtils;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.exceptions.SwitchException;


/**
 *
 * @author Vincent Calame
 */
public final class PermissionSummaryBuilder {


    private PermissionSummaryBuilder() {

    }

    public static PermissionSummary build(BdfServer bdfServer, BdfUser bdfUser) {
        return PermissionSummaryBuilder.build(bdfServer.getPermissionManager(), bdfUser.getRedacteur());
    }

    public static PermissionSummary build(PermissionManager permissionManager, Redacteur redacteur) {
        if (permissionManager.isAdmin(redacteur)) {
            return PermissionUtils.FICHOTHEQUEADMIN_PERMISSIONSUMMARY;
        }
        return build(redacteur, permissionManager.getRoleList(redacteur));
    }

    public static PermissionSummary build(Redacteur redacteur, List<Role> roleList) {
        Map<SubsetKey, SubsetSummary> map = new HashMap<SubsetKey, SubsetSummary>();
        Set<String> actionSet = new HashSet<String>();
        Set<String> roleNameSet = new HashSet<String>();
        boolean withDefaultRole = false;
        for (Role role : roleList) {
            roleNameSet.add(role.getName());
            if (RoleUtils.isDefaultRole(role)) {
                withDefaultRole = true;
            }
            for (RoleDef.SubsetEntry entry : role.getSubsetEntryList()) {
                Permission permission = entry.getPermission();
                SubsetKey subsetKey = entry.getSubsetKey();
                SubsetSummary subsetSummary = map.get(subsetKey);
                if (subsetSummary == null) {
                    subsetSummary = new SubsetSummary();
                    map.put(subsetKey, subsetSummary);
                }
                switch (permission.getLevel()) {
                    case Permission.NONE_LEVEL:
                        subsetSummary.updateRead(PermissionSummary.SUMMARYLEVEL_0_NONE);
                        subsetSummary.updateWrite(PermissionSummary.SUMMARYLEVEL_0_NONE);
                        subsetSummary.updateCreate(false);
                        break;
                    case Permission.ADMIN_LEVEL:
                        subsetSummary.updateRead(PermissionSummary.SUMMARYLEVEL_5_ADMIN);
                        subsetSummary.updateWrite(PermissionSummary.SUMMARYLEVEL_5_ADMIN);
                        subsetSummary.updateCreate(true);
                        break;
                    case Permission.STANDARD_LEVEL:
                        subsetSummary.updateRead(getReadStandardLevel(subsetKey));
                        subsetSummary.updateWrite(getWriteStandardLevel(subsetKey));
                        subsetSummary.updateCreate(true);
                        break;
                    case Permission.CUSTOM_LEVEL:
                        FichePermission fichePermission = (FichePermission) permission.getCustomPermission();
                        subsetSummary.updateRead(translateToLevel(fichePermission.read()));
                        subsetSummary.updateWrite(translateToLevel(fichePermission.write()));
                        subsetSummary.updateCreate(fichePermission.create());
                        break;
                    default:
                        throw new SwitchException("Unknown level: " + permission.getLevel());
                }
            }
            Attribute adminAttribute = role.getAttributes().getAttribute(BdfUserSpace.ADMIN_KEY);
            if (adminAttribute != null) {
                actionSet.addAll(adminAttribute);
            }
        }
        if (redacteur.getStatus().equals(FichothequeConstants.READONLY_STATUS)) {
            return new ReadonlyPermissionSummary(redacteur, withDefaultRole, map, actionSet, roleNameSet);
        } else {
            return new InternalPermissionSummary(redacteur, withDefaultRole, map, actionSet, roleNameSet);
        }
    }

    private static int getReadStandardLevel(SubsetKey subsetKey) {
        if (subsetKey.isCorpusSubset()) {
            return PermissionSummary.SUMMARYLEVEL_4_ALL;
        } else if ((subsetKey.isAddendaSubset()) || (subsetKey.isAlbumSubset())) {
            return PermissionSummary.SUMMARYLEVEL_3_CROISEMENT_TEST;
        } else {
            return PermissionSummary.SUMMARYLEVEL_4_ALL;
        }
    }

    private static int getWriteStandardLevel(SubsetKey subsetKey) {
        if (subsetKey.isCorpusSubset()) {
            return PermissionSummary.SUMMARYLEVEL_1_FICHE_OWN;
        } else if ((subsetKey.isAddendaSubset()) || (subsetKey.isAlbumSubset())) {
            return PermissionSummary.SUMMARYLEVEL_3_CROISEMENT_TEST;
        } else {
            return PermissionSummary.SUMMARYLEVEL_0_NONE;
        }
    }

    private static int translateToLevel(short permissionType) {
        switch (permissionType) {
            case FichePermission.NONE:
                return PermissionSummary.SUMMARYLEVEL_0_NONE;
            case FichePermission.OWN:
                return PermissionSummary.SUMMARYLEVEL_1_FICHE_OWN;
            case FichePermission.SPHERE:
                return PermissionSummary.SUMMARYLEVEL_2_FICHE_SPHERE;
            case FichePermission.ALL:
                return PermissionSummary.SUMMARYLEVEL_4_ALL;
            default:
                throw new SwitchException("Unknown type: " + permissionType);
        }
    }


    private static class InternalPermissionSummary implements PermissionSummary {

        private final Redacteur redacteur;
        private final boolean withDefaultRole;
        private final Map<SubsetKey, SubsetSummary> summaryMap;
        private final Set<String> actionSet;
        private final Set<String> roleNameSet;
        private final SubsetAccessPredicate subsetAccessPredicate = new SubsetAccessPredicate();

        private InternalPermissionSummary(Redacteur redacteur, boolean withDefaultRole, Map<SubsetKey, SubsetSummary> summaryMap, Set<String> actionSet, Set<String> roleNameSet) {
            this.redacteur = redacteur;
            this.withDefaultRole = withDefaultRole;
            this.summaryMap = summaryMap;
            this.actionSet = actionSet;
            this.roleNameSet = roleNameSet;
        }

        @Override
        public int getReadLevel(SubsetKey subsetKey) {
            SubsetSummary summary = summaryMap.get(subsetKey);
            if (summary != null) {
                return summary.readLevel;
            } else {
                if (withDefaultRole) {
                    return getReadStandardLevel(subsetKey);
                } else {
                    return PermissionSummary.SUMMARYLEVEL_0_NONE;
                }
            }
        }

        @Override
        public int getWriteLevel(SubsetKey subsetKey) {
            SubsetSummary summary = summaryMap.get(subsetKey);
            if (summary != null) {
                return summary.writeLevel;
            } else {
                if (withDefaultRole) {
                    return getWriteStandardLevel(subsetKey);
                } else {
                    return PermissionSummary.SUMMARYLEVEL_0_NONE;
                }
            }
        }

        @Override
        public boolean canCreate(SubsetKey subsetKey) {
            SubsetSummary summary = summaryMap.get(subsetKey);
            if (summary != null) {
                return summary.canCreate;
            } else {
                if (withDefaultRole) {
                    return true;
                } else {
                    return false;
                }
            }
        }

        @Override
        public boolean isSubsetAdmin(SubsetKey subsetKey) {
            SubsetSummary summary = summaryMap.get(subsetKey);
            if (summary != null) {
                return (summary.writeLevel == PermissionSummary.SUMMARYLEVEL_5_ADMIN);
            } else {
                return false;
            }
        }

        @Override
        public boolean isFichothequeAdmin() {
            return false;
        }

        @Override
        public boolean hasRole(String roleName) {
            return roleNameSet.contains(roleName);
        }

        @Override
        public boolean canDo(String actionName) {
            return actionSet.contains(actionName);
        }

        @Override
        public Predicate<Subset> getSubsetAccessPredicate() {
            return subsetAccessPredicate;
        }

        @Override
        public boolean canRead(FicheMeta ficheMeta) {
            int level = getReadLevel(ficheMeta.getSubsetKey());
            return can(ficheMeta, level);

        }

        @Override
        public boolean canWrite(FicheMeta ficheMeta) {
            int level = getWriteLevel(ficheMeta.getSubsetKey());
            return can(ficheMeta, level);
        }

        private boolean can(FicheMeta ficheMeta, int level) {
            switch (level) {
                case PermissionSummary.SUMMARYLEVEL_0_NONE:
                    return false;
                case PermissionSummary.SUMMARYLEVEL_1_FICHE_OWN:
                    return ficheMeta.belongsTo(redacteur.getGlobalId());
                case PermissionSummary.SUMMARYLEVEL_2_FICHE_SPHERE:
                    return SphereUtils.ownsToSphere(ficheMeta, redacteur.getSphere());
                case PermissionSummary.SUMMARYLEVEL_3_CROISEMENT_TEST:
                    throw new IllegalStateException("Croisement test level not allowed for corpus");
                case PermissionSummary.SUMMARYLEVEL_4_ALL:
                case PermissionSummary.SUMMARYLEVEL_5_ADMIN:
                    return true;
                default:
                    throw new SwitchException("Unknwon  summary level: " + level);
            }
        }


        private class SubsetAccessPredicate implements Predicate<Subset> {

            private SubsetAccessPredicate() {

            }

            @Override
            public boolean test(Subset subset) {
                return hasAccess(subset.getSubsetKey());
            }

        }

    }


    private static class ReadonlyPermissionSummary extends InternalPermissionSummary {


        private ReadonlyPermissionSummary(Redacteur redacteur, boolean withDefaultRole, Map<SubsetKey, SubsetSummary> summaryMap, Set<String> actionSet, Set<String> roleNameSet) {
            super(redacteur, withDefaultRole, summaryMap, actionSet, roleNameSet);
        }

        @Override
        public int getWriteLevel(SubsetKey subsetKey) {
            return PermissionSummary.SUMMARYLEVEL_0_NONE;
        }

        @Override
        public boolean canCreate(SubsetKey subsetKey) {
            return false;
        }

        @Override
        public boolean isSubsetAdmin(SubsetKey subsetKey) {
            return false;
        }

        @Override
        public boolean isFichothequeAdmin() {
            return false;
        }

        @Override
        public boolean canDo(String actionName) {
            return false;
        }

    }


    private static class SubsetSummary {

        private int readLevel = PermissionSummary.SUMMARYLEVEL_0_NONE;
        private int writeLevel = PermissionSummary.SUMMARYLEVEL_0_NONE;
        private boolean canCreate = false;

        private SubsetSummary() {
        }

        private void updateRead(int otherLevel) {
            if (otherLevel > readLevel) {
                this.readLevel = otherLevel;
            }
        }

        private void updateWrite(int otherLevel) {
            if (otherLevel > writeLevel) {
                this.writeLevel = otherLevel;
            }
        }

        private void updateCreate(boolean b) {
            if (b == true) {
                canCreate = true;
            }
        }

    }

}
