/******************************************************************************
 * program:     wp2latex                                                      *
 * function:    supporting module for online character conversion  	      *
 * modul:       cpbldr.cc						      *
 * description: The executable for generating codetables will be generated    *
 *		after compiling this module.				      *
 ******************************************************************************/
#include "cplib.cc"

#if !defined(__DJGPP__)
#include "../jobs/csext.h"
#endif

#if defined(__DJGPP__) || defined(_WIN32)
 #define DIR_DEPARATOR '\\'
#else
 #define DIR_DEPARATOR '/'
#endif


const char *Directory = NULL;


doublelist ReadCodeGenerator(const char *FileName)
{
FILE *F;
doublelist result;
string key,code,s;
int i;

  if(FileName==NULL) return result;
  if(*FileName==0) return result;
  F = fopen(FileName,"rb");
  if(F==NULL) return result;

  result.Flip(1);

  while(!feof(F))
    {
    if(key=="")
      {
      fGets2(F,s);
      if(s=="") continue;		// Line is empty
      s.trim();
      if(s[0]!='[') continue;		// No opening bracket found
      for(i=length(s)-1; i>1; i--)
        {
        if(s[i]==']') break;
        }
      if(s[i]!=']') continue;		// No closing bracket found
      key = copy(s,1,i-1);
      //printf("%s -> %s",s(),key());

      if(!key.isEmpty())
        {
        const char *Semicolon = strchr(key(),';');
        if(Semicolon!=NULL)
          {
          //printf("\nKey=%s", key());
          key = copy(key,0, Semicolon-key());
	  //printf(" -> %s", key());
          }
        key.trim();
        }
      }
         //the key name is guaranteed to be read
    while(!feof(F))  //read its code now
      {
      fGets2(F,s);
      s.trim();
      if(s=="") continue;
      if(s[0]==';') continue;			//remove comment lines
      if(s[0]=='/') if(s[1]=='/') continue;
      
      if(s[0]=='[')				//new key was found
      	   {
           if(!code.isEmpty()) result.Add(key,code);
           for(i=length(s)-1; i>1; i--)
               {
               if(s[i]==']') break;
               }
           if(s[i]==']')
             {
             key = copy(s,1,i-1);
             if(!key.isEmpty())
               {
               const char *Semicolon = strchr(key(),';');
               if(Semicolon!=NULL)
                 {
                 //printf("\nKey='%s'", key());
                 key = copy(key,0, Semicolon-key());
	         //printf(" -> '%s'", key());
                 }
               key.trim();
               }
             }
           else
	     {
             printf("\nInvalid key name: %s!",s());
             key = "";
             }
           code = "";
           break;
           }
      else {
      	   if(!code.isEmpty()) code+='\n';
      	   code+=s;
           }
      }
    }

  fclose(F);

  if(!key.isEmpty() && !code.isEmpty()) result.Add(key,code);

return result;
}


