/***************************************************************************
**
**  This file is part of GeopsyCore.
**
**  GeopsyCore 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 3 of the License, or
**  (at your option) any later version.
**
**  GeopsyCore 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2006-08-16
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

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

#include <qglobal.h>

#include <QGpCoreTools.h>
#include "Gse.h"

namespace GeopsyCore {

/*!
  \class Gse Gse.h
  \brief Call containing all GSE util function (compression/decompression)
 
  Translated in C++ from Fortran by Marc Wathelet, in other to be portable
  on all architecture including AMD64.
 
  Original notification in Fortran source:
 
    The following data compression and decompression routines have
    been developped by Dr. Shane Ingate and Dr. Ken Muirhead, at the
    Australian Seismological Centre, Bureau of Mineral Resources,
    Canberra, Australia.
    They provided me with these routines in March 1989 during the
    session of the Group of Scientific Experts (GSE) in Geneva.
    These compression/decompression algorithms are used by all members
    of the GSE during the large-scale data exchange experiment GSETT-2,
    carried out from 1989 to 1991.
    It is recommended to use second differences and the six-bit compression.
    The second differences can be computed by two subsequent calls of
    subroutine DIF1 (see above).
    These routines are already running on many different machines.
    Because the AND function is not standard FORTRAN 77, this operation
    has been moved to a separate subroutine INTAND. All users of these
    routines will have to modify INTAND so that it performs correctly on
    their computers.
                                 Urs Kradolfer, Swiss Seismological Service
*/

/*!
  Convert ascii into GSE character set +,-,0-9,a-z,A-Z (26*2+10+2=64)
*/
inline unsigned int Gse::ascii2gseChar(unsigned char c)
{
  TRACE;
  //                        0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
  static const uint charMap[]={
                            0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                            0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                            0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,
                            2,  3,  4,  5,  6,  7,  8,  9, 10, 11,  0,  0,  0,  0,  0,  0,
                            0, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
                           27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,  0,  0,  0,  0,  0,
                            0, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
                           53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,  0,  0,  0,  0,  0
                         };
  // Strip off all characters > 0xFF
  return charMap[ c & 0xFF ];
}

/*!
  Convert GSE character set +,-,0-9,a-z,A-Z (26*2+10+2=64) into ascii characters.
  c < 64 (not checked)
*/
inline unsigned char Gse::gseChar2ascii(unsigned int c)
{
  TRACE;
  // Lookup table for converting into ASCII
  static char charMap[]={ '+','-','0','1','2','3','4','5','6','7','8','9','A','B','C','D',
                          'E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T',
                          'U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j',
                          'k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z' };
  return charMap[ c ];
}

/*!
  Compress integer data into printable ASCCI characters.

  \a data is an array containing \a nData integers.

  Compression uses the least significant six bits of an eight bit byte (ease ASCII conversion).

  Coding:

  Bit  76543210   76543210   76543210        (dec)
       00101001   00111001   00000111  -->  604423
       00110110   11000110   11111100  --> -604423

  \li Bit 5 : continuation bit, if 0 then last byte for this sample.
  \li Bit 4 : sign bit for first byte only. For others: data.
  \li Bits 3 (or 4) to 0: data

      If 6 bytes are required:

               N data bits   Value
      Byte 1   5             0x0000 001F
      Byte 2   5             0x0000 03FF
      Byte 3   5             0x0000 7FFF
      Byte 4   5             0x000F FFFF
      Byte 5   5             0x01FF FFFF
      Byte 6   4             0x1FFF FFFF

      If 5 bytes are required:

               N data bits   Value
      Byte 1   5             0x0000 001F
      Byte 2   5             0x0000 03FF
      Byte 3   5             0x0000 7FFF
      Byte 4   5             0x000F FFFF
      Byte 5   4             0x00FF FFFF

      If 4 bytes are required:

               N data bits   Value
      Byte 1   5             0x0000 001F
      Byte 2   5             0x0000 03FF
      Byte 3   5             0x0000 7FFF
      Byte 4   4             0x0007 FFFF

      If 3 bytes are required:

               N data bits   Value
      Byte 1   5             0x0000 001F
      Byte 2   5             0x0000 03FF
      Byte 3   4             0x0000 3FFF

      If 2 bytes are required:

               N data bits   Value
      Byte 1   5             0x0000 001F
      Byte 2   4             0x0000 01FF

      If 1 byte is required:

               N data bits   Value
      Byte 1   4             0x0000 000F

  Each byte is composed of an unsigned number between 0 and 63. GSE format includes a dedicated
  character table to convert to ASCII (only +, -, 0 to 9, A to Z, and a to z are used).

  It returns a newly allocated pointer with the compressed data. \a iComp is set to the exact number
  of characters in the compressed-encoded buffer. \a iComp can be uninitialized.

  Data may be decompressed with decompress6().
*/
char * Gse::compress6(int nData, int * data, int& iComp)
{
  TRACE;
  static uint continuationBit=0x20;
  int nComp=nData;
  iComp=0;
  char * comp=new char [ nComp ];
  unsigned int signBit;
  int val;
  for(int i=0; i < nData; i++ ) {
    if(data[i]<0) {
      val=-data[i];
      signBit=0x10;
    } else {
      val=data[i];
      signBit=0x00;
    }
    if(iComp+6>=nComp) {
      nComp=nComp << 1;
      char * tmp=new char [ nComp ];
      memcpy(tmp, comp, sizeof(char)*iComp);
      delete [] comp;
      comp=tmp;
    }
    if(val<=0x00003FFF) {
      if(val<=0x000001FF) {
        if(val<=0x0000000F) { // 1 byte
          comp[iComp++]=gseChar2ascii(val | signBit);
        } else { // 2 bytes
          comp[iComp++]=gseChar2ascii(((val & 0x000001E0) >> 5) | signBit | continuationBit);
          comp[iComp++]=gseChar2ascii(  val & 0x0000001F);
        }
      } else { // 3 bytes
        comp[iComp++]=gseChar2ascii(((val & 0x00003C00) >> 10) | signBit | continuationBit);
        comp[iComp++]=gseChar2ascii(((val & 0x000003E0) >> 5) | continuationBit);
        comp[iComp++]=gseChar2ascii(  val & 0x0000001F);
      }
    } else {
      if(val<=0x0007FFFF) { // 4 bytes
        comp[iComp++]=gseChar2ascii(((val & 0x00078000) >> 15) | signBit | continuationBit);
        comp[iComp++]=gseChar2ascii(((val & 0x00007C00) >> 10) | continuationBit);
        comp[iComp++]=gseChar2ascii(((val & 0x000003E0) >> 5) | continuationBit);
        comp[iComp++]=gseChar2ascii(  val & 0x0000001F);
      } else {
        if(val<=0x00FFFFFF) { // 5 bytes
          comp[iComp++]=gseChar2ascii(((val & 0x00F00000) >> 20) | signBit | continuationBit);
          comp[iComp++]=gseChar2ascii(((val & 0x000F8000) >> 15) | continuationBit);
          comp[iComp++]=gseChar2ascii(((val & 0x00007C00) >> 10) | continuationBit);
          comp[iComp++]=gseChar2ascii(((val & 0x000003E0) >> 5) | continuationBit);
          comp[iComp++]=gseChar2ascii(  val & 0x0000001F);
        } else { // 6 bytes
          if(val>0x1FFFFFFF) {
            val=0x1FFFFFFF;
          }
          comp[iComp++]=gseChar2ascii(((val & 0x1E000000) >> 25) | signBit | continuationBit);
          comp[iComp++]=gseChar2ascii(((val & 0x01F00000) >> 20) | continuationBit);
          comp[iComp++]=gseChar2ascii(((val & 0x000F8000) >> 15) | continuationBit);
          comp[iComp++]=gseChar2ascii(((val & 0x00007C00) >> 10) | continuationBit);
          comp[iComp++]=gseChar2ascii(((val & 0x000003E0) >> 5) | continuationBit);
          comp[iComp++]=gseChar2ascii(  val & 0x0000001F);
        }
      }
    }
  }
  return comp;
}

