package com.tesora.dve.db.mysql.portal.protocol;

/*
 * #%L
 * Tesora Inc.
 * Database Virtualization Engine
 * %%
 * Copyright (C) 2011 - 2014 Tesora Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * This program 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.nio.ByteOrder;

public abstract class BaseMSPMessage<S> implements MSPMessage {
    public static final int INITIAL_CAPACITY = 256;

    byte sequenceID;
    private S state;
    private ByteBuf buffer;

    public BaseMSPMessage(){
        this.sequenceID = 0;
        this.state = null;
        this.buffer = null;
    }

    public BaseMSPMessage(byte sequence, S state){
        this.sequenceID = sequence;
        this.set(state);
    }

    public BaseMSPMessage(byte sequence, byte[] heapData){
        this.sequenceID = sequence;
        this.set(Unpooled.wrappedBuffer(heapData));
    }

    public BaseMSPMessage(byte sequence, ByteBuf buffer){
        this.sequenceID = sequence;
        this.set(buffer);
    }

    public BaseMSPMessage(byte sequence, S state, ByteBuf buffer){
        this.sequenceID = sequence;
        this.set(state,buffer);
    }

    @Override
    public abstract byte getMysqlMessageType();

    @Override
    public abstract MSPMessage newPrototype(byte sequenceID, ByteBuf source);

    protected S unmarshall(ByteBuf source) {
        throw new UnsupportedOperationException();
    }

    protected void marshall(S state, ByteBuf destination) {
        throw new UnsupportedOperationException();
    }

    public void writeTo(ByteBuf destination){
        defaultWriteTo(destination, false);
    }

    protected void defaultWriteTo(ByteBuf destination, boolean supressType) {
        ByteBuf leBuf = destination.order(ByteOrder.LITTLE_ENDIAN);
        int payloadSizeIndex = leBuf.writerIndex();
        leBuf.writeMedium(0);
        leBuf.writeByte(this.getSequenceID());
        int payloadStart = leBuf.writerIndex();
        if (!supressType && !(this instanceof MSPUntypedMessage))
            leBuf.writeByte(this.getMysqlMessageType());
        leBuf.writeBytes(this.readBuffer().slice());
        leBuf.setMedium(payloadSizeIndex, leBuf.writerIndex() - payloadStart);//patch up the length field.
    }

    protected S readState() {
        if (isStateSet())
            return this.state;

        if (isBufferSet()){
            S newState = this.unmarshall(this.buffer.slice().order(ByteOrder.LITTLE_ENDIAN));
            this.set(newState,this.buffer);
            return this.state;
        }

        throw new IllegalStateException(String.format("Cannot access state of %s, no fields or buffer provided.",this.getClass().getSimpleName()));
    }

    protected ByteBuf readBuffer() {
        if (isBufferSet())
            return this.buffer;

        if (isStateSet()){
            ByteBuf container = Unpooled.buffer(INITIAL_CAPACITY).order(ByteOrder.LITTLE_ENDIAN);
            marshall(this.state,container);
            this.set(state,container);
            return this.buffer;
        }

        throw new IllegalStateException(String.format("Cannot access buffer of %s, no fields or buffer provided.",this.getClass().getSimpleName()));
    }

    @Override
    public byte getSequenceID() {
        return sequenceID;
    }

    @Override
    public void setSequenceID(byte newSeq){
        this.sequenceID = newSeq;
    }

    @Override
    public ByteBuf unwrap() {
        if (buffer != null)
            return buffer;
        else
            return Unpooled.EMPTY_BUFFER;
    }

    public String toString(){
        return String.format("%s[buffer.length=%s]",this.getClass().getSimpleName(),buffer.readableBytes());
    }

    private void set(ByteBuf buffer) {
        this.set(this.state,buffer);
    }

    protected void set(S state) {
        this.set(state,this.buffer);
    }

    private void set(S state, ByteBuf buffer) {
        this.state = state;
        if (this.buffer != buffer){
            if (this.buffer != null)
                this.buffer.release();
            this.buffer = buffer;
        }
    }

    public boolean isStateSet(){
        return this.state != null;
    }

    public boolean isBufferSet(){
        return this.buffer != null;
    }

    @Override
    public int refCnt() {
        if (buffer != null)
            return buffer.refCnt();
        else
            return 0;
    }

    @Override
    public BaseMSPMessage<S> retain() {
        if (buffer != null)
            buffer.retain();
        return this;
    }

    @Override
    public BaseMSPMessage<S> retain(int increment) {
        if (buffer != null)
            buffer.retain(increment);
        return this;
    }

    @Override
    public boolean release() {
        if (buffer != null)
            return buffer.release();
        else
            return true;
    }

    @Override
    public boolean release(int decrement) {
        if (buffer != null)
            return buffer.release(decrement);
        else
            return true;
    }

}

@@ -2262,24 +2262,243 @@
 ressType) %7B%0A
+        boolean writeTypeOctect = !supressType && !(this instanceof MSPUntypedMessage);%0A        ByteBuf slice = this.readBuffer().slice();%0A        int payloadLength = slice.readableBytes() + (writeTypeOctect ? 1 : 0);%0A%0A
         Byte
@@ -2565,52 +2565,50 @@
     
-int payloadSizeIndex = leBuf.writerIndex(
+leBuf.ensureWritable(payloadLength + 4
 );%0A
+%0A
     
@@ -2629,17 +2629,29 @@
 eMedium(
-0
+payloadLength
 );%0A     
@@ -2696,120 +2696,36 @@
 ));%0A
-        int payloadStart = leBuf.writerIndex();%0A        if (!supressType && !(this instanceof MSPUntypedMessage)
+%0A        if (writeTypeOctect
 )%0A  
@@ -2775,24 +2775,25 @@
 ageType());%0A
+%0A
         leBu
@@ -2809,142 +2809,15 @@
 tes(
-this.readBuffer().slice());%0A        leBuf.setMedium(payloadSizeIndex, leBuf.writerIndex() - payloadStart);//patch up the length field.
+slice);
 %0A   

Fix BaseMSPMessage to write out header and payload without backtracking