
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include "z_ucs.h"
#include "config.h"
#include "splint.h"


size_t z_ucs_len(/*@in@*/ /*@null@*/ z_ucs *string)
{
  z_ucs *start_index = string;

  if (string == NULL)
    return 0;

  while (*string != 0)
  {
    string++;
  }

  return (size_t)(string - start_index);
}


/*@dependent@*/ z_ucs *z_ucs_cpy(/*@out@*/ /*@dependent@*/ z_ucs *dst,
    /*@in@*/ /*@dependent@*/ z_ucs *src)
{
  while (*src != 0)
  {
    *dst = *src;
    dst++;
    src++;
  }

  *dst = 0;

  return dst;
}


void z_ucs_ncpy(/*@out@*/ /*dependent@*/ z_ucs *dst,
    /*@in@*/ /*dependent@*/ z_ucs *src, size_t len)
{
  memmove(dst, src, len * sizeof(z_ucs));
}


z_ucs parse_utf_8_char(FILE *in)
{
  // FIXME: Fail on overlong UTF-8 characters.
  int current_char;
  int len;
  int i;
  int cmp_value1;
  int cmp_value2;
  z_ucs result;

  if ((current_char = fgetc(in)) == EOF)
    return UEOF;

  if ((current_char & 0x80) == 0)
    return (z_ucs)current_char & 0x7f;

  len = 2;
  cmp_value1 = 0xe0;
  cmp_value2 = 0xc0;

  while (len <= 6)
  {
    if ((current_char & cmp_value1) == cmp_value2)
    {
      cmp_value1 = 1;

      for (i=0; i<6-len; i++)
      {
        // cmp_value1 is never negative.
        /*@-shiftimplementation@*/
        cmp_value1 <<= 1;
        /*@+shiftimplementation@*/

        cmp_value1 |= 1;
      }

      result = (z_ucs)(current_char & cmp_value1);

      // Found a sequence of length len.
      for (i=0; i<len-1; i++)
      {
        if ((current_char = fgetc(in)) == EOF)
          return UEOF;

        result <<= 6;
        result |= (current_char & 0x3f);
      }

      return result;
    }

    /*@-shiftimplementation@*/
    cmp_value1 >>= 1;
    /*@+shiftimplementation@*/

    cmp_value1 |= 0x80;

    /*@-shiftimplementation@*/
    cmp_value2 >>= 1;
    /*@+shiftimplementation@*/

    cmp_value2 |= 0x80;

    len++;
  }

  return UEOF;
}


z_ucs utf_8_to_z_ucs(char **src)
{
  // FIXME: Fail on overlong UTF-8 characters.
  int len;
  int i;
  int cmp_value1;
  int cmp_value2;
  z_ucs result;

  if ((**src & 0x80) == (char)0)
  {
    result = (z_ucs)**src & 0x7f;
    (*src)++;
    return result;
  }

  len = 2;
  cmp_value1 = 0xe0;
  cmp_value2 = 0xc0;

  while (len <= 6)
  {
    if (((int)(**src) & cmp_value1) == cmp_value2)
    {
      // Found a sequence of length len.

      cmp_value1 = 1;

      for (i=0; i<6-len; i++)
      {
        /*@-shiftimplementation@*/
        cmp_value1 <<= 1;
        /*@+shiftimplementation@*/

        cmp_value1 |= 1;
      }

      result = (z_ucs)(**src & cmp_value1);
      (*src)++;

      for (i=0; i<len-1; i++)
      {
        result <<= 6;
        result |= (**src & 0x3f);
        (*src)++;
      }

      return result;
    }

    /*@-shiftimplementation@*/
    cmp_value1 >>= 1;
    /*@+shiftimplementation@*/

    cmp_value1 |= 0x80;

    /*@-shiftimplementation@*/
    cmp_value2 >>= 1;
    /*@+shiftimplementation@*/

    cmp_value2 |= 0x80;

    len++;
  }

  return -1;
}


z_ucs latin1_to_z_ucs(char c)
{
  return (z_ucs)((unsigned char)c & 0xff);
}


/*@null@*/ /*@dependent@*/ char *latin1_string_to_z_ucs(
    /*@out@*/ /*@dependent@*/ z_ucs *dest,
    /*@in@*/ /*@dependent@*/ char *src, size_t max_dest_size)
{
  if (max_dest_size < 2)
  {
    /*@-mustdefine@*/
    return NULL;
    /*@+mustdefine@*/
  }

  while (*src != (char)0)
  {
    if (max_dest_size == 1)
    {
      *dest = 0;
      return src;
    }

    *dest = (z_ucs)(*src & 0xff);

    dest++;
    src++;
  }

  *dest = 0;
  return NULL;
}


