/* External en/de-coding: utf-16 (utf-16le = 1200, utf-16be = 1201)
   (to be done utf-32, koi8-r = 20866, koi8-ru, cp866 - #866 (dos,old-windows), cp1251 - #1251 (old-windows), 
               utf-32le = 12000, utf-32be = 12001)
   Version 13-07-2025.
*/
#include <wchar.h>
#include <wctype.h>
#include <uchar.h>
#include <stddef.h>
#include <stdio.h>


# include "decl.h"
# include "macros.h"
# include "fileio.h"
# include "ifunc.h"

#define RI_EOF -1
// #define RI_EOF EOF
#define EXTRN_OUT(fd) (!((fd == stderr) || (fd == stdout)))

/*
 В Windows API тип wchar_t именуется как WCHAR и имеет фиксированный размер 16 бит, 
 что не позволяет кодировать весь набор символов Unicode (больше 1 миллиона). 
 Поэтому нарушается стандарт ANSI/ISO C, который требует, 
 чтобы символьный тип wchar_t поддерживал все представимые в системе символы в одном объекте wchar_t. По сути, 
 в WinAPI под WCHAR подразумевается 2-байтное слово из кодировки UTF-16LE (как тип WORD), 
 поэтому символы с кодами выше FFFF16 кодируются парой WCHAR (так называемые «суррогаты») и 
 всем API-функциям передаётся не количество символов, а размер символьного массива в машинных словах.

 В GNU/Linux тип wchar_t имеет размер 32 бита.
 
 При переносе под Linux здесь какие-то условные директивы препроцессора должны быть.
*/

/*
  Encoding:     utf-16le/utf-16be sequence (from stream fp_inp) 
            ==> U+number (unicode abstract number).
*/
wint_t ri_getwint_from_buf (char *);  /* Local function. */

/* ??? The following variant dows not work under Windows/GCC_MINGW
wint_t ri_fgetwc (FILE * fp_inp) {
#ifdef BY_GCC_MINGW_WIN
//   wint_t wchar = fgetwc (fp_inp);
   wint_t wchar = fgetc (fp_inp);
   if ( wchar == WEOF ) return EOF;
   return wchar;
//+   return fgetwc (fp_inp);
#else  // Linux x64
	return fgetc (fp_inp);
#endif
}
*/
#ifdef BY_GCC_MINGW_WIN
# define RW_EOF WEOF
# define ri_fgetwc fgetwc
#else  // Linux x64
# define RW_EOF EOF
# define ri_fgetwc getc
#endif

int ri_getInt32 (FILE * fp_inp, char mode) {
 wint_t wchar1, wchar2, wchar;
 
 if ( ((wchar1 = ri_fgetwc (fp_inp)) != RW_EOF) && ((wchar2 = ri_fgetwc (fp_inp)) != RW_EOF) ) { // ++windows, ++linux
   if ( mode == ENC_UTF_16BE ) 
      { wchar = wchar1; wchar1 = wchar2; wchar2 = wchar; };
             // byte permutation, (((int)wchar2)<<8) + (int)wchar1
   	return ((((int)wchar2)<<8) | (int)wchar1); 
   };
 return RI_EOF; 
}

/*
  Encoding:     utf-16le/utf-16be sequence (from byte buffer buf) 
            ==> U+number (unicode abstract number).
*/
// Позже объединить эту функцию и предыдущую, 
// как и аналогичные ниже - посредством дополнительных аргументов.
int ri_getInt32_from_buf (char * buf, char mode) {
 wint_t wchar1, wchar2, wchar;
 
 if ( ((wchar1 = ri_getwint_from_buf(buf)) != RW_EOF) && ((wchar2 = ri_getwint_from_buf(buf)) != RW_EOF) ) { // ++windows, ++ linux
   if ( mode == ENC_UTF_16BE ) 
      { wchar = wchar1; wchar1 = wchar2; wchar2 = wchar; };
             // byte permutation, (((int)wchar2)<<8) + (int)wchar1
   	return ((((int)wchar2)<<8) | (int)wchar1); 
   };
 return RI_EOF; 
}

static int buf_cnt = 0;

