/*
 * Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
 * 
 * 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.maxkey.authz.oauth2.provider;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.maxkey.authz.oauth2.common.util.OAuth2Utils;
import org.maxkey.domain.apps.oauth2.provider.ClientDetails;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.web.bind.annotation.SessionAttributes;

/**
 * A request for authorization by an OAuth 2 Client, normally received and
 * processed by the AuthorizationEndpoint. This class is meant to be manipulated
 * throughout the authorization process, and is therefore treated as ephemeral
 * and not to be stored long term. For long term storage, use the read-only
 * {@link OAuth2Request} class.
 * 
 * HTTP request parameters are stored in the parameters map, and any processing
 * the server makes throughout the lifecycle of a request are stored on
 * individual properties. The original request parameters will remain available
 * through the parameters map. For convenience, constants are defined in order
 * to get at those original values. However, the parameters map is unmodifiable
 * so that processing cannot drop the original values.
 * 
 * This class is {@link Serializable} in order to support storage of the
 * authorization request as a {@link SessionAttributes} member while the end
 * user through the authorization process (which may span several page
 * requests).
 * 
 * @author Ryan Heaton
 * @author Dave Syer
 * @author Amanda Anganes
 */
@SuppressWarnings("serial")
public class AuthorizationRequest extends BaseRequest implements Serializable {

	/**
	 * Map to hold the original, unchanged parameter set submitted by a user to
	 * signal approval of the token grant approval. Once set this should not be
	 * modified.
	 */
	private Map<String, String> approvalParameters = Collections.unmodifiableMap(new HashMap<String, String>());

	/**
	 * The value of the "state" parameter sent by the client in the request, if
	 * sent by the client. As this must be echoed back to the client unchanged,
	 * it should not be modified by any processing classes.
	 */
	private String state;

	/**
	 * Resolved requested response types initialized (by the
	 * OAuth2RequestFactory) with the response types originally requested.
	 */
	private Set<String> responseTypes = new HashSet<String>();

	/**
	 * Resolved resource IDs. This set may change during request processing.
	 */
	private Set<String> resourceIds = new HashSet<String>();

	/**
	 * Resolved granted authorities for this request. May change during request
	 * processing.
	 */
	private Collection<? extends GrantedAuthority> authorities = new HashSet<GrantedAuthority>();

	/**
	 * Whether the request has been approved by the end user (or other process).
	 * This will be altered by the User Approval Endpoint and/or the
	 * UserApprovalHandler as appropriate.
	 */
	private boolean approved = false;

	/**
	 * The resolved redirect URI of this request. A URI may be present in the
	 * original request, in the authorizationParameters, or it may not be
	 * provided, in which case it will be defaulted (by processing classes) to
	 * the Client's default registered value.
	 */
	private String redirectUri;

	/**
	 * Extension point for custom processing classes which may wish to store
	 * additional information about the OAuth2 request. Since this class will
	 * create a serializable OAuth2Request, all members of this extension map
	 * must be serializable.
	 */
	private Map<String, Serializable> extensions = new HashMap<String, Serializable>();

	/**
	 * Default constructor.
	 */
	public AuthorizationRequest() {
	}

	/**
	 * Full constructor.
	 */
	public AuthorizationRequest(Map<String, String> authorizationParameters, Map<String, String> approvalParameters, String clientId, Set<String> scope, Set<String> resourceIds, Collection<? extends GrantedAuthority> authorities, boolean approved, String state, String redirectUri,
	        Set<String> responseTypes) {
		setClientId(clientId);
		setRequestParameters(authorizationParameters); // in case we need to
													   // wrap the collection
		setScope(scope); // in case we need to parse
		if (resourceIds != null) {
			this.resourceIds = new HashSet<String>(resourceIds);
		}
		if (authorities != null) {
			this.authorities = new HashSet<GrantedAuthority>(authorities);
		}
		this.approved = approved;
		this.resourceIds = resourceIds;
		this.redirectUri = redirectUri;
		if (responseTypes != null) {
			this.responseTypes = responseTypes;
		}
		this.state = state;
	}

