/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          ExcelBook.cpp  -  description
                             -------------------
    begin                : Fri Jul 20 2001
    copyright            : (C) 2001-2005 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 <stdio.h>

#ifdef _DEBUG
  // Debug messages level 0..3
#define DEBUG 0
#endif

#include <sptk3/excel/CExcelBook.h>
#include <sptk3/excel/CExcelBAT.h>
#include <sptk3/excel/CExcelRecord.h>
#include <sptk3/excel/CExcelHeader.h>
#include <sptk3/excel/CExcelProperty.h>
#include <sptk3/excel/CExcelSST.h>
#include <sptk3/excel/CExcelFont.h>


#include <sptk3/CDateTime.h>

#include <ctype.h>
#include <string.h>

#include <sptk3/CException.h>

#ifndef _WIN32
#include <netinet/in.h>
#endif

using namespace std;

//---------------------------------------------------------------------------
CExcelBook::CExcelBook(string name) {
   sheets.attach(this);
   sharedStringTable.parentBook(this);

   m_callback = 0;
   m_name = name;
   m_BIFFversion = 0;

   //xfFormats = new IFMT[MAX_BIFF8FORMAT+1];
   //memcpy(xfFormats,Biff8formats,(MAX_BIFF8FORMAT+1) * sizeof(IFMT));

   m_sstRecordOffsets = NULL;
   m_sstRecordCount = 0;
   m_workBookData = NULL;
}
//---------------------------------------------------------------------------
CExcelBook::~CExcelBook() {
   if (m_sstRecordCount)
      delete m_sstRecordOffsets;
}
//---------------------------------------------------------------------------
void CExcelBook::clear() {
   m_lastError = "";
   m_codePage = 0;
   m_BIFFversion = 0;
   m_records.clear();
   if (m_sstRecordCount) {
      if (m_sstRecordOffsets)
         delete m_sstRecordOffsets;
      m_sstRecordCount = 0;
      m_sstRecordOffsets = 0;
   }
   m_bofPosition = 0;
   sheets.clear();
   sharedStringTable.clear();
   xfRecords.clear();
   m_styles.clear();

   m_excelHeader.clear();
   m_excelBAT.clear();
   m_excelSBAT.clear();
   m_excelProperties.clear();

   if (m_workBookData)
      m_workBookData->reset();
}
//---------------------------------------------------------------------------
void CExcelBook::init() {
   m_styles.init();
   xfRecords.init();
   sheets.clear();
}
//---------------------------------------------------------------------------
void CExcelBook::process_item (const CExcelRecord& record) {

   const char *rec = record.data();

   switch (record.type()) {

      case BOF:
         m_BIFFversion = CExcelRecord::getshort(rec,0);
         break;

      case BOUNDSHEET:
         {
            int pos = CExcelRecord::getint(rec,0);
            unsigned short len;
            char *ptr;
            if (m_BIFFversion < 0x600) {
               len = *(unsigned char *) (rec + 6);
               ptr = (char *) (rec + 7);
            } else {
               len = CExcelRecord::getshort(rec,6);
               ptr = (char *) (rec + 8);
            }
            ptr[len] = 0;
            CExcelSheet *newSheet = new CExcelSheet(ptr,pos,this);
            sheets.push_back(newSheet);
         }
         break;

      case CODEPAGE:
         m_codePage = CExcelRecord::getshort(rec,0);
         break;

      case EXTSST:
         decodeEXTSSTrecord(record);
         break;

      case FORMAT:
         {
            short id = CExcelRecord::getshort(rec,0);
            short len = *(unsigned char *) (rec + 2);
            string format(rec + 5,len);
            xfRecords.addNumericFormat(id,format);
         }
         break;

      case FONT1:
      case FONT2:
         xfRecords.addFont(record.bytes(),record.data());
         break;

      case SST:
         decodeSSTrecord(record);
         break;

      case STYLE:
         m_styles.push_back(CExcelStyle(rec));
         break;

      case XF:
         xfRecords.insert(rec);
         break;
   }
}

