/*
 * Copyright (C) 2008-2009 KenD00
 * 
 * This file is part of DumpHD.
 * 
 * DumpHD is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package dumphd.bdplus;

import java.io.IOException;

import dumphd.util.ByteArray;
import dumphd.util.ByteSource;
import dumphd.util.Utils;

/**
 * This class represents a segment of a subtable of a BD+ conversion table.
 * 
 * FIXME: A segment can contain an unsigned 32 bit number of entries. However in java all arrays / collections can take only signed 32 bit
 *        number of elements, so if a segments contains more than a 31 bit number of elements this is a problem!
 *        
 * @author KenD00
 */
public class Segment {

   /**
    * Length in bytes of a patch
    */
   public static final int PATCH_LENGTH = 5;
   
   /**
    * The index of this segment in the subtable it comes from
    */
   private int segmentIndex = 0;
   /**
    * Offset of this segment from the beginning of the conversion table
    */
   private long segmentOffset = 0;
   /**
    * Number of entries
    */
   private long entries = 0;
   /**
    * The index section of this segment
    */
   private byte[] indexData = null;
   /**
    * The patch entries of this segment
    */
   private byte[] entryData = null;

   
   /**
    * Creates an empty Segment.
    * 
    * @param index The index of this segment in its parent subtable
    */
   public Segment(int index) {
      segmentIndex = index;
   }
   
   /**
    * Creates a segment from the given source.
    * 
    * The source read pointer must be at the location where the segment starts, the whole conversion table must start at position 0.
    * 
    * @param source The conversion table to start
    * @param index The index of this segment in its parent subtable
    * @throws IOException
    */
   public Segment(ByteSource source, int index) throws IOException {
      segmentIndex = index;
      segmentOffset = source.getPosition();
      if (source.read(Utils.buffer, 0, 4) != 4) {
         throw new IOException("Unexpected EOF, segment too small for header");
      }
      entries = ByteArray.getVarLong(Utils.buffer, 0, 4);
      indexData = new byte[4 * (int)entries];
      if (source.read(indexData, 0, indexData.length) != indexData.length) {
         throw new IOException("Unexpected EOF, segment too small for index data");
      }
      entryData = new byte[16 * (int)entries];
      if (source.read(entryData, 0, entryData.length) != entryData.length) {
         throw new IOException("Unexpected EOF, segment too small for entry data");
      }
   }
   
   /**
    * Parses the given byte array to create a Segment.
    * 
    * The content of the byte array gets copied, changes of it after this method call do not affect this Segment. 
    * 
    * @param raw The raw Conversion Table
    * @param offset Start offset, must be the start of the Segment
    * @return The endoffset of the Segment inside the byte array
    * @throws IndexOutOfBoundsException
    */
   public int parse(byte[] raw, int offset) throws IndexOutOfBoundsException {
      segmentOffset = offset;
      entries = ByteArray.getVarLong(raw, offset, 4);
      offset += 4;
      indexData = new byte[4 * (int)entries];
      System.arraycopy(raw, offset, indexData, 0, indexData.length);
      offset += indexData.length;
      entryData = new byte[16 * (int)entries];
      System.arraycopy(raw, offset, entryData, 0, entryData.length);
      offset += entryData.length;
      return offset;
   }

   /**
    * @return The index of this segment in the subtable it comes from
    */
   public int getSegmentIndex() {
      return segmentIndex;
   }

   /**
    * @return Offset of this segment from the beginning of the conversion table
    */
   public long getSegmentOffset() {
      return segmentOffset;
   }

   /**
    * @return Number of entries
    */
   public int size() {
      return (int)entries;
   }

   /**
    * @param index Entry index
    * @return The base address of the entry
    */
   public long getBase(int index) {
      return ByteArray.getUInt(indexData, index * 4);
   }

   /**
    * @param index Entry index
    * @return The flags of the entry
    */
   public int getFlags(int index) {
      return entryData[index * 16] & 0xFF;
   }

   /**
    * @param index Entry index
    * @return The Adjust 0 value of the entry
    */
   public long getAdjust0(int index) {
      return (ByteArray.getUShort(entryData, index * 16 + 1) >>> 4) & 0xFFF;
   }

   /**
    * @param index Entry index
    * @return The Adjust 1 value of the entry
    */
   public long getAdjust1(int index) {
      return ByteArray.getUShort(entryData, index * 16 + 2) & 0xFFF;
   }

   /**
    * @param index Entry index
    * @return The Offset 0 value of the entry
    */
   public long getOffset0(int index) {
      return ByteArray.getUByte(entryData, index * 16 + 4);
   }

   /**
    * @param index Entry index
    * @return The Offset 1 value of the entry
    */
   public long getOffset1(int index) {
      return ByteArray.getUByte(entryData, index * 16 + 5);
   }

   /**
    * Write the Patch 0 into the given byte array.
    * 
    * Segment.PATCH_LENGTH bytes get written.
    * 
    * @param index Entry index
    * @param dst Byte array to write to
    * @param offset Offset to start writing.
    */
   public void getPatch0(int index, byte[] dst, int offset) {
      System.arraycopy(entryData, index * 16 + 6, dst, offset, PATCH_LENGTH);
   }

   /**
    * Write the Patch 1 into the given byte array.
    * 
    * Segment.PATCH_LENGTH bytes get written.
    * 
    * @param index Entry index
    * @param dst Byte array to write to
    * @param offset Offset to start writing.
    */
   public void getPatch1(int index, byte[] dst, int offset) {
      System.arraycopy(entryData, index * 16 + 11, dst, offset, PATCH_LENGTH);
   }
   
   /**
    * Returns the absolute address of Patch 0.
    * 
    * This is a calculated value.
    *  
    * @param index Entry index
    * @return Absolute address of Patch 0.
    */
   public long getAddress0(int index) {
      return (getBase(index) + getAdjust0(index)) * 0xC0 + getOffset0(index);
   }

   /**
    * Returns the absolute address of Patch 1.
    * 
    * This is a calculated value.
    *  
    * @param index Entry index
    * @return Absolute address of Patch 1.
    */
   public long getAddress1(int index) {
      return (getBase(index) + getAdjust0(index) + getAdjust1(index)) * 0xC0 + getOffset1(index);
   }
   
}
