/*
 * JBoss, a division of Red Hat
 * Copyright 2013, Red Hat Middleware, LLC, and individual
 * contributors as indicated by the @authors tag. See the
 * copyright.txt in the distribution for a full listing of
 * individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.oauth.webui;

import org.exoplatform.oauth.OAuthConst;
import org.exoplatform.portal.application.PortalRequestContext;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.webui.register.UIRegisterForm;
import org.exoplatform.portal.webui.register.UIRegisterInputSet;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.portal.webui.workspace.UIMaskWorkspace;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.User;
import org.exoplatform.services.organization.UserProfile;
import org.exoplatform.services.organization.UserProfileHandler;
import org.exoplatform.services.organization.impl.UserImpl;
import org.exoplatform.web.application.AbstractApplicationMessage;
import org.exoplatform.web.application.ApplicationMessage;
import org.exoplatform.web.security.AuthenticationRegistry;
import org.exoplatform.web.url.navigation.NavigationResource;
import org.exoplatform.web.url.navigation.NodeURL;
import org.exoplatform.webui.application.WebuiRequestContext;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.core.UIApplication;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.core.UIContainer;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.EventListener;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.gatein.security.oauth.common.OAuthConstants;
import org.gatein.security.oauth.exception.OAuthException;
import org.gatein.security.oauth.exception.OAuthExceptionCode;
import org.gatein.security.oauth.spi.OAuthPrincipal;

import javax.servlet.http.HttpServletRequest;

/**
 * This class is clone and modified from @{org.exoplatform.portal.webui.register.UIRegisterOAuth} in gatein-portal
 * to process and show invitation form when we detect an account matching with his oauth infomations
 *
 * Registration form for user, which has been successfully authenticated via OAuth2
 *
 * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
 */
@ComponentConfig(template = "system:/groovy/portal/webui/portal/UIRegisterOAuthForm.gtmpl")
public class UIRegisterOAuth extends UIContainer {

  private static Log                   log                     =
                                           ExoLogger.getLogger(org.exoplatform.portal.webui.register.UIRegisterOAuth.class);

    private static final String[] ACTIONS = { "SubscribeOAuth", "Reset", "Cancel" };

    static final String REGISTER_FORM_CONFIG_ID = "UIRegisterFormOAuth";

    private final AuthenticationRegistry authRegistry;

    private User portalUser = null;

    public UIRegisterOAuth() throws Exception {
        addChild(UIRegisterForm.class, REGISTER_FORM_CONFIG_ID, REGISTER_FORM_CONFIG_ID);
        addChild(UIOAuthInvitationForm.class, null, null);

        UIRegisterForm uiRegisterForm = getChild(UIRegisterForm.class);
        uiRegisterForm.setActions(ACTIONS);

        this.authRegistry = getApplicationComponent(AuthenticationRegistry.class);
    }

    private void setupUserToRegisterForm() {
        UIRegisterForm uiRegisterForm = getChild(UIRegisterForm.class);
        UIRegisterInputSet uiRegisterInputSet = uiRegisterForm.getChild(UIRegisterInputSet.class);

        uiRegisterInputSet.getUIStringInput("username").setValue(portalUser.getUserName());
        uiRegisterInputSet.getUIStringInput("firstName").setValue(portalUser.getFirstName());
        uiRegisterInputSet.getUIStringInput("lastName").setValue(portalUser.getLastName());
        uiRegisterInputSet.getUIStringInput("emailAddress").setValue(portalUser.getEmail());
        uiRegisterInputSet.getUIStringInput("password").setValue(null);
        uiRegisterInputSet.getUIStringInput("confirmPassword").setValue(null);
        uiRegisterInputSet.getUIStringInput("displayName").setValue(portalUser.getDisplayName());
    }

    @Override
    public void processRender(WebuiRequestContext context) throws Exception {
        HttpServletRequest request = Util.getPortalRequestContext().getRequest();
        User pUser = (User) authRegistry.getAttributeOfClient(request, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER);

        if (portalUser == null || (pUser != null && pUser != portalUser)) {
            if (pUser == null) {
                log.warn("portalUser from OAuth login is not available!");
                pUser = new UserImpl();
                pUser.setDisplayName("");
            }
            this.portalUser = pUser;
            setupUserToRegisterForm();

            User detectedUser = (User) authRegistry.getAttributeOfClient(request, OAuthConst.ATTRIBUTE_AUTHENTICATED_PORTAL_USER_DETECTED);
            if (detectedUser != null) {
                getChild(UIRegisterForm.class).setRendered(false);
                UIOAuthInvitationForm invitationForm = getChild(UIOAuthInvitationForm.class);
                invitationForm.setRendered(true);
                String detected = detectedUser.getUserName();
                if (!detected.equals(pUser.getUserName()) && detectedUser.getEmail().equals(pUser.getEmail())) {
                    detected = detectedUser.getEmail();
                }
                invitationForm.setDetectedUserName(detected);
            } else {
                getChild(UIRegisterForm.class).setRendered(true);
                getChild(UIOAuthInvitationForm.class).setRendered(false);
            }
        }

        super.processRender(context);
    }

    public static class ResetActionListener extends EventListener<UIRegisterForm> {

