/**
 *
 * Copyright 2004 Protique Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **/

package org.activemq.io.impl;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.jms.JMSException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.io.AbstractWireFormat;
import org.activemq.io.WireFormat;
import org.activemq.message.AbstractPacket;
import org.activemq.message.CachedValue;
import org.activemq.message.Packet;
import org.activemq.message.WireFormatInfo;

/**
 * Default implementation used for Java-Java protocols. When talking to non-Java nodes we may use a different wire
 * format.
 * 
 * @version $Revision: 1.1.1.1 $
 */
public abstract class AbstractDefaultWireFormat extends AbstractWireFormat implements Serializable {

    /**
     * Current wire format version for this implementation
     */
    public static final int WIRE_FORMAT_VERSION = 3;
    private static final Log log = LogFactory.getLog(AbstractDefaultWireFormat.class);
    
    protected transient  PacketReader messageReader;
    protected transient  PacketReader textMessageReader;
    protected transient  PacketReader objectMessageReader;
    protected transient  PacketReader bytesMessageReader;
    protected transient  PacketReader streamMessageReader;
    protected transient  PacketReader mapMessageReader;
    protected transient  PacketReader messageAckReader;
    protected transient  PacketReader receiptReader;
    protected transient  PacketReader consumerInfoReader;
    protected transient  PacketReader producerInfoReader;
    protected transient  PacketReader transactionInfoReader;
    protected transient  PacketReader xaTransactionInfoReader;
    protected transient  PacketReader brokerInfoReader;
    protected transient  PacketReader connectionInfoReader;
    protected transient  PacketReader sessionInfoReader;
    protected transient  PacketReader durableUnsubscribeReader;
    protected transient  PacketReader reponseReceiptReader;
    protected transient  PacketReader intReponseReceiptReader;
    protected transient  PacketReader capacityInfoReader;
    protected transient  PacketReader capacityInfoRequestReader;
    protected transient  PacketReader wireFormatInfoReader;
    protected transient  PacketReader keepAliveReader;
    protected transient  PacketReader brokerAdminCommandReader;
    protected transient  PacketReader cachedValueReader;
    protected transient  PacketReader cleanupConnectionAndSessionInfoReader;        
    protected transient  PacketWriter messageWriter;
    protected transient  PacketWriter textMessageWriter;
    protected transient  PacketWriter objectMessageWriter;
    protected transient  PacketWriter bytesMessageWriter;
    protected transient  PacketWriter streamMessageWriter;
    protected transient  PacketWriter mapMessageWriter;
    protected transient  PacketWriter messageAckWriter;
    protected transient  PacketWriter receiptWriter;
    protected transient  PacketWriter consumerInfoWriter;
    protected transient  PacketWriter producerInfoWriter;
    protected transient  PacketWriter transactionInfoWriter;
    protected transient  PacketWriter xaTransactionInfoWriter;
    protected transient  PacketWriter brokerInfoWriter;
    protected transient  PacketWriter connectionInfoWriter;
    protected transient  PacketWriter sessionInfoWriter;
    protected transient  PacketWriter durableUnsubscribeWriter;
    protected transient  PacketWriter reponseReceiptWriter;
    protected transient  PacketWriter intReponseReceiptWriter;
    protected transient  PacketWriter capacityInfoWriter;
    protected transient  PacketWriter capacityInfoRequestWriter;
    protected transient  PacketWriter wireFormatInfoWriter;
    protected transient  PacketWriter keepAliveWriter;
    protected transient  PacketWriter brokerAdminCommandWriter;
    protected transient  PacketWriter cachedValueWriter;
    protected transient  PacketWriter cleanupConnectionAndSessionInfoWriter;

    private List readers = new ArrayList();
    private List writers = new ArrayList();
    
    protected transient int currentWireFormatVersion;
    
    /**
     * Default Constructor
     */
    public AbstractDefaultWireFormat() {
        this.currentWireFormatVersion = WIRE_FORMAT_VERSION;
        initializeReaders();
        initializeWriters();
    }
    
    
    abstract public byte[] toBytes(Packet packet) throws IOException;
    abstract public Packet writePacket(Packet packet, DataOutput dataOut) throws IOException;    
    abstract protected Packet readPacket(DataInput dataIn, PacketReader reader) throws IOException;
    
    abstract protected void handleCachedValue(CachedValue cv);
    abstract public Object getValueFromReadCache(short key);
    abstract short getWriteCachedKey(Object value) throws IOException;
    

