/**
 * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.portal.kernel.servlet;

import com.liferay.portal.kernel.io.RestrictedByteArrayCacheOutputStream;
import com.liferay.portal.kernel.io.RestrictedByteArrayCacheOutputStream.FlushPreAction;
import com.liferay.portal.kernel.util.UnsyncPrintWriterPool;

import java.io.IOException;
import java.io.PrintWriter;

import java.nio.ByteBuffer;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Shuyang Zhou
 */
public class RestrictedByteBufferCacheServletResponse
	extends MetaInfoCacheServletResponse {

	public RestrictedByteBufferCacheServletResponse(
		HttpServletResponse response, int cacheCapacity) {

		super(response);

		_cacheCapacity = cacheCapacity;
	}

	public void flushCache() throws IOException {
		if (_restrictedByteArrayCacheOutputStream != null) {
			_restrictedByteArrayCacheOutputStream.flush();
		}
	}

	@Override
	public int getBufferSize() {
		if (isOverflowed()) {
			return super.getBufferSize();
		}

		if (_restrictedByteArrayCacheOutputStream == null) {
			return _cacheCapacity;
		}

		return _restrictedByteArrayCacheOutputStream.getCacheCapacity();
	}

	public ByteBuffer getByteBuffer() {
		if (_restrictedByteArrayCacheOutputStream == null) {
			return _emptyByteBuffer;
		}

		return _restrictedByteArrayCacheOutputStream.unsafeGetByteBuffer();
	}

	@Override
	public ServletOutputStream getOutputStream() throws IOException {
		if (calledGetWriter) {
			throw new IllegalStateException(
				"Unable to obtain OutputStream because Writer is already in " +
					"use");
		}

		if (_servletOutputStream != null) {
			return _servletOutputStream;
		}

		_restrictedByteArrayCacheOutputStream =
			new RestrictedByteArrayCacheOutputStream(
				super.getOutputStream(), _cacheCapacity,
				new FinishResponseFlushPreAction());

		_servletOutputStream = new ServletOutputStreamAdapter(
			_restrictedByteArrayCacheOutputStream);

		calledGetOutputStream = true;

		return _servletOutputStream;
	}

	@Override
	public PrintWriter getWriter() throws IOException {
		if (calledGetOutputStream) {
			throw new IllegalStateException(
				"Unable to obtain Writer because OutputStream is already in " +
					"use");
		}

		if (_printWriter != null) {
			return _printWriter;
		}

		ServletResponse servletResponse = getResponse();

		_restrictedByteArrayCacheOutputStream =
			new RestrictedByteArrayCacheOutputStream(
				servletResponse.getOutputStream(), _cacheCapacity,
				new FinishResponseFlushPreAction());

		_printWriter = UnsyncPrintWriterPool.borrow(
			_restrictedByteArrayCacheOutputStream, getCharacterEncoding());

		calledGetWriter = true;

		return _printWriter;
	}

	public boolean isOverflowed() {
		if (_restrictedByteArrayCacheOutputStream == null) {
			if (_cacheCapacity >= super.getBufferSize()) {
				return false;
			}

			return true;
		}

		return _restrictedByteArrayCacheOutputStream.isOverflowed();
	}

	@Override
	public void setBufferSize(int bufferSize) {
		if (isCommitted()) {
			throw new IllegalStateException("Set buffer size after commit");
		}

		if (bufferSize > getBufferSize()) {
			super.setBufferSize(bufferSize);

			try {
				flushCache();
			}
			catch (IOException ioe) {
				throw new IllegalStateException(
					"Unable to transfer restricted byte buffer to underneath" +
						" response's buffer",
				ioe);
			}
		}
	}

	@Override
	protected void resetBuffer(boolean nullOutReferences) {
		if (nullOutReferences) {
			calledGetOutputStream = false;
			calledGetWriter = false;

			_printWriter = null;
			_servletOutputStream = null;
			_restrictedByteArrayCacheOutputStream = null;
		}
		else if (_restrictedByteArrayCacheOutputStream != null) {
			_restrictedByteArrayCacheOutputStream.reset();
		}
	}

	private static ByteBuffer _emptyByteBuffer = ByteBuffer.allocate(0);

	private int _cacheCapacity;
	private PrintWriter _printWriter;
	private RestrictedByteArrayCacheOutputStream
		_restrictedByteArrayCacheOutputStream;
	private ServletOutputStream _servletOutputStream;

	private class FinishResponseFlushPreAction implements FlushPreAction {

		@Override
		public void beforeFlush() throws IOException {
			flushBuffer();
		}
	}

}