package com.googlecode.mp4parser.boxes.threegpp26244;

import com.coremedia.iso.IsoTypeReader;
import com.coremedia.iso.IsoTypeWriter;
import com.googlecode.mp4parser.AbstractFullBox;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.BitReaderBuffer;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.BitWriterBuffer;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * <h1>4cc = "{@value #TYPE}"</h1>
 * <pre>
 * aligned(8) class SegmentIndexBox extends FullBox(‘sidx’, version, 0) {
 *  unsigned int(32) reference_ID;
 *  unsigned int(32) timescale;
 *  if (version==0)
 *  {
 *   unsigned int(32) earliest_presentation_time;
 *   unsigned int(32) first_offset;
 *  }
 *  else
 *  {
 *   unsigned int(64) earliest_presentation_time;
 *   unsigned int(64) first_offset;
 *  }
 *  unsigned int(16) reserved = 0;
 *  unsigned int(16) reference_count;
 *  for(i=1; i &lt;= reference_count; i++)
 *  {
 *   bit (1)            reference_type;
 *   unsigned int(31)   referenced_size;
 *   unsigned int(32)   subsegment_duration;
 *   bit(1)             starts_with_SAP;
 *   unsigned int(3)    SAP_type;
 *   unsigned int(28)   SAP_delta_time;
 *  }
 * }
 * </pre>
 */
public class SegmentIndexBox extends AbstractFullBox {
    public static final String TYPE = "sidx";
    List<Entry> entries = new ArrayList<Entry>();

    long referenceId;
    long timeScale;
    long earliestPresentationTime;
    long firstOffset;
    int reserved;


    public SegmentIndexBox() {
        super(TYPE);
    }

    @Override
    protected long getContentSize() {
        long size = 4;
        size += 4;
        size += 4;
        size += getVersion() == 0 ? 8 : 16;
        size += 2; // reserved
        size += 2; // reference count

        size += entries.size() * 12;

        return size;
    }

    @Override
    protected void getContent(ByteBuffer byteBuffer) {
        writeVersionAndFlags(byteBuffer);
        IsoTypeWriter.writeUInt32(byteBuffer, referenceId);
        IsoTypeWriter.writeUInt32(byteBuffer, timeScale);
        if (getVersion() == 0) {
            IsoTypeWriter.writeUInt32(byteBuffer, earliestPresentationTime);
            IsoTypeWriter.writeUInt32(byteBuffer, firstOffset);
        } else {
            IsoTypeWriter.writeUInt64(byteBuffer, earliestPresentationTime);
            IsoTypeWriter.writeUInt64(byteBuffer, firstOffset);
        }
        IsoTypeWriter.writeUInt16(byteBuffer, reserved);
        IsoTypeWriter.writeUInt16(byteBuffer, entries.size());
        for (Entry entry : entries) {
            BitWriterBuffer b = new BitWriterBuffer(byteBuffer);
            b.writeBits(entry.getReferenceType(), 1);
            b.writeBits(entry.getReferencedSize(), 31);
            IsoTypeWriter.writeUInt32(byteBuffer, entry.getSubsegmentDuration());
            b = new BitWriterBuffer(byteBuffer);
            b.writeBits(entry.getStartsWithSap(), 1);
            b.writeBits(entry.getSapType(), 3);
            b.writeBits(entry.getSapDeltaTime(), 28);
        }

    }

    @Override
    protected void _parseDetails(ByteBuffer content) {
        parseVersionAndFlags(content);
        referenceId = IsoTypeReader.readUInt32(content);
        timeScale = IsoTypeReader.readUInt32(content);
        if (getVersion() == 0) {
            earliestPresentationTime = IsoTypeReader.readUInt32(content);
            firstOffset = IsoTypeReader.readUInt32(content);
        } else {
            earliestPresentationTime = IsoTypeReader.readUInt64(content);
            firstOffset = IsoTypeReader.readUInt64(content);
        }
        reserved = IsoTypeReader.readUInt16(content);
        int numEntries = IsoTypeReader.readUInt16(content);
        for (int i = 0; i < numEntries; i++) {
            BitReaderBuffer b = new BitReaderBuffer(content);
            Entry e = new Entry();
            e.setReferenceType((byte) b.readBits(1));
            e.setReferencedSize(b.readBits(31));
            e.setSubsegmentDuration(IsoTypeReader.readUInt32(content));
            b = new BitReaderBuffer(content);
            e.setStartsWithSap((byte) b.readBits(1));
            e.setSapType((byte) b.readBits(3));
            e.setSapDeltaTime(b.readBits(28));
            entries.add(e);
        }
    }


