/*******************************************************************************
 * Copyright (c) 2011 nxtControl GmbH, ACIN
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Stanislav Meduna, Ingo Hegny, Alois Zoitl
 *      - initial implementation and rework communication infrastructure
 *******************************************************************************/
#include "unicode_utils.h"
#include <string.h>

const TForteUInt32 CUnicodeUtilities::scm_unBOMMarker = 0xfeff;
const TForteUInt32 CUnicodeUtilities::scm_unBOMMarkerSwapped = 0xfffe;

/* RFC 3629
   Char. number range  |        UTF-8 octet sequence
      (hexadecimal)    |              (binary)
   --------------------+---------------------------------------------
   0000 0000-0000 007F | 0xxxxxxx
   0000 0080-0000 07FF | 110xxxxx 10xxxxxx
   0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
   0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
*/
int CUnicodeUtilities::parseUTF8Codepoint(const TForteByte *pa_pacCodepoint, TForteUInt32 &pa_rValue) {
  if ((pa_pacCodepoint[0] & 0x80) == 0) {
    pa_rValue = (pa_pacCodepoint[0] & 0x7f);
    return 1;
  } else if ((pa_pacCodepoint[0] & 0xe0) == 0xc0) {
    if((pa_pacCodepoint[1] & 0xc0) != 0x80) {
      return -1;
    }
    pa_rValue = ((pa_pacCodepoint[0] & 0x1f) << 6) |
                (pa_pacCodepoint[1] & 0x3f);
    return 2;
  } else if ((pa_pacCodepoint[0] & 0xf0) == 0xe0) {
    if ((pa_pacCodepoint[1] & 0xc0) != 0x80 ||
        (pa_pacCodepoint[2] & 0xc0) != 0x80) {
      return -1;
    }
    pa_rValue = ((pa_pacCodepoint[0] & 0x0f) << 12) |
                ((pa_pacCodepoint[1] & 0x3f) <<  6) |
                (pa_pacCodepoint[2] & 0x3f);
    return 3;
  } else if ((pa_pacCodepoint[0] & 0xf8) == 0xf0) {
    if ((pa_pacCodepoint[1] & 0xc0) != 0x80 ||
        (pa_pacCodepoint[2] & 0xc0) != 0x80 ||
        (pa_pacCodepoint[3] & 0xc0) != 0x80) {
      return -1;
    }
    pa_rValue = ((pa_pacCodepoint[0] & 0x07) << 18) |
                ((pa_pacCodepoint[1] & 0x3f) << 12) |
                ((pa_pacCodepoint[2] & 0x3f) <<  6) |
                (pa_pacCodepoint[3] & 0x3f);
    return 4;
  }

  pa_rValue = 0;
  return -1;
}

int CUnicodeUtilities::encodeUTF8Codepoint(TForteByte *pa_pacCodepoint, unsigned int pa_nBufferSize, TForteUInt32 pa_nValue) {
  if (pa_nValue < 0x80) {
    if (pa_pacCodepoint) {
      if(pa_nBufferSize < 1) {
        return -1;
      }
      pa_pacCodepoint[0] = (TForteByte) pa_nValue;
    }
    return 1;
  } else if (pa_nValue < 0x800) {
    if (pa_pacCodepoint) {
      if(pa_nBufferSize < 2) {
        return -1;
      }
      pa_pacCodepoint[0] = (TForteByte) (0xc0 | ((pa_nValue >> 6) & 0x1f));
      pa_pacCodepoint[1] = (TForteByte) (0x80 | ((pa_nValue     ) & 0x3f));
    }
    return 2;
  } else if (pa_nValue < 0x10000) {
    if (pa_pacCodepoint) {
      if(pa_nBufferSize < 3) {
        return -1;
      }
      pa_pacCodepoint[0] = (TForteByte) (0xe0 | ((pa_nValue >> 12) & 0x1f));
      pa_pacCodepoint[1] = (TForteByte) (0x80 | ((pa_nValue >>  6) & 0x3f));
      pa_pacCodepoint[2] = (TForteByte) (0x80 | ((pa_nValue      ) & 0x3f));
    }
    return 3;
  } else if (pa_nValue < 0x110000) {
    if (pa_pacCodepoint) {
      if(pa_nBufferSize < 4) {
        return -1;
      }
      pa_pacCodepoint[0] = (TForteByte) (0xf0 | ((pa_nValue >> 18) & 0x1f));
      pa_pacCodepoint[1] = (TForteByte) (0x80 | ((pa_nValue >> 12) & 0x3f));
      pa_pacCodepoint[2] = (TForteByte) (0x80 | ((pa_nValue >>  6) & 0x3f));
      pa_pacCodepoint[3] = (TForteByte) (0x80 | ((pa_nValue      ) & 0x3f));
    }
    return 4;
  }

  return -1;
}