string CheckPlainString(const string & command)
{
int i,state,num=0;
char ch;
string plain;

 state=0;
 for(i=0; i<length(command); i++)
   {
   ch = command[i];
   switch(state)
     {
     case 0:if(ch=='r') {state=1;break;}
     	    if(isspace(ch)) break;
     	    return string();
     case 1:if(ch=='e') {state=2;break;}
     	    return string();
     case 2:if(ch=='t') {state=3;break;}
     	    return string();
     case 3:if(ch=='u') {state=4;break;}
     	    return string();
     case 4:if(ch=='r') {state=5;break;}
     	    return string();
     case 5:if(ch=='n') {state=6;break;}
     	    return string();
     case 6:if(ch=='\"') {state=20;break;}
            if(ch=='(') {state=30;break;}
            if(ch=='U') {state=100;break;}
            if(isspace(ch)) break;
     	    return string();
            
     case 20:if(ch=='\"') {state=34;break;}
            if(ch=='\\') state=21;  		//add also \ into plain
            plain = plain + ch;
            break;
     case 21:     				//use every character after "\\" backslash
            state=20;
            plain=plain+ch;
            break;

     case 30:if(ch=='\"') {state=31;break;}	//leading "
     	    if(isspace(ch)) break;
            return string();
     case 31:if(ch=='\"') {state=33;break;}     //trailing "
            if(ch=='\\') state=32;              //add also \ into plain
            plain=plain+ch;
            break;
     case 32:     				//use every character after "\\" backslash
     	    state=31;
            plain=plain+ch;
            break;
     case 33:if(ch==')') {state=34;break;}
            if(isspace(ch)) break;
            return string();       
     case 34:if(ch==';') {state=35;break;}
            if(isspace(ch)) break;
            return string();
     case 35:if(isspace(ch)) break;	//skip trailing spaces;
	    if(ch=='/') {state=36;break;}	// Comment is present
            return string();
     case 36:if(ch=='/') return plain;
	    return string();

     case 100:if(ch=='s') {state=101;break;}
     	    return string();
     case 101:if(ch=='e') {state=102;break;}
     	    return string();
     case 102:if(ch=='r') {state=103;break;}
     	    return string();
     case 103:if(ch=='C') {state=104;break;}
     	    return string();
     case 104:if(ch=='h') {state=105;break;}
     	    return string();
     case 105:if(ch=='a') {state=106;break;}
     	    return string();
     case 106:if(ch=='r') {state=107;break;}
     	    return string();
     case 107:if(ch=='S') {state=108;break;}
     	    return string();
     case 108:if(ch=='e') {state=109;break;}
     	    return string();
     case 109:if(ch=='t') {state=110;break;}
     	    return string();
     case 110:if(ch=='(') {state=111;break;}
            if(isspace(ch)) break;
     	    return string();
     case 111:if(ch==',') {state=112;break;}
            if(isdigit(ch))
     		{
                num=10*num+ch-'0';break;
                }
            if(isspace(ch)) break;
     	    return string();
     case 112:if(ch=='c') {state=113;break;}     
     	    return string();
     case 113:if(ch=='q') {state=114;break;}
     	    return string();
     case 114:if(ch==')') {state=115;break;}
            if(isspace(ch) && num==0) break;
     	    return string();
     case 115:if(ch==';')
     		{                
                do {		//convert to octal system
                   plain=(char)(num%8+'0')+plain;
                   num=num/8;
                   } while(num>0);
                plain="\\"+plain;
                plain=(char)1+plain;
                state=116;
                break;
                }
            if(isspace(ch)) break;
     	    return string();
     case 116:if(isspace(ch)) break;	//skip trailing spaces;
            return string();
            
     }   
   }
return plain;
}


void ForgeFname(string &str, const char *Fname, bool Erase=true)
{
FILE *F;

 str = Directory;
 if(str.length()>0) str+=DIR_DEPARATOR;
 str += Fname;

 if(Erase)
 {
   F = fopen(str(),"rb");
   if(F != NULL)
   {
     fclose(F);
     printf("Old file \"%s\" exists!\n", str());
     remove(str());
   }
 }
}


