package jxl.read.biff;

import common.Assert;
import jxl.WorkbookSettings;
import jxl.biff.IntegerHelper;
import jxl.biff.RecordData;
import jxl.biff.StringHelper;

class SSTRecord extends RecordData {
  private int totalStrings;
  
  private int uniqueStrings;
  
  private String[] strings;
  
  private int[] continuationBreaks;
  
  private static class ByteArrayHolder {
    public byte[] bytes;
    
    private ByteArrayHolder() {}
  }
  
  private static class BooleanHolder {
    public boolean value;
    
    private BooleanHolder() {}
  }
  
  public SSTRecord(Record t, Record[] continuations, WorkbookSettings ws) {
    super(t);
    int totalRecordLength = 0;
    for (int i = 0; i < continuations.length; i++)
      totalRecordLength += continuations[i].getLength(); 
    totalRecordLength += getRecord().getLength();
    byte[] data = new byte[totalRecordLength];
    int pos = 0;
    System.arraycopy(getRecord().getData(), 0, data, 0, getRecord().getLength());
    pos += getRecord().getLength();
    this.continuationBreaks = new int[continuations.length];
    Record r = null;
    for (int j = 0; j < continuations.length; j++) {
      r = continuations[j];
      System.arraycopy(r.getData(), 0, data, pos, r.getLength());
      this.continuationBreaks[j] = pos;
      pos += r.getLength();
    } 
    this.totalStrings = IntegerHelper.getInt(data[0], data[1], data[2], data[3]);
    this.uniqueStrings = IntegerHelper.getInt(data[4], data[5], data[6], data[7]);
    this.strings = new String[this.uniqueStrings];
    readStrings(data, 8, ws);
  }
  
  private void readStrings(byte[] data, int offset, WorkbookSettings ws) {
    int pos = offset;
    String s = null;
    boolean asciiEncoding = false;
    boolean richString = false;
    boolean extendedString = false;
    int formattingRuns = 0;
    int extendedRunLength = 0;
    for (int i = 0; i < this.uniqueStrings; i++) {
      int numChars = IntegerHelper.getInt(data[pos], data[pos + 1]);
      pos += 2;
      byte optionFlags = data[pos];
      pos++;
      extendedString = ((optionFlags & 0x4) != 0);
      richString = ((optionFlags & 0x8) != 0);
      if (richString) {
        formattingRuns = IntegerHelper.getInt(data[pos], data[pos + 1]);
        pos += 2;
      } 
      if (extendedString) {
        extendedRunLength = IntegerHelper.getInt(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
        pos += 4;
      } 
      asciiEncoding = ((optionFlags & 0x1) == 0);
      ByteArrayHolder bah = new ByteArrayHolder();
      BooleanHolder bh = new BooleanHolder();
      bh.value = asciiEncoding;
      pos += getChars(data, bah, pos, bh, numChars);
      asciiEncoding = bh.value;
      if (asciiEncoding) {
        s = StringHelper.getString(bah.bytes, numChars, 0, ws);
      } else {
        s = StringHelper.getUnicodeString(bah.bytes, numChars, 0);
      } 
      this.strings[i] = s;
      if (richString)
        pos += 4 * formattingRuns; 
      if (extendedString)
        pos += extendedRunLength; 
      if (pos > data.length)
        Assert.verify(false, "pos exceeds record length"); 
    } 
  }
  
  private int getChars(byte[] source, ByteArrayHolder bah, int pos, BooleanHolder ascii, int numChars) {
    int charsRead, i = 0;
    boolean spansBreak = false;
    if (ascii.value) {
      bah.bytes = new byte[numChars];
    } else {
      bah.bytes = new byte[numChars * 2];
    } 
    while (i < this.continuationBreaks.length && !spansBreak) {
      spansBreak = (pos <= this.continuationBreaks[i] && pos + bah.bytes.length > this.continuationBreaks[i]);
      if (!spansBreak)
        i++; 
    } 
    if (!spansBreak) {
      System.arraycopy(source, pos, bah.bytes, 0, bah.bytes.length);
      return bah.bytes.length;
    } 
    int breakpos = this.continuationBreaks[i];
    System.arraycopy(source, pos, bah.bytes, 0, breakpos - pos);
    int bytesRead = breakpos - pos;
    if (ascii.value) {
      charsRead = bytesRead;
    } else {
      charsRead = bytesRead / 2;
    } 
    bytesRead += getContinuedString(source, bah, bytesRead, i, ascii, numChars - charsRead);
    return bytesRead;
  }
  
  private int getContinuedString(byte[] source, ByteArrayHolder bah, int destPos, int contBreakIndex, BooleanHolder ascii, int charsLeft) {
    int breakpos = this.continuationBreaks[contBreakIndex];
    int bytesRead = 0;
    while (charsLeft > 0) {
      Assert.verify((contBreakIndex < this.continuationBreaks.length), "continuation break index");
      if (ascii.value && source[breakpos] == 0) {
        int length = (contBreakIndex == this.continuationBreaks.length - 1) ? charsLeft : Math.min(charsLeft, this.continuationBreaks[contBreakIndex + 1] - breakpos - 1);
        System.arraycopy(source, breakpos + 1, bah.bytes, destPos, length);
        destPos += length;
        bytesRead += length + 1;
        charsLeft -= length;
        ascii.value = true;
      } else if (!ascii.value && source[breakpos] != 0) {
        int length = (contBreakIndex == this.continuationBreaks.length - 1) ? (charsLeft * 2) : Math.min(charsLeft * 2, this.continuationBreaks[contBreakIndex + 1] - breakpos - 1);
        System.arraycopy(source, breakpos + 1, bah.bytes, destPos, length);
        destPos += length;
        bytesRead += length + 1;
        charsLeft -= length / 2;
        ascii.value = false;
      } else if (!ascii.value && source[breakpos] == 0) {
        int chars = (contBreakIndex == this.continuationBreaks.length - 1) ? charsLeft : Math.min(charsLeft, this.continuationBreaks[contBreakIndex + 1] - breakpos - 1);
        for (int j = 0; j < chars; j++) {
          bah.bytes[destPos] = source[breakpos + j + 1];
          destPos += 2;
        } 
        bytesRead += chars + 1;
        charsLeft -= chars;
        ascii.value = false;
      } else {
        byte[] oldBytes = bah.bytes;
        bah.bytes = new byte[destPos * 2 + charsLeft * 2];
        for (int j = 0; j < destPos; j++)
          bah.bytes[j * 2] = oldBytes[j]; 
        destPos *= 2;
        int length = (contBreakIndex == this.continuationBreaks.length - 1) ? (charsLeft * 2) : Math.min(charsLeft * 2, this.continuationBreaks[contBreakIndex + 1] - breakpos - 1);
        System.arraycopy(source, breakpos + 1, bah.bytes, destPos, length);
        destPos += length;
        bytesRead += length + 1;
        charsLeft -= length / 2;
        ascii.value = false;
      } 
      contBreakIndex++;
      if (contBreakIndex < this.continuationBreaks.length)
        breakpos = this.continuationBreaks[contBreakIndex]; 
    } 
    return bytesRead;
  }
  
  public String getString(int index) {
    Assert.verify((index < this.uniqueStrings));
    return this.strings[index];
  }
}
