/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CExcelSST.cpp  -  description
                             -------------------
    begin                : Tue Apr 6 2004
    copyright            : (C) 2000-2006 by Alexey S.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/CExcelSST.h>
#include <sptk3/CException.h>
#include <vector>

using namespace std;
using namespace sptk;

unsigned CExcelSST::forceInsert(const char *str) {
   m_totalBookStrings++;
   unsigned index = m_stringList.size();
   unsigned len = strlen(str);
   m_stringList.push_back(str);
   m_stringMap[str] = index;
   m_sstSize += 3 + len;
   return index;
}

unsigned CExcelSST::insert(const char *str) {
   m_totalBookStrings++;
   CSstStringMap::iterator itor = m_stringMap.find(str);
   if (itor != m_stringMap.end()) {
      return itor->second;
   } else {
      unsigned index = m_stringList.size();
      unsigned len = strlen(str);
      m_stringList.push_back(str);
      m_stringMap[str] = index;
      m_sstSize += 3 + len;
      return index;
   }
}

void CExcelSST::clear() {
   m_stringList.clear();
   m_stringMap.clear();
   m_totalBookStrings = 0;
   m_sstSize = 8;
}

void CExcelSST::saveLongString(CExcelRecordsBuffer& sstRecords,short& recordType,char *sstData,unsigned& recordStart,unsigned& pos,unsigned len) {
   unsigned serviceSize = 0;
   if (recordType == SST) serviceSize = 8;

   unsigned occupiedSpace = pos - recordStart;
   unsigned availableInRecord = 8224 - occupiedSpace;

   unsigned endPos = pos + serviceSize + 3 + len;
   bool firstChunk = true;

   while (pos < endPos) {
      unsigned chunkSize = endPos - pos;
      if (chunkSize > availableInRecord)
         chunkSize = availableInRecord;
      if (firstChunk) {
         sstRecords.append(recordType,8224,sstData+recordStart);
         availableInRecord = 8223;
         recordType = CONTINUE;
         firstChunk = false;
      } else {
         char *ptr = sstData + pos -1;
         *ptr = 0;
         if (chunkSize+1 < 8224) {
            recordStart = pos - 1;
            pos += chunkSize;
            break;
         } else {
            sstRecords.append(recordType,chunkSize+1,ptr);
         }
      }
      pos += chunkSize;
   }
}

struct CExcelESSTBucket {
   unsigned start;
   unsigned offset;
};

#include <stdio.h>

void CExcelSST::saveRecords(CExcelRecordsBuffer& records) {
   unsigned stringsCount = m_stringList.size();
   if (!stringsCount) return;

   vector<CExcelESSTBucket>  extSSTBuckets;

   // Creating the raw SST data
   char *sstData = new char[m_sstSize];
   CExcelRecord::setint(sstData,0,m_totalBookStrings);
   CExcelRecord::setint(sstData,4,stringsCount);
   unsigned bufferPosition = 8;
   for (unsigned i = 0; i < stringsCount; i++) {
      idstring& str = m_stringList[i];
      unsigned stringLength = str.length();
      CExcelRecord::setint(sstData,bufferPosition,stringLength);
      memcpy(sstData+bufferPosition+3,str.c_str(),stringLength);
      bufferPosition += stringLength + 3;
   }
   if (bufferPosition != m_sstSize)
      throw CException("Expected SST size doesn't match actual size.",__FILE__,__LINE__);

   unsigned recordStart = 0;
   unsigned pos = 8;
   unsigned maxRecordLength = 8224;
   short    recordType = SST;
   unsigned stringNumber = 0;

   unsigned bucketSize = unsigned((stringsCount / 128.0) + 1);
   if (bucketSize < 8) bucketSize = 8;
   if (bucketSize > 65535) 
      bucketSize = 65535;
   //bucketSize = bucketSize / 2 * 2;
   CExcelESSTBucket bucket;

   unsigned stringsInBucket = bucketSize;

   while (pos < m_sstSize) {

      if (stringsInBucket == bucketSize) {
         bucket.offset = pos - recordStart + 4;
         bucket.start = records.bytes() + bucket.offset;
         extSSTBuckets.push_back(bucket);
         stringsInBucket = 0;
      }

      // Read the string
      unsigned stringLength = CExcelRecord::getshort(sstData,pos);
      unsigned nextStringPosition = pos + 3 + stringLength;
      unsigned recordLength = nextStringPosition - recordStart;

      if (recordLength > maxRecordLength) {
         recordLength = pos - recordStart;
         if (stringLength > maxRecordLength-3) {
            saveLongString(records,recordType,sstData,recordStart,pos,stringLength);
         } else {
            records.append(recordType,recordLength,sstData+recordStart);
            recordStart = pos;
         }
         recordType = CONTINUE;
      }
      pos = nextStringPosition;
      stringNumber++;
      stringsInBucket++;
   }
/*
   if (stringsInBucket) {
      bucket.offset = pos - recordStart + 4;
      bucket.start = records.bytes() + bucket.offset;
      extSSTBuckets.add(bucket);
      stringsInBucket = 0;
   }
*/
   bucket.offset = 0;
   bucket.start = 0;
   extSSTBuckets.push_back(bucket);

   if (recordStart < pos) {
      unsigned recordLength = pos - recordStart;
      if (recordStart == 0)
         records.append(SST,recordLength,sstData);
      else
         records.append(CONTINUE,recordLength,sstData+recordStart);
   }

   delete sstData;

   // Creating the EXTSST record
   unsigned bucketsCount = extSSTBuckets.size();
   if (!bucketsCount) bucketsCount = 1;
   unsigned esstBufferSize = 2 + 8 * bucketsCount;
   if (esstBufferSize < 1018) esstBufferSize = 1018;
   if (esstBufferSize > 1022) esstBufferSize = 2042;
   char *esstBuffer = new char[esstBufferSize];
   memset(esstBuffer,0,esstBufferSize);
   CExcelRecord::setshort(esstBuffer,0,bucketSize);
   pos = 2;
   unsigned cnt = extSSTBuckets.size();
   for (unsigned b = 0; b < cnt; b++) {
      CExcelESSTBucket& bucket = extSSTBuckets[b];
      CExcelRecord::setint(esstBuffer,pos,bucket.start);
      CExcelRecord::setint(esstBuffer,pos+4,bucket.offset);
      pos += 8;
   }
   records.append(EXTSST,esstBufferSize,esstBuffer);
   delete esstBuffer;
}

int CExcelSST::index(const char *str) const {
   CSstStringMap::const_iterator itor = m_stringMap.find(str);
   if (itor != m_stringMap.end())
      return itor->second;
   else return -1;
}
