/*! \file oldlzw.cpp
    \brief Some old LZW encoding and decoding stuff. Included by basestrm.cpp
   
    ASC does not use LZW for compressing new files, since the bzlib is the new
    compression library that replaces this one. But there are old files around
    which should still be loaded.
*/

//     $Id: oldlzw.cpp,v 1.9 2005/05/26 15:03:28 mbickel Exp $
//
//     $Log: oldlzw.cpp,v $
//     Revision 1.9  2005/05/26 15:03:28  mbickel
//      Fixed endian problems
//
//     Revision 1.8  2001/10/21 16:07:22  mbickel
//      New vehicle function: jam only own field
//      Fixed compilation problem
//
//     Revision 1.7  2001/02/18 15:37:16  mbickel
//      Some cleanup and documentation
//      Restructured: vehicle and building classes into separate files
//         tmap, tfield and helper classes into separate file (gamemap.h)
//      basestrm : stream mode now specified by enum instead of int
//
//     Revision 1.6  2001/01/28 14:04:15  mbickel
//      Some restructuring, documentation and cleanup
//      The resource network functions are now it their own files, the dashboard
//       as well
//      Updated the TODO list
//
//     Revision 1.5  2000/10/11 14:26:44  mbickel
//      Modernized the internal structure of ASC:
//       - vehicles and buildings now derived from a common base class
//       - new resource class
//       - reorganized exceptions (errors.h)
//      Split some files:
//        typen -> typen, vehicletype, buildingtype, basecontainer
//        controls -> controls, viewcalculation
//        spfst -> spfst, mapalgorithm
//      bzlib is now statically linked and sources integrated
//
//     Revision 1.4  2000/05/03 14:49:14  mbickel
//      Fixed a bug in LZW decompression
//
//     Revision 1.3  1999/11/22 18:27:47  mbickel
//      Restructured graphics engine:
//        VESA now only for DOS
//        BASEGFX should be platform independant
//        new interface for initialization
//      Rewrote all ASM code in C++, but it is still available for the Watcom
//        versions
//      Fixed bugs in RLE decompression, BI map importer and the view calculation
//
//     Revision 1.2  1999/11/16 03:42:17  tmwilson
//     	Added CVS keywords to most of the files.
//     	Started porting the code to Linux (ifdef'ing the DOS specific stuff)
//     	Wrote replacement routines for kbhit/getch for Linux
//     	Cleaned up parts of the code that gcc barfed on (char vs unsigned char)
//     	Added autoconf/automake capabilities
//     	Added files used by 'automake --gnu'
//
//
/*
    This file is part of Advanced Strategic Command; http://www.asc-hq.de
    Copyright (C) 1994-1999  Martin Bickel  and  Marc Schellenberger

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

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

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING. If not, write to the 
    Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
    Boston, MA  02111-1307  USA
*/



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


tlzwstreamcompression  :: tlzwstreamcompression ( void )
{
    mode = none;
    initread = 0;
    initwrite = 0;
    DecodeBufferSize = 0;    
    DecodeBuffer = NULL;
    rdictionary = NULL;
    readcnt = 0;
    wdictionary = NULL;
}


void tlzwstreamcompression  :: LZWOut ( CodeType code )
{
    writelzwdata ( &code, sizeof( CodeType )) ;

    LZWTotBytesOut += 2;
    LZWCurrBytesOut += 2;
}