wint_t ri_getwint_from_buf (char * buf) {
 // Разобраться с концом буфера!
 // printf("\n buf_cnt = %i, ri_get_from_bf: * (buf + buf_cnt) = x%x, * (buf + buf_cnt+1) = x%x ", buf_cnt, * (buf + buf_cnt), * (buf + buf_cnt+1));
 wint_t i = ( (((wint_t)(* (buf + buf_cnt)))<<8) | ((wint_t)(* (buf + (buf_cnt+1)))) );
 buf_cnt += 2;
 return i;
}

/*
// В случае успеха возвращаются значения
// в диапазонах $0000..$D7FF и $E000..$10FFFF.
*/
unsigned int ri_getwc_utf16_to_int (FILE * fp_inp, char mode) {
  int FirstW, SecondW, unicode;

  if (  ((FirstW = ri_getInt32(fp_inp, mode)) != RI_EOF)  ) {
   if ( (FirstW < 0xD800) || (FirstW > 0xDFFF) ) {  // regular utf-16 chracter
   	return FirstW; 
   };
   /* Surrogate pairs are considered below. */
   unicode = (int)(FirstW & 0x3ff) << 10;
   
  if (  ((SecondW = ri_getInt32(fp_inp, mode)) != RI_EOF) ) {
   if ( (SecondW < 0xDC00) || (SecondW > 0xDFFF) ) {  // incorrect utf-16 sequence
      SecondW = (SecondW < 0xDC00) ? 0xDC00 : 0xDFFF; // temporary definition,
                                            // the violated bounds is assigned
   };
   Second:
   unicode = unicode | (int)(SecondW & 0x3ff);
   return (unicode + 0x10000);
  };
  // incorrect utf-16 sequence
//  return RI_EOF;  // temporary definition
  SecondW = 0xDFFF; // the maximal bound is assigned
  goto Second;      // temporary definition
 }; 
 return EOF;
//? return RI_EOF;
}	


/*
Temporary definition:
+LF (U+000A): англ. line feed — подача строки <ПС>;
+CR (U+000D): carriage return — возврат каретки <ВК>;
+NEL (U+0085): next line — переход на следующую строку;
LS (U+2028): line separator — разделитель строк;
PS (U+2029): paragraph separator — разделитель абзацев.
+Последовательность CR+LF (U+000D U+000A) следует воспринимать как один перевод строки, а не два. 
*/

# define NEL 0x0085
# define LS  0x2028
# define PS  0x2029

/*
  Output ::= 1 | 2 | 0
     0 means the current input symbol do not force a new line;
     1 means the read part of the input stream has finished to sent a line;
     2 means a regular symbol is encountered after the CR.
*/
char new_line_enc_symbol(int symb , FILE * fp_inp, char mode, int descr ) {
    unsigned int i;
    if ( (symb == '\n') ) { 
      fmode_table [descr][2] = EMPTY_MARKER; // nothing to put in ungetc(i,fp_inp);
      return 1; 
    };
    if ( (symb == '\r') ) {
    	i = ri_getwc_utf16_to_int (fp_inp, mode);
    	if ( i == '\n' ) { // CR-NL
         fmode_table [descr][2] = EMPTY_MARKER; // nothing to put in ungetc(i,fp_inp);
    		return 1;
    	};
      fmode_table [descr][2] = i; // ungetc(i,fp_inp);
   	if ( i == '\r' ) { // CR-CR - double CR: the current line is interupted with NL;
         return 1;       // The first CR is to be used for interupting the line;
    	}                  // the second one has been ungetton in the stream.
      if ( (i == EOF) ) { return 3; }  // Комментировать этот случай нельзя!
    	// nothing to do; 
    	return 2;  // A regular symbol after CR, ugetc was done;
    	           // the symbol is the first one in the next line;
    };           

    if ( (symb == NEL) || (symb == LS) || (symb == PS) ) {
      fmode_table [descr][2] = EMPTY_MARKER;
      return 1; 
    }
/* 77777 30-05-2026 ????? Верно ли это? */
    if ( (symb == RI_EOF) ) { fprintf(stderr,"\n RI_EOF at the end of line__22"); return 1; }
//??    if ( (symb == EOF) ) { fprintf(stderr,"\n RI_EOF at the end of line__22"); return 1; }
    return 0; // Ниже неверный комментарий: это делать нельзя. ???Перестанут работать старые программы ...
              // The input symbol cannot compose any sequence  
              // interpreted as an end of the current input line.
}


