/*  
 * Copyright 2008 CoreMedia AG, Hamburg
 *
 * 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;


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

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

/**
 * <h1>4cc = "{@value #TYPE}"</h1>
 * <pre>
 * aligned(8) class ItemLocationBox extends FullBox('iloc', version, 0) {
 *  unsigned int(4) offset_size;
 *  unsigned int(4) length_size;
 *  unsigned int(4) base_offset_size;
 *  if (version == 1)
 *   unsigned int(4) index_size;
 *  else
 *   unsigned int(4) reserved;
 *  unsigned int(16) item_count;
 *  for (i=0; i&lt;item_count; i++) {
 *   unsigned int(16) item_ID;
 *   if (version == 1) {
 *    unsigned int(12) reserved = 0;
 *    unsigned int(4) construction_method;
 *   }
 *   unsigned int(16) data_reference_index;
 *   unsigned int(base_offset_size*8) base_offset;
 *   unsigned int(16) extent_count;
 *   for (j=0; j&lt;extent_count; j++) {
 *    if ((version == 1) &amp;&amp; (index_size &gt; 0)) {
 *     unsigned int(index_size*8) extent_index;
 *    }
 *    unsigned int(offset_size*8) extent_offset;
 *    unsigned int(length_size*8) extent_length;
 *   }
 *  }
 * }
 * </pre>
 */
public class ItemLocationBox extends AbstractFullBox {
    public int offsetSize = 8;
    public int lengthSize = 8;
    public int baseOffsetSize = 8;
    public int indexSize = 0;
    public List<Item> items = new LinkedList<Item>();

    public static final String TYPE = "iloc";

    public ItemLocationBox() {
        super(TYPE);
    }

    @Override
    protected long getContentSize() {
        long size = 8;
        for (Item item : items) {
            size += item.getSize();
        }
        return size;
    }


    @Override
    protected void getContent(ByteBuffer byteBuffer) {
        writeVersionAndFlags(byteBuffer);
        IsoTypeWriter.writeUInt8(byteBuffer, ((offsetSize << 4) | lengthSize));
        if (getVersion() == 1) {
            IsoTypeWriter.writeUInt8(byteBuffer, (baseOffsetSize << 4 | indexSize));
        } else {
            IsoTypeWriter.writeUInt8(byteBuffer, (baseOffsetSize << 4));
        }
        IsoTypeWriter.writeUInt16(byteBuffer, items.size());
        for (Item item : items) {
            item.getContent(byteBuffer);
        }
    }

    @Override
    public void _parseDetails(ByteBuffer content) {
        parseVersionAndFlags(content);
        int tmp = IsoTypeReader.readUInt8(content);
        offsetSize = tmp >>> 4;
        lengthSize = tmp & 0xf;
        tmp = IsoTypeReader.readUInt8(content);
        baseOffsetSize = tmp >>> 4;

        if (getVersion() == 1) {
            indexSize = tmp & 0xf;
        }
        int itemCount = IsoTypeReader.readUInt16(content);
        for (int i = 0; i < itemCount; i++) {
            items.add(new Item(content));
        }
    }


    public int getOffsetSize() {
        return offsetSize;
    }

    public void setOffsetSize(int offsetSize) {
        this.offsetSize = offsetSize;
    }

    public int getLengthSize() {
        return lengthSize;
    }

    public void setLengthSize(int lengthSize) {
        this.lengthSize = lengthSize;
    }

    public int getBaseOffsetSize() {
        return baseOffsetSize;
    }

    public void setBaseOffsetSize(int baseOffsetSize) {
        this.baseOffsetSize = baseOffsetSize;
    }

    public int getIndexSize() {
        return indexSize;
    }

    public void setIndexSize(int indexSize) {
        this.indexSize = indexSize;
    }

    public List<Item> getItems() {
        return items;
    }

    public void setItems(List<Item> items) {
        this.items = items;
    }


    public Item createItem(int itemId, int constructionMethod, int dataReferenceIndex, long baseOffset, List<Extent> extents) {
        return new Item(itemId, constructionMethod, dataReferenceIndex, baseOffset, extents);
    }

    Item createItem(ByteBuffer bb) {
        return new Item(bb);
    }

    public class Item {
        public int itemId;
        public int constructionMethod;
        public int dataReferenceIndex;
        public long baseOffset;
        public List<Extent> extents = new LinkedList<Extent>();

