/*
  bdf_map.c
  
  select and map glyphs
  
  Syntax:
    maplist := <mapcmd> { "," <mapcmd> }
    mapcmd := <default> | <maprange> | <exclude>
    default := "*"
    maprange := <range> [  ">" <num> ]
    exclude := "~" <range> 
    kern_exclude := "x" <range> 
    range := <num> [ "-" <num> ]
    num := <hexnum> | <decnum>
    hexnum := "$" <hexdigit> { <hexdigit> }
    decnum := <decdigit> { <decdigit> }
    decdigit := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
    hexdigit := "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F" | <decdigit>
    
    { }: zero, one ore more
    [ ]: zero or once
    |: alternative
  
*/

#include "bdf_font.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

static long range_from;
static long range_to;
static int is_exclude;
static int is_kern_exclude;
static long map_to;

static void skip_space(const char **s)
{
  for(;;)
  {
    if ( **s == '\0' )
      break;
    if ( **s > ' ' )
      break;
    (*s)++;
  }
}

static long get_dec(const char **s)
{
  long v = 0;
  for(;;)
  {
    if ( (**s) >= '0' && (**s)  <= '9' )
    {
      v*=10;
      v+= (**s)-'0';
      (*s)++;
    }
    else
    {
      break;
    }
  }  
  skip_space(s);
  return v;
}

static long get_hex(const char **s)
{
  long v = 0;
  for(;;)
  {
    if ( (**s) >= '0' && (**s)  <= '9' )
    {
      v*=16;
      v+= (**s)-'0';
      (*s)++;
    }
    else if ( (**s) >= 'a' && (**s)  <= 'f' )
    {
      v*=16;
      v+= (**s)-'a'+10;
      (*s)++;
    }
    else if ( (**s) >= 'A' && (**s)  <= 'F' )
    {
      v*=16;
      v+= (**s)-'A'+10;
      (*s)++;
    }
    else
    {
      break;
    }
  }
  skip_space(s);
  return v;
}

static long get_num(const char **s)
{
  if ( (**s) != '$' )
    return get_dec(s);
  (*s)++;
  skip_space(s);
  return get_hex(s);
}

static long get_mul(const char **s)
{
  long v;
  v = get_num(s);
  while ( (**s) == '*' )
  {
    (*s)++;
    skip_space(s);
    v *= get_num(s);
  }
  return v;
}

static long get_add(const char **s)
{
  long v;
  v = get_mul(s);
  while ( (**s) == '+' )
  {
    (*s)++;
    skip_space(s);
    v += get_mul(s);
  }
  return v;
}

static long get_addsub(const char **s)
{
  long v;
  int op;
  v = get_mul(s);
  while ( (**s) == '+' || (**s) == '-' )
  {
    op = **s;
    (*s)++;
    skip_space(s);
    if ( op == '+' )
      v += get_mul(s);
    else
      v -= get_mul(s);      
  }
  return v;
}

static void get_range(const char **s)
{
  range_from = get_add(s);
  if ( **s != '-' )
  {
    range_to = range_from;
  }
  else
  {
    (*s)++;
    skip_space(s);
    range_to = get_add(s);
  }
}

static void map_cmd(const char **s)
{
  if ( **s == '*' )
  {
    range_from = 32;
    range_to = 255;
    map_to = 32;
    is_exclude = 0;
    is_kern_exclude = 0;
    
    (*s)++;
    skip_space(s);
  }
  else if ( **s == '~' )
  {
    is_exclude = 1;
    map_to = 0;	/*will be ignored */
    
    (*s)++;
    skip_space(s);
    get_range(s);
    
  }
  else if ( **s == 'x' )
  {
    is_kern_exclude = 1;
    map_to = 0;	/*will be ignored */
    
    (*s)++;
    skip_space(s);
    get_range(s);
  }
  else 
  {
    is_exclude = 0;
    get_range(s);
    map_to = range_from;
    if ( **s == '>')
    {
      (*s)++;
      skip_space(s);
      map_to = get_addsub(s);
    }
  }
}