int CExcelBook::parseWorkBookGlobals(unsigned& pos) {
   m_records.clear();
   sharedStringTable.clear();
   xfRecords.clear();

   CExcelRecord   *record = NULL;

   unsigned eof = m_workBookData->bytes();
   char *data = m_workBookData->data();
   char *curr = 0L;
   char *curr2 = 0L;

#if DEBUG > 1
   string lfname = "tmp/CExcelBook("+m_name+").log";
   FILE *logFile = fopen(lfname.c_str(),"wt");
   if (!logFile) printf("can't open file %s for logs.\n",lfname.c_str());
#endif

   int percentage = -1;

   while ( pos < eof ) {
      short rectype = CExcelRecord::getshort(data,pos);
      short reclen  = CExcelRecord::getshort(data,pos+2);
      short reclen2 = 0;
      pos += 4;

      int perc = (pos * 100) / eof;
      if (perc != percentage) {
         percentage = perc;
         do_callback("Reading workbook",perc);
      }

#if DEBUG > 1
      if (logFile) {
         fprintf(logFile,"%06x (%04x) %-15s \treclen = %-3i ",pos,rectype,CExcelRecord::gettypename(rectype),reclen);
         for (int b = 0; b < reclen; b++) {
            unsigned char ch = data[pos+b];
            fprintf(logFile," %02x",ch);
         }
         fprintf(logFile,"\n");
      }
#endif

      if ( rectype == MSEOF ) break;

      curr = 0;

      if ( reclen >= 0 ) {
         curr = data + pos;
         pos += reclen;
      } else {
         char error[256];
         sprintf(error,"Record size is negative, record @ offset %06x",pos-4);
         throw CException(error,__FILE__,__LINE__);
      }

      if (rectype == CONTINUE) {
         char *ptr = curr;

         if (*ptr == 0 || *ptr == 1) {  // Workaround: sometimes combined records contain '\0'
            ptr++;         // as a very first character in a buffer. It should be excluded
            reclen--;      // at least for SST records.
         }

         record->append(ptr,reclen);
         if (reclen2)
            record->append(curr2,reclen2);
      } else {
         record = new CExcelRecord(rectype,reclen,curr);
         record->append(curr2,reclen2);
         m_records.push_back(record);
      }
   }

   unsigned i;
   for (i = 0; i < m_records.size(); i++) {
      try {
         CExcelRecord& record = *m_records[i];
         process_item(record);
      }
      catch (exception& e) {
         throw CException("Error parsing Workbook records:\n"+string(e.what()));
      }
   }
   /* 
   int maxFormatID = 0;
   for (i=0; i < m_formatTable.size(); i++) {
      if (maxFormatID < m_formatTable[i].id) maxFormatID = m_formatTable[i].id;
   }

   if (maxFormatID > MAX_BIFF8FORMAT) {
      IFMT *t = new IFMT[maxFormatID+1];
      memcpy(t,xfFormats,sizeof(IFMT)*(MAX_BIFF8FORMAT+1));
      delete xfFormats;
      xfFormats = t;
   }

   for (i=0; i < m_formatTable.size(); i++) {
      if ( strlen( m_formatTable[i].c_str() ) ) {
         short formatID = (short)m_formatTable[i].id;
         IFMT *xfFormat = xfFormats + formatID;
         xfFormat->id = (unsigned char)formatID;
         xfFormat->format = (char *)m_formatTable[i].c_str();
         if ( strchr(xfFormat->format,'$') )
            xfFormat->kind = VAR_MONEY;
         else
            if ( strstr(xfFormat->format,"mm")
               || strstr(xfFormat->format,"dd")
               || strstr(xfFormat->format,"yy")
               || strstr(xfFormat->format,"ss")
               || strstr(xfFormat->format,"hh") ) {
               xfFormat->kind = VAR_NONE;
               if ( strstr(xfFormat->format,"d") || strstr(xfFormat->format,"y") )
                  xfFormat->kind = VAR_DATE;
               if ( strstr(xfFormat->format,"s") || strstr(xfFormat->format,"h") )
                  xfFormat->kind = VAR_DATE_TIME;
            }
            else
               if ( strstr(xfFormat->format,".0") )
                  xfFormat->kind = VAR_FLOAT;
               else
                  xfFormat->kind = VAR_STRING;
      }
   }
*/ 
#if DEBUG > 1
   if (logFile)

      fclose(logFile);
#endif

   return sheets.size();
}

