/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2012 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any
 * later version. Please see the file LICENSE-GPL for details.
 *
 * Web Page: http://mielke.cc/brltty/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <stdio.h>
#include <string.h>

#include "log.h"
#include "charset_internal.h"
#include "lock.h"
#include "unicode.h"

const char defaultCharset[] = "ISO-8859-1";

static char *currentCharset = NULL;

size_t
getTextLength (const char *text) {
  return convertTextToWchars(NULL, text, 0);
}

size_t
convertWcharToUtf8 (wchar_t wc, Utf8Buffer utf8) {
  size_t utfs;

  if (!(wc & ~0X7F)) {
    *utf8 = wc;
    utfs = 1;
  } else {
    Utf8Buffer buffer;
    char *end = &buffer[0] + sizeof(buffer);
    char *byte = end;
    static const wchar_t mask = (1 << ((sizeof(wchar_t) * 8) - 6)) - 1;

    do {
      *--byte = (wc & 0X3F) | 0X80;
    } while ((wc = (wc >> 6) & mask));

    utfs = end - byte;
    if ((*byte & 0X7F) >= (1 << (7 - utfs))) {
      *--byte = 0;
      utfs++;
    }

    *byte |= ~((1 << (8 - utfs)) - 1);
    memcpy(utf8, byte, utfs);
  }

  utf8[utfs] = 0;
  return utfs;
}

wint_t
convertUtf8ToWchar (const char **utf8, size_t *utfs) {
  uint32_t character = UINT32_MAX;
  int state = 0;

  while (*utfs) {
    unsigned char byte = *(*utf8)++;
    (*utfs)--;

    if (!(byte & 0X80)) {
      if (character != UINT32_MAX) goto truncated;
      character = byte;
      break;
    }

    if (!(byte & 0X40)) {
      if (character == UINT32_MAX) break;
      character = (character << 6) | (byte & 0X3F);
      if (!--state) break;
    } else {
      if (!(byte & 0X20)) {
        state = 1;
      } else if (!(byte & 0X10)) {
        state = 2;
      } else if (!(byte & 0X08)) {
        state = 3;
      } else if (!(byte & 0X04)) {
        state = 4;
      } else if (!(byte & 0X02)) {
        state = 5;
      } else {
        state = 0;
      }

      if (character != UINT32_MAX) goto truncated;

      if (!state) {
        character = UINT32_MAX;
        break;
      }

      character = byte & ((1 << (6 - state)) - 1);
    }
  }

  while (*utfs) {
    if ((**utf8 & 0XC0) != 0X80) break;
    (*utf8)++, (*utfs)--;
    character = UINT32_MAX;
  }

  if (character == UINT32_MAX) goto error;
  if (character > WCHAR_MAX) character = UNICODE_REPLACEMENT_CHARACTER;
  return character;

truncated:
  (*utf8)--, (*utfs)++;
error:
  return WEOF;
}

size_t
getUtf8Length (const char *utf8) {
  size_t length = 0;

  while (*utf8) {
    size_t utfs = UTF8_LEN_MAX;
    wint_t character = convertUtf8ToWchar(&utf8, &utfs);

    if (character == WEOF) break;
    length += 1;
  }

  return length;
}

void
convertUtf8ToWchars (const char **utf8, wchar_t **characters, size_t count) {
  while (**utf8 && (count > 1)) {
    size_t utfs = UTF8_LEN_MAX;
    wint_t character = convertUtf8ToWchar(utf8, &utfs);

    if (character == WEOF) break;
    *(*characters)++ = character;
    count -= 1;
  }

  if (count) **characters = 0;
}

size_t
convertCharToUtf8 (char c, Utf8Buffer utf8) {
  wint_t wc = convertCharToWchar(c);
  if (wc == WEOF) return 0;
  return convertWcharToUtf8(wc, utf8);
}

int
convertUtf8ToChar (const char **utf8, size_t *utfs) {
  wint_t wc = convertUtf8ToWchar(utf8, utfs);
  if (wc == WEOF) return EOF;
  return convertWcharToChar(wc);
}

const char *
getWcharCharset (void) {
  static const char *wcharCharset = NULL;

  if (!wcharCharset) {
    char charset[0X10];
    snprintf(charset, sizeof(charset), "UCS-%lu%cE",
             (unsigned long)sizeof(wchar_t),
#ifdef WORDS_BIGENDIAN
             'B'
#else /* WORDS_BIGENDIAN */
             'L'
#endif /* WORDS_BIGENDIAN */
            );

    if (!(wcharCharset = strdup(charset))) logMallocError();
  }

  return wcharCharset;
}

const char *
setCharset (const char *name) {
  char *charset;

  if (name) {
    if (currentCharset && (strcmp(currentCharset, name) == 0)) return currentCharset;
  } else if (currentCharset) {
    return currentCharset;
  } else {
    name = getLocaleCharset();
  }

  if (!(charset = strdup(name))) {
    logMallocError();
    return NULL;
  }

  if (!registerCharacterSet(charset)) {
    free(charset);
    return NULL;
  }

  if (currentCharset) free(currentCharset);
  return currentCharset = charset;
}

const char *
getCharset (void) {
  return setCharset(NULL);
}

static LockDescriptor *
getCharsetLock (void) {
  static LockDescriptor *lock = NULL;
  return getLockDescriptor(&lock);
}

int
lockCharset (LockOptions options) {
  LockDescriptor *lock = getCharsetLock();
  if (!lock) return 0;
  return obtainLock(lock, options);
}

void
unlockCharset (void) {
  LockDescriptor *lock = getCharsetLock();
  if (lock) releaseLock(lock);
}

int
isPosixLocale (const char *locale) {
  if (strcmp(locale, "C") == 0) return 1;
  if (strcmp(locale, "POSIX") == 0) return 1;
  return 0;
}
