package jxl.write.biff;

import common.Assert;
import common.Logger;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import jxl.biff.BaseCompoundFile;
import jxl.biff.IntegerHelper;
import jxl.read.biff.BiffException;

final class CompoundFile extends BaseCompoundFile {
  private static Logger logger = Logger.getLogger(CompoundFile.class);
  
  private OutputStream out;
  
  private byte[] excelData;
  
  private int size;
  
  private int requiredSize;
  
  private int numBigBlockDepotBlocks;
  
  private int numSmallBlockDepotChainBlocks;
  
  private int numSmallBlockDepotBlocks;
  
  private int numExtensionBlocks;
  
  private int extensionBlock;
  
  private int excelDataBlocks;
  
  private int rootStartBlock;
  
  private int excelDataStartBlock;
  
  private int bbdStartBlock;
  
  private int sbdStartBlockChain;
  
  private int sbdStartBlock;
  
  private int additionalPropertyBlocks;
  
  private int numSmallBlocks;
  
  private int numPropertySets;
  
  private int numRootEntryBlocks;
  
  private ArrayList additionalPropertySets;
  
  private HashMap standardPropertySets;
  
  private int bbdPos;
  
  private byte[] bigBlockDepot;
  
  private static final class ReadPropertyStorage {
    BaseCompoundFile.PropertyStorage propertyStorage;
    
    byte[] data;
    
    int number;
    
    ReadPropertyStorage(BaseCompoundFile.PropertyStorage ps, byte[] d, int n) {
      this.propertyStorage = ps;
      this.data = d;
      this.number = n;
    }
  }
  
  public CompoundFile(byte[] data, int l, OutputStream os, jxl.read.biff.CompoundFile rcf) throws CopyAdditionalPropertySetsException, IOException {
    this.size = l;
    this.excelData = data;
    readAdditionalPropertySets(rcf);
    this.numRootEntryBlocks = 1;
    this.numPropertySets = 4 + ((this.additionalPropertySets != null) ? this.additionalPropertySets.size() : 0);
    if (this.additionalPropertySets != null) {
      this.numSmallBlockDepotChainBlocks = getBigBlocksRequired(this.numSmallBlocks * 4);
      this.numSmallBlockDepotBlocks = getBigBlocksRequired(this.numSmallBlocks * 64);
      this.numRootEntryBlocks += getBigBlocksRequired(this.additionalPropertySets.size() * 128);
    } 
    int blocks = getBigBlocksRequired(l);
    if (l < 4096) {
      this.requiredSize = 4096;
    } else {
      this.requiredSize = blocks * 512;
    } 
    this.out = os;
    this.excelDataBlocks = this.requiredSize / 512;
    this.numBigBlockDepotBlocks = 1;
    int blockChainLength = 109;
    int startTotalBlocks = this.excelDataBlocks + 8 + 8 + this.additionalPropertyBlocks + this.numSmallBlockDepotBlocks + this.numSmallBlockDepotChainBlocks + this.numRootEntryBlocks;
    int totalBlocks = startTotalBlocks + this.numBigBlockDepotBlocks;
    this.numBigBlockDepotBlocks = (int)Math.ceil(totalBlocks / 128.0D);
    totalBlocks = startTotalBlocks + this.numBigBlockDepotBlocks;
    this.numBigBlockDepotBlocks = (int)Math.ceil(totalBlocks / 128.0D);
    totalBlocks = startTotalBlocks + this.numBigBlockDepotBlocks;
    if (this.numBigBlockDepotBlocks > blockChainLength - 1) {
      this.extensionBlock = 0;
      int bbdBlocksLeft = this.numBigBlockDepotBlocks - blockChainLength + 1;
      this.numExtensionBlocks = (int)Math.ceil(bbdBlocksLeft / 127.0D);
      totalBlocks = startTotalBlocks + this.numExtensionBlocks + this.numBigBlockDepotBlocks;
      this.numBigBlockDepotBlocks = (int)Math.ceil(totalBlocks / 128.0D);
      totalBlocks = startTotalBlocks + this.numExtensionBlocks + this.numBigBlockDepotBlocks;
    } else {
      this.extensionBlock = -2;
      this.numExtensionBlocks = 0;
    } 
    this.excelDataStartBlock = this.numExtensionBlocks;
    this.sbdStartBlock = -2;
    if (this.additionalPropertySets != null && this.numSmallBlockDepotBlocks != 0)
      this.sbdStartBlock = this.excelDataStartBlock + this.excelDataBlocks + this.additionalPropertyBlocks + 16; 
    this.sbdStartBlockChain = -2;
    if (this.sbdStartBlock != -2)
      this.sbdStartBlockChain = this.sbdStartBlock + this.numSmallBlockDepotBlocks; 
    if (this.sbdStartBlockChain != -2) {
      this.bbdStartBlock = this.sbdStartBlockChain + this.numSmallBlockDepotChainBlocks;
    } else {
      this.bbdStartBlock = this.excelDataStartBlock + this.excelDataBlocks + this.additionalPropertyBlocks + 16;
    } 
    this.rootStartBlock = this.bbdStartBlock + this.numBigBlockDepotBlocks;
    if (totalBlocks != this.rootStartBlock + this.numRootEntryBlocks) {
      logger.warn("Root start block and total blocks are inconsistent  generated file may be corrupt");
      logger.warn("RootStartBlock " + this.rootStartBlock + " totalBlocks " + totalBlocks);
    } 
  }
  