//---------------------------------------------------------------------------
// Find and get sheets information
//---------------------------------------------------------------------------
int CExcelBook::scanSheetsInfo(FILE *f) {

   do_callback("Loading file",0);

   // Initialize the header
   m_excelHeader.read(f);

   // Read the BAT
   m_excelBAT.extract(&m_excelHeader,f);

   // Read the properties
   m_excelProperties.extract(&m_excelHeader,&m_excelBAT,f);

   CExcelProperty *workBookProperty = m_excelProperties.find("Workbook");
   if (!workBookProperty)
      workBookProperty = m_excelProperties.find("Book");

   if (!workBookProperty)
      throw CException("Can't find 'Workbook' or 'Book' property.",__FILE__,__LINE__);

   m_workBookData = &workBookProperty->streamData();

   // Brute force continuous scan of the Excel data
   // searching for the workbook and worksheet files inside
   bool workBookGlobalsScanned = false;
   unsigned currentWorkSheet = 0;
   unsigned sz = m_workBookData->bytes();
   const char *buf = m_workBookData->data();
   for (unsigned pos = m_bofPosition; pos < sz; pos++) {
      if (buf[pos] == 0x09 && buf[pos+1] == 0x08) {
         // Found the beginning of file
         switch (buf[pos+6]) {
            case 0x5:
               // Workbook globals
               parseWorkBookGlobals(pos);
               workBookGlobalsScanned = true;
               pos--;
               break;

            case 0x10:
               // Worksheet or dialog sheet
               if (!workBookGlobalsScanned) {
                  // The first thing we found is a worksheet
                  sheets.push_back(new CExcelSheet("Sheet 1",pos,this));
               }
               if (currentWorkSheet >= sheets.size()) {
                  throw CException("Incorrect worksheet number - greater than number of worksheets!",__FILE__,__LINE__);
               } else {
                  CExcelSheet *sheet = sheets[currentWorkSheet];
                  sheet->parseWorkSheet(*m_workBookData,pos);
                  currentWorkSheet++;
                  pos--;
               }
               if (workBookGlobalsScanned && currentWorkSheet == sheets.size()) {
                  do_callback("Completed",100);
                  return sheets.size();
               }
               break;
         }
      }
   }
   do_callback("Completed",100);

   return sheets.size();
}
//---------------------------------------------------------------------------
int CExcelBook::scanExcelFile(FILE *f) {
   if ( !scanSheetsInfo(f) ) {
      m_lastError = "No sheets found";
      return 0;
   }
   return 1;
}

int CExcelBook::readExcelFile(string fileName) {
   clear();
   FILE *f = fopen(fileName.c_str(),"rb");
   if (!f) throw CException("Can't open Excel file '"+fileName+"'");
   int rc = scanExcelFile(f);
   fclose(f);
   if (!rc) return 0;
   return 1;
}
//---------------------------------------------------------------------------
unsigned unicodeToString(CBuffer& dest, const char *ptr,unsigned len) {
   dest.checkSize(len);
   char *d = dest.data();
   const char *p = ptr;
   for (unsigned chars = 0;chars < len;chars++) {
      *d = *p;
      d++;
      p += 2;
   }
   *d = 0;
   dest.bytes(len);
   return len * 2;
}

unsigned regularThenUnicodeToString(CBuffer& dest, const char *ptr,unsigned len) {
   unsigned bytes = 0;
   dest.checkSize(len);
   char *d = dest.data();
   const char *p = ptr;
   unsigned chars = 0;
   bool unicode = false;
   for (;chars < len;chars++) {
      if ((unsigned char)*(p+1) == 0)
         unicode = true;
      if (unicode) {
         *d = *p;
         d++;
         p += 2;
      } else {
         *d = *p;
         d++;
         p++;
      }
   }
   *d = 0;
   bytes = p - ptr;
   dest.bytes(chars);
   return bytes;
}

bool sstAttributeValid(unsigned char attr) {
   if (attr > 8) return false;
   return (attr == 0 || attr == 1);
}

