/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.oak.benchmark.authorization.permission;

import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.oak.Oak;
import org.apache.jackrabbit.oak.benchmark.ReadDeepTreeTest;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.fixture.JcrCreator;
import org.apache.jackrabbit.oak.fixture.OakRepositoryFixture;
import org.apache.jackrabbit.oak.fixture.RepositoryFixture;
import org.apache.jackrabbit.oak.jcr.Jcr;
import org.apache.jackrabbit.oak.security.internal.SecurityProviderBuilder;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.security.Privilege;
import javax.security.auth.Subject;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import static javax.jcr.security.Privilege.JCR_ALL;

public class EagerCacheSizeTest extends ReadDeepTreeTest {

    private static final Logger log = LoggerFactory.getLogger(EagerCacheSizeTest.class);

    private final int numberOfACEs;
    private final int subjectSize;
    private final long eagerCacheSize;

    private final List<String> nodePaths = new ArrayList<>();
    private Subject subject;

    public EagerCacheSizeTest(int itemsToRead, int repeatedRead,  int numberOfACEs, int subjectSize, long eagerCacheSize, boolean doReport) {
        super(false, itemsToRead, doReport, false, repeatedRead);
        this.numberOfACEs = numberOfACEs;
        this.subjectSize = subjectSize;
        this.eagerCacheSize = eagerCacheSize;
    }

    @Override
    protected Repository[] createRepository(RepositoryFixture fixture) throws Exception {
        if (fixture instanceof OakRepositoryFixture) {
            return ((OakRepositoryFixture) fixture).setUpCluster(1, new JcrCreator() {
                @Override
                public Jcr customize(Oak oak) {
                    ConfigurationParameters params = ConfigurationParameters.of("eagerCacheSize", eagerCacheSize);
                    SecurityProvider securityProvider = SecurityProviderBuilder.newBuilder().with(ConfigurationParameters.of(AuthorizationConfiguration.NAME, params)).build();
                    return new Jcr(oak).with(securityProvider);
                }
            });
        } else {
            throw new IllegalArgumentException("Fixture " + fixture + " not supported for this benchmark.");
        }
    }

    @Override
    protected void beforeSuite() throws Exception {
        super.beforeSuite();

        // populate subject
        subject = new Subject();
        UserManager userManager = ((JackrabbitSession) adminSession).getUserManager();
        for (int i = 0; i < subjectSize; i++) {
            User user = userManager.createSystemUser("system_" +i, null);
            subject.getPrincipals().add(user.getPrincipal());
        }
        adminSession.save();

        JackrabbitAccessControlManager acMgr = (JackrabbitAccessControlManager) adminSession.getAccessControlManager();

        // grant read at the root
        Principal principal = subject.getPrincipals().iterator().next();
        Privilege[] readPrivs = AccessControlUtils.privilegesFromNames(acMgr, Privilege.JCR_READ);
        addEntry(acMgr, principal, PathUtils.ROOT_PATH, readPrivs);

        // create additional ACEs for each principal in the subject
        List<Privilege> allPrivileges = Lists.newArrayList(acMgr.privilegeFromName(JCR_ALL).getAggregatePrivileges());
        Iterator<Principal> principalIterator = Iterators.cycle(subject.getPrincipals());
        int cnt = 0;
        while (cnt < numberOfACEs) {
            if (!principalIterator.hasNext()) {
                throw new IllegalStateException("Cannot setup ACE. no principals available.");
            }
            if (addEntry(acMgr, principalIterator.next(), getRandom(nodePaths), getRandomPrivileges(allPrivileges))) {
                cnt++;
            }
        }
        adminSession.save();
    }

    @Override
    protected void visitingNode(Node node, int i) throws RepositoryException {
        super.visitingNode(node, i);
        String path = node.getPath();
        if (!path.contains(AccessControlConstants.REP_POLICY)) {
            nodePaths.add(path);
        }
    }

    @NotNull
    private static Privilege[] getRandomPrivileges(@NotNull List<Privilege> allPrivileges) {
        Collections.shuffle(allPrivileges);
        return allPrivileges.subList(0, 3).toArray(new Privilege[0]);
    }

    private static boolean addEntry(@NotNull JackrabbitAccessControlManager acMgr, @NotNull Principal principal, @NotNull String path, @NotNull Privilege[] privileges) throws RepositoryException {
        JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList(acMgr, path);
        if (acl == null) {
            throw new IllegalStateException("No policy to setup ACE.");
        }
        boolean added = acl.addAccessControlEntry(principal, privileges);
        if (added) {
            acMgr.setPolicy(acl.getPath(), acl);
        }
        return added;
    }

    @Override
    protected void afterSuite() throws Exception {
        try {
            UserManager userManager = ((JackrabbitSession) adminSession).getUserManager();
            for (Principal p : subject.getPrincipals()) {
                Authorizable a = userManager.getAuthorizable(p);
                if (a != null) {
                    a.remove();
                }
            }
            adminSession.save();
        }  finally  {
            super.afterSuite();
        }
    }

    @NotNull
    @Override
    protected Session getTestSession() {
        return loginSubject(subject);
    }

    @NotNull
    @Override
    protected String getImportFileName() {
        return "deepTree.xml";
    }

    @NotNull
    @Override
    protected String getTestNodeName() {
        return "EagerCacheSizeTest";
    }

}