    /**
     * Some wire formats require a handshake at start-up
     * @param dataOut
     * @param dataIn
     * @throws JMSException
     */
    public void initiateClientSideProtocol(DataOutputStream dataOut,DataInputStream dataIn) throws JMSException{
        WireFormatInfo info = new WireFormatInfo();
        info.setVersion(getCurrentWireFormatVersion());
        try {
            writePacket(info, dataOut);
            dataOut.flush();
        }
        catch (IOException e) {
            throw new JMSException("Failed to intiate protocol");
        }
    }
    
    /**
     * Some wire formats require a handshake at start-up
     * @param dataOut
     * @param dataIn
     * @throws JMSException
     */
    public void initiateServerSideProtocol(DataOutputStream dataOut,DataInputStream dataIn) throws JMSException{
    }
        
    /**
     * @return new WireFormat
     */
    abstract public WireFormat copy();
    
    /**
     * @param dataIn
     * @return
     * @throws IOException
     */
    public Packet readPacket(DataInput dataIn) throws IOException {
        int type = -1;
        while ((type = dataIn.readByte()) == 0);
        
        if (type == -1){
            throw new IOException("InputStream now closed");
        }
        return readPacket(type, dataIn);
    }

    /**
     * @param firstByte
     * @param dataIn
     * @return
     * @throws IOException
     * 
     */
    public Packet readPacket(int firstByte, DataInput dataIn) throws IOException {
        switch (firstByte) {
            case Packet.ACTIVEMQ_MESSAGE :
                return readPacket(dataIn, messageReader);
            case Packet.ACTIVEMQ_TEXT_MESSAGE :
                return readPacket(dataIn, textMessageReader);
            case Packet.ACTIVEMQ_OBJECT_MESSAGE :
                return readPacket(dataIn, objectMessageReader);
            case Packet.ACTIVEMQ_BYTES_MESSAGE :
                return readPacket(dataIn, bytesMessageReader);
            case Packet.ACTIVEMQ_STREAM_MESSAGE :
                return readPacket(dataIn, streamMessageReader);
            case Packet.ACTIVEMQ_MAP_MESSAGE :
                return readPacket(dataIn, mapMessageReader);
            case Packet.ACTIVEMQ_MSG_ACK :
                return readPacket(dataIn, messageAckReader);
            case Packet.RECEIPT_INFO :
                return readPacket(dataIn, receiptReader);
            case Packet.CONSUMER_INFO :
                return readPacket(dataIn, consumerInfoReader);
            case Packet.PRODUCER_INFO :
                return readPacket(dataIn, producerInfoReader);
            case Packet.TRANSACTION_INFO :
                return readPacket(dataIn, transactionInfoReader);
            case Packet.XA_TRANSACTION_INFO :
                return readPacket(dataIn, xaTransactionInfoReader);
            case Packet.ACTIVEMQ_BROKER_INFO :
                return readPacket(dataIn, brokerInfoReader);
            case Packet.ACTIVEMQ_CONNECTION_INFO :
                return readPacket(dataIn, connectionInfoReader);
            case Packet.SESSION_INFO :
                return readPacket(dataIn, sessionInfoReader);
            case Packet.DURABLE_UNSUBSCRIBE :
                return readPacket(dataIn, durableUnsubscribeReader);
            case Packet.RESPONSE_RECEIPT_INFO :
                return readPacket(dataIn, reponseReceiptReader);
            case Packet.INT_RESPONSE_RECEIPT_INFO :
                return readPacket(dataIn, intReponseReceiptReader);
            case Packet.CAPACITY_INFO :
                return readPacket(dataIn, capacityInfoReader);
            case Packet.CAPACITY_INFO_REQUEST :
                return readPacket(dataIn, capacityInfoRequestReader);
            case Packet.WIRE_FORMAT_INFO :
                WireFormatInfo info =  (WireFormatInfo)readPacket(dataIn, wireFormatInfoReader);
                if (info != null){
                    if (info.getVersion() < 3){
                        throw new IOException("Cannot support wire format version: " + info.getVersion());
                    }
                }
                return info;
            
            case Packet.KEEP_ALIVE :
            	return readPacket(dataIn, keepAliveReader);
            case Packet.BROKER_ADMIN_COMMAND :
            	return readPacket(dataIn, brokerAdminCommandReader);
            case Packet.CACHED_VALUE_COMMAND :
                CachedValue cv =  (CachedValue)readPacket(dataIn,cachedValueReader);
                handleCachedValue(cv);
                return null;
            case Packet.CLEANUP_CONNECTION_INFO :
                return readPacket(dataIn, cleanupConnectionAndSessionInfoReader);
            default :
                log.error("Could not find PacketReader for packet type: "
                        + AbstractPacket.getPacketTypeAsString(firstByte));
                return null;
        }
    }
    
