/**
 * @file   FLICONV.c
 * @brief  Sample program in C to convert files via the FLCICV interface
 *         The program is compatible with the iconv utility on linux systems
 * @author limes datentechnik gmbh
 ******************************************************************************/

/* defines code page for literals for IBM mainframes to German */
#ifdef __ZOS__
#  pragma convert(1141)
#endif

#include<ctype.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define  FICONV /* enable "iconv" macros for "fliconv" functions*/
#include"FLCICV.h"

static void printHelp(void) {
   fprintf(stderr,"\n");
   fprintf(stderr,"Usage: fliconv [OPTION...] [FILE...]\n");
   fprintf(stderr,"Convert given files from one character set to another\n");
   fprintf(stderr,"\n");
   fprintf(stderr,"  Information:\n");
   fprintf(stderr,"   -?            Prints this help list\n");
   fprintf(stderr,"   -h            Prints this help list\n");
   fprintf(stderr,"   -l            List all supported character sets\n");
   fprintf(stderr,"   -v            Print program version\n");
   fprintf(stderr,"   -a            Print about information\n");
   fprintf(stderr,"   -z            Print license text\n");
   fprintf(stderr,"   -p            Print progress information\n");
   fprintf(stderr,"   -s            Skip incomplete characters at the end of the data\n");
   fprintf(stderr,"\n");
   fprintf(stderr,"  Input/Output file control:\n");
   fprintf(stderr,"   FILE...       Input file list [STDIN]\n");
   fprintf(stderr,"   -o FILE       Output file [STDOUT]\n");
   fprintf(stderr,"\n");
   fprintf(stderr,"  Input/Output format specification:\n");
   fprintf(stderr,"   -f NAME       Encoding string or CCSID of original text [UTF-8]\n");
   fprintf(stderr,"   -t NAME       Encoding string or CCSID for output [UTF-8]\n");
   fprintf(stderr,"\n");
   fprintf(stderr,"  Input encoding string enhancements:\n");
   fprintf(stderr,"   //BOM         Manage byte order change inside data (only if same bit width)\n");
   fprintf(stderr,"   //ENL2LF      Convert EBCDIC new line (0x85/0x15) to line feed (0x0A/0x25)\n");
   fprintf(stderr,"\n");
   fprintf(stderr,"  Output encoding string enhancements:\n");
   fprintf(stderr,"   //BOM         Write byte order mark\n");
   fprintf(stderr,"   //ELF2NL      Convert EBCDIC line feed (0x0A/0x25) to new line (0x85/0x15)\n");
   fprintf(stderr,"   //TOUPPER     Upper case mapping\n");
   fprintf(stderr,"   //TOLOWER     Lower case mapping\n");
   fprintf(stderr,"   //TOSUPPER    Special upper case mapping\n");
   fprintf(stderr,"   //TOSLOWER    Special lower case mapping\n");
   fprintf(stderr,"   //TOFOLD      Special case folding\n");
   fprintf(stderr,"   //TOUSER      Use case mapping defined in user table/module (^CP=cp_list)\n");
   fprintf(stderr,"   //IGNORE      Ignore invalid characters\n");
   fprintf(stderr,"   //TRANSLIT['('[systab]')']            Transliterate invalid characters [ICONV]\n");
   fprintf(stderr,"   //SUBSTITUTE['('[codepoint_list]')']  Substitute invalid characters [0x1A]\n");
#ifdef __MVS__
   fprintf(stderr,"   //USRMOD'('module_name')'   Use a predefined user table module (CCUTNPAS/CCUTSEPA/CCUTDELA/CCUTDLAX/...)\n");
#else
   fprintf(stderr,"   //USRMOD'('module_name')'   Use a predefined user table module (ccutnpas/ccutsepa/ccutdela/ccutdlax/...)\n");
#endif
   fprintf(stderr,"   //USRTAB'('file_name')'     Use a custom text file containing a user table (see FLCL manual for more information)\n");
   fprintf(stderr,"   //REPORT['('[file_name]')'] Write a report file [STDERR]\n");
   fprintf(stderr,"\n");
   fprintf(stderr,"  Examples:\n");
   fprintf(stderr,"    fliconv -l\n");
   fprintf(stderr,"    fliconv -p -f UTF16LE//BOM -t 1141//ELF2NL//IGNORE//TRANSLIT//REPORT(report.txt) -o outdat.txt input1.txt input2.txt\n");
   fprintf(stderr,"\n");
   fprintf(stderr,"  Support:\n");
   fprintf(stderr,"    Web : https://www.flam.de/\n");
   fprintf(stderr,"    Mail: support@flam.de\n");
   fprintf(stderr,"\n");
}

/**
 * This test program implements the iconv utility with the FLUC character conversion module.
 * Use fliconv -? for the syntax, help and a sample
 */