/*
 The function ri_get_utf16 (FILE * fp_inp, char mode) reads a current line,
 given in the input mode encoding, of the stream fp_inp onto refal free-elements ponted by lfm.
 It does not insert the resulted expresion instead of the active Get call.
 The corresponding insertion should be done by a function calling the ri_get_utf16.
 The B-pointer has to be initialized by the caller as well.    
*/
static int ieof_tmp = EMPTY_MARKER; // This variable is used for removing Refal EOF (0) in the end of the last nonempty line. 
int ri_get_utf16 (FILE * fp_inp, char mode, int fdescr) {
	int flag = 1, j;
	unsigned int i;
	utf symb;

  	i = (fdescr == FORBIDDEN_STREAM_MARKER) ? EOF : fmode_table [fdescr][2];
  	if ( (i == EMPTY_MARKER) )  { i = ri_getwc_utf16_to_int (fp_inp, mode); ieof_tmp = EMPTY_MARKER; } // There is no ungot symbol.

	while (flag) {
		/* test for end-of-file. */
   		if ( (i == EOF) || (ieof_tmp == EOF) ) {
			  nns(0);
			  break;
		   } else {
           j = new_line_enc_symbol(i, fp_inp, mode, fdescr);
           if ( j != 0 ) {  // the current line has been finished;
              break;
           }
 
           if( i > 127 ) {
	   	     symb = r_int_to_utf(i);
     	   	  mns (symb);
           } else 
           { ns ((unsigned char)i); }; 
	     };
	     ieof_tmp = i = ri_getwc_utf16_to_int (fp_inp, mode); 
//	     i_tmp = i;
	     if (i == EOF) break;
	};
	
 	return 0;
}

// Here the buffer content is a flat byte sequence rather than an encoded one.
int ri_get_utf16_buf (FILE * fp_inp, char mode, int fdescr, char * buf) {
	int flag = 1, j;
	utf symb;
	
   buf_cnt = 0;
	unsigned int i = ri_getwc_utf16_from_buf_to_int (buf, mode);
	while (flag) {
		/* test for end-of-file. */
			if ( i == EOF ) {
			  nns(0);
			  break;
		   } else {
           j = new_line_enc_symbol(i, fp_inp, mode, fdescr);
           if ( j != 0 ) { break; } // the current line has been finished;
 
           if( i > 127 ) {
	   	     symb = r_int_to_utf(i);
     	   	  mns (symb);
           } else 
           { ns ((unsigned char)i); }; 
	     };
	     i = ri_getwc_utf16_from_buf_to_int (buf, mode); 
	};

   ri_get_utf16 (fp_inp, mode, fdescr);
   return 0;
}


// 77777
unsigned int ri_getwc_utf16_from_buf_to_int (char * buf, char mode) {
  int FirstW, SecondW, unicode;

  if (  ((FirstW = ri_getInt32_from_buf(buf,mode)) != RI_EOF)  ) {
   if ( (FirstW < 0xD800) || (FirstW > 0xDFFF) ) {  // regular utf-16 chracter
   	return FirstW; 
   };
   /* Surrogate pairs are considered below. */
   unicode = (int)(FirstW & 0x3ff) << 10;
   
  if (  ((SecondW = ri_getInt32_from_buf(buf,mode)) != RI_EOF) ) {
   if ( (SecondW < 0xDC00) || (SecondW > 0xDFFF) ) {  // incorrect utf-16 sequence
//      return RI_EOF;  // temporary definition
      SecondW = (SecondW < 0xDC00) ? 0xDC00 : 0xDFFF; // temporary definition,
                                            // the violated bounds is assigned
   };
   Second:
   unicode = unicode | (int)(SecondW & 0x3ff);
   return (unicode + 0x10000);
  };
  // incorrect utf-16 sequence
//  return RI_EOF;  // temporary definition
  SecondW = 0xDFFF; // the maximal bound is assigned
  goto Second;      // temporary definition
 }; 
 return EOF;
//? return RI_EOF;
}	


