/*
  hello_iconv.c

  Copyright 2000, Stephen J. Turnbull <stephen@xemacs.org>

   Pokazuje użycie funkcji iconv zgodnej z UNIX98
*/

#include <iconv.h>
#include <stdio.h>
#include <errno.h>

/* Prototypy zdefiniowanych funkcji */
void u2a (char *a_string, const unsigned short *u_string);
char *dotted (char *s, int n);
void usage ();

/* the input data should look vaguely familiar, if strangely formatted */
unsigned short unicode_string[15] =
{
  'h','e','l','l','o',',',' ','w','o','r','l','d','\n',0,
  0 /* Strażnik końca bufora, bo będzie rzutowanie do char[] i dotted() */
};

/* Bufor wyściowy */
char c_string[15] =
{
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0 /* Strażnik końca bufora dla napisu wyjściowego z dotted() */
};

/* the important stuff */
void u2a (char *to_c, const unsigned short *from_unicode)
{
    /*
      it's always a good idea to check status, but in I18N you are often
      getting your input from the most unreliable sources -- human users
      and network streams -- so it's imperative
    */
    int status = 0;

    /* Wstyd, C nie ma referencji, nieprawdaż?  */
    int u_count = 14*sizeof(unsigned short);
    int c_count = 14*sizeof(char);

    /*
      to rzutowanie _musi_ być wykonane; jest to sposób definicji 
      funkcji iconv 
    */
    const char *unicode_buffer = (const char *) from_unicode;

    /*
      declare and open the conversion descriptor

      note the UNICODELITTLE; Unicode is by default big-endian, but
      little-endian versions are allowed -- programmer must check

      my box is Intel, your mileage may vary

      alternatively, many Unicode streams are prefixed by the byte-order
      mark (BOM) 0xFEFF (ZERO-WIDTH NO-BREAK SPACE) -- 0xFFFE is not a
      character, so this allows autodetection

      but iconv(3) will not discard this character, if recognized, so a
      naive attempt to use it in a program like this results in EILSEQ,
      since there is no such ASCII character
    */
    iconv_t cd = iconv_open ("ASCII","UNICODELITTLE");

    /*
      Nie to jest konieczne dla ASCII, lecz co będzie, jeśli dane wprowadza
      użytkownik? Na mojej maszynie próba użycia niewłaściwego deskryptora
      konwersji kończy się na płaczem i SIGSEGV.
    */
    if (cd == (iconv_t) (-1))
    {
      printf ("Conversion to ASCII not available.\n");
      exit (1);
    }

    /* do the conversion, checking status and reporting errors */
    switch (status = iconv (cd, &unicode_buffer, &u_count, &to_c, &c_count))
    {
    case 0:
      /*
        W zależności od przydziału pamięci i reprezentacji napisu
        może być potrzebny ogranicznik tego napisu.
      */
      *to_c = '\0';
      break;
    case -1:
      printf ("Error in conversion:  ");
      switch (errno)
      {
      case E2BIG:
	printf ("output buffer too small");
	break;
      case EILSEQ:
	printf ("invalid multibyte sequence");
	break;
      case EINVAL:
	printf ("incomplete multibyte sequence");
	break;
      default:
	printf ("iconv error not in man page");
      }
      printf ("\n");
      /*
     W zależności od przydziału pamięci i reprezentacji napisu
     może być potrzebny ogranicznik tego napisu.
     Błędy E2BIG i EINVAL zazwyczaj nie są błędami krytycznymi; status CD
     umożliwiający wznowienie konwersji po ostatnim udanym przekształceniu 
     znaku nie daje oczywistej odpowiedzi jak tu go użyć, oszukamy go.
      */
      *to_c = '\0';
      break;
    default:
      /*
     W zależności od przydziału pamięci i reprezentacji napisu
     może być potrzebny ogranicznik tego napisu.
     Najprawdopodobniej jest to najlepsza rzecz, jaką możemy tu zrobić.
      */
      *to_c = '\0';
      printf ("%d characters irreversibly converted\n",
	      status);
    }

    /* ... i złota gwiazda za oczyszczenie: struktura danych cd może
   zawierać całkiem duże tabele, więc to może prowadzić do istotnych
   wycieków pamięci
    */
    iconv_close (cd);
}

/*
 Program sterownika, który obsługuje wiele wariantów wyświetlania 
 unikodowych napisów na terminalu; aby użyć - patrz usage().
*/
int main (int argc, char *argv[])
{

  if (argc != 2 || argv[1][1] != '\0')
  {
    usage (argv[0]);
    exit (1);
  }

  switch (argv[1][0]) {
  case '1':
    u2a (c_string, unicode_string);
    printf (c_string);
    break;
  case '2':
    printf (dotted ((char *) unicode_string, 13*sizeof(unsigned short)));
    break;
  case '3':
    printf ((char *) unicode_string);
    break;
  case '4':
    u2a (c_string, unicode_string);
    printf (dotted (c_string, 14));
    break;
  default:
    usage (argv[0]);
    exit (1);
  }

  exit(0);
}

/*
 Niewielka procedura zastępująca kropkami wszystkie bajty NULL w napisie. 
*/
char *dotted (char *s, int n)
{
  while (n > 0)
    if (s[--n] == '\0')
      s[n] = '.';

  return s;
}

/*
 Z czystego przymusu...
*/
void usage (char *s)
{
  printf ("usage: %s {1|2|3|4}\n", s);
  printf ("1 - printf converted C string\n");
  printf ("2 - printf Unicode string with dots replacing null bytes\n");
  printf ("3 - printf raw Unicode string\n");
  printf ("4 - printf converted C string with dots replacing null bytes\n");
}