/* our hashing lookup function */
 IndexType tlzwstreamcompression  :: LZWFind ( CodeType currcode, int in )
{
    IndexType ndx;
    int step = 11, pastzero = 0;

    ndx = ( ( IndexType ) currcode << 8 ) | in;
    ndx = ndx % DICTIONARY_SIZE;

    for ( ;; )
    {
        if ( wdictionary[ ndx ].code == UNUSED_CODE )
            break;
        if ( wdictionary[ ndx ].parent == currcode &&
             wdictionary[ ndx ].c == in )
            break;
        ndx += step;
        if ( ndx >= DICTIONARY_SIZE)
        {
            ndx -= DICTIONARY_SIZE;
            pastzero += 1;

            /*
             * Next is a safety check. If the step
             * value and the dictionary size are
             * relatively prime, there should never
             * be a problem. However, let's not loop
             * too many times.
             */

            if ( pastzero > 5 )
                step = 1;
        }

    }
    return ( ndx );
}


 void tlzwstreamcompression  :: initreading ( void )
{
  if ( mode == none ) {    // 1: read, 2: write
     char tempreadbuf[10];
     int tempreadbufsize = 0;

     int lzw = 1;
     int i;

     for (i = 0; i < strlen ( LZ_SIGNATURE ) + 3; i++) {
        try {
           readlzwdata ( &tempreadbuf[tempreadbufsize], 1 );
           tempreadbufsize++;
        } /* endtry */

        catch ( treadafterend ) {
           tempreadbufsize--;
           lzw = 0;
        } /* endcatch */
     }

     if ( lzw ) {
        if ( tempreadbuf[0] )
           lzw = 0;
        if ( strcmp ( &tempreadbuf[1], LZ_SIGNATURE ) ) 
           lzw = 0;
        if ( tempreadbuf[strlen ( LZ_SIGNATURE ) + 2] )
           lzw = 0;
     }
     if ( lzw == 1 ) {
        mode = reading;

        rdictionary = new Rdictionary [DICTIONARY_SIZE];
    
        if ( rdictionary == NULL )
            throw toutofmem ( DICTIONARY_SIZE * sizeof( struct Rdictionary ) );
     } else {
        lzw = 2;
        if ( tempreadbuf[0] )
           lzw = 0;
        if ( strcmp ( &tempreadbuf[1], RLE_SIGNATURE ) ) 
           lzw = 0;
        if ( tempreadbuf[strlen ( RLE_SIGNATURE ) + 2] )
           lzw = 0;

        if ( lzw == 2 ) {
           rlestartbyte = tempreadbuf[strlen ( RLE_SIGNATURE ) + 3];

           rlenum = 0;
           mode = readingrle;

        } else {
           for ( i = 0; i < tempreadbufsize; i++ )
              tempbuf.push ( tempreadbuf[i] );

           mode = readingdirect;
        }
     }
     initread = 1;

  } else
    if ( initwrite )
       throw tinvalidmode ( "tlzwstreamcompression ", tnstream::IOMode ( mode ), tnstream::reading );
}



 void tlzwstreamcompression  :: initwriting ( void )
{
  if ( mode == none ) {    

      freecode = STARTING_CODE;
  
      LZWTotBytesIn = 0;
      LZWTotBytesOut = 0;
      LZWCurrBytesIn = 0;
      LZWCurrBytesOut = 0;
  
      char c = 0;
      writelzwdata ( &c, 1 );
      writelzwdata ( LZ_SIGNATURE, strlen ( LZ_SIGNATURE ) + 1 );
      writelzwdata ( &c, 1 );
  
  
      wdictionary = new Wdictionary [ DICTIONARY_SIZE ];
                  
      if ( ! wdictionary )
          throw toutofmem ( DICTIONARY_SIZE * sizeof( struct Wdictionary ) );
  
      for ( i = 0; i < DICTIONARY_SIZE; i++ )
          wdictionary[ i ].code = UNUSED_CODE;
  
      currcodeloaded = 0;

      initwrite = 1;
      mode = writing;

  } else
    if ( initread )
       throw tinvalidmode ( "tlzwstreamcompression ", tnstream::IOMode ( mode ) , tnstream::writing );
}



 void tlzwstreamcompression  :: writedata ( const void* buf, int size )
{
   if ( !initwrite )
      initwriting();

   const char* buf2 = (char*) buf;
   int pos = 0;

   if ( size ) {
      if ( !currcodeloaded ) {
         currcode = buf2[pos++];

         LZWTotBytesIn += 1;
         LZWCurrBytesIn += 1;
         currcode &= 0xFF; /* make sure we don't sign extend */

         currcodeloaded = 1;
      }

      while ( pos < size )
      {
            in = buf2[pos++];

            LZWTotBytesIn += 1;
            LZWCurrBytesIn += 1;
            idx = LZWFind ( currcode, in );
            if ( wdictionary[ idx ].code == UNUSED_CODE )
            {
                /* not a match */
                LZWOut ( currcode );

                /* now, update the dictionary */
                if ( freecode < MAX_CODE )
                {
                    wdictionary[ idx ].c = in;
                    wdictionary[ idx ].code = freecode++;
                    wdictionary[ idx ].parent = currcode;
                }
                currcode = in;

                /* Had a miss; check compression efficiency */
                if ( LZWCurrBytesIn >= 10000 )
                {
                    unsigned ratio;
                    ratio = ( LZWCurrBytesOut * 100 ) /
                             LZWCurrBytesIn;

                    LZWCurrBytesIn = 0;
                    LZWCurrBytesOut = 0;
                    if ( ratio > LZWBestRatio )
                    {
                        if ( ratio > 50 &&
                             ( ratio > 90 ||
                             ratio > LZWLastRatio + 10 ))
                        {
                            LZWOut ( NEW_DICTIONARY );
                            for ( i = 0;
                                  i < DICTIONARY_SIZE; i++ )
                                wdictionary[ i ].code =
                                    UNUSED_CODE;
                            freecode = STARTING_CODE;
                        }
                    }
                    else
                        LZWBestRatio = ratio;
                    LZWLastRatio = ratio;
                }
            }
            else /* we match so far--keep going */
                currcode = wdictionary[ idx ].code;

      }
   }
}



 void tlzwstreamcompression  :: LZWIn ( void )
{
    readlzwdata ( &incode, sizeof( CodeType ) );
    incode = SDL_SwapLE16( incode );
}


