/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CExcelBAT.cpp  -  description
                             -------------------
    begin                : 19 March 2004
    copyright            : (C) 2004 by Alexey Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at
   your option) any later version.

   This library 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 Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#include <sptk3/excel/CExcelRecord.h>
#include <sptk3/excel/CExcelBAT.h>
#include <sptk3/excel/CExcelHeader.h>
#include <sptk3/excel/CExcelProperty.h>
#include <sptk3/CException.h>

#include <string.h>
#include <stdio.h>

CExcelBAT::CExcelBAT() {
   clear();
}

void CExcelBAT::clear() {
   m_xbatBlockCount = m_streamBlockCount = m_batBlockCount = 0;
   m_xbatStartBlock = m_batStartBlock = m_streamStartBlock = END_OF_CHAIN;
   m_batIndex.reset();
   m_batBuffer.reset();
}

void CExcelBAT::streamStartBlock(unsigned ssb) {
   m_streamStartBlock = ssb;
}

void CExcelBAT::streamBlockCount(unsigned streamBlockCount) {
   // Compute number of BAT blocks for stream blocks and END_OF_CHAIN
   m_batBlockCount = (streamBlockCount + 1) / 128;
   if (m_batBlockCount * 128 < streamBlockCount + 1)
      m_batBlockCount++;

   if (m_batBlockCount <= 109)
      m_xbatBlockCount = 0;
   else {
      m_xbatBlockCount = (m_batBlockCount - 109) / 128;
      if (m_xbatBlockCount * 128 < m_batBlockCount - 109)
         m_xbatBlockCount++;
   }

   m_batBuffer.checkSize(m_batBlockCount*512);
   m_batBuffer.bytes(m_batBlockCount*512);
   m_streamBlockCount = streamBlockCount;
}

const CBuffer& CExcelBAT::batIndex() const {
   return m_batIndex;
}

const CBuffer& CExcelBAT::xbatIndex() const {
   return m_xbatIndex;
}

const CBuffer& CExcelBAT::batData() const {
   return m_batBuffer;
}

void CExcelBAT::build(CExcelProperties *props,unsigned batStartBlock) {
   unsigned p;
   CExcelProperties& properties = *props;

   unsigned totalBatBlocks = 0;
   for (p = 0; p < properties.size(); p++) {
      CExcelProperty* property = properties[p];
      totalBatBlocks += property->blockCount() + 1;
      if (property->blockCount() == 0) totalBatBlocks++;
   }

   m_batBlockCount = totalBatBlocks / 128;
   if (m_batBlockCount * 128 < totalBatBlocks) m_batBlockCount++;

   // Required BAT size
   unsigned requiredSize = 
      m_batBlockCount * BIG_BLOCK_SIZE 
      + m_xbatBlockCount * 4
      + m_batBlockCount * 4
      + 8;
   unsigned roundedSize = requiredSize / BIG_BLOCK_SIZE * BIG_BLOCK_SIZE;
   if (requiredSize > roundedSize) roundedSize += BIG_BLOCK_SIZE;
   requiredSize = roundedSize;

   m_batBlockCount = requiredSize / BIG_BLOCK_SIZE;

   m_batBuffer.checkSize(requiredSize);
   m_batBuffer.bytes(requiredSize);

   m_xbatStartBlock = END_OF_CHAIN;
   if (m_xbatBlockCount)
      m_xbatStartBlock = batStartBlock;

   m_batStartBlock = batStartBlock + m_xbatBlockCount;

   // Build BAT index
   unsigned mainIndexSize = BIG_BLOCK_SIZE-_BAT_ARRAY_OFFSET;
   m_batIndex.checkSize(mainIndexSize);
   m_batIndex.bytes(mainIndexSize);
   memset(m_batIndex.data(),0xFF,mainIndexSize);

   unsigned offset = 0;
   unsigned batBlock = 0;
   // Building the BAT index - the part in the header
   for (; batBlock < m_batBlockCount && batBlock < 109; batBlock++) {
      unsigned block = m_batStartBlock + batBlock;
      CExcelRecord::setint(m_batIndex.data(),offset,block);
      offset += 4;
   }

   // Continue to build the BAT index - the XBAT part.
   // It's only needed when m_batBlockCount > 109
   m_xbatIndex.reset();
   unsigned xbatSize = m_xbatBlockCount * 0x200;
   if (xbatSize) {
      m_xbatIndex.checkSize(xbatSize);
      m_xbatIndex.bytes(xbatSize);
      memset(m_xbatIndex.data(),0xFF,xbatSize);
      offset = 0;
      for (; batBlock < m_batBlockCount; batBlock++) {
         unsigned block = m_batStartBlock + batBlock;
         CExcelRecord::setint(m_xbatIndex.data(),offset,block);
         offset += 4;
      }
   }

   // Build BAT
   char *buffer = m_batBuffer.data();
   memset(buffer,0xFF,m_batBuffer.bytes());
   offset = 0;

   unsigned block = m_streamStartBlock + 1;
   for (p = 0; p < properties.size(); p++) {
      CExcelProperty* property = properties[p];
      int cnt = property->blockCount();
      // Very first block number is stored in the property
      if (cnt <= 0) {
         property->firstBATBlock(0xFFFFFFFE);
      } else {
         property->firstBATBlock(block-1);
         for (int streamBlock = 1; streamBlock < cnt; streamBlock++) {
            CExcelRecord::setint(buffer,offset,block);
            offset += 4;
            block++;
         }
         CExcelRecord::setint(buffer,offset,0xFFFFFFFE); // END_OF_CHAIN for the property
         offset += 4;
         block++;
      }
   }

   for (unsigned xbt = 0; xbt < m_xbatBlockCount; xbt++) {
      CExcelRecord::setint(buffer,offset,0xFFFFFFFC);
      offset += 4;
   }

   for (unsigned bt = 0; bt < m_batBlockCount; bt++) {
      CExcelRecord::setint(buffer,offset,0xFFFFFFFD);
      offset += 4;
   }

   CExcelRecord::setint(buffer,offset,0xFFFFFFFE); // END_OF_CHAIN for the property
}