int main(int argc, char * argv[])
{
   FILE*                   pfIn=stdin;
   FILE*                   pfOut=stdout;
   static char             acIn[65536];
   size_t                  i,uiOut,uiSiz,uiLen,uiRst,uiErr,uiCnt=0;
   off_t                   x,y,uiFil,uiSum,uiTot=0;
   char*                   h;
   char*                   a[256];
   char*                   pcFrom="UTF-8";
   char*                   pcTo="UTF-8";
   char*                   pcOutFil=NULL;
   char*                   pcOutBuf=NULL;
   char*                   pcHlpIn;
   char*                   pcHlpOut;
   void*                   pvIcv;
   int                     k;
   int                     isProgress=0;
   int                     isSkip=0;

/* defines environment variable LANG to German EBCDIC for IBM mainframes */
#ifdef __ZOS__
   __setenv("LANG","de_DE.IBM-1141",1);
#endif

   if (argc==2) { /* parse HOST parameter string (comma separation) */
      a[0]=argv[0]; a[1]=argv[1];
      for (i=2,h=strchr(a[i-1],',');i<256 && h!=NULL;i++,h=strchr(a[i-1],',')) {
         h[0]=0x00;
         a[i]=h+1;
      }
      argc=i; argv=a;
   }

   for (i=1;i<argc;i++) { /* evaluate command line arguments */
      if (argv[i][0]=='-' && (toupper(argv[i][1])=='?' || toupper(argv[i][1])=='H')) {
         printHelp();
         exit(0);
      } else if (argv[i][0]=='-' && toupper(argv[i][1])=='L') {
         fliconv_list(NULL,NULL);
         exit(0);
      } else if (argv[i][0]=='-' && toupper(argv[i][1])=='V') {
         fprintf(stderr,"%s",iconv_version());
         exit(0);
      } else if (argv[i][0]=='-' && toupper(argv[i][1])=='A') {
         fprintf(stderr,"%s",iconv_about());
         exit(0);
      } else if (argv[i][0]=='-' && toupper(argv[i][1])=='Z') {
         fprintf(stderr,"%s",iconv_license());
         exit(0);
      } else if (argv[i][0]=='-' && toupper(argv[i][1])=='P') {
         isProgress=1;
      } else if (argv[i][0]=='-' && toupper(argv[i][1])=='S') {
         isSkip=1;
      } else if (argv[i][0]=='-' && toupper(argv[i][1])=='F') {
         if (i+1>=argc || argv[i+1][0]=='-') {
            fprintf(stderr,"\nNo FROM encoding string specified\n");
            printHelp();
            exit(8);
         }
         pcFrom=argv[i+1];
         i++;
      } else if (argv[i][0]=='-' && toupper(argv[i][1])=='T') {
         if (i+1>=argc || argv[i+1][0]=='-') {
            fprintf(stderr,"\nNo TO encoding string specified\n");
            printHelp();
            exit(8);
         }
         pcTo=argv[i+1];
         i++;
      } else if (argv[i][0]=='-' && toupper(argv[i][1])=='O') {
         if (i+1>=argc || argv[i+1][0]=='-') {
            fprintf(stderr,"\nNo output file specified\n");
            printHelp();
            exit(8);
         }
         pcOutFil=argv[i+1];
         i++;
      } else break;
   }

   if (pcOutFil!=NULL) { /* open output file */
      errno=0;
      pfOut=fopen(pcOutFil,"wb");
      if (pfOut==NULL) {
         fprintf(stderr,"\nOpen of output file failed (%d - %s)\n",errno,strerror(errno));
         exit(6);
      }
   }

   pvIcv=iconv_open(pcTo,pcFrom); /* open character conversion module */
   if (pvIcv==NULL) {
      fprintf(stderr,"\nOpen character conversion module failed (%d - %s)\n",errno,fliconv_strerror(errno));
      fprintf(stderr,"%s",iconv_error_trace());
      exit(5);
   }

   uiSiz=iconv_expansion(pvIcv)*sizeof(acIn); /* determine output buffer size */
   if (uiSiz==0) {
      fprintf(stderr,"\nDetermination of expansion factor failed\n");
      fprintf(stderr,"%s",iconv_error_trace());
      iconv_close(pvIcv);
      exit(5);
   }

   errno=0;
   pcOutBuf=(char*)malloc(uiSiz); /* allocation of output buffer */
   if (pcOutBuf==NULL) {
      fprintf(stderr,"\nAllocation of %d byte for output buffer failed (%d - %s)\n",(int)uiSiz,errno,strerror(errno));
      iconv_close(pvIcv);
      exit(4);
   }

   if (isProgress) fprintf(stderr,"\n");

   if (i==argc) { /* if no input file specified, activate use of STDIN */
      i--; argv[i]="";
   }
   for (k=0; i<argc; k++) {
      uiFil=uiSum=0;
      if (argv[i]!=NULL && strlen(argv[i])) { /* use input file */
         if (pcOutFil!=NULL && *pcOutFil && strcmp(argv[i],pcOutFil)==0) { /* skip output file as input file to prevent recursive endless read */
            i++; continue;
         }
         errno=0;
         pfIn=fopen(argv[i],"rb");
         if (pfIn==NULL) {
            fprintf(stderr,"\nOpen of input file (%s) failed (%d - %s)\n",argv[i],errno,strerror(errno));
            iconv_close(pvIcv);
            free(pcOutBuf);
            exit(6);
         }
         if (isProgress) {
            fseek(pfIn,0,SEEK_END); uiFil=ftell(pfIn); rewind(pfIn);
            fprintf(stderr,"%04d/%010"PRIi64" %s\n[",k+1,iconv_position(pvIcv),argv[i]);
         }
      } else { /* use STDIN */
         if (isProgress) fprintf(stderr,"%05d/%010"PRIi64" STDIN\n",k+1,iconv_position(pvIcv));
      }

      uiRst=0; errno=0; x=0; /* conversion loop */
      for (uiLen=fread(acIn+uiRst,1,sizeof(acIn)-uiRst,pfIn); (uiLen+uiRst) && (ferror(pfIn)==0 || feof(pfIn));
           uiLen=fread(acIn+uiRst,1,sizeof(acIn)-uiRst,pfIn)) {
         if (uiLen==0 && uiRst) { /* no new data but still an rest --> the last character is incomplete */
            if (isSkip) break; else {
               fprintf(stderr,"\nIncomplete multibyte character sequence at end of the data (Offset %"PRIi64")\n",iconv_position(pvIcv)+(int64_t)1);
               iconv_close(pvIcv);
               free(pcOutBuf);
               exit(1);
            }
         }
         uiSum+=uiLen; pcHlpIn=acIn; uiLen+=uiRst; pcHlpOut=pcOutBuf; uiOut=uiSiz;
         uiErr=iconv(pvIcv,&pcHlpIn,&uiLen,&pcHlpOut,&uiOut);
         uiOut=uiSiz-uiOut; uiTot+=uiOut;
         if (uiErr!=(size_t)-1) uiCnt+=uiErr;
         if ((uiErr==(size_t)-1 && errno!=EINVAL) || (feof(pfIn) && uiRst)) { /* conversion error */
            fprintf(stderr,"\nCharacter conversion failed at byte offset %"PRIi64" (%d - %s)\n",iconv_position(pvIcv)+(int64_t)1,errno,fliconv_strerror(errno));
            fprintf(stderr,"%s",iconv_error_trace());
            fwrite(pcOutBuf,1,uiOut,pfOut);
            iconv_close(pvIcv);
            free(pcOutBuf);
            exit(1);
         } else { /* write converted data */
            uiErr=fwrite(pcOutBuf,1,uiOut,pfOut);
            if (uiErr!=uiOut) {
               fprintf(stderr,"\nWrite data to file failed (%d - %s)\n",errno,strerror(errno));
               iconv_close(pvIcv);
               free(pcOutBuf);
               exit(2);
            }
            memcpy(acIn,pcHlpIn,uiLen); /* save rest (incomplete multibyte sequence) at the beginning of the buffer */
            uiRst=uiLen;
         }
         if (uiFil) { /* progress bar */
            for (y=(uiSum*70)/uiFil;x<y;x++) fprintf(stderr,"#");
         }
      }
      if (uiFil) fprintf(stderr,"]\n");

      if (ferror(pfIn)!=0 && ferror(pfIn)!=EOF) { /* conversion loop and based on a read error */
         fprintf(stderr,"\nRead data from file failed (%d - %s)\n",errno,strerror(errno));
         iconv_close(pvIcv);
         free(pcOutBuf);
         exit(3);
      }

      if (pfIn !=stdin) fclose(pfIn);
      i++;
   }

   if (isProgress) { /* print statistics information */
      fprintf(stderr,"\nStatistics:\n");
      fprintf(stderr,"Files converted:            %d\n",k);
      fprintf(stderr,"Total bytes converted:      %"PRIi64"\n",iconv_position(pvIcv));
      fprintf(stderr,"Total bytes written:        %"PRIi64"\n",(int64_t)uiTot);
      fprintf(stderr,"Non-reversible conversions: %"PRIi64"",(int64_t)uiCnt);
      if (uiCnt) fprintf(stderr," (use report file for more information)\n"); else fprintf(stderr,"\n");
      fprintf(stderr,"\n");
   }

   iconv_close(pvIcv);
   if (pfOut!=stdout) fclose(pfOut);
   free(pcOutBuf);
   exit(0);
}