	public OAuth2Request createOAuth2Request() {
		return new OAuth2Request(getRequestParameters(), getClientId(), getAuthorities(), isApproved(), getScope(), getResourceIds(), getRedirectUri(), getResponseTypes(), getExtensions());
	}

	/**
	 * Convenience constructor for unit tests, where client ID and scope are
	 * often the only needed fields.
	 * 
	 * @param clientId
	 * @param scopes
	 */
	public AuthorizationRequest(String clientId, Collection<String> scopes) {
		setClientId(clientId);
		setScope(scopes); // in case we need to parse
	}

	/**
	 * Convenience method to set resourceIds and authorities on this request by
	 * inheriting from a ClientDetails object.
	 * 
	 * @param clientDetails
	 */
	public void setResourceIdsAndAuthoritiesFromClientDetails(ClientDetails clientDetails) {
		setResourceIds(clientDetails.getResourceIds());
		setAuthorities(clientDetails.getAuthorities());
	}

	public Map<String, String> getApprovalParameters() {
		return approvalParameters;
	}

	public void setApprovalParameters(Map<String, String> approvalParameters) {
		this.approvalParameters = approvalParameters;
	}

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	public Set<String> getResponseTypes() {
		return responseTypes;
	}

	public void setResponseTypes(Set<String> responseTypes) {
		this.responseTypes = responseTypes;
	}

	public void setRedirectUri(String redirectUri) {
		this.redirectUri = redirectUri;
	}

	public void setApproved(boolean approved) {
		this.approved = approved;
	}

	public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
		if (authorities != null) {
			this.authorities = new HashSet<GrantedAuthority>(authorities);
		}
	}

	/**
	 * @return the extensions
	 */
	public Map<String, Serializable> getExtensions() {
		return extensions;
	}

	public void setExtensions(Map<String, Serializable> extensions) {
		this.extensions = extensions;
	}

	public void setResourceIds(Set<String> resourceIds) {
		this.resourceIds = resourceIds;
	}

	public void setClientId(String clientId) {
		super.setClientId(clientId);
	}

	/**
	 * Set the scope value. If the collection contains only a single scope
	 * value, this method will parse that value into a collection using
	 * {@link OAuth2Utils.parseParameterList}.
	 * 
	 * @see TokenRequest.setScope
	 * 
	 * @param scope
	 */
	public void setScope(Collection<String> scope) {
		super.setScope(scope);
	}

	/**
	 * Set the Request Parameters on this authorization request, which represent
	 * the original request parameters and should never be changed during
	 * processing. The map passed in is wrapped in an unmodifiable map instance.
	 * 
	 * @see TokenRequest.setRequestParameters
	 * 
	 * @param requestParameters
	 */
	public void setRequestParameters(Map<String, String> requestParameters) {
		super.setRequestParameters(requestParameters);
	}

	/**
	 * @return the resourceIds
	 */
	public Set<String> getResourceIds() {
		return resourceIds;
	}

	/**
	 * @return the authorities
	 */
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}

	/**
	 * @return the approved
	 */
	public boolean isApproved() {
		return approved;
	}

	/**
	 * @return the redirectUri
	 */
	public String getRedirectUri() {
		return redirectUri;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + ((approvalParameters == null) ? 0 : approvalParameters.hashCode());
		result = prime * result + ((responseTypes == null) ? 0 : responseTypes.hashCode());
		result = prime * result + ((state == null) ? 0 : state.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		AuthorizationRequest other = (AuthorizationRequest) obj;
		if (approvalParameters == null) {
			if (other.approvalParameters != null)
				return false;
		} else if (!approvalParameters.equals(other.approvalParameters))
			return false;
		if (responseTypes == null) {
			if (other.responseTypes != null)
				return false;
		} else if (!responseTypes.equals(other.responseTypes))
			return false;
		if (state == null) {
			if (other.state != null)
				return false;
		} else if (!state.equals(other.state))
			return false;
		return true;
	}

}
