/*
 * Zebra barcode library
 *
 * Copyright 2010 Mindaugas Kavaliauskas <dbtopas at dbtopas.lt>
 *
 * 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, 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 LICENSE.txt.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA (or visit https://www.gnu.org/licenses/).
 *
 * As a special exception, the Harbour Project gives permission for
 * additional uses of the text contained in its release of Harbour.
 *
 * The exception is that, if you link the Harbour libraries with other
 * files to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the Harbour library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the Harbour
 * Project under the name Harbour.  If you copy code from other
 * Harbour Project or Free Software Foundation releases into a copy of
 * Harbour, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for Harbour, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 *
 */

#include "hbzebra.h"


static const unsigned short s_code[] = {
   00633,   /*           00 */
   00663,   /* !    !    01 */
   01463,   /* "    "    02 */
   00311,   /* #    #    03 */
   00611,   /* $    $    04 */
   00621,   /* %    %    05 */
   00231,   /* &    &    06 */
   00431,   /* '    '    07 */
   00461,   /* (    (    08 */
   00223,   /* )    )    09 */
   00423,   /* *    *    10 */
   00443,   /* +    +    11 */
   00715,   /* ,    ,    12 */
   00731,   /* -    -    13 */
   01631,   /* .    .    14 */
   00635,   /* /    /    15 */
   00671,   /* 0    0    16 */
   01471,   /* 1    1    17 */
   01163,   /* 2    2    18 */
   00723,   /* 3    3    19 */
   01623,   /* 4    4    20 */
   00473,   /* 5    5    21 */
   00563,   /* 6    6    22 */
   01667,   /* 7    7    23 */
   00627,   /* 8    8    24 */
   00647,   /* 9    9    25 */
   01447,   /* :    :    26 */
   00467,   /* ;    ;    27 */
   00547,   /* <    <    28 */
   01147,   /* =    =    29 */
   00333,   /* >    >    30 */
   01433,   /* ?    ?    31 */
   01543,   /* @    @    32 */
   00305,   /* A    A    33 */
   00321,   /* B    B    34 */
   01421,   /* C    C    35 */
   00215,   /* D    D    36 */
   00261,   /* E    E    37 */
   01061,   /* F    F    38 */
   00213,   /* G    G    39 */
   00243,   /* H    H    40 */
   01043,   /* I    I    41 */
   00355,   /* J    J    42 */
   01615,   /* K    K    43 */
   01661,   /* L    L    44 */
   00335,   /* M    M    45 */
   01435,   /* N    N    46 */
   01561,   /* O    O    47 */
   01567,   /* P    P    48 */
   01613,   /* Q    Q    49 */
   01643,   /* R    R    50 */
   00273,   /* S    S    51 */
   01073,   /* T    T    52 */
   01673,   /* U    U    53 */
   00327,   /* V    V    54 */
   01427,   /* W    W    55 */
   01507,   /* X    X    56 */
   00267,   /* Y    Y    57 */
   01067,   /* Z    Z    58 */
   01307,   /* [    [    59 */
   01367,   /* \    \    60 */
   01023,   /* ]    ]    61 */
   01217,   /* ^    ^    62 */
   00145,   /* _    _    63 */
   00605,   /* NUL   `   64 */
   00151,   /* SOH   a   65 */
   01411,   /* STX   b   66 */
   00641,   /* ETX   c   67 */
   01441,   /* EOT   d   68 */
   00115,   /* ENQ   e   69 */
   00415,   /* ACK   f   70 */
   00131,   /* BEL   g   71 */
   01031,   /* BS    h   72 */
   00541,   /* HT    i   73 */
   01141,   /* LF    j   74 */
   01103,   /* VT    k   75 */
   00123,   /* FF    l   76 */
   01357,   /* CR    m   77 */
   00503,   /* SO    n   78 */
   01361,   /* SI    o   79 */
   00745,   /* DLE   p   80 */
   00751,   /* DC1   q   81 */
   01711,   /* DC2   r   82 */
   00475,   /* DC3   s   83 */
   00571,   /* DC4   t   84 */
   01171,   /* NAK   u   85 */
   00457,   /* SYN   v   86 */
   00517,   /* ETB   w   87 */
   01117,   /* CAN   x   88 */
   01733,   /* EM    y   89 */
   01573,   /* SUB   z   90 */
   01557,   /* ESC   {   91 */
   00365,   /* FS    |   92 */
   01705,   /* GS    }   93 */
   01721,   /* RS    ~   94 */
   00275,   /* US   DEL  95 */
   01075,   /* FNC3 FNC3 96 */
   00257,   /* FNC2 FNC2 97 */
   01057,   /* ShiB ShiA 98 */
   01735,   /* CodC CodC 99 */
   01675,   /* CodB FNC4 CodB 100 */
   01727,   /* FNC4 CodA CodA 101 */
   01657,   /* FNC1 FNC1 FNC1 102 */
   00413,   /* Start Code A   103 */
   00113,   /* Start Code B   104 */
   00713    /* Start Code C   105 */
};

