/*
 * Copyright 2009 castLabs GmbH, Berlin
 *
 * Licensed 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 com.coremedia.iso.boxes.fragment;

import com.coremedia.iso.IsoTypeReader;
import com.coremedia.iso.IsoTypeWriter;
import com.googlecode.mp4parser.AbstractFullBox;

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

import static com.googlecode.mp4parser.util.CastUtils.l2i;

/**
 * <h1>4cc = "{@value #TYPE}"</h1>
 * aligned(8) class TrackRunBox
 * extends FullBox('trun', version, tr_flags) {
 * unsigned int(32)	sample_count;
 * // the following are optional fields
 * signed int(32)	data_offset;
 * unsigned int(32)	first_sample_flags;
 * // all fields in the following array are optional
 * {
 * unsigned int(32)	sample_duration;
 * unsigned int(32)	sample_size;
 * unsigned int(32)	sample_flags
 * if (version == 0)
 * { unsigned int(32)	sample_composition_time_offset; }
 * else
 * { signed int(32)		sample_composition_time_offset; }
 * }[ sample_count ]
 * }
 */

public class TrackRunBox extends AbstractFullBox {
    public static final String TYPE = "trun";
    private int dataOffset;
    private SampleFlags firstSampleFlags;
    private List<Entry> entries = new ArrayList<Entry>();


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

    public static class Entry {
        private long sampleDuration;
        private long sampleSize;
        private SampleFlags sampleFlags;
        private long sampleCompositionTimeOffset;

        public Entry() {
        }

        public Entry(long sampleDuration, long sampleSize, SampleFlags sampleFlags, int sampleCompositionTimeOffset) {
            this.sampleDuration = sampleDuration;
            this.sampleSize = sampleSize;
            this.sampleFlags = sampleFlags;
            this.sampleCompositionTimeOffset = sampleCompositionTimeOffset;
        }

        public long getSampleDuration() {
            return sampleDuration;
        }

        public long getSampleSize() {
            return sampleSize;
        }

        public SampleFlags getSampleFlags() {
            return sampleFlags;
        }

        public long getSampleCompositionTimeOffset() {
            return sampleCompositionTimeOffset;
        }

        public void setSampleDuration(long sampleDuration) {
            this.sampleDuration = sampleDuration;
        }

        public void setSampleSize(long sampleSize) {
            this.sampleSize = sampleSize;
        }

        public void setSampleFlags(SampleFlags sampleFlags) {
            this.sampleFlags = sampleFlags;
        }

        public void setSampleCompositionTimeOffset(int sampleCompositionTimeOffset) {
            this.sampleCompositionTimeOffset = sampleCompositionTimeOffset;
        }

        @Override
        public String toString() {
            return "Entry{" +
                    "sampleDuration=" + sampleDuration +
                    ", sampleSize=" + sampleSize +
                    ", sampleFlags=" + sampleFlags +
                    ", sampleCompositionTimeOffset=" + sampleCompositionTimeOffset +
                    '}';
        }
    }

    public void setDataOffset(int dataOffset) {
        if (dataOffset == -1) {
            setFlags(getFlags() & (0xFFFFFF ^ 1));
        } else {
            setFlags(getFlags() | 0x1); // turn on dataoffset
        }
        this.dataOffset = dataOffset;
    }

    public long[] getSampleCompositionTimeOffsets() {
        if (isSampleCompositionTimeOffsetPresent()) {
            long[] result = new long[entries.size()];

            for (int i = 0; i < result.length; i++) {
                result[i] = entries.get(i).getSampleCompositionTimeOffset();
            }
            return result;
        }
        return null;
    }

    public TrackRunBox() {
        super(TYPE);
    }

    protected long getContentSize() {
        long size = 8;
        int flags = getFlags();

        if ((flags & 0x1) == 0x1) { //dataOffsetPresent
            size += 4;
        }
        if ((flags & 0x4) == 0x4) { //firstSampleFlagsPresent
            size += 4;
        }

        long entrySize = 0;
        if ((flags & 0x100) == 0x100) { //sampleDurationPresent
            entrySize += 4;
        }
        if ((flags & 0x200) == 0x200) { //sampleSizePresent
            entrySize += 4;
        }
        if ((flags & 0x400) == 0x400) { //sampleFlagsPresent
            entrySize += 4;
        }
        if ((flags & 0x800) == 0x800) { //sampleCompositionTimeOffsetPresent
            entrySize += 4;
        }
        size += entrySize * entries.size();
        return size;
    }