void bf_map_cmd(bf_t *bf, const char **s)
{
  int i;
  bg_t *bg;
  static int is_log_disabled_for_single_glyphs = 0;
  
  if ( **s == ',' || **s == '\0' )
    return;
      
  map_cmd(s);

    
  if ( range_from != range_to )
  {
    bf_Log(bf, "Map: exclude=%d from=%ld/$%lx to=%ld/$%lx map=%ld/$%lx", is_exclude, range_from, range_from, range_to, range_to, map_to, map_to);
  }
  else if ( is_log_disabled_for_single_glyphs == 0 )
  {
    bf_Log(bf, "Map: exclude=%d from=%ld/$%lx to=%ld/$%lx map=%ld/$%lx (further single glyph logs disabled)", is_exclude, range_from, range_from, range_to, range_to, map_to, map_to);
    is_log_disabled_for_single_glyphs = 1;
  }
  
  
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    if ( bg->encoding >= range_from && bg->encoding <= range_to )
    {
      if ( is_kern_exclude != 0 )
      {
	      bg->is_excluded_from_kerning = 1;
      }
      else
      {
	      if ( is_exclude != 0 )
	      {
		bg->map_to = -1;
	      }
	      else
	      {
		bg->map_to = bg->encoding - range_from + map_to;
	      }
      }
    }
  }
  
}

void bf_map_list(bf_t *bf, const char **s)
{
  int i;
  bg_t *bg;
  
  /* exclude everything first */
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    bg->map_to = -1;
  }
  
  /* process the mapping list list */
  skip_space(s);
  for(;;)
  {
    bf_map_cmd(bf, s);
    if ( **s != ',' )
      break;
    (*s)++;
    skip_space(s);
  }
}

void bf_Map(bf_t *bf, const char *map_cmd_list)
{
  if ( strlen(map_cmd_list) < 1024 )
    bf_Log(bf, "Map: map_cmd_list='%s'", map_cmd_list);
  bf_map_list(bf, &map_cmd_list);
}

int bf_MapFile(bf_t *bf, const char *map_file_name)
{
  struct stat buf;
  char *s;
  FILE *fp;
  if ( map_file_name == NULL )
    return 1;
  if ( map_file_name[0] == '\0' )
    return 1;
  
  
  if ( stat(map_file_name, &buf) != 0 )
    return 0;
  fp = fopen(map_file_name, "r");
  if ( fp == NULL )
    return 0;
  s = malloc(buf.st_size+1);
  if ( s == NULL )
    return 0;
  if ( fread(s, buf.st_size, 1, fp) != 1 ) {
    free(s);
    return 0;
  }
  s[buf.st_size] = '\0';
  fclose(fp);
  bf_Map(bf, s);
  free(s);
  return 1;
}

/* add all characters in utf8 text file to font */

int bf_Utf8File(bf_t *bf, const char *utf8_file_name) {
  static const int utf8_verbose = 0;
  struct stat buf;
  FILE *fp;
  int byte = 0;
  uint32_t code = 0;
  int more_bytes = 0;
  int utf8_errs = 0;
  int i;
  bg_t *bg;

  if (utf8_file_name == NULL)
    return 1;
  if (utf8_file_name[0] == '\0')
    return 1;
  if (stat(utf8_file_name, &buf) != 0)
    return 0;
  fp = fopen(utf8_file_name, "r");
  if (fp == NULL)
    return 0;

  while ((byte = getc(fp)) != EOF) {
    code = 0;
    more_bytes = 0;
    if (byte <= 0x7F) {
      code = byte;
      more_bytes = 0;
    } else if ((byte & 0xE0) == 0xC0) {
      code = byte & 0x1F;
      more_bytes = 1;
    } else if ((byte & 0xF0) == 0xE0) {
      code = byte & 0x0F;
      more_bytes = 2;
    } else if ((byte & 0xF8) == 0xF0) {
      code = byte & 0x07;
      more_bytes = 3;
    } else if ((byte & 0xFC) == 0xF8) {
      code = byte & 0x03;
      more_bytes = 4;
    } else if ((byte & 0xFE) == 0xFC) {
      code = byte & 0x01;
      more_bytes = 5;
    } else {
      if (utf8_verbose)
        printf("? 0x%X\r\n", byte);
      utf8_errs++;
      continue;
    }
    while (more_bytes > 0) {
      byte = getc(fp);
      if (byte == EOF)
        break;
      if ((byte & 0xC0) != 0x80) {
        utf8_errs++;
        break;
      }
      code = (code << 6) | (byte & 0x3F);
      --more_bytes;
    }
    if (more_bytes == 0) {
      /* find glyph in font */
      for (i = 0; i < bf->glyph_cnt; i++) {
        bg = bf->glyph_list[i];
        if (bg->encoding == code) {
          if (utf8_verbose)
            printf("glyph found: 0x%X\r\n", code);
          bg->map_to = bg->encoding;
          break;
        }
      }
      if (utf8_verbose && (i == bf->glyph_cnt))
        printf("glyph not found: 0x%X\r\n", code);
    }
  }
  if (utf8_errs != 0)
    printf("%u utf8 errors\r\n", utf8_errs);

  fclose(fp);
  return 1;
}