    protected PacketWriter getWriter(Packet packet) throws IOException {
        PacketWriter answer = null;
        switch (packet.getPacketType()) {
            case Packet.ACTIVEMQ_MESSAGE :
                answer = messageWriter;
                break;
            case Packet.ACTIVEMQ_TEXT_MESSAGE :
                answer = textMessageWriter;
                break;
            case Packet.ACTIVEMQ_OBJECT_MESSAGE :
                answer = objectMessageWriter;
                break;
            case Packet.ACTIVEMQ_BYTES_MESSAGE :
                answer = bytesMessageWriter;
                break;
            case Packet.ACTIVEMQ_STREAM_MESSAGE :
                answer = streamMessageWriter;
                break;
            case Packet.ACTIVEMQ_MAP_MESSAGE :
                answer = mapMessageWriter;
                break;
            case Packet.ACTIVEMQ_MSG_ACK :
                answer = messageAckWriter;
                break;
            case Packet.RECEIPT_INFO :
                answer = receiptWriter;
                break;
            case Packet.CONSUMER_INFO :
                answer = consumerInfoWriter;
                break;
            case Packet.PRODUCER_INFO :
                answer = producerInfoWriter;
                break;
            case Packet.TRANSACTION_INFO :
                answer = transactionInfoWriter;
                break;
            case Packet.XA_TRANSACTION_INFO :
                answer = xaTransactionInfoWriter;
                break;
            case Packet.ACTIVEMQ_BROKER_INFO :
                answer = brokerInfoWriter;
                break;
            case Packet.ACTIVEMQ_CONNECTION_INFO :
                answer = connectionInfoWriter;
                break;
            case Packet.SESSION_INFO :
                answer = sessionInfoWriter;
                break;
            case Packet.DURABLE_UNSUBSCRIBE :
                answer = durableUnsubscribeWriter;
                break;
            case Packet.RESPONSE_RECEIPT_INFO :
                answer = reponseReceiptWriter;
                break;
            case Packet.INT_RESPONSE_RECEIPT_INFO :
                answer = intReponseReceiptWriter;
                break;
            case Packet.CAPACITY_INFO :
                answer = capacityInfoWriter;
                break;
            case Packet.CAPACITY_INFO_REQUEST :
                answer = capacityInfoRequestWriter;
                break;
            case Packet.WIRE_FORMAT_INFO :
                answer = wireFormatInfoWriter;
                break;
            case Packet.KEEP_ALIVE :
                answer = keepAliveWriter;
                break;
            case Packet.BROKER_ADMIN_COMMAND :
                answer = brokerAdminCommandWriter;
                break;
            case Packet.CACHED_VALUE_COMMAND:
                answer = cachedValueWriter;
                break;
            case Packet.CLEANUP_CONNECTION_INFO:
                answer = cleanupConnectionAndSessionInfoWriter;
                break;
            default :
                log.error("no PacketWriter for packet: " + packet);
        }
        return answer;
    }
    
    /**
     * Can this wireformat process packets of this version
     * @param version the version number to test
     * @return true if can accept the version
     */
    public boolean canProcessWireFormatVersion(int version){
        return version <= WIRE_FORMAT_VERSION;
    }
    
    /**
     * @return the current version of this wire format
     */
    public int getCurrentWireFormatVersion(){
        return currentWireFormatVersion;
    }
    
    /**
     * set the current version
     * @param version
     */
    public void setCurrentWireFormatVersion(int version){
        this.currentWireFormatVersion = version;
        for (int i =0; i < readers.size(); i++){
            PacketReader reader = (PacketReader)readers.get(i);
            reader.setWireFormatVersion(version);
        }
        for (int i =0; i < writers.size(); i++){
            PacketWriter writer = (PacketWriter)writers.get(i);
            writer.setWireFormatVersion(version);
        }
    }

