package fi.helsinki.cs.ohtu.mpeg2.video;

import java.io.IOException;

import fi.helsinki.cs.ohtu.mpeg2.util.BitOutputStream;
import fi.helsinki.cs.ohtu.mpeg2.util.StartCode;
import fi.helsinki.cs.ohtu.mpeg2.util.UnsignedIntegerField;

/**
 * Represents the baseline picture header.
 *
 * In MPEG-2 usage, this will be always followed by a PictureCodingExtensionHeader. Most of the actual header's fields
 * don't make sense in MPEG-2 either, so they are not presented in the API.
 *
 * @author Olli Salli
 */
public class PictureHeader {
    /**
     * Represents the allowed values for the coding type field.
     *
     * @author Olli Salli
     */
    public enum CodingType {
        /**
         * Intra-coded (I) picture
         */
        INTRA (1),

        /**
         * Predictive-coded (P) picture
         */
        PREDICTIVE (2),

        /**
         * Bidirectionally-predictive-coded (B) picture
         */
        BIDIRECTIONALLY_PREDICTIVE (3);

        /**
         * Writes the value to a bit stream.
         *
         * @param stream The stream to write to.
         * @throws IOException Propagated from the stream write.
         */
        public void writeTo(BitOutputStream stream) throws IOException {
            field.writeTo(stream);
        }

        private CodingType(int value) {
            this.field = new UnsignedIntegerField(3, value);
        }

        private UnsignedIntegerField field;
    }

    /**
     * Constructs a new instance.
     *
     * @param temporalReference Initial value for the temporal reference. Must fit into 10 bits.
     * @param codingType Initial value for the coding type.
     * @param vbvDelay Initial value for the video buffering verifier delay. Must fit into 16 bits.
     */
    public PictureHeader(int temporalReference,
                         CodingType codingType,
                         int vbvDelay) {
        this.temporalReference = new UnsignedIntegerField(10, temporalReference);
        this.codingType = codingType;
        this.vbvDelay = new UnsignedIntegerField(16, vbvDelay);
    }

    /**
     * Gets the temporal reference.
     *
     * @return The reference. Only the lowest 10 bits will be meaningful.
     */
    public long getTemporalReference() {
        return this.temporalReference.getValue();
    }

    /**
     * Sets the temporal reference.
     *
     * @param temporalReference The new reference. Must fit into 10 bits.
     */
    public void setTemporalReference(int temporalReference) {
        this.temporalReference.setValue(temporalReference);
    }

    /**
     * Gets the picture coding type.
     *
     * @return The type.
     */
    public CodingType getCodingType() {
        return this.codingType;
    }

    /**
     * Sets the picture coding type.
     *
     * @param codingType The new type.
     */
    public void setCodingType(CodingType codingType) {
        this.codingType = codingType;
    }

    /**
     * Gets the video buffering verifier delay.
     *
     * @return The delay. Only the lowest 16 bits will be meaningful.
     */
    public long getVBVDelay() {
        return this.vbvDelay.getValue();
    }

    /**
     * Sets the video buffering verifier delay.
     *
     * @param vbvDelay The new delay. Must fit into 16 bits.
     */
    public void setVBVDelay(int vbvDelay) {
        this.vbvDelay.setValue(vbvDelay);
    }
    /**
     * Writes the header to a bit stream.
     *
     * @param stream The stream to write to.
     * @throws IOException Propagated from the stream write.
     */
    public void writeTo(BitOutputStream stream) throws IOException {
        StartCode.PICTURE.writeTo(stream);
        temporalReference.writeTo(stream);
        codingType.writeTo(stream);
        vbvDelay.writeTo(stream);

        // MPEG-1 legacy forwards (for P and B pictures) and backwards (just for B pictures) full pel vector / f code
        if (codingType != CodingType.INTRA) {
            stream.writeBit(0);
            stream.writeBits(new int[] {1, 1, 1});

            if (codingType == CodingType.BIDIRECTIONALLY_PREDICTIVE) {
                stream.writeBit(0);
                stream.writeBits(new int[] {1, 1, 1});
            }
        }

        // Extra-bit picture flag, use with value 1 left reserved in MPEG-2
        stream.writeBit(0);
    }

    UnsignedIntegerField temporalReference;
    CodingType codingType;
    UnsignedIntegerField vbvDelay;
}