bool sstStringValid(const char *ptr,unsigned & offset) {
   // Check the length
   unsigned len = CExcelRecord::getshort(ptr,0);
   if (!len) return false;

   // Check the attributes
   unsigned char  attr = *(ptr + 2);
   bool           unicode = attr & 1;
   if ( !sstAttributeValid(attr) ) return false;
   if (unicode) len *= 2;

   // Check the first character
   char *text = (char *)ptr + 3;
   if ( *(unsigned char *) (ptr + 3) < 0xA ) return false;

   // Check the actual length (non-unicode only);
   char *str = text + len;
   char saveChar = *str;
   *str = 0;
   if ( !unicode && strlen(text) < len) {
      *str = saveChar;
      return false;
   }

   for (unsigned i = 0; i < len; i++) {
      if ( (unsigned char) text[i] < 0xA ) {
         *str = saveChar;
         return false;
      }
   }

   *str = saveChar;

   offset = len + 3;

   return true;
}

bool sstRecordValid(const char *ptr) {
   unsigned offset;

   // Examine the current string
   if ( !sstStringValid(ptr,offset) )
      return false;

   ptr += offset;

   // Examine the next string
   return sstStringValid(ptr,offset);
}

unsigned bufferPosition = 0;

int CExcelBook::decodeSSTrecord(const CExcelRecord& record) {
#if DEBUG > 2
   FILE *f = fopen("tmp/sst","w+t");
   if (f) {
      fwrite((char *)record.data(),1,record.bytes(),f);
      fclose(f);
   }
#endif
   const char *rec = (char *)record.data();
   const char *ptr = rec + 8;
   unsigned reclen = record.bytes();
   //rec[reclen] = 0;
   CBuffer buffer;
   bufferPosition = 0;
   while (ptr < rec + reclen) {
      bufferPosition = ptr - record.data();
      unsigned short len = CExcelRecord::getshort(ptr,0);
      bool           unicode = *(ptr + 2) & 1;
      unsigned			formattingRuns = 0;

      if (sharedStringTable.size()>1497) {
         formattingRuns = 0;
      }

      if ((*(ptr+2) & 0x08) && *(ptr+4) == 0x00) {
         formattingRuns = *(ptr+3);
         ptr += 2;
      }
      buffer.checkSize(len+1);
      ptr += 3;
      unsigned bytes = len;
      if (unicode)
         bytes = unicodeToString(buffer,ptr,len);
      else {
         buffer.set(ptr,len);
         buffer.data()[len] = 0;
         if (strlen(buffer.data()) < len) {
            // 0-chars in the middle of the string,
            // means that string started as regular and
            // then turned into unicode
            // use partial unicode workaround
            bytes = regularThenUnicodeToString(buffer,ptr,len);
         }
      }
      ptr += bytes;
      int l1 = buffer.bytes();
      if ( l1 < len ) {
         while (!sstRecordValid(ptr)) {
            ptr++;
         }
      }
      if (formattingRuns)
         ptr += formattingRuns * 4;
      sharedStringTable.forceInsert(buffer.data());
   }
   //printf("SST: %i rows\n",sharedStringTable.size());
   return sharedStringTable.size();
}
//---------------------------------------------------------------------------
#define ISSTINF_RECORD_SIZE 8

int CExcelBook::decodeEXTSSTrecord(const CExcelRecord& record) {
   unsigned reclen = record.bytes();
   char *rec = new char[reclen];
   memcpy(rec,record.data(),reclen);

   int recordCount = reclen / ISSTINF_RECORD_SIZE;

   if (m_sstRecordCount) {
      delete m_sstRecordOffsets;
      m_sstRecordOffsets = NULL;
   }

   if (recordCount)
      m_sstRecordOffsets = new int[recordCount];

   m_sstRecordCount = recordCount;

   for (int i = 2; i < recordCount; i++) {
      //int dest = 0;
      int offset = 2 + ISSTINF_RECORD_SIZE * i;
      //memcpy(&dest,rec+offset,sizeof(int));
      //int streamPos = reorderBytes(dest);
      int streamPos = CExcelRecord::getint(rec,offset);
      m_sstRecordOffsets[i] = streamPos;
   }

   delete rec;

   return 1;
}

typedef CExcelRecord * PExcelRecord;

