/*
 * CDDL HEADER START
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://www.sun.com/cddl/cddl.html and legal/CDDLv1.0.txt
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright 2006 Sun Microsystems Inc. All Rights Reserved
 * CDDL HEADER END
 */

package org.ajax4jsf.portlet.application;

import com.sun.faces.application.InterweavingResponse;

import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.FacesException;
import javax.portlet.PortletURL;
import javax.portlet.RenderResponse;


/**
 * ViewHandlerPortletRenderResponseWrapper is the Portlet implementation of the InterweavingResponse wrapper.
 * This class is loaded only if InterweavingResponse is present in JSF RI.
 */
public class ViewHandlerPortletRenderResponseWrapper implements RenderResponse, InterweavingResponse {
    
    private RenderResponse response;
    private ByteArrayWebOutputStream bawos;
    private PrintWriter pw ;
    private CharArrayWriter caw;
    
    public ViewHandlerPortletRenderResponseWrapper(RenderResponse response) {
        this.response = response;
    }
    
    public void resetBuffers() throws IOException {
        if (caw != null) {
            caw.reset();
        } else if (bawos != null) {
            bawos.resetByteArray();
        }
    }
    
    public boolean isBytes() {
        return (bawos != null);
    }
    
    public boolean isChars() {
        return (caw != null);
    }
    
    public char[] getChars() {
        if (caw != null) {
            return caw.toCharArray();
        }
        return null;
    }
    
    public byte[] getBytes() {
        if (bawos != null) {
            return bawos.toByteArray();
        }
        return null;
    }
    
    public int getStatus() {
        return Integer.MIN_VALUE;
    }
    
    public String getContentType() {
        return response.getContentType();
    }
    
    public PortletURL createRenderURL() {
        return response.createRenderURL();
    }
    
    public PortletURL createActionURL() {
        return response.createActionURL();
    }
    
    public String getNamespace() {
        return response.getNamespace();
    }
    
    public void setTitle(String title) {
        response.setTitle(title);
    }
    
    public void setContentType(String type) {
        response.setContentType(type);
    }
    
    public String getCharacterEncoding() {
        return response.getCharacterEncoding();
    }
    
    public Locale getLocale() {
        return response.getLocale();
    }
    
    public void setBufferSize(int size) {
        response.setBufferSize(size);
    }
    
    public int getBufferSize() {
        return response.getBufferSize();
    }
    
    public void flushBuffer() throws IOException {
        response.flushBuffer();
    }
    
    public void resetBuffer() {
        response.resetBuffer();
    }
    
    public boolean isCommitted() {
        return response.isCommitted();
    }
    
    public void reset() {
        response.reset();
    }
    
    public PrintWriter getWriter() throws IOException {
        if (bawos != null) {
            throw new IllegalStateException();
        }
        if (pw == null) {
            caw = new CharArrayWriter(1024);
            pw = new PrintWriter(caw);
        }
        
        return pw;
    }
    
    public OutputStream getPortletOutputStream() throws IOException {
        if (pw != null) {
            throw new IllegalStateException();
        }
        if (bawos == null) {
            bawos = new ByteArrayWebOutputStream();
        }
        return bawos;
    }
    
    public void addProperty(String key, String value) {
        response.addProperty(key, value);
    }
    
    public void setProperty(String key, String value) {
        response.setProperty(key, value);
    }
    
    public String encodeURL(String path) {
        return response.encodeURL(path);
    }
    
    public void flushContentToWrappedResponse() throws IOException {
        if (caw != null) {
            pw.flush();
            caw.writeTo(response.getWriter());
            caw.reset();
        } else if (bawos != null) {
            try {
                bawos.writeTo(response.getWriter(),
                        response.getCharacterEncoding());
            } catch (IllegalStateException ise) {
                bawos.writeTo(response.getPortletOutputStream());
            }
            bawos.resetByteArray();
        }
        
    }
    public void flushToWriter(Writer writer, String encoding) throws IOException {
        if (caw != null) {
            pw.flush();
            caw.writeTo(writer);
            caw.reset();
        } else if (bawos != null) {
            bawos.writeTo(writer, encoding);
            bawos.resetByteArray();
        }
        writer.flush();
    }
    
}

// ------------------------------------------------------------- Private Classes

/**
 * This steam convers byte content to character.
 */
class ByteArrayWebOutputStream extends OutputStream {
    
    private DirectByteArrayOutputStream baos;
    
    public ByteArrayWebOutputStream() {
        baos = new DirectByteArrayOutputStream(1024);
    }
    
    public void write(int n) {
        baos.write(n);
    }
    
    /**
     * <p>It's important to not expose this as reset.</p>
     */
    
    public void resetByteArray() {
        baos.reset();
    }
    
    public byte[] toByteArray() {
        return baos.toByteArray();
    }
    
    
    /**
     * Converts the buffered bytes into chars based on the
     * specified encoding and writes them to the provided Writer.
     *
     * @param writer   target Writer
     * @param encoding character encoding
     */
    public void writeTo(Writer writer, String encoding) {
        
        ByteBuffer bBuff = baos.getByteBuffer();
        CharsetDecoder decoder = Charset.forName(encoding).newDecoder();
        
        try {
            CharBuffer cBuff = decoder.decode(bBuff);
            writer.write(cBuff.array());
        } catch (CharacterCodingException cce) {
            throw new FacesException(cce);
        } catch (IOException ioe) {
            throw new FacesException(ioe);
        }
    }
    
    
    /**
     * <p>Write the buffered bytes to the provided OutputStream.</p>
     *
     * @param stream the stream to write to
     */
    public void writeTo(OutputStream stream) {
        try {
            stream.write(baos.getByteBuffer().array());
        } catch (IOException ioe) {
            throw new FacesException(ioe);
        }
    }
    
    private static class DirectByteArrayOutputStream extends ByteArrayOutputStream {
        
        // -------------------------------------------------------- Constructors
        
        
        public DirectByteArrayOutputStream(int initialCapacity) {
            super(initialCapacity);
        }
        
        // ------------------------------------------------------- PublicMethods
        
        
        /**
         * Return the buffer backing this ByteArrayOutputStream as a
         * ByteBuffer.
         *
         * @return buf wrapped in a ByteBuffer
         */
        public ByteBuffer getByteBuffer() {
            return (ByteBuffer.wrap(buf, 0, count));
        }
        
    }
}