/*
 * Compile with:
 *   gcc -o testutf8 -g -O0 -fno-inline testutf8.c
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <assert.h>




typedef unsigned char guchar;
typedef unsigned int gunichar;
typedef signed int gssize;

/*--------------------- copied from glib ----------------------------*/

#define UNICODE_VALID(Char)                   \
    ((Char) < 0x110000 &&                     \
     (((Char) & 0xFFFFF800) != 0xD800) &&     \
     ((Char) < 0xFDD0 || (Char) > 0xFDEF) &&  \
     ((Char) & 0xFFFE) != 0xFFFE)

#define CONTINUATION_CHAR                           \
 do {                                               \
  if ((*(guchar *)p & 0xc0) != 0x80) /* 10xxxxxx */ \
    goto error;                                     \
  val <<= 6;                                        \
  val |= (*(guchar *)p) & 0x3f;                     \
 } while(0)


static const char *
fast_validate_len (const char *str,
		   gssize      max_len)
{
  gunichar val = 0;
  gunichar min = 0;
  const char *p;

  for (p = str; (max_len < 0 || (p - str) < max_len) && *p; p++)
    {
      printf("%d ", p - str);

      if (*(guchar *)p < 128)
	/* done */;
      else 
	{
	  const char *last;
	  
	  last = p;
	  if ((*(guchar *)p & 0xe0) == 0xc0) /* 110xxxxx */
	    {
	      if (max_len >= 0 && max_len - (p - str) < 2)
		goto error;
	      
	      if ((*(guchar *)p & 0x1e) == 0)
		goto error;
	      p++;
	      if ((*(guchar *)p & 0xc0) != 0x80) /* 10xxxxxx */
		goto error;
	    }
	  else
	    {
	      if ((*(guchar *)p & 0xf0) == 0xe0) /* 1110xxxx */
		{
		  if (max_len >= 0 && max_len - (p - str) < 3)
		    goto error;
		  
		  min = (1 << 11);
		  val = *(guchar *)p & 0x0f;
		  goto TWO_REMAINING;
		}
 	      else if ((*(guchar *)p & 0xf8) == 0xf0) /* 11110xxx */
		{
		  if (max_len >= 0 && max_len - (p - str) < 4)
		    goto error;

		  min = (1 << 16);
		  val = *(guchar *)p & 0x07;
		}
	      else
		goto error;
	      
	      p++;
	      CONTINUATION_CHAR;
	    TWO_REMAINING:
	      p++;
	      CONTINUATION_CHAR;
	      p++;
	      CONTINUATION_CHAR;
	      
	      if (val < min)
		goto error;
	      if (!UNICODE_VALID(val))
		goto error;
	    } 
	  
	  continue;
	  
	error:
	  return last;
	}
    }

  return p;
}

static int
my_g_utf8_validate (const char   *str,
		 gssize        max_len,    
		 const char **end)

{
  const char *p;

  assert(max_len > 0);

  p = fast_validate_len (str, max_len);

  if (end)
    *end = p;

  if ((max_len >= 0 && p != str + max_len) ||
      (max_len < 0 && *p != '\0'))
    return 0;
  else
    return 1;
}

/*-------------------------------------------------------------------*/

int
main (int argc, char *argv[])
{
	char *fname;
	int fd;
	struct stat statbuf;
	char *mapped_file;

	if (argc != 2)
	{
		printf("enter a filename\n");
		exit(1);
	}

	fname = argv[1];

	printf("opening %s\n", fname);

	fd = open (fname, O_RDONLY);
	if (fd == -1)
	{
		perror ("open");
		exit(1);
	}

	if (fstat (fd, &statbuf) != 0) 
	{
		perror ("fstat");
		exit(1);
	}

	printf("stat size: %d\n", statbuf.st_size);

	mapped_file = mmap (0, /* start */
			    statbuf.st_size, 
			    PROT_READ,
			    MAP_PRIVATE, /* flags */
			    fd,
			    0 /* offset */);

	if (mapped_file == MAP_FAILED)
	{
		perror ("mmap");
		exit(1);
	}

	my_g_utf8_validate (mapped_file, statbuf.st_size, NULL);

	return 0;
}