/* the active decompression routine */
 unsigned tlzwstreamcompression  :: LZWLoadBuffer ( unsigned count, CodeType code )
{
    if ( code >= freecode )
       throw tbufferoverflow();
                   
    while ( code >= PRESET_CODE_MAX )
    {
        DecodeBuffer[ count++ ] = rdictionary[ code ].c;
        if ( count == DecodeBufferSize )
        {
            DecodeBuffer = (unsigned char *) realloc ( DecodeBuffer, DecodeBufferSize + 1000 );

            if ( ! DecodeBuffer )
                throw toutofmem ( DecodeBufferSize + 1000 );
            else      
                DecodeBufferSize += 1000;
        }
        code = rdictionary[ code ].parent;
    }
    DecodeBuffer[ count++ ] = code;
    return ( count );
}



int tlzwstreamcompression  :: readdata ( void* buf, int size, bool excpt  )
{
   if ( !initread )
      initreading();

   int pos = 0;
   char* buf2 = (char*) buf ;


   if ( mode == readingdirect ) {
      int tp = 0;
      while ( pos < size  &&  tempbuf.size() )  {
         buf2 [pos++] = tempbuf.front();
         tempbuf.pop();
         tp++;
       }
       if ( size-pos > 0 )
          tp += readlzwdata ( &buf2[pos], size-pos, excpt );

       return tp;

   } else



   if ( mode == readingrle ) {
      while ( pos < size ) {

          if ( rlenum ) {
             buf2[pos++] = rledata;
             rlenum--;
          } else {

             // getting byte

             if ( !readlzwdata ( &rledata, 1, excpt ))
                return pos;

             if ( rledata == rlestartbyte ) {
                readlzwdata ( &rlenum, 1 );
                if ( rlenum > 2 )
                   readlzwdata ( &rledata, 1 );
             } else
                buf2[pos++] = rledata;
          }

      } /* endwhile */
      return pos;

   } else {

       while ( pos < size  &&  tempbuf.size() ) {
          buf2 [pos++] = tempbuf.front();
          tempbuf.pop();
       }


       if ( pos < size ) {
   
          /* prime the pump */
      
          if (!DecodeBufferSize)
          {
              DecodeBufferSize = 1000;
              DecodeBuffer =  (unsigned char * ) new char [ DecodeBufferSize ];
              if ( DecodeBuffer == NULL )
                  throw toutofmem ( DecodeBufferSize );
          }
      
      
          if ( !readcnt ) {
   
          priming:
             freecode = STARTING_CODE;
             LZWIn (  );
         
             if ( incode == END_OF_INPUT )
                 goto done;
   
             /* the first character always is itself */
   
             oldcode = incode;
             inchar = incode;
             buf2[ pos++ ] = incode;
   
          }
      
          while ( pos < size ) {
              LZWIn ( );
   
              if ( incode == END_OF_INPUT )
                  break;
                  
              if ( incode == NEW_DICTIONARY )
                  goto priming;
                  
              if ( incode >= freecode )
              {
                  /* We have a code that's not in our rdictionary! */
                  /* This can happen only one way--see text */
      
                  count = LZWLoadBuffer ( 1, oldcode );
      
                  /* Make last char same as first. Can use either */
                  /* inchar or the DecodeBuffer[count-1] */
      
                  DecodeBuffer[ 0 ] = inchar;
              }
              else
                  count = LZWLoadBuffer ( 0, incode );
      
              if ( count == 0 )
                 throw ASCexception();
                             
              inchar = DecodeBuffer[ count - 1 ];
              while ( count )
              {
                 if ( pos < size )
                    buf2 [pos++] = DecodeBuffer[--count];
                 else 
                    tempbuf.push ( DecodeBuffer[--count] );
              }
      
              /* now, update the rdictionary */
              if ( freecode < MAX_CODE )
              {
                  rdictionary[ freecode ].parent = oldcode;
                  rdictionary[ freecode ].c = inchar;
                  freecode += 1;
      
              }
              oldcode = incode;
          }
       }
    done:
      readcnt++;

      return pos;
   }

   // return 0;
}


 void tlzwstreamcompression  :: close( void )
{
   if ( mode == writing ) {
      LZWOut ( currcode );
      LZWOut ( END_OF_INPUT );
      mode = none;
   }
}


 tlzwstreamcompression  :: ~tlzwstreamcompression ( )
{
   if ( mode == writing )
      close();

    if ( rdictionary ) {
       delete[] rdictionary;
       rdictionary = NULL;
    }
    if ( wdictionary ) {
       delete[] wdictionary;
       wdictionary = NULL;
    }

    if ( DecodeBuffer ) {
       delete[] DecodeBuffer;
       DecodeBuffer = NULL;
    }

}