    public List<Entry> getEntries() {
        return entries;
    }

    public void setEntries(List<Entry> entries) {
        this.entries = entries;
    }

    public long getReferenceId() {
        return referenceId;
    }

    public void setReferenceId(long referenceId) {
        this.referenceId = referenceId;
    }

    public long getTimeScale() {
        return timeScale;
    }

    public void setTimeScale(long timeScale) {
        this.timeScale = timeScale;
    }

    public long getEarliestPresentationTime() {
        return earliestPresentationTime;
    }

    public void setEarliestPresentationTime(long earliestPresentationTime) {
        this.earliestPresentationTime = earliestPresentationTime;
    }

    public long getFirstOffset() {
        return firstOffset;
    }

    public void setFirstOffset(long firstOffset) {
        this.firstOffset = firstOffset;
    }

    public int getReserved() {
        return reserved;
    }

    public void setReserved(int reserved) {
        this.reserved = reserved;
    }

    public static class Entry {
        byte referenceType;
        int referencedSize;
        long subsegmentDuration;
        byte startsWithSap;
        byte sapType;
        int sapDeltaTime;

        public Entry() {
        }

        public Entry(int referenceType, int referencedSize, long subsegmentDuration, boolean startsWithSap, int sapType, int sapDeltaTime) {
            this.referenceType = (byte) referenceType;
            this.referencedSize = referencedSize;
            this.subsegmentDuration = subsegmentDuration;
            this.startsWithSap = (byte) (startsWithSap?1:0);
            this.sapType = (byte) sapType;
            this.sapDeltaTime = sapDeltaTime;
        }

        /**
         * When set to 1 indicates that the reference is to a segment index ('sidx') box;
         * otherwise the reference is to media content (e.g., in the case of files based on this specification, to a
         * movie fragment box); if a separate index segment is used, then entries with reference t
         * @return the reference type
         */
        public byte getReferenceType() {
            return referenceType;
        }

        /**
         * When set to 1 indicates that the reference is to a segment index ('sidx') box;
         * otherwise the reference is to media content (e.g., in the case of files based on this specification, to a
         * movie fragment box); if a separate index segment is used, then entries with reference t
         * @param referenceType the new reference type
         */
        public void setReferenceType(byte referenceType) {
            this.referenceType = referenceType;
        }

        public int getReferencedSize() {
            return referencedSize;
        }

        public void setReferencedSize(int referencedSize) {
            this.referencedSize = referencedSize;
        }

        public long getSubsegmentDuration() {
            return subsegmentDuration;
        }

        public void setSubsegmentDuration(long subsegmentDuration) {
            this.subsegmentDuration = subsegmentDuration;
        }

        public byte getStartsWithSap() {
            return startsWithSap;
        }

        public void setStartsWithSap(byte startsWithSap) {
            this.startsWithSap = startsWithSap;
        }

        public byte getSapType() {
            return sapType;
        }

        public void setSapType(byte sapType) {
            this.sapType = sapType;
        }

        public int getSapDeltaTime() {
            return sapDeltaTime;
        }

        public void setSapDeltaTime(int sapDeltaTime) {
            this.sapDeltaTime = sapDeltaTime;
        }

        @Override
        public String toString() {
            return "Entry{" +
                    "referenceType=" + referenceType +
                    ", referencedSize=" + referencedSize +
                    ", subsegmentDuration=" + subsegmentDuration +
                    ", startsWithSap=" + startsWithSap +
                    ", sapType=" + sapType +
                    ", sapDeltaTime=" + sapDeltaTime +
                    '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Entry entry = (Entry) o;

            if (referenceType != entry.referenceType) return false;
            if (referencedSize != entry.referencedSize) return false;
            if (sapDeltaTime != entry.sapDeltaTime) return false;
            if (sapType != entry.sapType) return false;
            if (startsWithSap != entry.startsWithSap) return false;
            if (subsegmentDuration != entry.subsegmentDuration) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = (int) referenceType;
            result = 31 * result + referencedSize;
            result = 31 * result + (int) (subsegmentDuration ^ (subsegmentDuration >>> 32));
            result = 31 * result + (int) startsWithSap;
            result = 31 * result + (int) sapType;
            result = 31 * result + sapDeltaTime;
            return result;
        }
    }

    @Override
    public String toString() {
        return "SegmentIndexBox{" +
                "entries=" + entries +
                ", referenceId=" + referenceId +
                ", timeScale=" + timeScale +
                ", earliestPresentationTime=" + earliestPresentationTime +
                ", firstOffset=" + firstOffset +
                ", reserved=" + reserved +
                '}';
    }
}
