/*
 * @(#)CDROutputStream_1_2.java	1.14 10/03/23
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.sun.corba.se.impl.encoding;

import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.CompletionStatus;
import com.sun.corba.se.spi.ior.iiop.GIOPVersion;
import com.sun.corba.se.impl.encoding.CodeSetConversion;
import com.sun.corba.se.impl.orbutil.ORBConstants;

public class CDROutputStream_1_2 extends CDROutputStream_1_1
{
    // There's a situation with chunking with fragmentation
    // in which the alignment for a primitive value is needed
    // to fill fragment N, but the primitive won't fit so
    // must go into fragment N + 1.  The behavior is the same
    // as that for specialChunks.
    //
    // Unfortunately, given the current code, we can't reuse
    // specialChunk.  If you wrap each of the following
    // write calls with handleSpecialChunkBegin/End, you
    // will lose your state because the primitive calls will
    // change the variables, etc.
    //
    // All of the CDR code should be rewritten moving chunking
    // to a different level, perhaps in the buffer managers.
    // We want to move to a compositional model rather than
    // using inheritance.
    //
    // Note that in the grow case, chunks are _NOT_ closed
    // at grow points, now.
    //
    // **** NOTE ****
    // Since we will not support valuetypes with GIOP 1.1, that
    // also means we do not support chunking there.
    //
    protected boolean primitiveAcrossFragmentedChunk = false;

    // Used in chunking.  Here's how this works:
    //
    // When chunking and writing an array of primitives, a string, or a 
    // wstring, _AND_ it won't fit in the buffer do the following.  (As
    // you can see, this is a very "special" chunk.)
    //
    //     1.  Write the length of the chunk including the array length
    //     2.  Set specialChunk to true
    // 3 applies to ALL chunking:
    //     3.  In grow, if we need to fragment and specialChunk is false
    //               a) call end_block
    //               b) fragment
    // Now back to the array only case:
    //     [write the data]
    //     4.  if specialChunk is true 
    //               a) Close the chunk
    //               b) Set specialChunk to false

    protected boolean specialChunk = false;

    // Indicates whether the header should be padded. In GIOP 1.2 and above, the
    // body must be aligned on a 8-octet boundary, and so the header needs to be
    // padded appropriately. However, if there is no body to a request or reply
    // message, there is no need to pad the header, in the unfragmented case.
    private boolean headerPadding;
    
    protected void handleSpecialChunkBegin(int requiredSize)
    {
        // If we're chunking and the item won't fit in the buffer
        if (inBlock && requiredSize + bbwi.position() > bbwi.buflen) {

            // Duplicating some code from end_block.  Compute
            // and write the total chunk length.

            int oldSize = bbwi.position();
            bbwi.position(blockSizeIndex - 4);

            //write_long(oldSize - blockSizeIndex);
            writeLongWithoutAlign((oldSize - blockSizeIndex) + requiredSize);
            bbwi.position(oldSize);
    
            // Set the special flag so we don't end the chunk when
            // we fragment
            specialChunk = true;
        }
    }

    protected void handleSpecialChunkEnd()
    {
        // If we're in a chunk and the item spanned fragments
        if (inBlock && specialChunk) {

            // This is unnecessary, but I just want to show that
            // we're done with the current chunk.  (the end_block
            // call is inappropriate here)
            inBlock = false;
            blockSizeIndex = -1;
            blockSizePosition = -1;
            
            // Start a new chunk since we fragmented during the item.
            // Thus, no one can go back to add more to the chunk length
            start_block();

            // Now turn off the flag so we go back to the normal
            // behavior of closing a chunk when we fragment and
            // reopening afterwards.
            specialChunk = false;
        }
    }
    
    // Called after writing primitives
    private void checkPrimitiveAcrossFragmentedChunk()
    {
        if (primitiveAcrossFragmentedChunk) {
            primitiveAcrossFragmentedChunk = false;

            inBlock = false;

            // It would be nice to have a StreamPosition
            // abstraction if we could avoid allocation
            // overhead.
            blockSizeIndex = -1;
            blockSizePosition = -1;

            // Start a new chunk
            start_block();
        }        
    }


    public void write_octet(byte x) {
        super.write_octet(x);
        checkPrimitiveAcrossFragmentedChunk();
    }

    public void write_short(short x) {
        super.write_short(x);
        checkPrimitiveAcrossFragmentedChunk();
    }

    public void write_long(int x) {
        super.write_long(x);
        checkPrimitiveAcrossFragmentedChunk();
    }

    public void write_longlong(long x) {
        super.write_longlong(x);
        checkPrimitiveAcrossFragmentedChunk();
    }

    // Called by RequestMessage_1_2 or ReplyMessage_1_2 classes only.
    void setHeaderPadding(boolean headerPadding) {
        this.headerPadding = headerPadding;
    }

    protected void alignAndReserve(int align, int n) {

        // headerPadding bit is set by the write operation of RequestMessage_1_2
        // or ReplyMessage_1_2 classes. When set, the very first body write
        // operation (from the stub code) would trigger an alignAndReserve 
        // method call, that would in turn add the appropriate header padding,
        // such that the body is aligned on a 8-octet boundary. The padding
        // is required for GIOP versions 1.2 and above, only if body is present.
        if (headerPadding == true) {
            headerPadding = false;
            alignOnBoundary(ORBConstants.GIOP_12_MSG_BODY_ALIGNMENT);
        }
        
        // In GIOP 1.2, we always end fragments at our
        // fragment size, which is an "evenly divisible
        // 8 byte boundary" (aka divisible by 16).  A fragment can 
        // end with appropriate alignment padding, but no padding
        // is needed with respect to the next GIOP fragment
        // header since it ends on an 8 byte boundary.

        bbwi.position(bbwi.position() + computeAlignment(align));

        if (bbwi.position() + n  > bbwi.buflen)
            grow(align, n);
    }

    protected void grow(int align, int n) {
        
        // Save the current size for possible post-fragmentation calculation
        int oldSize = bbwi.position();

        // See notes where specialChunk is defined, as well as the
        // above notes for primitiveAcrossFragmentedChunk.
        //
        // If we're writing a primitive and chunking, we need to update
        // the chunk length to include the length of the primitive (unless
        // this complexity is handled by specialChunk).
        //
        // Note that this is wasted processing in the grow case, but that
        // we don't actually close the chunk in that case.
        boolean handleChunk = (inBlock && !specialChunk);
        if (handleChunk) {
            int oldIndex = bbwi.position();

            bbwi.position(blockSizeIndex - 4);

            writeLongWithoutAlign((oldIndex - blockSizeIndex) + n);

            bbwi.position(oldIndex);
        }

        bbwi.needed = n;
        bufferManagerWrite.overflow(bbwi);

        // At this point, if we fragmented, we should have a ByteBufferWithInfo
        // with the fragment header already marshalled.  The buflen and position
        // should be updated accordingly, and the fragmented flag should be set.

        // Note that fragmented is only true in the streaming and collect cases.
        if (bbwi.fragmented) {

            // Clear the flag
            bbwi.fragmented = false;

            // Update fragmentOffset so indirections work properly.
            // At this point, oldSize is the entire length of the
            // previous buffer.  bbwi.position() is the length of the
            // fragment header of this buffer.
            fragmentOffset += (oldSize - bbwi.position());

            // We just fragmented, and need to signal that we should
            // start a new chunk after writing the primitive.
            if (handleChunk)
                primitiveAcrossFragmentedChunk = true;
            
        }
    }

    public GIOPVersion getGIOPVersion() {
        return GIOPVersion.V1_2;
    }

    public void write_wchar(char x)
    {
        // In GIOP 1.2, a wchar is encoded as an unsigned octet length
        // followed by the octets of the converted wchar.  This is good,
        // but it causes problems with our chunking code.  We don't
        // want that octet to get put in a different chunk at the end
        // of the previous fragment.  
        //
        // Ensure that this won't happen by overriding write_wchar_array
        // and doing our own handleSpecialChunkBegin/End here.
        CodeSetConversion.CTBConverter converter = getWCharConverter();

        converter.convert(x);

        handleSpecialChunkBegin(1 + converter.getNumBytes());

        write_octet((byte)converter.getNumBytes());

        byte[] result = converter.getBytes();

        // Write the bytes without messing with chunking
        // See CDROutputStream_1_0
        internalWriteOctetArray(result, 0, converter.getNumBytes());

        handleSpecialChunkEnd();
    }

    public void write_wchar_array(char[] value, int offset, int length)
    {
        if (value == null) {
	    throw wrapper.nullParam(CompletionStatus.COMPLETED_MAYBE);
        }   

        CodeSetConversion.CTBConverter converter = getWCharConverter();

        // Unfortunately, because of chunking, we have to convert the
        // entire char[] to a byte[] array first so we can know how
        // many bytes we're writing ahead of time.  You can't split
        // an array of primitives into multiple chunks.
        int totalNumBytes = 0;

        // Remember that every wchar starts with an octet telling
        // its length.  The buffer size is an upper bound estimate.
        int maxLength = (int)Math.ceil(converter.getMaxBytesPerChar() * length);
        byte[] buffer = new byte[maxLength + length];

        for (int i = 0; i < length; i++) {
            // Convert one wchar
            converter.convert(value[offset + i]);

            // Make sure to add the octet length
            buffer[totalNumBytes++] = (byte)converter.getNumBytes();

            // Copy it into our buffer
            System.arraycopy(converter.getBytes(), 0,
                             buffer, totalNumBytes,
                             converter.getNumBytes());

            totalNumBytes += converter.getNumBytes();
        }

        // Now that we know the total length, we can deal with chunking.
        // Note that we don't have to worry about alignment since they're
        // just octets.
        handleSpecialChunkBegin(totalNumBytes);

        // Must use totalNumBytes rather than buffer.length since the
        // buffer.length is only the upper bound estimate.
        internalWriteOctetArray(buffer, 0, totalNumBytes);

        handleSpecialChunkEnd();
    }    

    public void write_wstring(String value) {
        if (value == null) {
	    throw wrapper.nullParam(CompletionStatus.COMPLETED_MAYBE);
        }

        // In GIOP 1.2, wstrings are not terminated by a null.  The
        // length is the number of octets in the converted format.
        // A zero length string is represented with the 4 byte length
        // value of 0.
        if (value.length() == 0) {
            write_long(0);
            return;
        }

        CodeSetConversion.CTBConverter converter = getWCharConverter();

        converter.convert(value);

        handleSpecialChunkBegin(computeAlignment(4) + 4 + converter.getNumBytes());

        write_long(converter.getNumBytes());

        // Write the octet array without tampering with chunking
        internalWriteOctetArray(converter.getBytes(), 0, converter.getNumBytes());

        handleSpecialChunkEnd();
    }
}