    protected void getContent(ByteBuffer byteBuffer) {
        writeVersionAndFlags(byteBuffer);
        IsoTypeWriter.writeUInt32(byteBuffer, entries.size());
        int flags = getFlags();

        if ((flags & 0x1) == 1) { //dataOffsetPresent
            IsoTypeWriter.writeUInt32(byteBuffer, dataOffset);
        }
        if ((flags & 0x4) == 0x4) { //firstSampleFlagsPresent
            firstSampleFlags.getContent(byteBuffer);
        }

        for (Entry entry : entries) {
            if ((flags & 0x100) == 0x100) { //sampleDurationPresent
                IsoTypeWriter.writeUInt32(byteBuffer, entry.sampleDuration);
            }
            if ((flags & 0x200) == 0x200) { //sampleSizePresent
                IsoTypeWriter.writeUInt32(byteBuffer, entry.sampleSize);
            }
            if ((flags & 0x400) == 0x400) { //sampleFlagsPresent
                entry.sampleFlags.getContent(byteBuffer);
            }
            if ((flags & 0x800) == 0x800) { //sampleCompositionTimeOffsetPresent
                if (getVersion() == 0) {
                    IsoTypeWriter.writeUInt32(byteBuffer, entry.sampleCompositionTimeOffset);
                } else {
                    byteBuffer.putInt((int) entry.sampleCompositionTimeOffset);
                }
            }
        }
    }

    @Override
    public void _parseDetails(ByteBuffer content) {
        parseVersionAndFlags(content);
        long sampleCount = IsoTypeReader.readUInt32(content);

        if ((getFlags() & 0x1) == 1) { //dataOffsetPresent
            dataOffset = l2i(IsoTypeReader.readUInt32(content));
        } else {
            dataOffset = -1;
        }
        if ((getFlags() & 0x4) == 0x4) { //firstSampleFlagsPresent
            firstSampleFlags = new SampleFlags(content);
        }

        for (int i = 0; i < sampleCount; i++) {
            Entry entry = new Entry();
            if ((getFlags() & 0x100) == 0x100) { //sampleDurationPresent
                entry.sampleDuration = IsoTypeReader.readUInt32(content);
            }
            if ((getFlags() & 0x200) == 0x200) { //sampleSizePresent
                entry.sampleSize = IsoTypeReader.readUInt32(content);
            }
            if ((getFlags() & 0x400) == 0x400) { //sampleFlagsPresent
                entry.sampleFlags = new SampleFlags(content);
            }
            if ((getFlags() & 0x800) == 0x800) { //sampleCompositionTimeOffsetPresent
                if (getVersion() == 0) {
                    entry.sampleCompositionTimeOffset = IsoTypeReader.readUInt32(content);
                } else {
                    entry.sampleCompositionTimeOffset = content.getInt();
                }
            }
            entries.add(entry);
        }

    }

    public long getSampleCount() {
        return entries.size();
    }

    public boolean isDataOffsetPresent() {
        return (getFlags() & 0x1) == 1;
    }

    public boolean isFirstSampleFlagsPresent() {
        return (getFlags() & 0x4) == 0x4;
    }


    public boolean isSampleSizePresent() {
        return (getFlags() & 0x200) == 0x200;
    }

    public boolean isSampleDurationPresent() {
        return (getFlags() & 0x100) == 0x100;
    }

    public boolean isSampleFlagsPresent() {
        return (getFlags() & 0x400) == 0x400;
    }

    public boolean isSampleCompositionTimeOffsetPresent() {
        return (getFlags() & 0x800) == 0x800;
    }

    public void setDataOffsetPresent(boolean v) {
        if (v) {
            setFlags(getFlags() | 0x01);
        } else {
            setFlags(getFlags() & (0xFFFFFF ^ 0x1));
        }
    }

    public void setSampleSizePresent(boolean v) {
        if (v) {
            setFlags(getFlags() | 0x200);
        } else {
            setFlags(getFlags() & (0xFFFFFF ^ 0x200));
        }
    }

    public void setSampleDurationPresent(boolean v) {

        if (v) {
            setFlags(getFlags() | 0x100);
        } else {
            setFlags(getFlags() & (0xFFFFFF ^ 0x100));
        }
    }

    public void setSampleFlagsPresent(boolean v) {
        if (v) {
            setFlags(getFlags() | 0x400);
        } else {
            setFlags(getFlags() & (0xFFFFFF ^ 0x400));
        }
    }

    public void setSampleCompositionTimeOffsetPresent(boolean v) {
        if (v) {
            setFlags(getFlags() | 0x800);
        } else {
            setFlags(getFlags() & (0xFFFFFF ^ 0x800));
        }

    }

    public int getDataOffset() {
        return dataOffset;
    }

    public SampleFlags getFirstSampleFlags() {
        return firstSampleFlags;
    }

    public void setFirstSampleFlags(SampleFlags firstSampleFlags) {
        if (firstSampleFlags == null) {
            setFlags(getFlags() & (0xFFFFFF ^ 0x4));
        } else {
            setFlags(getFlags() | 0x4);
        }
        this.firstSampleFlags = firstSampleFlags;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("TrackRunBox");
        sb.append("{sampleCount=").append(entries.size());
        sb.append(", dataOffset=").append(dataOffset);
        sb.append(", dataOffsetPresent=").append(isDataOffsetPresent());
        sb.append(", sampleSizePresent=").append(isSampleSizePresent());
        sb.append(", sampleDurationPresent=").append(isSampleDurationPresent());
        sb.append(", sampleFlagsPresentPresent=").append(isSampleFlagsPresent());
        sb.append(", sampleCompositionTimeOffsetPresent=").append(isSampleCompositionTimeOffsetPresent());
        sb.append(", firstSampleFlags=").append(firstSampleFlags);
        sb.append('}');
        return sb.toString();
    }

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