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 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,
        /** mototorola 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();
    }
}