  private void readAdditionalPropertySets(jxl.read.biff.CompoundFile readCompoundFile) throws CopyAdditionalPropertySetsException, IOException {
    if (readCompoundFile == null)
      return; 
    this.additionalPropertySets = new ArrayList();
    this.standardPropertySets = new HashMap();
    int blocksRequired = 0;
    int numPropertySets = readCompoundFile.getNumberOfPropertySets();
    for (int i = 0; i < numPropertySets; i++) {
      BaseCompoundFile.PropertyStorage ps = readCompoundFile.getPropertySet(i);
      boolean standard = false;
      if (ps.name.equalsIgnoreCase("Root Entry")) {
        standard = true;
        ReadPropertyStorage rps = new ReadPropertyStorage(ps, null, i);
        this.standardPropertySets.put("Root Entry", rps);
      } 
      for (int j = 0; j < STANDARD_PROPERTY_SETS.length && !standard; j++) {
        if (ps.name.equalsIgnoreCase(STANDARD_PROPERTY_SETS[j])) {
          BaseCompoundFile.PropertyStorage ps2 = readCompoundFile.findPropertyStorage(ps.name);
          Assert.verify((ps2 != null));
          if (ps2 == ps) {
            standard = true;
            ReadPropertyStorage rps = new ReadPropertyStorage(ps, null, i);
            this.standardPropertySets.put(STANDARD_PROPERTY_SETS[j], rps);
          } 
        } 
      } 
      if (!standard)
        try {
          byte[] data = null;
          if (ps.size > 0) {
            data = readCompoundFile.getStream(i);
          } else {
            data = new byte[0];
          } 
          ReadPropertyStorage rps = new ReadPropertyStorage(ps, data, i);
          this.additionalPropertySets.add(rps);
          if (data.length > 4096) {
            int blocks = getBigBlocksRequired(data.length);
            blocksRequired += blocks;
          } else {
            int blocks = getSmallBlocksRequired(data.length);
            this.numSmallBlocks += blocks;
          } 
        } catch (BiffException e) {
          logger.error(e);
          throw new CopyAdditionalPropertySetsException();
        }  
    } 
    this.additionalPropertyBlocks = blocksRequired;
  }
  