void CExcelBAT::extract(CExcelHeader *fileHeader,FILE *f) {
   const char *excelData = fileHeader->data();
   m_batBlockCount = fileHeader->batBlockCount();
   m_xbatBlockCount = fileHeader->xbatBlockCount();
   m_xbatStartBlock = fileHeader->xbatStartBlock();
   char readBuffer[512];

   m_batIndex.reset();
   m_batBuffer.reset();

   // Getting the main BAT index
   unsigned headerBatBlockCount = m_batBlockCount;
   if (headerBatBlockCount > 109) headerBatBlockCount = 109;
   m_batIndex.append(excelData+0x4C,headerBatBlockCount*4);

   unsigned position = 0;

   if (m_xbatBlockCount) {
      // Reading extended BAT index, if presented
      for (unsigned b = 0; b < m_xbatBlockCount; b++) {
         unsigned xbatBlockIndex = m_xbatStartBlock + b;

         unsigned newPosition = (xbatBlockIndex + 1) * BIG_BLOCK_SIZE;
         if (newPosition != position) {
            unsigned rc = fseek(f,newPosition,SEEK_SET);
            if (rc)
               throw CException("Bad Excel file position request on reading BAT blocks");
            position = newPosition;
         }

         if (fread(readBuffer,1,BIG_BLOCK_SIZE,f) < BIG_BLOCK_SIZE)
            throw CException("Can't read BAT from Excel file");

         unsigned indexBlockSize = BIG_BLOCK_SIZE - 4; // Last integer in XBAT block is ignored.
         if (b == m_xbatBlockCount - 1) { // Adding the END_OF_CHAIN at the end
            for (unsigned offset = 0; offset < BIG_BLOCK_SIZE; offset += 4) {
               int xbatBlockIndex = CExcelRecord::getint(readBuffer,offset);
               if (xbatBlockIndex < 0) {
                  indexBlockSize = offset;
                  break;
               }
            }
         }

         m_batIndex.append(readBuffer,indexBlockSize);

         position += BIG_BLOCK_SIZE;
      }
   }

   unsigned indexSize = m_batIndex.bytes();
   const char *indexData = m_batIndex.data();

   // Reading BAT and XBAT through the combined index
   //printf("\nIndexSize %i\n",indexSize);
   for (unsigned offset = 0; offset < indexSize; offset+=4) {
      int batBlockIndex = CExcelRecord::getint(indexData,offset);

      unsigned newPosition = (batBlockIndex + 1) * BIG_BLOCK_SIZE;

      //printf("%08x Offset %i, batBlockIndex %04x\n",newPosition,offset,batBlockIndex);

      if (newPosition != position) {
         unsigned rc = fseek(f,newPosition,SEEK_SET);
         if (rc)
            throw CException("Bad Excel file position request on reading BAT blocks");
         position = newPosition;
      }

      if (fread(readBuffer,1,BIG_BLOCK_SIZE,f) < BIG_BLOCK_SIZE)
         throw CException("Can't read BAT from Excel file");

      m_batBuffer.append(readBuffer,BIG_BLOCK_SIZE);

      position += BIG_BLOCK_SIZE;
   }
}
