package org.pac4j.core.profile;

import org.pac4j.core.authorization.authorizer.Authorizer;
import org.pac4j.core.authorization.authorizer.IsAuthenticatedAuthorizer;
import org.pac4j.core.context.Pac4jConstants;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.exception.HttpAction;
import org.pac4j.core.exception.TechnicalException;

import java.util.*;

/**
 * This class is a generic way to manage the current user profile(s), i.e. the one(s) of the current authenticated user.
 *
 * @author Jerome Leleu
 * @since 1.8.0
 */
public class ProfileManager<U extends CommonProfile> {

    private final Authorizer<U> IS_AUTHENTICATED_AUTHORIZER = new IsAuthenticatedAuthorizer<U>();

    protected final WebContext context;

    public ProfileManager(final WebContext context) {
        this.context = context;
    }

    /**
     * Retrieve the first user profile if it exists, ignoring any {@link AnonymousProfile} if possible.
     *
     * @param readFromSession if the user profile must be read from session
     * @return the user profile
     */
    public Optional<U> get(final boolean readFromSession) {
        final LinkedHashMap<String, U> allProfiles = retrieveAll(readFromSession);
        return ProfileHelper.flatIntoOneProfile(allProfiles);
    }

    /**
     * Retrieve all user profiles.
     *
     * @param readFromSession if the user profiles must be read from session
     * @return the user profiles
     */
    public List<U> getAll(final boolean readFromSession) {
        final LinkedHashMap<String, U> profiles = retrieveAll(readFromSession);
        return ProfileHelper.flatIntoAProfileList(profiles);
    }

    /**
     * Retrieve the map of profiles from the session or the request.
     *
     * @param readFromSession if the user profiles must be read from session
     * @return the map of profiles
     */
    private LinkedHashMap<String, U> retrieveAll(final boolean readFromSession) {
        LinkedHashMap<String, U> profiles = new LinkedHashMap<>();
        final Object objSession = this.context.getRequestAttribute(Pac4jConstants.USER_PROFILES);
        if (objSession != null && objSession instanceof LinkedHashMap) {
            profiles = (LinkedHashMap<String, U>) objSession;
        }
        if (readFromSession) {
            final Object objRequest = this.context.getSessionAttribute(Pac4jConstants.USER_PROFILES);
            if (objRequest != null && objRequest instanceof LinkedHashMap) {
                profiles.putAll((LinkedHashMap<String, U>) objRequest);
            }
        }
        return profiles;
    }

    /**
     * Remove the current user profile(s).
     *
     * @param removeFromSession if the user profile(s) must be removed from session
     */
    public void remove(final boolean removeFromSession) {
        if (removeFromSession) {
            this.context.setSessionAttribute(Pac4jConstants.USER_PROFILES, new LinkedHashMap<String, U>());
        }
        this.context.setRequestAttribute(Pac4jConstants.USER_PROFILES, new LinkedHashMap<String, U>());
    }

    /**
     * Save the given user profile (replace the current one if multi profiles are not supported, add it otherwise).
     *
     * @param saveInSession if the user profile must be saved in session
     * @param profile a given user profile
     * @param multiProfile whether multiple profiles are supported
     */
    public void save(final boolean saveInSession, final U profile, final boolean multiProfile) {
        final LinkedHashMap<String, U> profiles;

        String clientName = profile.getClientName();
        if (clientName == null) {
            clientName = "DEFAULT";
        }
        if (multiProfile) {
            profiles = retrieveAll(saveInSession);
            profiles.remove(clientName);
        } else {
            profiles = new LinkedHashMap<>();
        }
        profiles.put(clientName, profile);

        if (saveInSession) {
            this.context.setSessionAttribute(Pac4jConstants.USER_PROFILES, profiles);
        }
        this.context.setRequestAttribute(Pac4jConstants.USER_PROFILES, profiles);
    }

    /**
     * Perform a logout by removing the current user profile(s) from the session as well.
     */
    public void logout() {
        remove(true);
    }

    /**
     * Tests if the current user is authenticated (meaning a user profile exists which is not an {@link AnonymousProfile}).
     *
     * @return whether the current user is authenticated
     */
    public boolean isAuthenticated() {
        try {
            return IS_AUTHENTICATED_AUTHORIZER.isAuthorized(null, getAll(true));
        } catch (final HttpAction e) {
            throw new TechnicalException(e);
        }
    }
}

@@ -1942,16 +1942,22 @@
         
+final 
 LinkedHa
@@ -2028,26 +2028,23 @@
  Object 
-objSession
+request
  = this.
@@ -2118,40 +2118,51 @@
 if (
-objSession != null && objSession
+request != null) %7B%0A            if  (request
  ins
@@ -2198,24 +2198,28 @@
         
+    
 profiles
  = (Link
@@ -2210,19 +2210,24 @@
 profiles
- = 
+.putAll(
 (LinkedH
@@ -2249,20 +2249,174 @@
 U%3E) 
-objSession;%0A
+request);%0A            %7D%0A            if (request instanceof CommonProfile) %7B%0A                profiles.put(((CommonProfile) request).getClientName(), (U) request);%0A    
     
@@ -2413,32 +2413,42 @@
 ;%0A            %7D%0A
+        %7D%0A
         if (read
@@ -2487,26 +2487,32 @@
  Object 
-objRequest
+sessionAttribute
  = this.
@@ -2589,41 +2589,26 @@
  if 
-(objRequest != null && objRequest
+ (sessionAttribute
  ins
@@ -2695,18 +2695,194 @@
 U%3E) 
-objRequest
+sessionAttribute);%0A            %7D%0A            if (sessionAttribute instanceof CommonProfile) %7B%0A                profiles.put(((CommonProfile) request).getClientName(), (U) sessionAttribute
 );%0A 

Add support for single profile instances