  public void write() throws IOException {
    writeHeader();
    writeExcelData();
    writeDocumentSummaryData();
    writeSummaryData();
    writeAdditionalPropertySets();
    writeSmallBlockDepot();
    writeSmallBlockDepotChain();
    writeBigBlockDepot();
    writePropertySets();
  }
  
  private void writeAdditionalPropertySets() throws IOException {
    if (this.additionalPropertySets == null)
      return; 
    for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) {
      ReadPropertyStorage rps = i.next();
      byte[] data = rps.data;
      if (data.length > 4096) {
        int numBlocks = getBigBlocksRequired(data.length);
        int requiredSize = numBlocks * 512;
        this.out.write(data, 0, data.length);
        byte[] padding = new byte[requiredSize - data.length];
        this.out.write(padding, 0, padding.length);
      } 
    } 
  }
  
  private void writeExcelData() throws IOException {
    this.out.write(this.excelData, 0, this.size);
    byte[] padding = new byte[this.requiredSize - this.size];
    this.out.write(padding);
  }
  
  private void writeDocumentSummaryData() throws IOException {
    byte[] padding = new byte[4096];
    this.out.write(padding);
  }
  
  private void writeSummaryData() throws IOException {
    byte[] padding = new byte[4096];
    this.out.write(padding);
  }
  
  private void writeHeader() throws IOException {
    byte[] headerBlock = new byte[512];
    byte[] extensionBlockData = new byte[512 * this.numExtensionBlocks];
    System.arraycopy(IDENTIFIER, 0, headerBlock, 0, IDENTIFIER.length);
    headerBlock[24] = 62;
    headerBlock[26] = 3;
    headerBlock[28] = -2;
    headerBlock[29] = -1;
    headerBlock[30] = 9;
    headerBlock[32] = 6;
    headerBlock[57] = 16;
    IntegerHelper.getFourBytes(this.numBigBlockDepotBlocks, headerBlock, 44);
    IntegerHelper.getFourBytes(this.sbdStartBlockChain, headerBlock, 60);
    IntegerHelper.getFourBytes(this.numSmallBlockDepotChainBlocks, headerBlock, 64);
    IntegerHelper.getFourBytes(this.extensionBlock, headerBlock, 68);
    IntegerHelper.getFourBytes(this.numExtensionBlocks, headerBlock, 72);
    IntegerHelper.getFourBytes(this.rootStartBlock, headerBlock, 48);
    int pos = 76;
    int blocksToWrite = Math.min(this.numBigBlockDepotBlocks, 109);
    int blocksWritten = 0;
    int i;
    for (i = 0; i < blocksToWrite; i++) {
      IntegerHelper.getFourBytes(this.bbdStartBlock + i, headerBlock, pos);
      pos += 4;
      blocksWritten++;
    } 
    for (i = pos; i < 512; i++)
      headerBlock[i] = -1; 
    this.out.write(headerBlock);
    pos = 0;
    for (int extBlock = 0; extBlock < this.numExtensionBlocks; extBlock++) {
      blocksToWrite = Math.min(this.numBigBlockDepotBlocks - blocksWritten, 127);
      for (int j = 0; j < blocksToWrite; j++) {
        IntegerHelper.getFourBytes(this.bbdStartBlock + blocksWritten + j, extensionBlockData, pos);
        pos += 4;
      } 
      blocksWritten += blocksToWrite;
      int nextBlock = (blocksWritten == this.numBigBlockDepotBlocks) ? -2 : (extBlock + 1);
      IntegerHelper.getFourBytes(nextBlock, extensionBlockData, pos);
      pos += 4;
    } 
    if (this.numExtensionBlocks > 0) {
      for (int j = pos; j < extensionBlockData.length; j++)
        extensionBlockData[j] = -1; 
      this.out.write(extensionBlockData);
    } 
  }
  
  private void checkBbdPos() throws IOException {
    if (this.bbdPos >= 512) {
      this.out.write(this.bigBlockDepot);
      this.bigBlockDepot = new byte[512];
      this.bbdPos = 0;
    } 
  }
  
  private void writeBlockChain(int startBlock, int numBlocks) throws IOException {
    int blocksToWrite = numBlocks - 1;
    int blockNumber = startBlock + 1;
    while (blocksToWrite > 0) {
      int bbdBlocks = Math.min(blocksToWrite, (512 - this.bbdPos) / 4);
      for (int i = 0; i < bbdBlocks; i++) {
        IntegerHelper.getFourBytes(blockNumber, this.bigBlockDepot, this.bbdPos);
        this.bbdPos += 4;
        blockNumber++;
      } 
      blocksToWrite -= bbdBlocks;
      checkBbdPos();
    } 
    IntegerHelper.getFourBytes(-2, this.bigBlockDepot, this.bbdPos);
    this.bbdPos += 4;
    checkBbdPos();
  }
  
  private void writeAdditionalPropertySetBlockChains() throws IOException {
    if (this.additionalPropertySets == null)
      return; 
    int blockNumber = this.excelDataStartBlock + this.excelDataBlocks + 16;
    for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) {
      ReadPropertyStorage rps = i.next();
      if (rps.data.length > 4096) {
        int numBlocks = getBigBlocksRequired(rps.data.length);
        writeBlockChain(blockNumber, numBlocks);
        blockNumber += numBlocks;
      } 
    } 
  }
  
  private void writeSmallBlockDepotChain() throws IOException {
    if (this.sbdStartBlockChain == -2)
      return; 
    byte[] smallBlockDepotChain = new byte[this.numSmallBlockDepotChainBlocks * 512];
    int pos = 0;
    int sbdBlockNumber = 1;
    for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) {
      ReadPropertyStorage rps = i.next();
      if (rps.data.length <= 4096 && rps.data.length != 0) {
        int numSmallBlocks = getSmallBlocksRequired(rps.data.length);
        for (int j = 0; j < numSmallBlocks - 1; j++) {
          IntegerHelper.getFourBytes(sbdBlockNumber, smallBlockDepotChain, pos);
          pos += 4;
          sbdBlockNumber++;
        } 
        IntegerHelper.getFourBytes(-2, smallBlockDepotChain, pos);
        pos += 4;
        sbdBlockNumber++;
      } 
    } 
    this.out.write(smallBlockDepotChain);
  }
  
  private void writeSmallBlockDepot() throws IOException {
    if (this.additionalPropertySets == null)
      return; 
    byte[] smallBlockDepot = new byte[this.numSmallBlockDepotBlocks * 512];
    int pos = 0;
    for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) {
      ReadPropertyStorage rps = i.next();
      if (rps.data.length <= 4096) {
        int smallBlocks = getSmallBlocksRequired(rps.data.length);
        int length = smallBlocks * 64;
        System.arraycopy(rps.data, 0, smallBlockDepot, pos, rps.data.length);
        pos += length;
      } 
    } 
    this.out.write(smallBlockDepot);
  }
  
  private void writeBigBlockDepot() throws IOException {
    this.bigBlockDepot = new byte[512];
    this.bbdPos = 0;
    for (int i = 0; i < this.numExtensionBlocks; i++) {
      IntegerHelper.getFourBytes(-3, this.bigBlockDepot, this.bbdPos);
      this.bbdPos += 4;
      checkBbdPos();
    } 
    writeBlockChain(this.excelDataStartBlock, this.excelDataBlocks);
    int summaryInfoBlock = this.excelDataStartBlock + this.excelDataBlocks + this.additionalPropertyBlocks;
    int j;
    for (j = summaryInfoBlock; j < summaryInfoBlock + 7; j++) {
      IntegerHelper.getFourBytes(j + 1, this.bigBlockDepot, this.bbdPos);
      this.bbdPos += 4;
      checkBbdPos();
    } 
    IntegerHelper.getFourBytes(-2, this.bigBlockDepot, this.bbdPos);
    this.bbdPos += 4;
    checkBbdPos();
    for (j = summaryInfoBlock + 8; j < summaryInfoBlock + 15; j++) {
      IntegerHelper.getFourBytes(j + 1, this.bigBlockDepot, this.bbdPos);
      this.bbdPos += 4;
      checkBbdPos();
    } 
    IntegerHelper.getFourBytes(-2, this.bigBlockDepot, this.bbdPos);
    this.bbdPos += 4;
    checkBbdPos();
    writeAdditionalPropertySetBlockChains();
    if (this.sbdStartBlock != -2) {
      writeBlockChain(this.sbdStartBlock, this.numSmallBlockDepotBlocks);
      writeBlockChain(this.sbdStartBlockChain, this.numSmallBlockDepotChainBlocks);
    } 
    for (j = 0; j < this.numBigBlockDepotBlocks; j++) {
      IntegerHelper.getFourBytes(-3, this.bigBlockDepot, this.bbdPos);
      this.bbdPos += 4;
      checkBbdPos();
    } 
    writeBlockChain(this.rootStartBlock, this.numRootEntryBlocks);
    if (this.bbdPos != 0) {
      for (j = this.bbdPos; j < 512; j++)
        this.bigBlockDepot[j] = -1; 
      this.out.write(this.bigBlockDepot);
    } 
  }
  
  private int getBigBlocksRequired(int length) {
    int blocks = length / 512;
    return (length % 512 > 0) ? (blocks + 1) : blocks;
  }
  
  private int getSmallBlocksRequired(int length) {
    int blocks = length / 64;
    return (length % 64 > 0) ? (blocks + 1) : blocks;
  }
  
  private void writePropertySets() throws IOException {
    byte[] propertySetStorage = new byte[512 * this.numRootEntryBlocks];
    int pos = 0;
    int[] mappings = null;
    if (this.additionalPropertySets != null) {
      mappings = new int[this.numPropertySets];
      for (int j = 0; j < STANDARD_PROPERTY_SETS.length; j++) {
        ReadPropertyStorage rps = (ReadPropertyStorage)this.standardPropertySets.get(STANDARD_PROPERTY_SETS[j]);
        if (rps != null) {
          mappings[rps.number] = j;
        } else {
          logger.warn("Standard property set " + STANDARD_PROPERTY_SETS[j] + " not present in source file");
        } 
      } 
      int newMapping = STANDARD_PROPERTY_SETS.length;
      for (Iterator iterator = this.additionalPropertySets.iterator(); iterator.hasNext(); ) {
        ReadPropertyStorage rps = iterator.next();
        mappings[rps.number] = newMapping;
        newMapping++;
      } 
    } 
    int child = 0;
    int previous = 0;
    int next = 0;
    int size = 0;
    if (this.additionalPropertySets != null) {
      size += getBigBlocksRequired(this.requiredSize) * 512;
      size += getBigBlocksRequired(4096) * 512;
      size += getBigBlocksRequired(4096) * 512;
      for (Iterator iterator = this.additionalPropertySets.iterator(); iterator.hasNext(); ) {
        ReadPropertyStorage rps = iterator.next();
        if (rps.propertyStorage.type != 1) {
          if (rps.propertyStorage.size >= 4096) {
            size += getBigBlocksRequired(rps.propertyStorage.size) * 512;
            continue;
          } 
          size += getSmallBlocksRequired(rps.propertyStorage.size) * 64;
        } 
      } 
    } 
    BaseCompoundFile.PropertyStorage ps = new BaseCompoundFile.PropertyStorage(this, "Root Entry");
    ps.setType(5);
    ps.setStartBlock(this.sbdStartBlock);
    ps.setSize(size);
    ps.setPrevious(-1);
    ps.setNext(-1);
    ps.setColour(0);
    child = 1;
    if (this.additionalPropertySets != null) {
      ReadPropertyStorage rps = (ReadPropertyStorage)this.standardPropertySets.get("Root Entry");
      child = mappings[rps.propertyStorage.child];
    } 
    ps.setChild(child);
    System.arraycopy(ps.data, 0, propertySetStorage, pos, 128);
    pos += 128;
    ps = new BaseCompoundFile.PropertyStorage(this, "Workbook");
    ps.setType(2);
    ps.setStartBlock(this.excelDataStartBlock);
    ps.setSize(this.requiredSize);
    previous = 3;
    next = -1;
    if (this.additionalPropertySets != null) {
      ReadPropertyStorage rps = (ReadPropertyStorage)this.standardPropertySets.get("Workbook");
      previous = (rps.propertyStorage.previous != -1) ? mappings[rps.propertyStorage.previous] : -1;
      next = (rps.propertyStorage.next != -1) ? mappings[rps.propertyStorage.next] : -1;
    } 
    ps.setPrevious(previous);
    ps.setNext(next);
    ps.setChild(-1);
    System.arraycopy(ps.data, 0, propertySetStorage, pos, 128);
    pos += 128;
    ps = new BaseCompoundFile.PropertyStorage(this, "\005SummaryInformation");
    ps.setType(2);
    ps.setStartBlock(this.excelDataStartBlock + this.excelDataBlocks);
    ps.setSize(4096);
    previous = 1;
    next = 3;
    if (this.additionalPropertySets != null) {
      ReadPropertyStorage rps = (ReadPropertyStorage)this.standardPropertySets.get("\005SummaryInformation");
      if (rps != null) {
        previous = (rps.propertyStorage.previous != -1) ? mappings[rps.propertyStorage.previous] : -1;
        next = (rps.propertyStorage.next != -1) ? mappings[rps.propertyStorage.next] : -1;
      } 
    } 
    ps.setPrevious(previous);
    ps.setNext(next);
    ps.setChild(-1);
    System.arraycopy(ps.data, 0, propertySetStorage, pos, 128);
    pos += 128;
    ps = new BaseCompoundFile.PropertyStorage(this, "\005DocumentSummaryInformation");
    ps.setType(2);
    ps.setStartBlock(this.excelDataStartBlock + this.excelDataBlocks + 8);
    ps.setSize(4096);
    ps.setPrevious(-1);
    ps.setNext(-1);
    ps.setChild(-1);
    System.arraycopy(ps.data, 0, propertySetStorage, pos, 128);
    pos += 128;
    if (this.additionalPropertySets == null) {
      this.out.write(propertySetStorage);
      return;
    } 
    int bigBlock = this.excelDataStartBlock + this.excelDataBlocks + 16;
    int smallBlock = 0;
    for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) {
      ReadPropertyStorage rps = i.next();
      int block = (rps.data.length > 4096) ? bigBlock : smallBlock;
      ps = new BaseCompoundFile.PropertyStorage(this, rps.propertyStorage.name);
      ps.setType(rps.propertyStorage.type);
      ps.setStartBlock(block);
      ps.setSize(rps.propertyStorage.size);
      previous = (rps.propertyStorage.previous != -1) ? mappings[rps.propertyStorage.previous] : -1;
      next = (rps.propertyStorage.next != -1) ? mappings[rps.propertyStorage.next] : -1;
      child = (rps.propertyStorage.child != -1) ? mappings[rps.propertyStorage.child] : -1;
      ps.setPrevious(previous);
      ps.setNext(next);
      ps.setChild(child);
      System.arraycopy(ps.data, 0, propertySetStorage, pos, 128);
      pos += 128;
      if (rps.data.length > 4096) {
        bigBlock += getBigBlocksRequired(rps.data.length);
        continue;
      } 
      smallBlock += getSmallBlocksRequired(rps.data.length);
    } 
    this.out.write(propertySetStorage);
  }
}