/*
 * UTF-16 converts > 0xffff into two 16-bit code units, called a surrogate pair, by the following scheme:
 *
 * 0x10000 is subtracted from the code point, leaving a 20 bit number in the range 0..0xFFFFF.
 * The top ten bits (a number in the range 0..0x3FF) are added to 0xD800 to give the first code unit
 *   or high surrogate, which will be in the range 0xD800..0xDBFF.
 * The low ten bits (also in the range 0..0x3FF) are added to 0xDC00 to give the second
 *   code unit or low surrogate, which will be in the range 0xDC00..0xDFFF.
*/
int CUnicodeUtilities::parseUTF16Codepoint(const TForteByte *pa_pacCodepoint, TForteUInt32 &pa_rValue, bool pa_bLittleEndian) {
  TForteUInt16 nFirstWord = static_cast<TForteUInt16>(pa_bLittleEndian ?
                                                        (pa_pacCodepoint[0] | (pa_pacCodepoint[1] << 8)) :
                                                        ((pa_pacCodepoint[0] << 8) | pa_pacCodepoint[1]));

  if ((nFirstWord & 0xfc00) == 0xd800) {
    TForteUInt16 nSecondWord = static_cast<TForteUInt16>(pa_bLittleEndian ?
      (pa_pacCodepoint[2] | (pa_pacCodepoint[3] << 8)) :
      ((pa_pacCodepoint[2] << 8) | pa_pacCodepoint[3]));

    if((nSecondWord & 0xfc00) != 0xdc00) {
      return -1;
    }

    pa_rValue = 0x10000 + (((nFirstWord & 0x3ff) << 10) | (nSecondWord & 0x3ff));

    return 4;
  }

  pa_rValue = nFirstWord;

  return 2;
}


int CUnicodeUtilities::encodeUTF16Codepoint(TForteByte *pa_pacCodepoint, unsigned int pa_nBufferSize, TForteUInt32 pa_nValue, bool pa_bLittleEndian) {
  if((pa_nValue >= 0xd800 && pa_nValue < 0xe000) || pa_nValue >= 0x110000) {
    return -1;
  }

  if (pa_nValue < 0x10000) {
    if (pa_pacCodepoint) {
      if(pa_nBufferSize < 2) {
        return -1;
      }
      if (pa_bLittleEndian) {
        pa_pacCodepoint[0] = (TForteByte) ((pa_nValue     ) & 0xff);
        pa_pacCodepoint[1] = (TForteByte) ((pa_nValue >> 8) & 0xff);
      } else {
        pa_pacCodepoint[0] = (TForteByte) ((pa_nValue >> 8) & 0xff);
        pa_pacCodepoint[1] = (TForteByte) ((pa_nValue     ) & 0xff);
      }
    }
    return 2;
  } else {
    if (pa_pacCodepoint) {
      TForteUInt32 nVal = pa_nValue - 0x10000;
      TForteUInt16 nHighSurrogate = static_cast<TForteUInt16>(0xd800 | ((nVal >> 10) & 0x3ff));
      TForteUInt16 nLowSurrogate  = static_cast<TForteUInt16>(0xdc00 | ((nVal      ) & 0x3ff));

      if(pa_nBufferSize < 4) {
        return -1;
      }

      if (pa_bLittleEndian) {
        pa_pacCodepoint[0] = (TForteByte) ((nHighSurrogate     ) & 0xff);
        pa_pacCodepoint[1] = (TForteByte) ((nHighSurrogate >> 8) & 0xff);
        pa_pacCodepoint[2] = (TForteByte) ((nLowSurrogate      ) & 0xff);
        pa_pacCodepoint[3] = (TForteByte) ((nLowSurrogate  >> 8) & 0xff);
      } else {
        pa_pacCodepoint[0] = (TForteByte) ((nHighSurrogate >> 8) & 0xff);
        pa_pacCodepoint[1] = (TForteByte) ((nHighSurrogate     ) & 0xff);
        pa_pacCodepoint[2] = (TForteByte) ((nLowSurrogate  >> 8) & 0xff);
        pa_pacCodepoint[3] = (TForteByte) ((nLowSurrogate      ) & 0xff);
      }
    }
    return 4;
  }
}

int CUnicodeUtilities::checkUTF8(const char *pa_pacValue, int pa_nLength, unsigned int &pa_rnMaxWidth) {
  const TForteByte *pRunner = (const TForteByte *) pa_pacValue;
  size_t nRemLen = (pa_nLength == -1) ? strlen(pa_pacValue) : (size_t) pa_nLength;
  TForteUInt32 nCodepoint;
  int nRetVal = 0;
  size_t i = 0;
  pa_rnMaxWidth = 7;

  while (i < nRemLen) {
    int nRes = parseUTF8Codepoint(pRunner, nCodepoint);
    if (nRes < 0 || nRes + i > nRemLen) {
      return -1;
    }
    if(nCodepoint != CUnicodeUtilities::scm_unBOMMarker) {
      if(nCodepoint >= 0x10000) {
        pa_rnMaxWidth = 21;
      } else if(nCodepoint >= 0x100 && pa_rnMaxWidth < 16) {
        pa_rnMaxWidth = 16;
      } else if(nCodepoint >= 0x80 && pa_rnMaxWidth < 8) {
        pa_rnMaxWidth = 8;
      }
    }

    i += nRes;
    pRunner += nRes;
    ++nRetVal;
  }

  return nRetVal;
}
