package com.bumptech.glide.load.data;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Adds an exif segment with an orientation attribute to a wrapped {@link InputStream} containing
 * image data.
 *
 * <p>This class assumes that the wrapped stream contains an image format that can contain
 * exif information and performs no verification. </p>
 */
public final class ExifOrientationStream extends FilterInputStream {
  /** Allow two bytes for the file format. */
  private static final int SEGMENT_START_POSITION = 2;
  private static final byte[] EXIF_SEGMENT = new byte[] {
      /** segment start id. */
      (byte) 0xFF,
      /** segment type. */
      (byte) 0xE1,
      /** segmentLength. */
      0x00,
      (byte) 0x1C,
      /** exif identifier. */
      0x45,
      0x78,
      0x69,
      0x66,
      0x00,
      0x00,
      /** motorola byte order (big endian). */
      (byte) 0x4D,
      (byte) 0x4D,
      /** filler? */
      0x00,
      0x00,
      /** first id offset. */
      0x00,
      0x00,
      0x00,
      0x08,
      /** tagCount. */
      0x00,
      0x01,
      /** exif tag type. */
      0x01,
      0x12,
      /** 2 byte format. */
      0x00,
      0x02,
      /** component count. */
      0x00,
      0x00,
      0x00,
      0x01,
      /** 2 byte orientation value, the first byte of which is always 0. */
      0x00,
  };
  private static final int SEGMENT_LENGTH = EXIF_SEGMENT.length;
  private static final int ORIENTATION_POSITION = SEGMENT_LENGTH + SEGMENT_START_POSITION;
  private final byte orientation;
  private int position;

  public ExifOrientationStream(InputStream in, int orientation) {
    super(in);
    if (orientation < -1 || orientation > 8) {
      throw new IllegalArgumentException("Cannot add invalid orientation: " + orientation);
    }
    this.orientation = (byte) orientation;
  }

  @Override
  public boolean markSupported() {
    return false;
  }

  @Override
  public void mark(int readlimit) {
    throw new UnsupportedOperationException();
  }

  @Override
  public int read() throws IOException {
    final int result;
    if (position < SEGMENT_START_POSITION || position > ORIENTATION_POSITION) {
      result = super.read();
    } else if (position == ORIENTATION_POSITION) {
      result = orientation;
    } else {
      result = EXIF_SEGMENT[position - SEGMENT_START_POSITION] & 0xFF;
    }
    if (result != -1) {
      position++;
    }
    return result;
  }

  @Override
  public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
    int read;
    if (position > ORIENTATION_POSITION) {
      read = super.read(buffer, byteOffset, byteCount);
    } else if (position == ORIENTATION_POSITION) {
      buffer[byteOffset] = orientation;
      read = 1;
    } else if (position < SEGMENT_START_POSITION) {
      read = super.read(buffer, byteOffset, SEGMENT_START_POSITION - position);
    } else {
      read = Math.min(ORIENTATION_POSITION - position, byteCount);
      System.arraycopy(EXIF_SEGMENT, position - SEGMENT_START_POSITION, buffer, byteOffset, read);
    }
    if (read > 0) {
      position += read;
    }
    return read;
  }

  @Override
  public long skip(long byteCount) throws IOException {
    long skipped = super.skip(byteCount);
    if (skipped > 0) {
      position += skipped;
    }
    return skipped;
  }

  @Override
  public void reset() throws IOException {
    throw new UnsupportedOperationException();
  }
}
