/*
 * 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.geode.pdx.internal;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Date;

import org.apache.geode.DataSerializer;
import org.apache.geode.internal.offheap.StoredObject;
import org.apache.geode.internal.tcp.ByteBufferInputStream;
import org.apache.geode.internal.tcp.ImmutableByteBufferInputStream;
import org.apache.geode.pdx.PdxSerializationException;

/**
 * Used by PdxReaderImpl to manage the raw bytes of a PDX.
 *
 * @since GemFire 6.6
 */
public class PdxInputStream extends ImmutableByteBufferInputStream {

  /**
   * Create a pdx input stream by whose contents are the first length bytes from the given input
   * stream.
   *
   * @param existing the input stream whose content will go into this stream. Note that this
   *        existing stream will be read by this class (a copy is not made) so it should not be
   *        changed externally.
   * @param length the number of bytes to put in this stream
   */
  public PdxInputStream(ByteBufferInputStream existing, int length) {
    super(existing, length);
  }

  /**
   * Create a pdx input stream whose contents are the given bytes
   *
   * @param bytes the content of this stream. Note that this byte array will be read by this class
   *        (a copy is not made) so it should not be changed externally.
   */
  public PdxInputStream(byte[] bytes) {
    super(bytes);
  }

  /**
   * Create a pdx input stream whose contents are the given bytes
   *
   * @param bb the content of this stream. Note that bb will be read by this class (a copy is not
   *        made) so it should not be changed externally.
   */
  public PdxInputStream(ByteBuffer bb) {
    super(bb);
  }

  /**
   * Create a pdx input stream by copying another. A somewhat shallow copy is made.
   *
   * @param copy the input stream to copy. Note that this copy stream will be read by this class (a
   *        copy is not made) so it should not be changed externally.
   */
  public PdxInputStream(PdxInputStream copy) {
    super(copy);
  }

  public PdxInputStream() {
    // for serialization
  }

  public PdxInputStream(StoredObject blob) {
    super(blob);
  }

  public String readString(int positionForField) {
    position(positionForField);
    return readString();
  }

  public Object readObject(int positionForField) {
    position(positionForField);
    return readObject();
  }

  public char[] readCharArray(int positionForField) {
    position(positionForField);
    return readCharArray();
  }

  public boolean[] readBooleanArray(int positionForField) {
    position(positionForField);
    return readBooleanArray();
  }

  public byte[] readByteArray(int positionForField) {
    position(positionForField);
    return readByteArray();
  }

  public short[] readShortArray(int positionForField) {
    position(positionForField);
    return readShortArray();
  }

  public int[] readIntArray(int positionForField) {
    position(positionForField);
    return readIntArray();
  }

  public long[] readLongArray(int positionForField) {
    position(positionForField);
    return readLongArray();
  }

  public float[] readFloatArray(int positionForField) {
    position(positionForField);
    return readFloatArray();
  }

  public double[] readDoubleArray(int positionForField) {
    position(positionForField);
    return readDoubleArray();
  }

  public String[] readStringArray(int positionForField) {
    position(positionForField);
    return readStringArray();
  }

  public Object[] readObjectArray(int positionForField) {
    position(positionForField);
    return readObjectArray();
  }

  public byte[][] readArrayOfByteArrays(int positionForField) {
    position(positionForField);
    return readArrayOfByteArrays();
  }

  public Date readDate(int offset) {
    long time = readLong(offset);
    return convertLongToDate(time);
  }

  @Override
  public boolean readBoolean(int pos) {
    try {
      return super.readBoolean(pos);
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX boolean field", e);
    }
  }

  @Override
  public byte readByte(int pos) {
    try {
      return super.readByte(pos);
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX byte field", e);
    }
  }

  @Override
  public char readChar(int pos) {
    try {
      return super.readChar(pos);
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX char field", e);
    }

  }

  @Override
  public double readDouble(int pos) {
    try {
      return super.readDouble(pos);
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX double field", e);
    }
  }

  @Override
  public float readFloat(int pos) {
    try {
      return super.readFloat(pos);
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX float field", e);
    }
  }

  @Override
  public int readInt(int pos) {
    try {
      return super.readInt(pos);
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX int field", e);
    }
  }

  @Override
  public long readLong(int pos) {
    try {
      return super.readLong(pos);
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX long field", e);
    }
  }

  @Override
  public short readShort(int pos) {
    try {
      return super.readShort(pos);
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX short field", e);
    }
  }

  @Override
  public void position(int absPos) {
    try {
      super.position(absPos);
    } catch (IllegalArgumentException e) {
      throw new PdxSerializationException("Internal error; failed to set position to " + absPos, e);
    }
  }

  public String readString() {
    try {
      return DataSerializer.readString(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public Object readObject() {
    try {
      return DataSerializer.readObject(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    } catch (ClassNotFoundException e) {
      throw new PdxSerializationException("Class not found deserializing a PDX field", e);
    }
  }

  public char[] readCharArray() {
    try {
      return DataSerializer.readCharArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public boolean[] readBooleanArray() {
    try {
      return DataSerializer.readBooleanArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public byte[] readByteArray() {
    try {
      return DataSerializer.readByteArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public short[] readShortArray() {
    try {
      return DataSerializer.readShortArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public int[] readIntArray() {
    try {
      return DataSerializer.readIntArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public long[] readLongArray() {
    try {
      return DataSerializer.readLongArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public float[] readFloatArray() {
    try {
      return DataSerializer.readFloatArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public double[] readDoubleArray() {
    try {
      return DataSerializer.readDoubleArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public String[] readStringArray() {
    try {
      return DataSerializer.readStringArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public Object[] readObjectArray() {
    try {
      return DataSerializer.readObjectArray(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    } catch (ClassNotFoundException e) {
      throw new PdxSerializationException("Class not found while deserializing a PDX field", e);
    }
  }

  public byte[][] readArrayOfByteArrays() {
    try {
      return DataSerializer.readArrayOfByteArrays(this);
    } catch (IOException e) {
      throw new PdxSerializationException("Exception deserializing a PDX field", e);
    }
  }

  public Date readDate() {
    long time = readLong();
    return convertLongToDate(time);
  }

  @Override
  public boolean readBoolean() {
    try {
      return super.readBoolean();
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX boolean field", e);
    }
  }

  @Override
  public byte readByte() {
    try {
      return super.readByte();
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX byte field", e);
    }
  }

  @Override
  public char readChar() {
    try {
      return super.readChar();
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX char field", e);
    }
  }

  @Override
  public double readDouble() {
    try {
      return super.readDouble();
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX double field", e);
    }
  }

  @Override
  public float readFloat() {
    try {
      return super.readFloat();
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX float field", e);
    }
  }

  @Override
  public int readInt() {
    try {
      return super.readInt();
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX int field", e);
    }
  }

  @Override
  public long readLong() {
    try {
      return super.readLong();
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX long field", e);
    }
  }

  @Override
  public short readShort() {
    try {
      return super.readShort();
    } catch (IndexOutOfBoundsException e) {
      throw new PdxSerializationException("Failed reading a PDX short field", e);
    }
  }

  @Override
  public ByteSource slice(int startOffset, int endOffset) {
    try {
      return super.slice(startOffset, endOffset);
    } catch (IllegalArgumentException e) {
      throw new PdxSerializationException(
          "Internal error; failed to slice start=" + startOffset + " end=" + endOffset, e);
    }
  }

  private Date convertLongToDate(long v) {
    Date result = null;
    if (v != -1L) {
      result = new Date(v);
    }
    return result;
  }
}