/* Ord('?') = U+2BD1 */
// void ri_putc_enc (char ch ,FILE * fd, char mode) {
void ri_putc_enc (utf symb, FILE * fd, char mode) {
   unsigned char char1, char2, ch;
   unsigned u_ord;
   
		    /* Imitating an unpritable utf-symbol. */
	/*?
   if( ((outUnprn == NO) || (fd == stderr)) 
	    && ((('\x00' <= ch) && (ch <= '\x1F')) || (('\x80' <= ch) && (ch <= '\x9F'))) ) {
	} {  char1 = 0x1b; char2 = 0xd1; 
	} // q_sign = '?'
	*/

	if( IS_ENC_UTF_16(mode) ) {
      if ( symb > 127 ) {
         u_ord = ri_utf_to_int(symb); 
         char1 = u_ord >> 8; char2 = u_ord & 0xFF;
      } else {
      	u_ord = symb;
      	char1 = 0x00; char2 = u_ord;
      	goto outp;
      };
      if( (u_ord < 0xD800) || (u_ord > 0x10FFFF) ) { // regular character
         if ( mode == ENC_UTF_16LE ) 
             { ch = char1; char1 = char2; char2 = ch; 
             }; // byte permutation, (((int)wchar2)<<8) + (int)wchar1
      } else if ( u_ord >= 0x10000 ) { // Surrogate pairs
// printf("\n  Surrogate pairs u_ord = 0x%x",u_ord);
      u_ord -= 0x10000;
      unsigned short shortLo = ( (unsigned short)(u_ord & 0x3FF) | 0xDC00 ); 
      unsigned short shortHi = ( (unsigned short)(u_ord >> 10) | 0xD800 ); 
      char1 = shortHi >> 8; char2 = shortHi & 0xFF;
      if ( mode == ENC_UTF_16LE ) 
          { ch = char1; char1 = char2; char2 = ch; }; // byte permutation
      fputc(char1,fd);	fputc(char2,fd);
      char1 = shortLo >> 8; char2 = shortLo & 0xFF;
    outp:  
      if ( mode == ENC_UTF_16LE ) 
          { ch = char1; char1 = char2; char2 = ch; }; // byte permutation
     };

	fputc(char1,fd);
	fputc(char2,fd);
	};
	return;
}

void ri_putc_enc_nl (FILE * fd, char mode) {
	if ( EXTRN_OUT(fd) ) {
	   char ch = '\n';
	   if ( IS_ENC_UTF_16(mode) ) {
# ifdef FOR_OS_WINDOWS_UTF8
        /* It seems to me, here is the only simple way to overcome the problem of 
           the following automatical replacement \n --> \r\n,
           if a text-mode is used for writing in a file.
           Microsoft Windows [Version 10.0.26100.4351]
                                  Nemytykh, 30-06-2025
        */
	   	ch = '\r';
# endif 
   		ri_putc_enc(ch,fd,mode);  // See the problem mentioned above.
//?        fputwc('\n',fd);   // Этот вариант хуже (редактор RJ (win) результат хуже показывает)
	   } else { /* This case includes the utf-8 encoding */
	   	goto end; // putc('\n',fd); 
	   }
	} else {
	end:
		putc('\n',fd);
	}
	return;
}


/* The buffer contains only bytes, s.t. ord(byte) < 128 . */
void ri_fputs_ascii_enc (char * buf, FILE *fd, char mode) {
   char shift;
   char utf16[] = { 0,0 };
   
	if( IS_ENC_UTF_16(mode) ) {
	   shift = (mode == ENC_UTF_16LE) ? 0:1;
   	while ( *buf ) {
   		utf16[shift] = * buf++;
   		putc( utf16[0], fd);
   		putc( utf16[1], fd);
   	}
	}
	return;
}

/* The string may include utf-8 sequences . */
void ri_fputs_compsym_enc(char * cp, FILE *fd, char mode) {
   utf symb;
   int j;
   
   while ( *cp ) {
      if ( *cp < 128) {
      	ri_putc_enc(*cp,fd, mode);
      	cp++;
       } else {
   	   symb = r_isunicode( cp );
   	   if  ( symb ) {
   	      ri_putc_enc (symb ,fd, mode);
   		   j = r_get_utf_ln();
            cp += j;
   	   } else {
// ???  		   fprintf (stderr, "internal error in ri_fputs_compsym_enc (%s,...,...)\n",cp);
   		   exit (1);
      	} 
      }
   }
   ri_putc_enc (' ' ,fd, mode);
	return;
}