    private void initializeReaders() {
        messageReader = new ActiveMQMessageReader(this);
        readers.add(messageReader);
        textMessageReader = new ActiveMQTextMessageReader(this);
        readers.add(textMessageReader);
        objectMessageReader = new ActiveMQObjectMessageReader(this);
        readers.add(objectMessageReader);
        bytesMessageReader = new ActiveMQBytesMessageReader(this);
        readers.add(bytesMessageReader);
        streamMessageReader = new ActiveMQStreamMessageReader(this);
        readers.add(streamMessageReader);
        mapMessageReader = new ActiveMQMapMessageReader(this);
        readers.add(mapMessageReader);
        messageAckReader = new MessageAckReader(this);
        readers.add(messageAckReader);
        receiptReader = new ReceiptReader();
        readers.add(receiptReader);
        consumerInfoReader = new ConsumerInfoReader();
        readers.add(consumerInfoReader);
        producerInfoReader = new ProducerInfoReader();
        readers.add(producerInfoReader);
        transactionInfoReader = new TransactionInfoReader();
        readers.add(transactionInfoReader);
        xaTransactionInfoReader = new XATransactionInfoReader();
        readers.add(xaTransactionInfoReader);
        brokerInfoReader = new BrokerInfoReader();
        readers.add(brokerInfoReader);
        connectionInfoReader = new ConnectionInfoReader();
        readers.add(connectionInfoReader);
        sessionInfoReader = new SessionInfoReader();
        readers.add(sessionInfoReader);
        durableUnsubscribeReader = new DurableUnsubscribeReader();
        readers.add(durableUnsubscribeReader);
        reponseReceiptReader = new ResponseReceiptReader();
        readers.add(reponseReceiptReader);
        intReponseReceiptReader = new IntResponseReceiptReader();
        readers.add(intReponseReceiptReader);
        capacityInfoReader = new CapacityInfoReader();
        readers.add(capacityInfoReader);
        capacityInfoRequestReader = new CapacityInfoRequestReader();
        readers.add(capacityInfoReader);
        wireFormatInfoReader = new WireFormatInfoReader(this);
        readers.add(wireFormatInfoReader);
        keepAliveReader = new KeepAliveReader();
        readers.add(keepAliveReader);
        brokerAdminCommandReader = new BrokerAdminCommandReader();
        readers.add(brokerAdminCommandReader);
        cachedValueReader = new CachedValueReader();
        readers.add(cachedValueReader);
        cleanupConnectionAndSessionInfoReader = new CleanupConnectionInfoReader();
        readers.add(cleanupConnectionAndSessionInfoReader);
    }
    
    private void initializeWriters(){
        messageWriter = new ActiveMQMessageWriter(this);
        writers.add(messageWriter);
        textMessageWriter = new ActiveMQTextMessageWriter(this);
        writers.add(textMessageWriter);
        objectMessageWriter = new ActiveMQObjectMessageWriter(this);
        writers.add(objectMessageWriter);
        bytesMessageWriter = new ActiveMQBytesMessageWriter(this);
        writers.add(bytesMessageWriter);
        streamMessageWriter = new ActiveMQStreamMessageWriter(this);
        writers.add(streamMessageWriter);
        mapMessageWriter = new ActiveMQMapMessageWriter(this);
        writers.add(mapMessageWriter);
        messageAckWriter = new MessageAckWriter(this);
        writers.add(messageAckWriter);
        receiptWriter = new ReceiptWriter();
        writers.add(receiptWriter);
        consumerInfoWriter = new ConsumerInfoWriter();
        writers.add(consumerInfoWriter);
        producerInfoWriter = new ProducerInfoWriter();
        writers.add(producerInfoWriter);
        transactionInfoWriter = new TransactionInfoWriter();
        writers.add(transactionInfoWriter);
        xaTransactionInfoWriter = new XATransactionInfoWriter();
        writers.add(xaTransactionInfoWriter);
        brokerInfoWriter = new BrokerInfoWriter();
        writers.add(brokerInfoWriter);
        connectionInfoWriter = new ConnectionInfoWriter();
        writers.add(connectionInfoWriter);
        sessionInfoWriter = new SessionInfoWriter();
        writers.add(sessionInfoWriter);
        durableUnsubscribeWriter = new DurableUnsubscribeWriter();
        writers.add(durableUnsubscribeWriter);
        reponseReceiptWriter = new ResponseReceiptWriter();
        writers.add(reponseReceiptWriter);
        intReponseReceiptWriter = new IntResponseReceiptWriter();
        writers.add(intReponseReceiptWriter);
        capacityInfoWriter = new CapacityInfoWriter();
        writers.add(capacityInfoWriter);
        capacityInfoRequestWriter = new CapacityInfoRequestWriter();
        writers.add(capacityInfoWriter);
        wireFormatInfoWriter = new WireFormatInfoWriter();
        writers.add(wireFormatInfoWriter);
        keepAliveWriter = new KeepAliveWriter();
        writers.add(keepAliveWriter);
        brokerAdminCommandWriter = new BrokerAdminCommandWriter();
        writers.add(brokerAdminCommandWriter);
        cachedValueWriter = new CachedValueWriter();
        writers.add(cachedValueWriter);
        cleanupConnectionAndSessionInfoWriter = new CleanupConnectionInfoWriter();
        writers.add(cleanupConnectionAndSessionInfoWriter);
    }
    
}