void CExcelBook::saveRecords(CExcelRecordsBuffer& records) {
   // BOF record - the beginning of the workbook
   records.reset();
   records.append(BOF,16,"\x0\x6\x5\x0\xBB\xD\xCC\x7\x41\x0\x0\x0\x6\x0\x0\x0");

   // Interface
   records.append(INTERFACEHDR,2,"\xB0\x4");
   records.append(MMS,2,"\x0\x0");
   records.append(INTERFACEEND,0,"");

   // Write Access
   {
      char *ptr = new char[113];
      memset(ptr,' ',112);
      CExcelRecord::setshort(ptr,0,4);
      ptr[2] = 0;
      memcpy(ptr+3,"Alex",4);
      records.append(WRITEACCESS,112,ptr);
      delete ptr;
   }

   // Defining a codepage
   records.append(CODEPAGE,2,"\xB0\x4");

   // This is not a double-stream file
   records.append(DSF,2,"\x0\x0");
   //records.append(0x1C0,0,"");

   // TABID record - the index of sheets
   {
      short tabidBufferSize = sheets.size() * 2;
      char *tabidBuffer = new char[tabidBufferSize];
      for (unsigned i = 0; i < sheets.size(); i++)
         CExcelRecord::setshort(tabidBuffer,i*2,i+1);
      records.append(TABID,tabidBufferSize,tabidBuffer);
      delete tabidBuffer;
   }

   records.append(FNGROUPCOUNT,2,"\x0E\x00");
   records.append(WINDOWPROTECT,2,"\x00\x00");
   records.append(PROTECT,2,"\x00\x00");
   records.append(PASSWORD,2,"\x00\x00");
   records.append(PROT4REV,2,"\x00\x00");
   records.append(PROT4REVPASS,2,"\x00\x00");

   records.append(WINDOW1,18,"\x78\x00\x4B\x00\x9F\x42\x50\x28\x38\x00\x00\x00\x00\x00\x01\x00\x58\02");

   records.append(BACKUP,2,"\x00\x00");
   records.append(HIDEOBJ,2,"\x00\x00");
   records.append(MS1904,2,"\x00\x00");
   records.append(PRECISION,2,"\x01\x00");
   records.append(REFRESHALL,2,"\x00\x00");
   records.append(BOOKBOOL,2,"\x00\x00");

   // Insert the Fonts information. At least five fonts must be there.
   xfRecords.m_fonts.saveRecords(records);

   xfRecords.saveRecords(records);
   palette.saveRecords(records);

   unsigned styleCount = m_styles.size();
   for (unsigned styleIndex = 0; styleIndex < styleCount; styleIndex++) {
      CExcelStyle& style = m_styles[styleIndex];
      records.append(STYLE,4,style.data());
   }

   records.append(USESELFS,2,"\x1\x0");

   /// The SST should be rebuilt - we may have some changes in the data
   /// not reflected in original SST
   CExcelSST& sst = sharedStringTable;
   sst.clear();

   // The sheets information.
   // Sheet records should be updated later with the correct value for the sheet position
   // in the file
   unsigned sheetIndex = 0;
   for (; sheetIndex < sheets.size(); sheetIndex++) {
      CExcelSheet *sheet = sheets[sheetIndex];
      unsigned nameLength = sheet->name().length();
      char *ptr = new char[nameLength+9];
      CExcelRecord::setint(ptr,0,0);                  // Sheet' BOF position - to be determined
      CExcelRecord::setshort(ptr,4,0);                // Sheet options
      CExcelRecord::setshort(ptr,6,nameLength);       // Sheet name length
      memcpy(ptr+8,sheet->name().c_str(),nameLength);  // Sheet name
      records.addSheetPosition(records.bytes());
      records.append(BOUNDSHEET,nameLength+8,ptr);
      delete ptr;

      unsigned maxRow = sheet->rowCount();
      unsigned maxCol = sheet->colCount();
      for (unsigned r=0; r < maxRow; r++) {
         for (unsigned c=0; c < maxCol; c++) {
            CExcelRow& arow = sheet->rows[r];
            CExcelCell& cell = arow[c];
            if (cell.dataType() == VAR_STRING)
               sst.insert(cell.asString().c_str());
         }
      }
   }

   records.append(COUNTRY,4,"\x1\x0\x1\x0");
   //records.append(0x1C0,8,"\xC1\x01\x0\x0\x22\xbe\x01\x00");

   sst.saveRecords(records);

   records.append(MSEOF,0,"");

   unsigned sheetStreamPosition = records.bytes();

   for (sheetIndex = 0; sheetIndex < sheets.size(); sheetIndex++) {
      CExcelSheet *sheet = sheets[sheetIndex];
      sheet->encode(sheetStreamPosition);
      sheetStreamPosition += sheet->recordsBuffer().bytes();
#if DEBUG > 2
      sheet->recordsBuffer().saveToFile("tmp/"+sheet->name()+"-saved.dat");
#endif
   }

   unsigned sheetStarts = records.bytes();

   for (sheetIndex = 0; sheetIndex < sheets.size(); sheetIndex++) {
      CExcelSheet *sheet = sheets[sheetIndex];

      // Updating BOF position in the BOUNDSHEET record
      unsigned offset = records.sheetPosition(sheetIndex);
      char *ptr = records.data()+offset+4;
      CExcelRecord::setint(ptr,0,sheetStarts);  // Sheet' BOF position
      sheetStarts += sheet->recordsBuffer().bytes();

      // Appending sheet's data to the file
      records.append(sheet->recordsBuffer());
   }
}

