/*
 * This source file is part of the bstring string library.  This code was
 * written by Paul Hsieh in 2002-2015, and is covered by the BSD open source
 * license and the GPL. Refer to the accompanying documentation for details
 * on usage and license.
 */

/*
 * utf8util.c
 *
 * This file is not necessarily part of the core bstring library itself, but
 * is just an generic module for implementing utf8 utility functions.
 */

#include "utf8util.h"

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*) 0)
#endif
#endif

/* Surrogate range is wrong, there is a maximum, the BOM alias is illegal and
 * 0xFFFF is illegal */
#define isLegalUnicodeCodePoint(v)                                             \
  ((((v) < 0xD800L) || ((v) > 0xDFFFL)) &&                                     \
   (((unsigned long) (v)) <= 0x0010FFFFL) && (((v) | 0x1F0001) != 0x1FFFFFL))

void utf8IteratorInit(
    struct utf8Iterator* iter, unsigned char* data, int slen) {
  if (iter) {
    iter->data  = data;
    iter->slen  = (iter->data && slen >= 0) ? slen : -1;
    iter->start = -1;
    iter->next  = (iter->slen >= 0) ? 0 : -1;
    iter->error = (iter->slen >= 0) ? 0 : 1;
  }
}

void utf8IteratorUninit(struct utf8Iterator* iter) {
  if (iter) {
    iter->data  = NULL;
    iter->slen  = -1;
    iter->start = iter->next = -1;
  }
}

int utf8ScanBackwardsForCodePoint(
    unsigned char* msg, int len, int pos, cpUcs4* out) {
  cpUcs4 v1, v2, v3, v4, x;
  int ret;
  if (NULL == msg || len < 0 || (unsigned) pos >= (unsigned) len) {
    return -__LINE__;
  }
  if (!out) out = &x;
  ret = 0;
  if (msg[pos] < 0x80) {
    *out = msg[pos];
    return 0;
  } else if (msg[pos] < 0xC0) {
    if (0 == pos) return -__LINE__;
    ret = -__LINE__;
    if (msg[pos - 1] >= 0xC1 && msg[pos - 1] < 0xF8) {
      pos--;
      ret = 1;
    } else {
      if (1 == pos) return -__LINE__;
      if ((msg[pos - 1] | 0x3F) != 0xBF) return -__LINE__;
      if (msg[pos - 2] >= 0xE0 && msg[pos - 2] < 0xF8) {
        pos -= 2;
        ret = 2;
      } else {
        if (2 == pos) return -__LINE__;
        if ((msg[pos - 2] | 0x3F) != 0xBF) return -__LINE__;
        if ((msg[pos - 3] | 0x07) == 0xF7) {
          pos -= 3;
          ret = 3;
        } else
          return -__LINE__;
      }
    }
  }
  if (msg[pos] < 0xE0) {
    if (pos + 1 >= len) return -__LINE__;
    v1 = msg[pos] & ~0xE0;
    v2 = msg[pos + 1] & ~0xC0;
    v1 = (v1 << 6) + v2;
    if (v1 < 0x80) return -__LINE__;
    *out = v1;
    return ret;
  }
  if (msg[pos] < 0xF0) {
    if (pos + 2 >= len) return -__LINE__;
    v1 = msg[pos] & ~0xF0;
    v2 = msg[pos + 1] & ~0xC0;
    v3 = msg[pos + 2] & ~0xC0;
    v1 = (v1 << 12) + (v2 << 6) + v3;
    if (v1 < 0x800) return -__LINE__;
    if (!isLegalUnicodeCodePoint(v1)) return -__LINE__;
    *out = v1;
    return ret;
  }

  if (msg[pos] >= 0xF8) return -__LINE__;

  if (pos + 3 >= len) return -__LINE__;
  v1 = msg[pos] & ~0xF8;
  v2 = msg[pos + 1] & ~0xC0;
  v3 = msg[pos + 2] & ~0xC0;
  v4 = msg[pos + 3] & ~0xC0;
  v1 = (v1 << 18) + (v2 << 12) + (v3 << 6) + v4;
  if (v1 < 0x10000) return -__LINE__;
  if (!isLegalUnicodeCodePoint(v1)) return -__LINE__;
  *out = v1;
  return ret;
}

/*
Code point                UTF-8
----------                -----
U-00000000 - U-0000007F:  0xxxxxxx
U-00000080 - U-000007FF:  110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF:  1110xxxx 10xxxxxx 10xxxxxx
U-00010000 - U-001FFFFF:  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

U-00200000 - U-03FFFFFF:  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 - U-7FFFFFFF:  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*/

/*
 *  Returns next read code point for iterator.
 *
 *  iter->data + iter->start points at the characters just read.
 *
 *  iter->data + iter->next points at the characters that will be read next.
 *
 *  iter->error is boolean indicating whether or not last read contained an
 * error.
 */