/*@null@*/ /*@dependent@*/ char *utf_8_string_to_z_ucs(
    /*@out@*/ /*@dependent@*/ z_ucs *dest,
    /*@in@*/ char *src,
    int max_dest_size)
{
  if (max_dest_size < 2)
  {
    /*@-mustdefine@*/
    return NULL;
    /*@+mustdefine@*/
  }

  while (*src != (char)0)
  {
    if (max_dest_size == 1)
    {
      *dest = 0;
      return src;
    }

    *dest = utf_8_to_z_ucs(&src);
    max_dest_size--;
    dest++;
  }

  *dest = 0;

  return NULL;
}


static int get_utf8_code_length(unsigned char first_utf_8_byte)
{
  if (     (first_utf_8_byte & 0xffffff80) == (unsigned char)0)
    return 1;
  else if ((first_utf_8_byte & 0xfffff800) == (unsigned char)0)
    return 2;
  else if ((first_utf_8_byte & 0xffff0000) == (unsigned char)0)
    return 3;
  else if ((first_utf_8_byte & 0xffe00000) == (unsigned char)0)
    return 4;
  else if ((first_utf_8_byte & 0xfc000000) == (unsigned char)0)
    return 5;
  else if ((first_utf_8_byte & 0x80000000) == (unsigned char)0)
    return 6;
  else
    return -1;
}


int z_ucs_to_utf_8(/*@out@*/ /*@null@*/ char *dst, z_ucs **src,
    size_t max_dst_size)
{
  int len = 0;
  int char_len;
  int i;
  z_ucs mask1;
  z_ucs mask2;
  z_ucs src_val;

  if ( (max_dst_size < 1) && (dst != NULL) )
  {
    /*@-mustdefine@*/
    return -1;
    /*@+mustdefine@*/
  }

  while (**src != 0)
  {
    char_len = get_utf8_code_length((unsigned char)**src);
    if (char_len == -1)
    {
      printf("Illegal code: %c/%x\n", (char)**src, (unsigned)**src);
      printf("&0xfffff800: %x.\n", (unsigned)(**src & 0xfffff800));
      exit(EXIT_FAILURE);
    }

    if (dst != NULL)
    {
      if (max_dst_size < (size_t)(char_len + 1))
      {
        *dst = (char)0;
        return len;
      }

      if (char_len == 1)
      {
        *dst = (char)**src;
        dst++;
      }
      else
      {
        mask1 = 0x01;
        mask2 = 0xfc;
        for (i=0; i<6-char_len; i++)
        {
          mask1 <<= 1;
          mask1 |= 1;
          mask2 <<= 1; 
        }
        mask2 &= 0xff;

        dst += char_len - 1;
        src_val = **src;

        for (i=0; i<char_len-1; i++)
        {
          *dst = (char)((src_val & 0x3f) | 0x80);
          src_val >>= 6;
          dst--;
        }

        *dst = (char)((src_val & mask1) | mask2);

        dst += char_len;
      }

      max_dst_size -= char_len;
    }

    (*src)++;
    len += char_len;
  }

  if (dst != NULL)
    *dst = (char)0;

  return len + 1;
}


char z_ucs_to_latin1(z_ucs src)
{
  if ((src & 0xffffff00) == 0)
    return (char)(src & 0xff);
  else
    return (char)('?');
}


int z_ucs_cmp(z_ucs *s1, z_ucs *s2)
{
  while (*s1 != 0)
  {
    if (*s1 != *s2)
      return (*s1 > *s2 ? 1 : 0);

    s1++;
    s2++;
  }

  return 0;
}


int z_ucs_cmp_latin1(z_ucs *s1, char *s2)
{
  while (*s1 != 0)
  {
    if ((char)*s1 != *s2)
      return -1;

    if ((*s1 & 0xffffff00) != 0)
      return -1;

    s1++;
    s2++;
  }

  if (*s2 != '\0')
    return -1;
  else
    return 0;
}


/*@dependent@*/ /*@null@*/ z_ucs *z_ucs_chr(z_ucs *s1, z_ucs chr)
{
  while (*s1 != 0)
  {
    if (*s1 == chr)
      return s1;

    s1++;
  }

  return NULL;
}


/*@dependent@*/ /*@null@*/ z_ucs *z_ucs_rchr(z_ucs *s1, z_ucs chr)
{
  z_ucs *ptr = s1 + z_ucs_len(s1);

  while (ptr >= s1)
  {
    if (*ptr == chr)
      return ptr;

    ptr--;
  }

  return NULL;
}


/*
void single_byte_to_z_ucs(z_ucs *dst, char* src)
{
  while (*src != (char)0)
  {
    *dst = (z_ucs)*src;
    dst++;
  }
}
*/