/*!
  Decompress integer data that has been compressed into ASCII characters and
  returns values in int format.
 
  \a buf is an array containing \a n characters.
  \a nData is the number of expected integers
  \a data is the result vector.
 
  It returns false and warns through log stream if compressed buffer does not contain
  nData integers.
 
  \sa compress6
*/
bool Gse::decompress6(int nComp, const char * comp, int nData, int * data)
{
  TRACE;
  bool continuationBit, signBit;
  int val;
  int iData=0;
  unsigned int gseChar;
  for(int i=0; i < nComp; i++ ) {
    if(isspace( comp[ i ] ))
      continue;
    gseChar=ascii2gseChar(comp[ i ] );
    continuationBit=gseChar & 0x20;
    signBit=gseChar & 0x10;
    gseChar=gseChar & 0x0F;
    val=gseChar;
    while(continuationBit) {
      i++;
      if(isspace( comp[ i ] ))
        continue;
      gseChar=ascii2gseChar(comp[ i ] );
      continuationBit=gseChar & 0x20;
      gseChar=gseChar & 0x1F;
      val=val << 5;
      val += gseChar;
    }
    if(signBit)
      val=-val;
    // Check the size of the output data vector
    if(iData==nData) {
      App::log(tr("Corrupted compressed data, more than %1 integers are "
                  "obtained after decompression\n").arg(nData));
      return false;
    }
    data[ iData ]=val;
    iData++;
  }
  if(iData!=nData) {
    App::log(tr("Corrupted compressed data, less than %1 integers are "
                "obtained after decompression\n").arg(nData));
    return false;
  } else
    return true;
}

/*!
  Computes the sample difference (used for data compression).
  This function is called twice for usual GSE formats
*/
void Gse::diff(int * data, int nData)
{
  TRACE;
  int lastVal=data[0], originalVal;
  for(int i=1; i< nData; i++) {
    originalVal=data[i];
    data[i]-= lastVal;
    lastVal=originalVal;
  }
}

/*!
  Removes the sample difference (used for data decompression) and restore original signal.
  This function is called twice for usual GSE formats
*/
void Gse::remdif(int * data, int nData)
{
  TRACE;
  for(int i=1; i< nData; i++) {
    data[i]+=data[i-1];
  }
}

/*!
  Compute checksum for GSE format from decompressed integer vector \a data which contains \a nData.
  Must be apply to fully decompressed data (including double difference).

  Checksum calculation (Annex three, page 145)
*/
int Gse::checksum(int * data, int nData)
{
  TRACE;
  int cs=0;
  int modulo=100000000;
  for(int i=0;i < nData;i++ ) {
    int iy=data[ i ];
    if( ::abs(iy) >= modulo) iy=iy - (iy/modulo) * modulo;
    cs=cs + iy;
    if( ::abs(cs) >= modulo)
      cs=cs - (cs/modulo) * modulo;
  }
  cs=::abs(cs);
  return cs;
}

} // namespace GeopsyCore