        @Override
        public void execute(Event<UIRegisterForm> event) throws Exception {
            UIRegisterForm registerForm = event.getSource();
            UIRegisterOAuth uiRegisterOAuth = registerForm.getAncestorOfType(UIRegisterOAuth.class);
            uiRegisterOAuth.setupUserToRegisterForm();
            event.getRequestContext().addUIComponentToUpdateByAjax(registerForm);
        }
    }

    public static class CancelActionListener extends UIMaskWorkspace.CloseActionListener {

        @Override
        public void execute(Event<UIComponent> event) throws Exception {
            UIComponent ui = event.getSource();
            super.execute(event);

            AuthenticationRegistry authRegistry = event.getSource().getApplicationComponent(AuthenticationRegistry.class);
            HttpServletRequest httpRequest = Util.getPortalRequestContext().getRequest();

            // Clear whole context of OAuth login. See OAuthAuthenticationFilter.cleanAuthenticationContext
            authRegistry.removeAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_OAUTH_PRINCIPAL);
            authRegistry.removeAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER);
            authRegistry.removeAttributeOfClient(httpRequest, OAuthConst.ATTRIBUTE_AUTHENTICATED_PORTAL_USER_DETECTED);

            UIRegisterOAuth uiOauth = ui.getAncestorOfType(UIRegisterOAuth.class);
            if(uiOauth != null) {
                uiOauth.portalUser = null;
            }

            if (log.isTraceEnabled()) {
                log.trace("Registration with OAuth properties terminated. Clearing authentication context");
            }
        }
    }

    public static class SubscribeOAuthActionListener extends UIRegisterForm.SubscribeActionListener {

        @Override
        public void execute(Event<UIRegisterForm> event) throws Exception {
            WebuiRequestContext context = WebuiRequestContext.getCurrentInstance();

            UIApplication uiApp = context.getUIApplication();
            UIRegisterForm uiRegisterForm = event.getSource();
            PortalRequestContext portalRequestContext = Util.getPortalRequestContext();

            AuthenticationRegistry authRegistry = uiRegisterForm.getApplicationComponent(AuthenticationRegistry.class);
            HttpServletRequest httpRequest = portalRequestContext.getRequest();
            OAuthPrincipal oauthPrincipal = (OAuthPrincipal)authRegistry.getAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_OAUTH_PRINCIPAL);

            if(oauthPrincipal == null) {
                uiApp.addMessage(new ApplicationMessage("UIAccountSocial.msg.not-in-oauth-process", new Object[0], AbstractApplicationMessage.INFO));
                return;
            }

            super.execute(event);
            User newUser = (User)context.getAttribute("UIRegisterForm$User");

            // This means that registration has been successful
            if (newUser != null) {
                // Save OAuth username as part of user profile of new user
                OrganizationService orgService = uiRegisterForm.getApplicationComponent(OrganizationService.class);
                UserProfileHandler profileHandler = orgService.getUserProfileHandler();
                UserProfile newUserProfile = profileHandler.findUserProfileByName(newUser.getUserName());

                if (newUserProfile == null) {
                    newUserProfile = orgService.getUserProfileHandler().createUserProfileInstance(newUser.getUserName());
                }
                newUserProfile.setAttribute(oauthPrincipal.getOauthProviderType().getUserNameAttrName(), oauthPrincipal.getUserName());
                try {
                    profileHandler.saveUserProfile(newUserProfile, true);
                } catch (OAuthException gtnOAuthException) {
                    // Show warning message if user with this facebookUsername (or googleUsername) already exists
                    // NOTE: It could happen only in case of parallel registration of same oauth user from more browser windows
                    if (gtnOAuthException.getExceptionCode() == OAuthExceptionCode.DUPLICATE_OAUTH_PROVIDER_USERNAME) {

                        // Drop new user
                        orgService.getUserHandler().removeUser(newUser.getUserName(), true);

                        // Clear previous message about successful creation of user because we dropped him. Add message about duplicate oauth username
                        Object[] args = new Object[] {gtnOAuthException.getExceptionAttribute(OAuthConstants.EXCEPTION_OAUTH_PROVIDER_USERNAME),
                                gtnOAuthException.getExceptionAttribute(OAuthConstants.EXCEPTION_OAUTH_PROVIDER_NAME)};
                        ApplicationMessage appMessage = new ApplicationMessage("UIAccountSocial.msg.failed-registration", args, ApplicationMessage.WARNING);
                        uiApp.addMessage(appMessage);
                        return;
                    } else {
                        throw gtnOAuthException;
                    }
                }

                // Clean portalUser from context as we don't need it anymore
                authRegistry.removeAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER);
                authRegistry.removeAttributeOfClient(httpRequest, OAuthConst.ATTRIBUTE_AUTHENTICATED_PORTAL_USER_DETECTED);

                // Clear messages (message about successful registration of user)
                uiApp.clearMessages();

                // Close the registration popup
                UIMaskWorkspace.CloseActionListener closePopupListener = new UIMaskWorkspace.CloseActionListener();
                closePopupListener.execute((Event)event);

                // Redirect to finish login with new user
                SiteKey siteKey = portalRequestContext.getSiteKey();
                NodeURL urlToRedirect = portalRequestContext.createURL(NodeURL.TYPE);
                urlToRedirect.setResource(new NavigationResource(siteKey, portalRequestContext.getNodePath()));

                portalRequestContext.getJavascriptManager().addJavascript("window.location = '" + urlToRedirect.toString() + "';");
            }

        }
    }
}
