/** 
 * 
 * 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;
import org.activemq.message.*;
import java.io.ByteArrayInputStream;
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.net.DatagramPacket;
import javax.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Represents a strategy of encoding packets on the wire or on disk using some kind of serialization or wire format.
 * <p/>We use a default efficient format for Java to Java communication but other formats to other systems can be used,
 * such as using simple text strings when talking to JavaScript or coming up with other formats for talking to C / C#
 * languages or proprietary messaging systems we wish to interface with at the wire level etc.
 * 
 * @version $Revision: 1.1.1.1 $
 */
public abstract class AbstractWireFormat implements WireFormat {
    private static final Log log = LogFactory.getLog(AbstractWireFormat.class);
    protected DataOutputStream transportDataOut;
    protected DataInputStream transportDataIn;
    protected boolean cachingEnabled;

    /**
     * Read a packet from a Datagram packet from the given channelID. If the packet is from the same channel ID as it
     * was sent then we have a loop-back so discard the packet
     * 
     * @param channelID is the unique channel ID
     * @param dpacket
     * @return the packet read from the datagram or null if it should be discarded
     * @throws IOException
     */
    public Packet readPacket(String channelID, DatagramPacket dpacket) throws IOException {
        DataInput in = new DataInputStream(new ByteArrayInputStream(dpacket.getData(), dpacket.getOffset(), dpacket
                .getLength()));
        String id = in.readUTF();
        if (channelID == null) {
            log
                    .trace("We do not have a channelID which is probably caused by a synchronization issue, we're receiving messages before we're fully initialised");
        }
        else if (channelID.equals(id)) {
            if (log.isTraceEnabled()) {
                log.trace("Discarding packet from id: " + id);
            }
            return null;
        }
        int type = in.readByte();
        Packet packet = readPacket(type, in);
        //        if (packet instanceof ActiveMQMessage) {
        //            System.out.println("##### read packet from channel: " + id + " in channel: " + channelID + " message: " +
        // packet);
        //        }
        //
        return packet;
    }

    /**
     * Writes the given package to a new datagram
     * 
     * @param channelID is the unique channel ID
     * @param packet is the packet to write
     * @return @throws IOException
     * @throws JMSException
     */
    public DatagramPacket writePacket(String channelID, Packet packet) throws IOException, JMSException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        DataOutputStream dataOut = new DataOutputStream(out);
        channelID = channelID != null ? channelID : "";
        dataOut.writeUTF(channelID);
        writePacket(packet, dataOut);
        dataOut.close();
        byte[] data = out.toByteArray();
        return new DatagramPacket(data, data.length);
    }

    /**
     * Reads the packet from the given byte[]
     * 
     * @param bytes
     * @param offset
     * @param length
     * @return @throws IOException
     */
    public Packet fromBytes(byte[] bytes, int offset, int length) throws IOException {
        DataInput in = new DataInputStream(new ByteArrayInputStream(bytes, offset, length));
        return readPacket(in);
    }

    /**
     * Reads the packet from the given byte[]
     * 
     * @param bytes
     * @return @throws IOException
     */
    public Packet fromBytes(byte[] bytes) throws IOException {
        DataInput in = new DataInputStream(new ByteArrayInputStream(bytes));
        return readPacket(in);
    }

    /**
     * A helper method which converts a packet into a byte array
     * 
     * @param packet
     * @return a byte array representing the packet using some wire protocol
     * @throws IOException
     * @throws JMSException
     */
    public byte[] toBytes(Packet packet) throws IOException, JMSException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        DataOutputStream dataOut = new DataOutputStream(out);
        writePacket(packet, dataOut);
        dataOut.close();
        return out.toByteArray();
    }

    /**
     * some transports may register their streams (e.g. Tcp)
     * 
     * @param dataOut
     * @param dataIn
     */
    public void registerTransportStreams(DataOutputStream dataOut, DataInputStream dataIn) {
        transportDataOut = dataOut;
        transportDataIn = dataIn;
    }

    /**
     * Some wire formats require a handshake at start-up
     * 
     * @throws IOException
     */
    public void initiateClientSideProtocol() throws IOException {
    }

    /**
     * Some wire formats require a handshake at start-up
     * 
     * @throws IOException
     */
    public void initiateServerSideProtocol() throws IOException {
    }

    
    /**
     * @return Returns the enableCaching.
     */
    public boolean isCachingEnabled() {
        return cachingEnabled;
    }

    /**
     * @param enableCaching The enableCaching to set.
     */
    public void setCachingEnabled(boolean enableCaching) {
        this.cachingEnabled = enableCaching;
    }
    
    /**
     * some wire formats will implement their own fragementation
     * @return true unless a wire format supports it's own fragmentation
     */
    public boolean doesSupportMessageFragmentation(){
        return true;
    }
    
    
    /**
     * Some wire formats will not be able to understand compressed messages
     * @return true unless a wire format cannot understand compression
     */
    public boolean doesSupportMessageCompression(){
        return true;
    }
    /**
     * @return Returns the transportDataOut.
     */
    public DataOutputStream getTransportDataOut() {
        return transportDataOut;
    }
    /**
     * @param transportDataOut The transportDataOut to set.
     */
    public void setTransportDataOut(DataOutputStream transportDataOut) {
        this.transportDataOut = transportDataOut;
    }
    /**
     * @return Returns the transportDataIn.
     */
    public DataInputStream getTransportDataIn() {
        return transportDataIn;
    }
    /**
     * @param transportDataIn The transportDataIn to set.
     */
    public void setTransportDataIn(DataInputStream transportDataIn) {
        this.transportDataIn = transportDataIn;
    }
}