#define CODESET_A  0
#define CODESET_B  1

#define START_A    103
#define START_B    104
#define START_C    105

#define SELECT_A   101
#define SELECT_B   100
#define SELECT_C   99

#define SHIFT_AB   98


static int _code128_charno( char ch, int iCodeSet )
{
   if( iCodeSet == CODESET_A )
   {
      if( ch >= ' ' && ch <= '_' )
         return ch - ' ';
      else if( ( unsigned char ) ch <= 31 )
         return ch + 64;
      else
         return -1;
   }
   else if( iCodeSet == CODESET_B )
   {
      if( ch >= ' ' && ( unsigned char ) ch <= 127 )
         return ch - ' ';
      else
         return -1;
   }
   return -1;
}

PHB_ZEBRA hb_zebra_create_code128( const char * szCode, HB_SIZE nLen, int iFlags )
{
   PHB_ZEBRA  pZebra;
   int        i, j, k, csum, iCodeSet, iCodeLen, iLen = ( int ) nLen;
   int *      pCode;

   HB_SYMBOL_UNUSED( iFlags );

   pZebra = hb_zebra_create();
   pZebra->iType = HB_ZEBRA_TYPE_CODE128;

   j = 0;
   for( i = 0; i < iLen; i++ )
   {
      if( ( unsigned char ) szCode[ i ] >= 128 )
      {
         pZebra->iError = HB_ZEBRA_ERROR_INVALIDCODE;
         return pZebra;
      }
      if( szCode[ i ] >= ' ' && szCode[ i ] <= 126 )
         j++;
   }

   /* make print string */
   pZebra->szCode = ( char * ) hb_xgrab( j + 1 );
   j = 0;
   for( i = 0; i < iLen; i++ )
   {
      if( szCode[ i ] >= 32 && szCode[ i ] <= 126 )
         pZebra->szCode[ j++ ] = szCode[ i ];
   }
   pZebra->szCode[ j ] = '\0';

   /* generate code set switch characters */
   pCode = ( int * ) hb_xgrab( sizeof( int ) * iLen * 2 );
   iCodeSet = CODESET_B; /* to pacify MSVC warning only. It will be assigned later */
   iCodeLen = 0;
   /* determine the first optimal codeset */
   for( i = 0; i < iLen; i++ )
   {
      if( _code128_charno( szCode[ i ], CODESET_A ) == -1 )
      {
         iCodeSet = CODESET_B;
         pCode[ iCodeLen++ ] = START_B;
         break;
      }
      else if( _code128_charno( szCode[ i ], CODESET_B ) == -1 )
      {
         iCodeSet = CODESET_A;
         pCode[ iCodeLen++ ] = START_A;
         break;
      }
   }
   if( iCodeLen == 0 )
   {
      iCodeSet = CODESET_B;
      pCode[ iCodeLen++ ] = START_B;
   }

   /* encode source data */
   /* Warning: digit optimizer works in optimal way with this encoder code. Be careful
      if you'll change encoder code, digit optimizer can require adjustment also [Mindaugas] */
   for( i = 0; i < iLen; i++ )
   {
      int iCode = _code128_charno( szCode[ i ], iCodeSet );

      if( iCode != -1 )
         pCode[ iCodeLen++ ] = iCode;
      else
      {
         /* We should generate codeset switch instead of shift for the last character.
            This will help digit optimizer to do its job [Mindaugas] */
         if( i + 1 < iLen &&
             _code128_charno( szCode[ i + 1 ], iCodeSet == CODESET_A ? CODESET_B : CODESET_A ) == -1 )
         {
            pCode[ iCodeLen++ ] = SHIFT_AB;
            pCode[ iCodeLen++ ] = _code128_charno( szCode[ i ], iCodeSet == CODESET_A ? CODESET_B : CODESET_A );
         }
         else
         {
            if( iCodeSet == CODESET_A )
            {
               iCodeSet = CODESET_B;
               pCode[ iCodeLen++ ] = SELECT_B;
            }
            else
            {
               iCodeSet = CODESET_A;
               pCode[ iCodeLen++ ] = SELECT_A;
            }
            pCode[ iCodeLen++ ] = _code128_charno( szCode[ i ], iCodeSet );
         }
      }
   }

   /* optimize digits */
   iCodeSet = pCode[ 0 ] == START_A ? CODESET_A : CODESET_B;
   for( i = 1; i < iCodeLen; i++ )
   {
      if( iCodeSet == CODESET_A && pCode[ i ] == SELECT_B )
         iCodeSet = CODESET_B;
      else if( iCodeSet == CODESET_B && pCode[ i ] == SELECT_A )
         iCodeSet = CODESET_A;

      if( 16 <= pCode[ i ] && pCode[ i ] <= 25 )
      {
         for( j = i + 1; j < iCodeLen && 16 <= pCode[ j ] && pCode[ j ] <= 25; j++ )
            ;

         if( j - i == 2 && i == 1 && j == iCodeLen )
         {
            /* [StartB] 1 2  -->  [StartC] [12] */
            pCode[ 0 ] = START_C;
            pCode[ 1 ] = ( pCode[ 1 ] - 16 ) * 10 + pCode[ 2 ] - 16;
            iCodeLen = 2;
            break;
         }
         else if( ( j - i >= 4 && ( i == 1 || j == iCodeLen || pCode[ j ] == SELECT_A || pCode[ j ] == SELECT_B ) ) ||
                  j - i >= 6 )
         {
            if( i == 1 )
            {
               /* [StartN] 1 2 3 4  -->  [StartC] [12] [34] */
               /* [StartN] 1 2 3 4 5  -->  [StartC] [12] [34] [CodeN] 5 */
               /* [StartN] 1 2 3 4 X ... -->  [StartC] [12] [34] [CodeN] X ... */
               /* [StartN] 1 2 3 4 5 X ... -->  [StartC] [12] [34] [CodeN] 5 X ... */
               pCode[ 0 ] = START_C;
               for( k = 1; k < j - 1; k += 2 )
                  pCode[ i++ ] = ( pCode[ k ] - 16 ) * 10 + pCode[ k + 1 ] - 16;

               if( k < iCodeLen )
               {
                  j = i;
                  pCode[ i++ ] = iCodeSet == CODESET_A ? SELECT_A : SELECT_B;
                  for( ; k < iCodeLen; k++ )
                     pCode[ i++ ] = pCode[ k ];
               }
               iCodeLen = i;
            }
            else
            {
               /* ... X 1 2 3 4  -->  ... X [CodeC] [12] [34] */
               /* ... X 1 2 3 4 [CodeN] ... -->  ... X [CodeC] [12] [34] [CodeN] ... */
               /* ... X 1 2 3 4 5  -->  ... X 1 [CodeC] [23] [45] */
               /* ... X 1 2 3 4 5 [CodeN] ...  -->  ... X 1 [CodeC] [23] [45] [CodeN] ... */
               /* ... X 1 2 3 4 5 6 Y ...  -->  ... X [CodeC] [12] [34] [56] [CodeN] Y ... */
               /* ... X 1 2 3 4 5 6 7 Y ...  -->  ... X 1 [CodeC] [23] [45] [67] [CodeN] Y ... */

               if( ( j - i ) & 1 )
               {
                  /* digit count is odd */
                  i++;
               }

               pCode[ i + 1 ] = ( pCode[ i ] - 16 ) * 10 + pCode[ i + 1 ] - 16;
               pCode[ i ] = SELECT_C;
               i += 2;
               for( k = i; k < j; k += 2 )
                  pCode[ i++ ] = ( pCode[ k ] - 16 ) * 10 + pCode[ k + 1 ] - 16;
               j = i;
               if( k < iCodeLen )
               {
                  if( pCode[ k ] != SELECT_A && pCode[ k ] != SELECT_B )
                     pCode[ i++ ] = iCodeSet == CODESET_A ? SELECT_A : SELECT_B;

                  for( ; k < iCodeLen; k++ )
                     pCode[ i++ ] = pCode[ k ];
               }
               iCodeLen = i;
            }
         }
         i = j - 1;
      }
   }

   pZebra->pBits = hb_bitbuffer_create();
   csum = pCode[ 0 ];
   for( i = 0; i < iCodeLen; i++ )
   {
      hb_bitbuffer_cat_int( pZebra->pBits, s_code[ pCode[ i ] ], 11 );
      csum += i * pCode[ i ];
   }

   hb_xfree( pCode );
   /* checksum */
   hb_bitbuffer_cat_int( pZebra->pBits, s_code[ csum % 103 ], 11 );

   hb_bitbuffer_cat_int( pZebra->pBits, 0x1AE3, 13 );
   return pZebra;
}


HB_FUNC( HB_ZEBRA_CREATE_CODE128 )
{
   PHB_ITEM pItem = hb_param( 1, HB_IT_STRING );

   if( pItem )
      hb_zebra_ret( hb_zebra_create_code128( hb_itemGetCPtr( pItem ), hb_itemGetCLen( pItem ), hb_parni( 2 ) ) );
   else
      hb_errRT_BASE( EG_ARG, 3012, NULL, HB_ERR_FUNCNAME, HB_ERR_ARGS_BASEPARAMS );
}