void WriteCodeGenerator(doublelist & WP2LaTeX, const char *Table, const char *Switch)
{
FILE *F, *Fcp;
int i, j;
doublelist SimpleTable;
unsigned TablePosStart;

 j = 0;
 for(i=0; i<WP2LaTeX.length(); i++)
   {
   string s2(WP2LaTeX.Member(i,1));

   string Plain = CheckPlainString(s2);

/*   if(Plain.length()==0)
   {
     printf("\n >>%s<<",s2());
   } */

   if(Plain.length()>0 && i>128) j++;
   }

 TablePosStart = (WP2LaTeX.length()-j+1);

 F = fopen(Switch,"wb");
 fprintf(F,
    "/*Do not edit this file. It is automatically generated.*/\n"
    "\n"
    "#ifdef __Limited_Proc_Size\n"
    " char *BorlandCPPIsShit(WORD wchar, TconvertedPass1 *cq);\n"
    " char *BorlandCPPIsShit2(WORD wchar, TconvertedPass1 *cq);\n"
    "#endif\n"
    "\n"
    "extern const char *SimpleTable[];\n"
   "\n"
    "/*This function expands an extended WP character into LaTEX sequence*/\n"
    "const char *Ext_chr_str(WORD wchar_code, TconvertedPass1 *cq, const CpTranslator *ConvertCP)\n"
    "{\n"
    "#ifdef DEBUG\n"
    "  fprintf(cq->log,\"\\n#Ext_chr_str(0x%%X) \",wchar_code);fflush(cq->log);\n"
    "#endif\n"
    "\n"
    "  //if(ConvertCP==NULL) ConvertCP=cq->ConvertCpg;\n"
    " if(ConvertCP!=NULL)	/*Translate other character sets*/\n"
    "	{\n"
    "	WORD WC2 = (*ConvertCP)[wchar_code];\n"
    "	if(WC2==0) return(\" -?- \");\n"
    "	wchar_code=WC2;\n"
    "	}\n"
    "\n"
    " if(Convert2Target!=NULL)\n"
    "   {\n"
    "   WORD unicode;\n"
    "   switch(OutCodePage)\n"
    "     {\n"
    "     case UTF8: unicode = (*Convert2Target)[wchar_code];\n"
    "                if(unicode>=128) return ExpandUTF8(unicode);\n"
    "                break;  // standard ASCII character\n"
    "     case 852:\n"
    "     case 1250: unicode = (*Convert2Target)[wchar_code];\n"
    "                if(unicode>=128) return ExpandPlainChar(unicode);\n"
    "                break;  // standard ASCII character\n"
    "     }\n"
    "   }\n"
    "\n"
    " while(wchar_code >= %u)\n"
    "	{\n"
    "	wchar_code -= %u;\n"
    "	if(wchar_code>=%d) return(\" -?- \");  //sizeof(SimpleTable)/sizeof(char *)\n"
    "	const char *ret=SimpleTable[wchar_code];\n"
    "	if(*ret!=1) return(ret);	// Element has been found in the table\n"
    "	wchar_code = UserCharSet(ret[1],cq);\n"
    "	}\n"
    "\n"
    "switch(wchar_code) {\n"
    "\n", TablePosStart, TablePosStart, j);

 Fcp = fopen(Table,"wb");
 j = 0;
 for(i=0; i<WP2LaTeX.length(); i++)
   {
   string s1(WP2LaTeX.Member(i,0));
   string s2(WP2LaTeX.Member(i,1));

   string Plain = CheckPlainString(s2);
   if(Plain.length()>0 && i>128)
     {
     SimpleTable.Add(s1(),Plain());
     }
   else
     {
     j++;
     s2 = replacesubstring(s2,"\n","\n\t");
     fprintf(F,"case %d: /*%s*/\n\t%s\n",j,s1(),s2());
     fprintf(Fcp,"%d %s\n",j,s1());

     if(j==350)
      fprintf(F,"\n"
       "#ifdef __Limited_Proc_Size\n"
       "       }\n"
       "return(BorlandCPPIsShit(wchar_code,cq));}\n"
       "char *BorlandCPPIsShit(WORD wchar_code, TconvertedPass1 *cq)\n"
       "{switch(wchar_code) {\n"
       "#endif\n"
       "\n");

     if(j==618)
      fprintf(F,"\n"
       "#ifdef __Limited_Proc_Size\n"
       "       }\n"
       "return(BorlandCPPIsShit2(wchar_code,cq));}\n"
       "char *BorlandCPPIsShit2(WORD wchar_code, TconvertedPass1 *cq)\n"
       "{switch(wchar_code) {\n"
       "#endif\n"
       "\n");
     }
   }

  fprintf(F,"\n"
   " // #endif\n"
   "     }\n"
   "  UnknownCharacters++;\n"
   "  return(\" -?- \");\n"
   "}"); 

  fprintf(F,"\nconst char *SimpleTable[] = {\n");
  for(i=0;i<SimpleTable.length();i++)
   {
   string s1(SimpleTable.Member(i,0));
   string s2(SimpleTable.Member(i,1));

   fprintf(F,"  \"%s\"",s2());
   if(i<SimpleTable.length()-1) fputc(',',F);
   fprintf(F," /*%s*/\n",s1());

   fprintf(Fcp,"%d %s\n", i+TablePosStart, s1());
   }
  if(SimpleTable.length()<=0) fprintf(F,"NULL");
  fprintf(F," };\n");
 
 fclose(F);
 fclose(Fcp);
}


#if !defined(__DJGPP__)
FILE *Flock = NULL;
string FileLock;
#endif



/** This procedure compares "Src" file with "Dest"
 * @param[in]	 Dest	destination file name.
 * @param[in]	 Src	source file name.
 * @return	0 on success; <0 on failure. */
