//
//  ========================================================================
//  Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.servlets.gzip;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.eclipse.jetty.util.IncludeExclude;
import org.eclipse.jetty.util.StringUtil;

/*------------------------------------------------------------ */
/**
 */
public abstract class CompressedResponseWrapper extends HttpServletResponseWrapper
{

    public static final int DEFAULT_BUFFER_SIZE = 8192;
    public static final int DEFAULT_MIN_COMPRESS_SIZE = 256;

    private IncludeExclude<String> _mimeTypes;
    private int _bufferSize=DEFAULT_BUFFER_SIZE;
    private int _minCompressSize=DEFAULT_MIN_COMPRESS_SIZE;
    protected HttpServletRequest _request;

    private PrintWriter _writer;
    private AbstractCompressedStream _compressedStream;
    private String _etag;
    private long _contentLength=-1;
    private boolean _noCompression;

    /* ------------------------------------------------------------ */
    public CompressedResponseWrapper(HttpServletRequest request, HttpServletResponse response)
    {
        super(response);
        _request = request;
    }


    /* ------------------------------------------------------------ */
    public long getContentLength()
    {
        return _contentLength;
    }

    /* ------------------------------------------------------------ */
    @Override
    public int getBufferSize()
    {
        return _bufferSize;
    }
    
    /* ------------------------------------------------------------ */
    public int getMinCompressSize()
    {
        return _minCompressSize;
    }
    
    /* ------------------------------------------------------------ */
    public String getETag()
    {
        return _etag;
    }

    /* ------------------------------------------------------------ */
    public HttpServletRequest getRequest()
    {
        return _request;
    }
    
    /* ------------------------------------------------------------ */
    /**
     */
    public void setMimeTypes(IncludeExclude<String> mimeTypes)
    {
        _mimeTypes = mimeTypes;
    }