cpUcs4 utf8IteratorGetNextCodePoint(struct utf8Iterator* iter, cpUcs4 errCh) {
  unsigned char* chrs;
  unsigned char c, d, e;
  long v;
  int i, ofs;

  if (NULL == iter || iter->next < 0) return errCh;
  if (iter->next >= iter->slen) {
    iter->start = iter->slen;
    return errCh;
  }
  if (NULL == iter->data || iter->next < 0 || utf8IteratorNoMore(iter))
    return errCh;
  chrs = iter->data + iter->next;

  iter->error = 0;
  c           = chrs[0];
  ofs         = 0;

  if (c < 0xC0 || c > 0xFD) {
    if (c >= 0x80) goto ErrMode;
    v   = c;
    ofs = 1;
  } else if (c < 0xE0) {
    if (iter->next >= iter->slen + 1) goto ErrMode;
    v = (c << 6u) - (0x0C0 << 6u);
    c = (unsigned char) ((unsigned) chrs[1] - 0x080);
    v += c;
    if (c >= 0x40 || v < 0x80) goto ErrMode;
    ofs = 2;
  } else if (c < 0xF0) {
    if (iter->next >= iter->slen + 2) goto ErrMode;
    v = (c << 12) - (0x0E0 << 12u);
    c = (unsigned char) ((unsigned) chrs[1] - 0x080);
    d = (unsigned char) ((unsigned) chrs[2] - 0x080);
    v += (c << 6u) + d;
    if ((c | d) >= 0x40 || v < 0x800 || !isLegalUnicodeCodePoint(v))
      goto ErrMode;
    ofs = 3;
  } else if (c < 0xF8) {
    if (iter->next >= iter->slen + 3) goto ErrMode;
    v = (c << 18) - (0x0F0 << 18u);
    c = (unsigned char) ((unsigned) chrs[1] - 0x080);
    d = (unsigned char) ((unsigned) chrs[2] - 0x080);
    e = (unsigned char) ((unsigned) chrs[3] - 0x080);
    v += (c << 12u) + (d << 6u) + e;
    if ((c | d | e) >= 0x40 || v < 0x10000 || !isLegalUnicodeCodePoint(v))
      goto ErrMode;
    ofs = 4;
  } else { /* 5 and 6 byte encodings are invalid */
  ErrMode:;
    iter->error = 1;
    v           = errCh;
    for (i = iter->next + 1; i < iter->slen; i++)
      if ((iter->data[i] & 0xC0) != 0x80) break;
    ofs = i - iter->next;
  }

  iter->start = iter->next;
  iter->next += ofs;
  return v;
}

/*
 *  Returns next read code point for iterator.
 *
 *  iter->data + iter->start points at the characters to be read.
 *
 *  iter->data + iter->next points at the characters that will be read next.
 *
 *  iter->error is boolean indicating whether or not last read contained an
 * error.
 */
cpUcs4 utf8IteratorGetCurrCodePoint(struct utf8Iterator* iter, cpUcs4 errCh) {
  unsigned char* chrs;
  unsigned char c, d, e;
  long v;

  if (NULL == iter || iter->next < 0) return errCh;
  if (iter->next >= iter->slen) {
    iter->start = iter->slen;
    return errCh;
  }
  if (NULL == iter->data || iter->next < 0 || utf8IteratorNoMore(iter))
    return errCh;
  chrs = iter->data + iter->next;

  iter->error = 0;
  c           = chrs[0];

  if (c < 0xC0 || c > 0xFD) {
    if (c >= 0x80) goto ErrMode;
    v = c;
  } else if (c < 0xE0) {
    if (iter->next >= iter->slen + 1) goto ErrMode;
    v = (c << 6u) - (0x0C0 << 6u);
    c = (unsigned char) ((unsigned) chrs[1] - 0x080);
    v += c;
    if (c >= 0x40 || v < 0x80) goto ErrMode;
  } else if (c < 0xF0) {
    if (iter->next >= iter->slen + 2) goto ErrMode;
    v = (c << 12lu) - (0x0E0 << 12u);
    c = (unsigned char) ((unsigned) chrs[1] - 0x080);
    d = (unsigned char) ((unsigned) chrs[2] - 0x080);
    v += (c << 6u) + d;
    if ((c | d) >= 0x40 || v < 0x800 || !isLegalUnicodeCodePoint(v))
      goto ErrMode;
  } else if (c < 0xF8) {
    if (iter->next >= iter->slen + 3) goto ErrMode;
    v = (c << 18lu) - (0x0F0 << 18u);
    c = (unsigned char) ((unsigned) chrs[1] - 0x080);
    d = (unsigned char) ((unsigned) chrs[2] - 0x080);
    e = (unsigned char) ((unsigned) chrs[3] - 0x080);
    v += (c << 12lu) + (d << 6u) + e;
    if ((c | d | e) >= 0x40 || v < 0x10000 || !isLegalUnicodeCodePoint(v))
      goto ErrMode;
  } else { /* 5 and 6 byte encodings are invalid */
  ErrMode:;
    iter->error = 1;
    v           = errCh;
  }
  return v;
}