int CompareFile(FILE *F1, FILE *F2)
{
char Buff1[32];
char Buff2[32];
size_t sz1, sz2;

 if(F1==NULL || F2==NULL) return -1;

 do
 {
   sz1 = fread(Buff1,1,sizeof(Buff1),F1);
   sz2 = fread(Buff2,1,sizeof(Buff2),F2);
   if(sz1 != sz2) return -2;
   if(!memcmp(Buff1,Buff2,sz1)) return -3;
 } while(sz1 > 0);

return(0);
}


/// Multithreading build fails when a file growing in progress is accessed by
/// another job. It is safe to create a different file and reneme it when it is finished.
int FcloseEpilog(FILE *Ftmp, const char *TmpFilename, const char *PersistentFilename)
{
string FileName2;
FILE *Fper;

 ForgeFname(FileName2, PersistentFilename);

 if((Fper=fopen(FileName2(),"rb"))!=NULL)	// Destroy older persistent file if exists.
 {
   if(Ftmp)
   {
     fseek(Ftmp,0,SEEK_SET);
     if(CompareFile(Ftmp,Fper) == 0)
     {
       printf("Both files \"%s\" to \"%s\" have same contents!\n", TmpFilename, FileName2());
       fclose(Ftmp);
       fclose(Fper);
       remove(TmpFilename);  // No need to replace file
       return 0;
     }     
   }
   fclose(Fper);	// Second file is different must be replaced.   
   remove(FileName2());		// Unlink older persistent name.
 }

 if(Ftmp) {fclose(Ftmp);Ftmp=NULL;}
 if(rename(TmpFilename, FileName2()))
 {
   printf("Cannot rename file from \"%s\" to \"%s\"!\n", TmpFilename, FileName2());
#if !defined(__DJGPP__)
   if(Flock)
   {
     fclose(Flock);
     unlink(FileLock());
     Flock=NULL;
   }
#endif
   return -1;
 }
return 0;
}