    /* ------------------------------------------------------------ */
    /**
     */
    @Override
    public void setBufferSize(int bufferSize)
    {
        _bufferSize = bufferSize;
        if (_compressedStream!=null)
            _compressedStream.setBufferSize(bufferSize);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setMinCompressSize(int)
     */
    public void setMinCompressSize(int minCompressSize)
    {
        _minCompressSize = minCompressSize;
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setContentType(java.lang.String)
     */
    @Override
    public void setContentType(String ct)
    {
        super.setContentType(ct);

        if (!_noCompression && (_compressedStream==null || _compressedStream.getOutputStream()==null))
        {
            if (ct!=null)
            {
                int colon=ct.indexOf(";");
                if (colon>0)
                    ct=ct.substring(0,colon);

                if (!_mimeTypes.matches(StringUtil.asciiToLowerCase(ct)))
                    noCompression();
            }
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setStatus(int, java.lang.String)
     */
    @SuppressWarnings("deprecation")
    @Override
    public void setStatus(int sc, String sm)
    {
        super.setStatus(sc,sm);
        if (sc<200 || sc==204 || sc==205 || sc>=300)
            noCompression();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setStatus(int)
     */
    @Override
    public void setStatus(int sc)
    {
        super.setStatus(sc);
        if (sc<200 || sc==204 || sc==205 || sc>=300)
            noCompression();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setContentLength(int)
     */
    @Override
    public void setContentLength(int length)
    {
        if (_noCompression)
            super.setContentLength(length);
        else
            setContentLength((long)length);
    }

    /* ------------------------------------------------------------ */
    protected void setContentLength(long length)
    {
        _contentLength=length;
        if (_compressedStream!=null)
            _compressedStream.setContentLength();
        else if (_noCompression && _contentLength>=0)
        {
            HttpServletResponse response = (HttpServletResponse)getResponse();
            if(_contentLength<Integer.MAX_VALUE)
            {
                response.setContentLength((int)_contentLength);
            }
            else
            {
                response.setHeader("Content-Length", Long.toString(_contentLength));
            }
        }
    }
    
    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#addHeader(java.lang.String, java.lang.String)
     */
    @Override
    public void addHeader(String name, String value)
    {
        if ("content-length".equalsIgnoreCase(name))
        {
            _contentLength=Long.parseLong(value);
            if (_compressedStream!=null)
                _compressedStream.setContentLength();
        }
        else if ("content-type".equalsIgnoreCase(name))
        {
            setContentType(value);
        }
        else if ("content-encoding".equalsIgnoreCase(name))
        {
            super.addHeader(name,value);
            if (!isCommitted())
            {
                noCompression();
            }
        }
        else if ("etag".equalsIgnoreCase(name))
            _etag=value;
        else
            super.addHeader(name,value);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#flushBuffer()
     */
    @Override
    public void flushBuffer() throws IOException
    {
        if (_writer!=null)
            _writer.flush();
        if (_compressedStream!=null)
            _compressedStream.flush();
        else
            getResponse().flushBuffer();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#reset()
     */
    @Override
    public void reset()
    {
        super.reset();
        if (_compressedStream!=null)
            _compressedStream.resetBuffer();
        _writer=null;
        _compressedStream=null;
        _noCompression=false;
        _contentLength=-1;
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#resetBuffer()
     */
    @Override
    public void resetBuffer()
    {
        super.resetBuffer();
        if (_compressedStream!=null)
            _compressedStream.resetBuffer();
        _writer=null;
        _compressedStream=null;
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#sendError(int, java.lang.String)
     */
    @Override
    public void sendError(int sc, String msg) throws IOException
    {
        resetBuffer();
        super.sendError(sc,msg);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#sendError(int)
     */
    @Override
    public void sendError(int sc) throws IOException
    {
        resetBuffer();
        super.sendError(sc);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#sendRedirect(java.lang.String)
     */
    @Override
    public void sendRedirect(String location) throws IOException
    {
        resetBuffer();
        super.sendRedirect(location);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#noCompression()
     */
    public void noCompression()
    {
        if (!_noCompression)
            setDeferredHeaders();
        _noCompression=true;
        if (_compressedStream!=null)
        {
            try
            {
                _compressedStream.doNotCompress(false);
            }
            catch (IOException e)
            {
                throw new IllegalStateException(e);
            }
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#finish()
     */
    public void finish() throws IOException
    {
        if (_writer!=null && !_compressedStream.isClosed())
            _writer.flush();
        if (_compressedStream!=null)
            _compressedStream.finish();
        else 
            setDeferredHeaders();
    }

    /* ------------------------------------------------------------ */
    private void setDeferredHeaders()
    {
        if (!isCommitted())
        {
            if (_contentLength>=0)
            {
                if (_contentLength < Integer.MAX_VALUE)
                    super.setContentLength((int)_contentLength);
                else
                    super.setHeader("Content-Length",Long.toString(_contentLength));
            }
            if(_etag!=null)
                super.setHeader("ETag",_etag);
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setHeader(java.lang.String, java.lang.String)
     */
    @Override
    public void setHeader(String name, String value)
    {
        if (_noCompression)
            super.setHeader(name,value);
        else if ("content-length".equalsIgnoreCase(name))
        {
            setContentLength(Long.parseLong(value));
        }
        else if ("content-type".equalsIgnoreCase(name))
        {
            setContentType(value);
        }
        else if ("content-encoding".equalsIgnoreCase(name))
        {
            super.setHeader(name,value);
            if (!isCommitted())
            {
                noCompression();
            }
        }
        else if ("etag".equalsIgnoreCase(name))
            _etag=value;
        else
            super.setHeader(name,value);
    }

    /* ------------------------------------------------------------ */
    @Override
    public boolean containsHeader(String name)
    {
        if (!_noCompression && "etag".equalsIgnoreCase(name) && _etag!=null)
            return true;
        return super.containsHeader(name);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#getOutputStream()
     */
    @Override
    public ServletOutputStream getOutputStream() throws IOException
    {
        if (_compressedStream==null)
        {
            if (getResponse().isCommitted() || _noCompression)
                return getResponse().getOutputStream();

            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
        }
        else if (_writer!=null)
            throw new IllegalStateException("getWriter() called");

        return _compressedStream;
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#getWriter()
     */
    @Override
    public PrintWriter getWriter() throws IOException
    {
        if (_writer==null)
        {
            if (_compressedStream!=null)
                throw new IllegalStateException("getOutputStream() called");

            if (getResponse().isCommitted() || _noCompression)
                return getResponse().getWriter();

            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
            _writer=newWriter(_compressedStream,getCharacterEncoding());
        }
        return _writer;
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setIntHeader(java.lang.String, int)
     */
    @Override
    public void setIntHeader(String name, int value)
    {
        if ("content-length".equalsIgnoreCase(name))
        {
            _contentLength=value;
            if (_compressedStream!=null)
                _compressedStream.setContentLength();
        }
        else
            super.setIntHeader(name,value);
    }

    /* ------------------------------------------------------------ */
    /**
     * Allows derived implementations to replace PrintWriter implementation.
     *
     * @param out the out
     * @param encoding the encoding
     * @return the prints the writer
     * @throws UnsupportedEncodingException the unsupported encoding exception
     */
    protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
    {
        return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
    }

    /* ------------------------------------------------------------ */
    /**
     *@return the underlying CompressedStream implementation
     */
    protected abstract AbstractCompressedStream newCompressedStream(HttpServletRequest _request, HttpServletResponse response) throws IOException;

}
