/*
    Copyright (c) 2007-2014 Contributors as noted in the AUTHORS file

    This file is part of 0MQ.

    0MQ is free software; you can redistribute it and/or modify it under
    the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    0MQ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package zmq;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public abstract class EncoderBase implements IEncoder
{
    //  Where to get the data to write from.
    private byte[] writeBuf;
    private FileChannel writeChannel;
    private int writePos;

    //  Next step. If set to -1, it means that associated data stream
    //  is dead.
    private int next;

    //  If true, first byte of the message is being written.
    @SuppressWarnings("unused")
    private boolean beginning;

    //  How much data to write before next step should be executed.
    private int toWrite;

    //  The buffer for encoded data.
    private ByteBuffer buffer;

    private int bufferSize;

    private boolean error;

    protected EncoderBase(int bufferSize)
    {
        this.bufferSize = bufferSize;
        buffer = ByteBuffer.allocateDirect(bufferSize);
        error = false;
    }

    //  The function returns a batch of binary data. The data
    //  are filled to a supplied buffer. If no buffer is supplied (data_
    //  points to NULL) decoder object will provide buffer of its own.

    @Override
    public Transfer getData(ByteBuffer buffer)
    {
        if (buffer == null) {
            buffer = this.buffer;
        }

        buffer.clear();

        while (buffer.hasRemaining()) {
            //  If there are no more data to return, run the state machine.
            //  If there are still no data, return what we already have
            //  in the buffer.
            if (toWrite == 0) {
                //  If we are to encode the beginning of a new message,
                //  adjust the message offset.

                if (!next()) {
                    break;
                }
            }

            //  If there is file channel to send,
            //  send current buffer and the channel together

            if (writeChannel != null) {
                buffer.flip();
                Transfer t = new Transfer.FileChannelTransfer(buffer, writeChannel,
                                                    (long) writePos, (long) toWrite);
                writePos = 0;
                toWrite = 0;

                return t;
            }
            //  If there are no data in the buffer yet and we are able to
            //  fill whole buffer in a single go, let's use zero-copy.
            //  There's no disadvantage to it as we cannot stuck multiple
            //  messages into the buffer anyway. Note that subsequent
            //  write(s) are non-blocking, thus each single write writes
            //  at most SO_SNDBUF bytes at once not depending on how large
            //  is the chunk returned from here.
            //  As a consequence, large messages being sent won't block
            //  other engines running in the same I/O thread for excessive
            //  amounts of time.
            if (this.buffer.position() == 0 && toWrite >= bufferSize) {
                Transfer t;
                ByteBuffer b = ByteBuffer.wrap(writeBuf);
                b.position(writePos);
                t = new Transfer.ByteBufferTransfer(b);
                writePos = 0;
                toWrite = 0;

                return t;
            }

            //  Copy data to the buffer. If the buffer is full, return.
            int toCopy = Math.min(toWrite, buffer.remaining());
            if (toCopy > 0) {
                buffer.put(writeBuf, writePos, toCopy);
                writePos += toCopy;
                toWrite -= toCopy;
            }
        }

        buffer.flip();
        return new Transfer.ByteBufferTransfer(buffer);
    }

    @Override
    public boolean hasData()
    {
        return toWrite > 0;
    }

    protected int state()
    {
        return next;
    }

    protected void state(int state)
    {
        next = state;
    }

    protected void encodingError()
    {
        error = true;
    }

    public final boolean isError()
    {
        return error;
    }

    protected abstract boolean next();

    protected void nextStep(Msg msg, int state, boolean beginning)
    {
        if (msg == null) {
            nextStep(null, 0, state, beginning);
        }
        else {
            nextStep(msg.data(), msg.size(), state, beginning);
        }
    }

    protected void nextStep(byte[] buf, int toWrite,
                            int next, boolean beginning)
    {
        writeBuf = buf;
        writeChannel = null;
        writePos = 0;
        this.toWrite = toWrite;
        this.next = next;
        this.beginning = beginning;
    }

    protected void nextStep(FileChannel ch, long pos, long toWrite,
                            int next, boolean beginning)
    {
        writeBuf = null;
        writeChannel = ch;
        writePos = (int) pos;
        this.toWrite = (int) toWrite;
        this.next = next;
        this.beginning = beginning;
    }
}

@@ -951,22 +951,26 @@
 private 
-byte%5B%5D
+ByteBuffer
  writeBu
@@ -3739,47 +3739,34 @@
     
-ByteBuffer b = ByteBuffer.wrap
+writeBuf.position
 (write
-Buf
+Pos
 );%0A 
@@ -3784,27 +3784,41 @@
     
-b.position(writePos
+writeBuf.limit(writePos + toWrite
 );%0A 
@@ -3868,17 +3868,24 @@
 ransfer(
-b
+writeBuf
 );%0A     
@@ -4166,37 +4166,76 @@
     
-buffer.put(writeBuf, 
+writeBuf.position(writePos);%0A                writeBuf.limit(
 writePos
 , to
@@ -4230,17 +4230,18 @@
 writePos
-,
+ +
  toCopy)
@@ -4242,16 +4242,54 @@
 oCopy);%0A
+                buffer.put(writeBuf);%0A
         
@@ -5060,24 +5060,11 @@
 msg.
-data(), msg.size
+buf
 (), 
@@ -5237,16 +5237,54 @@
 uf = buf
+ != null ? ByteBuffer.wrap(buf) : null
 ;%0A      
@@ -5378,32 +5378,32 @@
 is.next = next;%0A
-
         this.beg
@@ -5421,32 +5421,312 @@
 ginning;%0A    %7D%0A%0A
+    protected void nextStep(ByteBuffer buf,%0A          int next, boolean beginning)%0A    %7B%0A       writeBuf = buf;%0A       writeChannel = null;%0A       writePos = buf.position();%0A       this.toWrite = buf.remaining();%0A       this.next = next;%0A       this.beginning = beginning;%0A    %7D%0A%0A
     protected vo

Use ByteBuffer to avoid copying when DirectByteBuffer or pos!=0 || limit != capacity