int main(int argc, char *argv[])
{
FILE *F;
codepage internal;
codepage cp852, cp1250, cp1251, cp1252, cp1253, cp1254, cp1255, cp1276, cpkam,
         cpwp4a, cpwp5, cpwp5_cz, cpwp6,
	 iso_8859_1, iso_8859_2, iso_8859_3, iso_8859_4,
         unicode,koi8cs,koi8r,
	 mtef2,
         MAC_ROMAN;
codepage cphtml;
codepage symbol, Wingdings;
Translator trn;
doublelist llHTML;
string FileName;

 puts("<<<cplib>>> (c)1999-2022 J.Fojtik");

 if(argc>1)
   {
   int i = 0;
   while(++i < argc)
     {
     if(!strcmp(argv[i],"-dir"))
	{
	i++;
	if(i<argc) Directory=argv[i];
	}
     }
   }


#if !defined(__DJGPP__)
 //Sleep(1000);
 ForgeFname(FileLock,"lock.lck",false);
 while((Flock=fopen(FileLock(),"rb")) != NULL)
 {
   fclose(Flock);
   if(remove(FileLock())==0) break;
   printf("Lock \"%s\" found, waiting for cleanup from concurrent app.\n", FileLock());
   Sleep(500);
 }
 while((Flock=fopen(FileLock(),"wbx")) == NULL)
 {
   printf("Lock \"%s\" found, waiting for cleanup from concurrent app.\n", FileLock());
   Sleep(500);
 }
 fprintf(Flock,"running");
#endif

 //printf("\n dir=%s.",Directory);

 //printf("%ld\n",coreleft());

 doublelist WP2LaTeX(ReadCodeGenerator("chars.c_"));
 //WP2LaTeX.doublelist::operator=(WP2LaTeX,ReadCodeGenerator("chars.c_"));

 WP2LaTeX.Flip(0);
 ForgeFname(FileName, "internal.enc");
 WriteCodeGenerator(WP2LaTeX,FileName(),"charactr.cc_");
 WP2LaTeX.erase();

 ForgeFname(FileName, "internal");
 if(!LoadCodePage(FileName(), internal)) return -1;
 if(!LoadCodePage("unicode", unicode,0)) return -2;

	// WP speciffic code pages.
 if(!LoadCodePage("wp6",cpwp6,0)) return -9;
 if(!LoadCodePage("wp5",cpwp5,0)) return -10;
 if(!LoadCodePage("wp5_cz",cpwp5_cz,0)) return -11;
 if(!LoadCodePage("wp4a",cpwp4a,0)) return -12;

 LoadCodePage("mtef2",mtef2,0);
 LoadCodePage("symbol",symbol,0);
 LoadCodePage("Wingdings",Wingdings,0);
 LoadCodePage("mac_roma",MAC_ROMAN,0);
 if(MAC_ROMAN.name){free(MAC_ROMAN.name);MAC_ROMAN.name=strdup("MacRoman");}

 if(!LoadCodePage("cp852",cp852,0)) return -3;
 if(!LoadCodePage("cp1250",cp1250,0)) return -4;
 if(!LoadCodePage("cp1251",cp1251,0)) return -5;
 if(!LoadCodePage("cp1252",cp1252,0)) return -6;
 if(!LoadCodePage("cp1253",cp1253,0)) return -7;
 LoadCodePage("cp1254",cp1254,0);
 if(!LoadCodePage("cp1255",cp1255,0)) return -8;
 if(!LoadCodePage("cp1276",cp1276,0)) return -9;

 if(!LoadCodePage("kam",cpkam,0)) return -10;
 LoadCodePage("koi8cs",koi8cs,0);
 LoadCodePage("koi8_r",koi8r,0);

 LoadCodePage("is8859_1",iso_8859_1,0);
 if(iso_8859_1.name) {free(iso_8859_1.name);iso_8859_1.name=strdup("iso_8859_1");}
 LoadCodePage("is8859_2",iso_8859_2,0);
 if(iso_8859_2.name) {free(iso_8859_2.name);iso_8859_2.name=strdup("iso_8859_2");}
 LoadCodePage("is8859_3",iso_8859_3,0);
 if(iso_8859_3.name) {free(iso_8859_3.name);iso_8859_3.name=strdup("iso_8859_3");}
 LoadCodePage("is8859_4",iso_8859_4,0);
 if(iso_8859_4.name) {free(iso_8859_4.name);iso_8859_4.name=strdup("iso_8859_4");}


 //if(heapcheck()<=0)
 //	asm int 3;
 printf("%ld\n",coreleft());


 ForgeFname(FileName, "trn.new");
 if((F=fopen(FileName(),"wb"))==NULL)
 {
   printf("Cannot write to the file \"%s\"", FileName());
   return -1;
 }
 fprintf(F,"/*Do not edit this file, it is generated automatically from .enc files*/\n");


 CreateTranslator(trn,cpwp4a, internal);
 WriteTranslator(cpwp4a,internal,trn,F);

 CreateTranslator(trn,cpwp5, internal);
 WriteTranslator(cpwp5,internal,trn,F);

 CreateTranslator(trn,cpwp5_cz, internal);
 WriteTranslator(cpwp5_cz,internal,trn,F);

 CreateTranslator(trn,cpwp6, internal);
 WriteTranslator(cpwp6,internal,trn,F);

//if(heapcheck()<=0)
//	asm int 3;
//goto end;

/*
CreateTranslator(trn,cpkam,internal);
WriteTranslator(cpkam,internal,trn,F);

CreateTranslator(trn,cp1250,internal);
WriteTranslator(cp1250,internal,trn,F);
*/

/*
trn=CreateTranslator(cp1250,cpkam);
WriteTranslator(cp1250,cpkam,trn,F);
*/

//end:
 if(FcloseEpilog(F,FileName(),"trn.trn")) return -1;


 LoadTextCodePage("html",llHTML);
 llHTML.sort(strcmp);
 CreateCodePage("html",cphtml,llHTML);

 ForgeFname(FileName, "html.new");
 if((F=fopen(FileName(),"w"))==NULL)
 {
   printf("Cannot write to the file \"%s\"", FileName());
   return -3;
 }
 fprintf(F,"/*Do not edit this file. It is automatically generated.*/\n");
 CreateTranslator(trn,cphtml, internal);
 WriteTranslator(cphtml,internal,trn,F);
 WriteStringTable(F,cphtml,llHTML);
 if(FcloseEpilog(F,FileName(),"html.trn")) return -1;


 ForgeFname(FileName, "cpg.new");
 if((F=fopen(FileName(),"w"))==NULL)
 {
   printf("Cannot write to the file \"%s\"", FileName());
   return -2;
 }
 fprintf(F,"/*Do not edit this file. It is automatically generated.*/\n");
 CreateTranslator(trn,cpkam,internal);
 WriteTranslator(cpkam,internal,trn,F);

 CreateTranslator(trn,iso_8859_1,internal);
 WriteTranslator(iso_8859_1,internal,trn,F);

 CreateTranslator(trn,iso_8859_2,internal);
 WriteTranslator(iso_8859_2,internal,trn,F);

 CreateTranslator(trn,iso_8859_3,internal);
 WriteTranslator(iso_8859_3,internal,trn,F);

 CreateTranslator(trn,iso_8859_4,internal);
 WriteTranslator(iso_8859_4,internal,trn,F);

 CreateTranslator(trn,cp852,internal);
 WriteTranslator(cp852,internal,trn,F);

 CreateTranslator(trn,cp1250,internal);
 WriteTranslator(cp1250,internal,trn,F);

 CreateTranslator(trn,cp1251,internal);
 WriteTranslator(cp1251,internal,trn,F);

 CreateTranslator(trn,cp1252,internal);
 WriteTranslator(cp1252,internal,trn,F);

 CreateTranslator(trn,cp1253,internal);
 WriteTranslator(cp1253,internal,trn,F);

 CreateTranslator(trn,cp1254,internal);
 WriteTranslator(cp1254,internal,trn,F);

 CreateTranslator(trn,cp1255,internal);
 WriteTranslator(cp1255,internal,trn,F);

 CreateTranslator(trn,unicode,internal);
 WriteTranslator(unicode,internal,trn,F);

 CreateTranslator(trn,koi8cs,internal);
 WriteTranslator(koi8cs,internal,trn,F);

 CreateTranslator(trn,koi8r,internal);
 WriteTranslator(koi8r,internal,trn,F);

	// *************** Output codepage generator *****************
 CreateTranslator(trn, internal, unicode);
 WriteTranslator(internal, unicode, trn, F);

 CreateTranslator(trn, internal, cp1276);
 WriteTranslator(internal, cp1276, trn, F);

 CreateTranslator(trn, internal, symbol);
 WriteTranslator(internal, symbol, trn, F);

/*
 CreateTranslator(trn, internal, cp1250);
 WriteTranslator(internal, cp1250, trn, F);

 CreateTranslator(trn, internal, cp852);
 WriteTranslator(internal, cp852, trn, F);

 CreateTranslator(trn, internal, iso_8859_1);
 WriteTranslator(internal, iso_8859_1, trn, F);

 CreateTranslator(trn, internal, iso_8859_2);
 WriteTranslator(internal, iso_8859_2, trn, F);
*/
 if(FcloseEpilog(F,FileName(),"cpg.trn")) return -1;


 ForgeFname(FileName, "mtef.new");
 if((F=fopen(FileName(),"w"))==NULL)
 {
   printf("Cannot write to the file \"%s\"", FileName());
   goto SkipMtef;
 }

 fprintf(F,"/*Do not edit this file. It is automatically generated.*/\n");
 CreateTranslator(trn, symbol, internal);
 WriteTranslator(symbol, internal, trn, F);

 CreateTranslator(trn, Wingdings, internal);
 WriteTranslator(Wingdings, internal, trn, F);

 CreateTranslator(trn, mtef2, internal);
 WriteTranslator(mtef2, internal, trn, F);
 if(FcloseEpilog(F,FileName(),"mtef.trn")) return -1;


SkipMtef:
 ForgeFname(FileName, "macroman.new");
 if((F=fopen(FileName(),"w"))==NULL)
 {
   printf("Cannot write to the file \"%s\"", FileName());
   return -3;
 }
 fprintf(F,"/*Do not edit this file. It is automatically generated.*/\n");
 CreateTranslator(trn,MAC_ROMAN,internal);
 WriteTranslator(MAC_ROMAN,internal,trn,F);
 if(FcloseEpilog(F,FileName(),"macroman.trn")) return -1;


 trn.erase();

#if !defined(__DJGPP__)
 if(Flock)
 {
   fclose(Flock);
   unlink(FileLock());
 }
#endif

return 0;
}