/*
 * Copyright 2012 Shared Learning Collaborative, LLC
 *
 * Licensed 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.slc.sli.api.security.context;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.slc.sli.api.config.EntityDefinition;
import org.slc.sli.api.resources.generic.util.ResourceHelper;
import org.slc.sli.api.security.SLIPrincipal;
import org.slc.sli.api.security.context.validator.GenericContextValidator;
import org.slc.sli.api.security.context.validator.IContextValidator;
import org.slc.sli.api.security.context.validator.TeacherToStudentValidator;
import org.slc.sli.api.security.context.validator.TeacherToSubStudentEntityValidator;
import org.slc.sli.api.service.EntityNotFoundException;
import org.slc.sli.domain.Entity;
import org.slc.sli.domain.NeutralCriteria;
import org.slc.sli.domain.NeutralQuery;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;

import com.sun.jersey.spi.container.ContainerRequest;

/**
 * ContextValidator
 * Determines if the principal has context to a resource.
 * Verifies the requested endpoint is accessible by the principal
 */
@Component
public class ContextValidator implements ApplicationContextAware {

    private List<IContextValidator> validators;

    @Autowired
    private ResourceHelper resourceHelper;

    @Autowired
    private PagingRepositoryDelegate<Entity> repo;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        validators = new ArrayList<IContextValidator>();
        validators.addAll(applicationContext.getBeansOfType(IContextValidator.class).values());

        IContextValidator genVal = null;
        IContextValidator studentVal = null;
        IContextValidator subEntityVal = null;
        //Make GenericContextValidator last, since we want to use that as a last resort
        for (IContextValidator validator : validators) {
            if (validator instanceof GenericContextValidator) {
                genVal = validator;
            } else if (validator instanceof TeacherToStudentValidator) {
                studentVal = validator;
            } else if (validator instanceof TeacherToSubStudentEntityValidator) {
                subEntityVal = validator;
            }
        }

        //move generic validator to end
        validators.remove(genVal);
        validators.add(genVal);
        
        //temporarily disable teacher-student validator
        // temporarily disable teacher-sub-student entity validator
        validators.remove(studentVal);
        validators.remove(subEntityVal);
    }

    public void validateContextToUri(ContainerRequest request, SLIPrincipal principal) {
        validateUserHasAccessToEndpoint(request, principal);
        validateUserHasContextToRequestedEntities(request, principal);
    }

    private void validateUserHasContextToRequestedEntities(ContainerRequest request, SLIPrincipal principal) {

        if (request.getPathSegments().size() < 3) {
            return;
        }

        String rootEntity = request.getPathSegments().get(1).getPath();
        EntityDefinition def = resourceHelper.getEntityDefinition(rootEntity);
        if (def == null) {
            return;
        }

        /*
         * e.g.
         * !isTransitive - /v1/staff/<ID>/disciplineActions
         * isTransitive - /v1/staff/<ID>
         */
        boolean isTransitive = request.getPathSegments().size() < 4;
        String idsString = request.getPathSegments().get(2).getPath();
        Set<String> ids = new HashSet<String>(Arrays.asList(idsString.split(",")));
        validateContextToEntities(def, ids, isTransitive);
    }

    public void validateContextToEntities(EntityDefinition def, Collection<String> entityIds, boolean isTransitive) {
        Set<String> idSet = null;
        if (entityIds instanceof Set) {
            idSet = (Set<String>) entityIds;
        } else {
            idSet = new HashSet<String>(entityIds);
        }
        IContextValidator validator = findValidator(def.getType(), isTransitive);
        if (validator != null) {
            if (!validator.validate(def.getType(), idSet)) {
                if (!exists(idSet, def.getStoredCollectionName())) {
                    throw new EntityNotFoundException("Could not locate " + def.getType() + " with ids " + entityIds);
                }
                throw new AccessDeniedException("Cannot access entities " + entityIds);
            }
        }
    }

    
    private boolean exists(Set<String> ids, String collectionName) {
        NeutralQuery query = new NeutralQuery(0);
        query.addCriteria(new NeutralCriteria("_id", NeutralCriteria.CRITERIA_IN, ids));
        long count = repo.count(collectionName, query);
        return count == ids.size();
    }

    private void validateUserHasAccessToEndpoint(ContainerRequest request, SLIPrincipal principal) {
        //TODO replace stub
        // make data driven from v1_resource
        // each resource will have an accessibleBy key with an array value, listing each of the user types that can accesses the resource
        // example accessibleBy: ['teacher', 'staff']

    }

    /**
     * 
     * @param toType
     * @param isTransitive
     * @return
     * @throws IllegalStateException
     */
    private IContextValidator findValidator(String toType, boolean isTransitive) throws IllegalStateException {

        IContextValidator found = null;
        for (IContextValidator validator : this.validators) {
            if (validator.canValidate(toType, isTransitive)) {
                found = validator;
                break;
            }
        }

        if (found == null) {
            warn("No {} validator to {}.", isTransitive ? "TRANSITIVE" : "NOT TRANSITIVE", toType);
        }

        return found;
    }

}

@@ -780,33 +780,98 @@
 til.
-List;%0Aimport java.util.Se
+Iterator;%0Aimport java.util.List;%0Aimport java.util.Set;%0A%0Aimport javax.ws.rs.core.PathSegmen
 t;%0A%0A
@@ -3898,37 +3898,258 @@
     
-if (request.getPathSegments()
+List%3CPathSegment%3E segs = request.getPathSegments();%0A        for (Iterator%3CPathSegment%3E i = segs.iterator(); i.hasNext(); ) %7B%0A            if (i.next().getPath().isEmpty()) %7B%0A                i.remove();%0A            %7D%0A        %7D%0A        %0A        if (segs
 .siz
@@ -4222,33 +4222,12 @@
 y = 
-request.getPathSegments()
+segs
 .get
@@ -4553,33 +4553,12 @@
 e = 
-request.getPathSegments()
+segs
 .siz
@@ -4597,33 +4597,12 @@
 g = 
-request.getPathSegments()
+segs
 .get
@@ -4880,24 +4880,124 @@
 ansitive) %7B%0A
+        %0A        //exists call requires a Set to function correctly, so convert to Set if necessary%0A
         Set%3C

Handle trailing slash in URL