package ca.uhn.fhir.rest.server.servlet;

/*
 * #%L
 * HAPI FHIR - Server Framework
 * %%
 * Copyright (C) 2014 - 2017 University Health Network
 * %%
 * 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.
 * #L%
 */
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.GZIPInputStream;

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

import org.apache.commons.io.IOUtils;

import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding.IRequestReader;

public class ServletRequestDetails extends RequestDetails {

	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestDetails.class);
	/**
	 * @see BaseMethodBinding#loadRequestContents(RequestDetails)
	 */
	private static volatile IRequestReader ourRequestReader;
	private RestfulServer myServer;
	private HttpServletRequest myServletRequest;
	private HttpServletResponse myServletResponse;
	private byte[] requestContents;

	public ServletRequestDetails() {
		super();
		setResponse(new ServletRestfulResponse(this));
	}

	@Override
	protected byte[] getByteStreamRequestContents() {
		/*
		 * This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on
		 * servlet-api in clients since there is no point. So we dynamically load a class that does the servlet processing
		 * in servers. Down the road it may make sense to just split the method binding classes into server and client
		 * versions, but this isn't actually a huge deal I don't think.
		 */
		IRequestReader reader = ourRequestReader;
		if (reader == null) {
			try {
				Class.forName("javax.servlet.ServletInputStream");
				String className = BaseMethodBinding.class.getName() + "$" + "ActiveRequestReader";
				try {
					reader = (IRequestReader) Class.forName(className).newInstance();
				} catch (Exception e1) {
					throw new ConfigurationException("Failed to instantiate class " + className, e1);
				}
			} catch (ClassNotFoundException e) {
				String className = BaseMethodBinding.class.getName() + "$" + "InactiveRequestReader";
				try {
					reader = (IRequestReader) Class.forName(className).newInstance();
				} catch (Exception e1) {
					throw new ConfigurationException("Failed to instantiate class " + className, e1);
				}
			}
			ourRequestReader = reader;
		}

		try {
			InputStream inputStream = reader.getInputStream(this);
			requestContents = IOUtils.toByteArray(inputStream);

			if (myServer.isUncompressIncomingContents()) {
				String contentEncoding = myServletRequest.getHeader(Constants.HEADER_CONTENT_ENCODING);
				if ("gzip".equals(contentEncoding)) {
					ourLog.debug("Uncompressing (GZip) incoming content");
					if (requestContents.length > 0) {
						GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(requestContents));
						requestContents = IOUtils.toByteArray(gis);
					}
				}
			}
			// FIXME resource leak
			return requestContents;
		} catch (IOException e) {
			ourLog.error("Could not load request resource", e);
			throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage()));
		}
	}

	@Override
	public Charset getCharset() {
		Charset charset = null;

		String charsetString = myServletRequest.getCharacterEncoding();
		if (isNotBlank(charsetString)) {
			charset = Charset.forName(charsetString);
		}

		return charset;
	}

	@Override
	public FhirContext getFhirContext() {
		return getServer().getFhirContext();
	}

	@Override
	public String getHeader(String name) {
		return getServletRequest().getHeader(name);
	}

	@Override
	public List<String> getHeaders(String name) {
		Enumeration<String> headers = getServletRequest().getHeaders(name);
		return headers == null ? Collections.<String> emptyList() : Collections.list(getServletRequest().getHeaders(name));
	}

	@Override
	public InputStream getInputStream() throws IOException {
		return getServletRequest().getInputStream();
	}

	@Override
	public Reader getReader() throws IOException {
		return getServletRequest().getReader();
	}

	@Override
	public RestfulServer getServer() {
		return myServer;
	}

	@Override
	public String getServerBaseForRequest() {
		return getServer().getServerBaseForRequest(getServletRequest());
	}

	public HttpServletRequest getServletRequest() {
		return myServletRequest;
	}

	public HttpServletResponse getServletResponse() {
		return myServletResponse;
	}

	public void setServer(RestfulServer theServer) {
		this.myServer = theServer;
	}

	public void setServletRequest(HttpServletRequest myServletRequest) {
		this.myServletRequest = myServletRequest;
	}

	public void setServletResponse(HttpServletResponse myServletResponse) {
		this.myServletResponse = myServletResponse;
	}

}