unsigned CExcelBook::blockCount(unsigned start,unsigned end,unsigned blockSize) {
   unsigned bc = (end - start) / blockSize;
   if (bc * blockSize < (end - start)) bc += blockSize;
   return bc;
}

int CExcelBook::writeExcelFile(string fileName) {
   FILE *outputFile = fopen(fileName.c_str(),"w+b");
   if (!outputFile)
      throw CException("Can't create file '"+fileName+"'");

   int rc = writeExcelFile(outputFile);

   fclose(outputFile);

   return rc;
}

int CExcelBook::writeExcelFile(FILE *outputFile) {
   CExcelHeader  excelHeader;
   CExcelRecordsBuffer records(4096);

   CBuffer extraInfoBuffer(0x1000);
   extraInfoBuffer.bytes(extraInfoBuffer.size());
   //extraInfoBuffer.bytes(0);
   char *eptr = extraInfoBuffer.data();
   memset(eptr,0,extraInfoBuffer.bytes());
   eptr[0] = char(0xFE);
   eptr[1] = char(0xFF);
   strcpy(eptr+2,
      "This file is created with Simply Powerful Toolkit (SPTK) "
      "(C) Alexey Parshin 2001-2005 (http://sptk.tts-sf.com)");

   // Create a records array
   saveRecords(records);

   unsigned actualSize = records.bytes(); 
   if (actualSize < 4097) actualSize = 4097;
   unsigned finalSize = actualSize / BIG_BLOCK_SIZE * BIG_BLOCK_SIZE;
   if (finalSize < actualSize) finalSize += BIG_BLOCK_SIZE;

   unsigned tailSize = finalSize - records.bytes();
   if (tailSize) {
      char *tail = new char[tailSize];
      memset(tail,0,tailSize);
      records.append(tail,tailSize);
      delete tail;
   }

   CBuffer& streamData = records;

   unsigned fullStreamSize = streamData.bytes();

   // Define main BAT
   m_excelBAT.clear();
   //m_excelBAT.streamBlockCount(fullStreamSize / BIG_BLOCK_SIZE);

   unsigned currentBlock = 0;

   // Place XBAT right after the stream
   // Place BAT after XBAT
   unsigned firstStreamBlock = currentBlock;
   m_excelBAT.streamStartBlock(firstStreamBlock);
   m_excelBAT.streamBlockCount(fullStreamSize / BIG_BLOCK_SIZE);

   // Define the Property Table
   CExcelProperties  properties;
   properties.push_back(new CExcelProperty("Root Entry",5,_NODE_BLACK,0xFFFFFFFF,0xFFFFFFFF,2,0xFFFFFFFE,0));
   properties.push_back(new CExcelProperty("Workbook",2,_NODE_BLACK,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,firstStreamBlock,actualSize));
   properties.push_back(new CExcelProperty("\x05SummaryInformation",2,_NODE_BLACK,1,3,0xFFFFFFFF,firstStreamBlock+extraInfoBuffer.bytes(),extraInfoBuffer.bytes()));
   properties.push_back(new CExcelProperty("\x05""DocumentSummaryInformation",2,_NODE_BLACK,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,firstStreamBlock+extraInfoBuffer.bytes()*2,extraInfoBuffer.bytes()));

   // BAT parameters are defined, build it
   unsigned batStartBlock = firstStreamBlock+(fullStreamSize + extraInfoBuffer.bytes()*2) / BIG_BLOCK_SIZE;
   m_excelBAT.build(&properties,batStartBlock);

   // Update BAT block count and
   excelHeader.setBatTable(m_excelBAT);

   // Property block starts at ..
   excelHeader.setPropertyTableStart(m_excelBAT.batStartBlock() + m_excelBAT.bytes() / BIG_BLOCK_SIZE);

   unsigned bytes;
   //unsigned i;

   // Writing the header
   bytes = fwrite(excelHeader.data(),1,BIG_BLOCK_SIZE,outputFile);
   if (bytes < BIG_BLOCK_SIZE)
      throw CException("Can't write Excel header to output file - disk is full?");

   fwrite(streamData.data(),1,streamData.bytes(),outputFile);
   fwrite(extraInfoBuffer.data(),1,extraInfoBuffer.bytes(),outputFile);
   fwrite(extraInfoBuffer.data(),1,extraInfoBuffer.bytes(),outputFile);

   // Storing XBAT
   if (m_excelBAT.xbatBlockCount()) {
      bytes = fwrite(m_excelBAT.xbatIndex().data(),BIG_BLOCK_SIZE,m_excelBAT.xbatBlockCount(),outputFile);
      if (bytes < m_excelBAT.xbatBlockCount())
         throw CException("Can't write Excel XBAT to output file - disk is full?");
   }

   // Storing BAT blocks ..
   bytes = fwrite(m_excelBAT.batData().data(),1,m_excelBAT.bytes(),outputFile);
   if (bytes < m_excelBAT.bytes())
      throw CException("Can't write Excel BAT to output file - disk is full?");

   properties.save(outputFile);

   return true;
}

