/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.commons.chain2.web.servlet;

import java.util.Map;
import java.util.Objects;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.chain2.web.WebContextBase;

/**
 * <p>Concrete implementation of {@link org.apache.commons.chain2.web.WebContext} suitable
 * for use in Servlets and JSP pages.  The abstract methods are mapped to the appropriate
 * collections of the underlying servlet context, request, and response
 * instances that are passed to the constructor (or the initialize method).</p>
 *
 * @version $Id$
 */
public class ServletWebContextBase extends WebContextBase
        implements ServletWebContext<String, Object> {

    private static final long serialVersionUID = 20120724L;

    // ------------------------------------------------------ Instance Variables

    /**
     * <p>The lazily instantiated <code>Map</code> of application scope
     * attributes.</p>
     */
    private ServletApplicationScopeMap applicationScope = null;

    /**
     * <p>The <code>ServletContext</code> for this web application.</p>
     */
    private transient ServletContext context = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of header name-value
     * combinations (immutable).</p>
     */
    private ServletHeaderMap header = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of header name-values
     * combinations (immutable).</p>
     */
    private ServletHeaderValuesMap headerValues = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of context initialization
     * parameters.</p>
     */
    private ServletInitParamMap initParam = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of cookies.</p>
     */
    private ServletCookieMap cookieValues = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of request
     * parameter name-value.</p>
     */
    private ServletParamMap param = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of request
     * parameter name-values.</p>
     */
    private ServletParamValuesMap paramValues = null;

    /**
     * <p>The <code>HttpServletRequest</code> for this request.</p>
     */
    private transient HttpServletRequest request = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of request scope
     * attributes.</p>
     */
    private ServletRequestScopeMap requestScope = null;

    /**
     * <p>The <code>HttpServletResponse</code> for this request.</p>
     */
    private transient HttpServletResponse response = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of session scope
     * attributes.</p>
     */
    private ServletSessionScopeMap sessionScope = null;

    // ------------------------------------------------------------ Constructors

    /**
     * <p>Construct an uninitialized {@link ServletWebContextBase} instance.</p>
     */
    public ServletWebContextBase() {
        super();
    }

    /**
     * <p>Construct a {@link ServletWebContextBase} instance that is initialized
     * with the specified Servlet API objects.</p>
     *
     * @param ctx The <code>ServletContext</code> for this web application
     * @param req The <code>HttpServletRequest</code> for this request
     * @param res The <code>HttpServletResponse</code> for this request
     */
    public ServletWebContextBase(final ServletContext ctx,
            final HttpServletRequest req, final HttpServletResponse res) {
        initialize(ctx, req, res);
    }

    // ---------------------------------------------------------- Public Methods

    /**
     * @see org.apache.commons.chain2.web.servlet.ServletWebContext#getContext()
     */
    @Override
    public ServletContext getContext() {
        return this.context;
    }

    /**
     * @see org.apache.commons.chain2.web.servlet.ServletWebContext#getRequest()
     */
    @Override
    public HttpServletRequest getRequest() {
        return this.request;
    }

    /**
     * @see org.apache.commons.chain2.web.servlet.ServletWebContext#getResponse()
     */
    @Override
    public HttpServletResponse getResponse() {
        return this.response;
    }

    /**
     * @see org.apache.commons.chain2.web.servlet.ServletWebContext
     * #initialize(javax.servlet.ServletContext,
     * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void initialize(final ServletContext ctx,
            final HttpServletRequest req, final HttpServletResponse res) {
        // Save the specified Servlet API object references
        this.context = ctx;
        this.request = req;
        this.response = res;

        // Perform other setup as needed
    }

    /**
     * @see org.apache.commons.chain2.web.servlet.ServletWebContext#release()
     */
    @Override
    public void release() {
        // Release references to allocated collections
        this.applicationScope = null;
        this.header = null;
        this.headerValues = null;
        this.initParam = null;
        this.param = null;
        this.paramValues = null;
        this.cookieValues = null;
        this.requestScope = null;
        this.sessionScope = null;

        // Release references to Servlet API objects
        this.context = null;
        this.request = null;
        this.response = null;
    }

    // ------------------------------------------------------ WebContextBase Methods

    /**
     * @see org.apache.commons.chain2.web.WebContext#getApplicationScope()
     */
    @Override
    public Map<String, Object> getApplicationScope() {
        if (this.applicationScope == null && this.context != null) {
            this.applicationScope = new ServletApplicationScopeMap(this.context);
        }
        return this.applicationScope;
    }

    /**
     * @see org.apache.commons.chain2.web.WebContext#getHeader()
     */
    @Override
    public Map<String, String> getHeader() {
        if (this.header == null && this.request != null) {
            this.header = new ServletHeaderMap(this.request);
        }
        return this.header;
    }

    /**
     * @see org.apache.commons.chain2.web.WebContext#getHeaderValues()
     */
    @Override
    public Map<String, String[]> getHeaderValues() {
        if (this.headerValues == null && this.request != null) {
            this.headerValues = new ServletHeaderValuesMap(this.request);
        }
        return this.headerValues;
    }

    /**
     * @see org.apache.commons.chain2.web.WebContext#getInitParam()
     */
    @Override
    public Map<String, String> getInitParam() {
        if (this.initParam == null && this.context != null) {
            this.initParam = new ServletInitParamMap(this.context);
        }
        return this.initParam;
    }

    /**
     * @see org.apache.commons.chain2.web.WebContext#getParam()
     */
    @Override
    public Map<String, String> getParam() {
        if (this.param == null && this.request != null) {
            this.param = new ServletParamMap(this.request);
        }
        return this.param;
    }

    /**
     * @see org.apache.commons.chain2.web.WebContext#getParamValues()
     */
    @Override
    public Map<String, String[]> getParamValues() {
        if (this.paramValues == null && this.request != null) {
            this.paramValues = new ServletParamValuesMap(this.request);
        }
        return this.paramValues;
    }

    /**
     * @see org.apache.commons.chain2.web.WebContext#getCookies()
     */
    @Override
    public Map<String, Cookie> getCookies() {
        if (this.cookieValues == null && this.request != null) {
            this.cookieValues = new ServletCookieMap(this.request);
        }
        return this.cookieValues;
    }

    /**
     * @see org.apache.commons.chain2.web.WebContext#getRequestScope()
     */
    @Override
    public Map<String, Object> getRequestScope() {
        if (this.requestScope == null && this.request != null) {
            this.requestScope = new ServletRequestScopeMap(this.request);
        }
        return this.requestScope;
    }

    /**
     * @see org.apache.commons.chain2.web.WebContext#getSessionScope()
     */
    @Override
    public Map<String, Object> getSessionScope() {
        if (this.sessionScope == null && this.request != null) {
            this.sessionScope = new ServletSessionScopeMap(this.request);
        }
        return this.sessionScope;
    }

    /**
     * @see java.util.concurrent.ConcurrentHashMap#hashCode()
     */
    @Override
    public int hashCode() {
        return Objects.hash(
                getApplicationScope(),
                getHeader(),
                getHeaderValues(),
                getInitParam(),
                getParam(),
                getParamValues(),
                getCookies(),
                getRequestScope(),
                getSessionScope()) * 31 + super.hashCode();
    }

    /**
     * @see org.apache.commons.chain2.impl.ContextBase#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object o) {
        if (o != this) {
            if (!ServletWebContextBase.class.isInstance(o)) {
                return false;
            }

            ServletWebContextBase target = (ServletWebContextBase) o;
            return super.equals(target)
                    && Objects.equals(getApplicationScope(), target.getApplicationScope())
                    && Objects.equals(getHeader(), target.getHeader())
                    && Objects.equals(getHeaderValues(), target.getHeaderValues())
                    && Objects.equals(getInitParam(), target.getInitParam())
                    && Objects.equals(getParam(), target.getParam())
                    && Objects.equals(getParamValues(), target.getParamValues())
                    && Objects.equals(getCookies(), target.getCookies())
                    && Objects.equals(getRequestScope(), target.getRequestScope())
                    && Objects.equals(getSessionScope(), target.getSessionScope());
        }
        return true;
    }
}
