/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.authenticate;

import javax.servlet.http.HttpServletRequest;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.dspace.authenticate.service.AuthenticationService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;
import org.dspace.core.factory.CoreServiceFactory;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.EPersonService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Access point for the stackable authentication methods.
 * <p>
 * This class initializes the "stack" from the DSpace configuration,
 * and then invokes methods in the appropriate order on behalf of clients.
 * <p>
 * See the AuthenticationMethod interface for details about what each
 * function does.
 * <p>
 * <b>Configuration</b><br>
 * The stack of authentication methods is defined by one property in the DSpace configuration:
 * <pre>
 *   plugin.sequence.org.dspace.eperson.AuthenticationMethod = <em>a list of method class names</em>
 *     <em>e.g.</em>
 *   plugin.sequence.org.dspace.eperson.AuthenticationMethod = \
 *       org.dspace.eperson.X509Authentication, \
 *       org.dspace.eperson.PasswordAuthentication
 * </pre>
 * <p>
 * The "stack" is always traversed in order, with the methods
 * specified first (in the configuration) thus getting highest priority.
 *
 * @see AuthenticationMethod
 *
 * @author Larry Stone
 * @version $Revision$
 */
public class AuthenticationServiceImpl implements AuthenticationService
{

    /** SLF4J logging category */
    private final Logger log = (Logger) LoggerFactory.getLogger(AuthenticationServiceImpl.class);

    @Autowired(required = true)
    protected EPersonService ePersonService;

    protected AuthenticationServiceImpl()
    {

    }

    public List<AuthenticationMethod> getAuthenticationMethodStack() {
        return Arrays.asList((AuthenticationMethod[])CoreServiceFactory.getInstance().getPluginService().getPluginSequence(AuthenticationMethod.class));
    }

    @Override
    public int authenticate(Context context,
                            String username,
                            String password,
                            String realm,
                            HttpServletRequest request)
    {
        return authenticateInternal(context, username, password, realm,
                                     request, false);
    }

    @Override
    public int authenticateImplicit(Context context,
                            String username,
                            String password,
                            String realm,
                            HttpServletRequest request)
    {
        return authenticateInternal(context, username, password, realm,
                                     request, true);
    }

    protected int authenticateInternal(Context context,
                            String username,
                            String password,
                            String realm,
                            HttpServletRequest request,
                            boolean implicitOnly)
    {
        // better is lowest, so start with the highest.
        int bestRet = AuthenticationMethod.BAD_ARGS;

        // return on first success, otherwise "best" outcome.
        for (AuthenticationMethod aMethodStack : getAuthenticationMethodStack()) {
            if (!implicitOnly || aMethodStack.isImplicit()) {
                int ret = 0;
                try {
                    ret = aMethodStack.authenticate(context, username, password, realm, request);
                } catch (SQLException e) {
                    ret = AuthenticationMethod.NO_SUCH_USER;
                }
                if (ret == AuthenticationMethod.SUCCESS) {
                    EPerson me = context.getCurrentUser();
                    me.setLastActive(new Date());
                    try {
                        ePersonService.update(context, me);
                    } catch (SQLException ex) {
                        log.error("Could not update last-active stamp", ex);
                    } catch (AuthorizeException ex) {
                        log.error("Could not update last-active stamp", ex);
                    }
                    return ret;
                }
                if (ret < bestRet) {
                    bestRet = ret;
                }
            }
        }
        return bestRet;
    }

    @Override
    public boolean canSelfRegister(Context context,
                                   HttpServletRequest request,
                                   String username)
        throws SQLException
    {
        for (AuthenticationMethod method : getAuthenticationMethodStack())
        {
            if (method.canSelfRegister(context, request, username))
            {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean allowSetPassword(Context context,
                                    HttpServletRequest request,
                                    String username)
        throws SQLException
    {
        for (AuthenticationMethod method : getAuthenticationMethodStack())
        {
            if (method.allowSetPassword(context, request, username))
            {
                return true;
            }
        }
        return false;
    }

    @Override
    public void initEPerson(Context context,
                                   HttpServletRequest request,
                                   EPerson eperson)
        throws SQLException
    {
        for (AuthenticationMethod method : getAuthenticationMethodStack())
        {
            method.initEPerson(context, request, eperson);
        }
    }

    @Override
    public List<Group> getSpecialGroups(Context context,
                                         HttpServletRequest request)
        throws SQLException
    {
        List<Group> result = new ArrayList<>();
        int totalLen = 0;

        for (AuthenticationMethod method : getAuthenticationMethodStack())
        {
            List<Group> gl = method.getSpecialGroups(context, request);
            if (gl.size() > 0)
            {
                result.addAll(gl);
                totalLen += gl.size();
            }
        }

        return result;
    }

    @Override
    public Iterator<AuthenticationMethod> authenticationMethodIterator()
    {
        return getAuthenticationMethodStack().iterator();
    }
}
