/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.activemq.artemis.jms.client;

import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.MessageEOFException;
import javax.jms.MessageFormatException;

import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.client.ClientMessage;
import org.apache.activemq.artemis.api.core.client.ClientSession;
import org.apache.activemq.artemis.core.message.impl.MessageImpl;

import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesMessageReset;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadBoolean;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadByte;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadBytes;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadChar;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadDouble;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadFloat;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadInt;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadLong;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadShort;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadUTF;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadUnsignedByte;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesReadUnsignedShort;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteBoolean;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteByte;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteBytes;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteChar;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteDouble;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteFloat;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteInt;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteLong;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteObject;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteShort;
import static org.apache.activemq.artemis.reader.BytesMessageUtil.bytesWriteUTF;

/**
 * ActiveMQ Artemis implementation of a JMS {@link BytesMessage}.
 */
public class ActiveMQBytesMessage extends ActiveMQMessage implements BytesMessage {

   // Static -------------------------------------------------------
   public static final byte TYPE = Message.BYTES_TYPE;

   // Attributes ----------------------------------------------------

   private int bodyLength;

   // Constructor ---------------------------------------------------

   /**
    * This constructor is used to construct messages prior to sending
    */
   protected ActiveMQBytesMessage(final ClientSession session) {
      super(ActiveMQBytesMessage.TYPE, session);
   }

   /**
    * Constructor on receipt at client side
    */
   protected ActiveMQBytesMessage(final ClientMessage message, final ClientSession session) {
      super(message, session);
   }

   /**
    * Foreign message constructor
    */
   public ActiveMQBytesMessage(final BytesMessage foreign, final ClientSession session) throws JMSException {
      super(foreign, ActiveMQBytesMessage.TYPE, session);

      foreign.reset();

      byte[] buffer = new byte[1024];
      int n = foreign.readBytes(buffer);
      while (n != -1) {
         writeBytes(buffer, 0, n);
         n = foreign.readBytes(buffer);
      }
   }

   // BytesMessage implementation -----------------------------------

   @Override
   public boolean readBoolean() throws JMSException {
      checkRead();
      try {
         return bytesReadBoolean(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public byte readByte() throws JMSException {
      checkRead();
      try {
         return bytesReadByte(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public int readUnsignedByte() throws JMSException {
      checkRead();
      try {
         return bytesReadUnsignedByte(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public short readShort() throws JMSException {
      checkRead();
      try {
         return bytesReadShort(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public int readUnsignedShort() throws JMSException {
      checkRead();
      try {
         return bytesReadUnsignedShort(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public char readChar() throws JMSException {
      checkRead();
      try {
         return bytesReadChar(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public int readInt() throws JMSException {
      checkRead();
      try {
         return bytesReadInt(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public long readLong() throws JMSException {
      checkRead();
      try {
         return bytesReadLong(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public float readFloat() throws JMSException {
      checkRead();
      try {
         return bytesReadFloat(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public double readDouble() throws JMSException {
      checkRead();
      try {
         return bytesReadDouble(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
   }

   @Override
   public String readUTF() throws JMSException {
      checkRead();
      try {
         return bytesReadUTF(message.getBodyBuffer());
      }
      catch (IndexOutOfBoundsException e) {
         throw new MessageEOFException("");
      }
      catch (Exception e) {
         JMSException je = new JMSException("Failed to get UTF");
         je.setLinkedException(e);
         je.initCause(e);
         throw je;
      }
   }

   @Override
   public int readBytes(final byte[] value) throws JMSException {
      checkRead();
      return bytesReadBytes(message.getBodyBuffer(), value);
   }

   @Override
   public int readBytes(final byte[] value, final int length) throws JMSException {
      checkRead();
      return bytesReadBytes(message.getBodyBuffer(), value, length);

   }

   @Override
   public void writeBoolean(final boolean value) throws JMSException {
      checkWrite();
      bytesWriteBoolean(message.getBodyBuffer(), value);
   }

   @Override
   public void writeByte(final byte value) throws JMSException {
      checkWrite();
      bytesWriteByte(message.getBodyBuffer(), value);
   }

   @Override
   public void writeShort(final short value) throws JMSException {
      checkWrite();
      bytesWriteShort(message.getBodyBuffer(), value);
   }

   @Override
   public void writeChar(final char value) throws JMSException {
      checkWrite();
      bytesWriteChar(message.getBodyBuffer(), value);
   }

   @Override
   public void writeInt(final int value) throws JMSException {
      checkWrite();
      bytesWriteInt(message.getBodyBuffer(), value);
   }

   @Override
   public void writeLong(final long value) throws JMSException {
      checkWrite();
      bytesWriteLong(message.getBodyBuffer(), value);
   }

   @Override
   public void writeFloat(final float value) throws JMSException {
      checkWrite();
      bytesWriteFloat(message.getBodyBuffer(), value);
   }

   @Override
   public void writeDouble(final double value) throws JMSException {
      checkWrite();
      bytesWriteDouble(message.getBodyBuffer(), value);
   }

   @Override
   public void writeUTF(final String value) throws JMSException {
      checkWrite();
      try {
         bytesWriteUTF(message.getBodyBuffer(), value);
      }
      catch (Exception e) {
         JMSException je = new JMSException("Failed to write UTF");
         je.setLinkedException(e);
         je.initCause(e);
         throw je;
      }

   }

   @Override
   public void writeBytes(final byte[] value) throws JMSException {
      checkWrite();
      bytesWriteBytes(message.getBodyBuffer(), value);
   }

   @Override
   public void writeBytes(final byte[] value, final int offset, final int length) throws JMSException {
      checkWrite();
      bytesWriteBytes(message.getBodyBuffer(), value, offset, length);
   }

   @Override
   public void writeObject(final Object value) throws JMSException {
      checkWrite();
      if (!bytesWriteObject(message.getBodyBuffer(), value)) {
         throw new MessageFormatException("Invalid object for properties");
      }
   }

   @Override
   public void reset() throws JMSException {
      if (!readOnly) {
         readOnly = true;

         bodyLength = message.getBodySize();
      }

      bytesMessageReset(message.getBodyBuffer());
   }

   @Override
   public void doBeforeReceive() throws ActiveMQException {
      bodyLength = message.getBodySize();
   }

   // ActiveMQRAMessage overrides ----------------------------------------

   @Override
   public void clearBody() throws JMSException {
      super.clearBody();

      try {
         getBuffer().clear();
      }
      catch (RuntimeException e) {
         JMSException e2 = new JMSException(e.getMessage());
         e2.initCause(e);
         throw e2;
      }
   }

   @Override
   public long getBodyLength() throws JMSException {
      checkRead();

      return bodyLength;
   }

   @Override
   public void doBeforeSend() throws Exception {
      reset();
   }

   // Public --------------------------------------------------------

   @Override
   public byte getType() {
      return ActiveMQBytesMessage.TYPE;
   }

   private ActiveMQBuffer getBuffer() {
      return message.getBodyBuffer();
   }

   @Override
   @SuppressWarnings("unchecked")
   public boolean isBodyAssignableTo(Class c) {
      return c.isAssignableFrom(byte[].class);
   }

   @Override
   protected <T> T getBodyInternal(Class<T> c) {
      if (bodyLength == 0)
         return null;
      byte[] dst = new byte[bodyLength];
      message.getBodyBuffer().getBytes(MessageImpl.BODY_OFFSET, dst);
      return (T) dst;
   }
}