        public Item(ByteBuffer in) {
            itemId = IsoTypeReader.readUInt16(in);

            if (getVersion() == 1) {
                int tmp = IsoTypeReader.readUInt16(in);
                constructionMethod = tmp & 0xf;
            }

            dataReferenceIndex = IsoTypeReader.readUInt16(in);
            if (baseOffsetSize > 0) {
                baseOffset = IsoTypeReaderVariable.read(in, baseOffsetSize);
            } else {
                baseOffset = 0;
            }
            int extentCount = IsoTypeReader.readUInt16(in);


            for (int i = 0; i < extentCount; i++) {
                extents.add(new Extent(in));
            }
        }

        public Item(int itemId, int constructionMethod, int dataReferenceIndex, long baseOffset, List<Extent> extents) {
            this.itemId = itemId;
            this.constructionMethod = constructionMethod;
            this.dataReferenceIndex = dataReferenceIndex;
            this.baseOffset = baseOffset;
            this.extents = extents;
        }

        public int getSize() {
            int size = 2;

            if (getVersion() == 1) {
                size += 2;
            }

            size += 2;
            size += baseOffsetSize;
            size += 2;


            for (Extent extent : extents) {
                size += extent.getSize();
            }
            return size;
        }

        public void setBaseOffset(long baseOffset) {
            this.baseOffset = baseOffset;
        }

        public void getContent(ByteBuffer bb) {
            IsoTypeWriter.writeUInt16(bb, itemId);

            if (getVersion() == 1) {
                IsoTypeWriter.writeUInt16(bb, constructionMethod);
            }


            IsoTypeWriter.writeUInt16(bb, dataReferenceIndex);
            if (baseOffsetSize > 0) {
                IsoTypeWriterVariable.write(baseOffset, bb, baseOffsetSize);
            }
            IsoTypeWriter.writeUInt16(bb, extents.size());

            for (Extent extent : extents) {
                extent.getContent(bb);
            }
        }

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

            Item item = (Item) o;

            if (baseOffset != item.baseOffset) return false;
            if (constructionMethod != item.constructionMethod) return false;
            if (dataReferenceIndex != item.dataReferenceIndex) return false;
            if (itemId != item.itemId) return false;
            if (extents != null ? !extents.equals(item.extents) : item.extents != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = itemId;
            result = 31 * result + constructionMethod;
            result = 31 * result + dataReferenceIndex;
            result = 31 * result + (int) (baseOffset ^ (baseOffset >>> 32));
            result = 31 * result + (extents != null ? extents.hashCode() : 0);
            return result;
        }

        @Override
        public String toString() {
            return "Item{" +
                    "baseOffset=" + baseOffset +
                    ", itemId=" + itemId +
                    ", constructionMethod=" + constructionMethod +
                    ", dataReferenceIndex=" + dataReferenceIndex +
                    ", extents=" + extents +
                    '}';
        }
    }


    public Extent createExtent(long extentOffset, long extentLength, long extentIndex) {
        return new Extent(extentOffset, extentLength, extentIndex);
    }

    Extent createExtent(ByteBuffer bb) {
        return new Extent(bb);
    }


    public class Extent {
        public long extentOffset;
        public long extentLength;
        public long extentIndex;

        public Extent(long extentOffset, long extentLength, long extentIndex) {
            this.extentOffset = extentOffset;
            this.extentLength = extentLength;
            this.extentIndex = extentIndex;
        }


        public Extent(ByteBuffer in) {
            if ((getVersion() == 1) && indexSize > 0) {
                extentIndex = IsoTypeReaderVariable.read(in, indexSize);
            }
            extentOffset = IsoTypeReaderVariable.read(in, offsetSize);
            extentLength = IsoTypeReaderVariable.read(in, lengthSize);
        }

        public void getContent(ByteBuffer os) {
            if ((getVersion() == 1) && indexSize > 0) {
                IsoTypeWriterVariable.write(extentIndex, os, indexSize);
            }
            IsoTypeWriterVariable.write(extentOffset, os, offsetSize);
            IsoTypeWriterVariable.write(extentLength, os, lengthSize);
        }

        public int getSize() {
            return (indexSize > 0 ? indexSize : 0) + offsetSize + lengthSize;
        }


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

            Extent extent = (Extent) o;

            if (extentIndex != extent.extentIndex) return false;
            if (extentLength != extent.extentLength) return false;
            if (extentOffset != extent.extentOffset) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = (int) (extentOffset ^ (extentOffset >>> 32));
            result = 31 * result + (int) (extentLength ^ (extentLength >>> 32));
            result = 31 * result + (int) (extentIndex ^ (extentIndex >>> 32));
            return result;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("Extent");
            sb.append("{extentOffset=").append(extentOffset);
            sb.append(", extentLength=").append(extentLength);
            sb.append(", extentIndex=").append(extentIndex);
            sb.append('}');
            return sb.toString();
        }
    }


}