int CExcelBook::readCSVFile(string fileName) {
   clear();
   string sheetName = "Sheet " + int2string(sheets.size());
   CExcelSheet *newSheet = new CExcelSheet(sheetName.c_str(),0,this);
   newSheet->readCSVFile(fileName);
   sheets.push_back(newSheet);
   return true;
}

int CExcelBook::writeCSVFile(string fileName,unsigned sheetNumber) {
   CExcelSheet *sheet = sheets[sheetNumber];
   sheet->writeCSVFile(fileName);
   return true;
}

int CExcelBook::writeCSVFile(FILE *outputFile,unsigned sheetNumber) {
   CExcelSheet *sheet = sheets[sheetNumber];
   sheet->writeCSVFile(outputFile);
   return true;
}

void CExcelBook::do_callback(const char *operation,int percentage) {
   if (!m_callback) return;
   m_callback(operation,percentage);
}

CExcelSheet * CExcelBook::newSheet(const char *sheetName) {
   CExcelSheet *sheet = new CExcelSheet(sheetName,0,this);
   sheets.push_back(sheet);
   return sheet;
}

unsigned CExcelBook::defineFont(const char *fontName,unsigned fontHeight,unsigned fontAttr,unsigned charSet) {
   return xfRecords.addFont(fontName,fontHeight,fontAttr,charSet);
}

const CExcelFont& CExcelBook::font(unsigned _fontIndex) const {
   const CExcelFonts& fonts = xfRecords.m_fonts;
   return fonts[_fontIndex];
}

CExcelCellFormat *CExcelBook::defineFormat(
   unsigned fontIndex,unsigned numericFormatIndex,
   unsigned align,bool wrap,CExcelBorderLine border,
   unsigned pattern,
   unsigned patternForeColorIndex,unsigned patternBackColorIndex
)
{
   CExcelCellFormat *f = xfRecords.insert(
         fontIndex,numericFormatIndex,
         align,wrap,border,
         pattern,patternForeColorIndex,patternBackColorIndex
      );
   return f;
}
