/*******************************************************************************
 * Copyright (c) 2013 fortiss GmbH
 * 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:
 *   Alois Zoitl
 *    - initial API and implementation and/or initial documentation
 *******************************************************************************/
#include "string_utils.h"
#include <forte_dint.h>
#include <forte_udint.h>
#include <forte_lint.h>
#include <forte_ulint.h>
#include "../../arch/devlog.h"

#include <errno.h>
#include <string.h>

bool forte::core::util::isAtoFChar(char pa_cValue){
  pa_cValue = static_cast<char>(toupper(pa_cValue));
  return ((pa_cValue >= 'A') && (pa_cValue <= 'F'));
}

TForteInt8 forte::core::util::charAtoFToInt(char pa_cValue){
  return static_cast<TForteInt8>((toupper(pa_cValue) - 'A') + 10);
}

long int forte::core::util::strtol(const char *nptr, char **endptr, int base) {
  long int nRetVal = 0;
  bool bNegativeNumber = false;
  errno = 0;

  //in difference to IEC 611313-3 literals base 16 literals may have a leading -
  if((*nptr) == '-'){
    bNegativeNumber = true;
    ++nptr;
  }

  if(16 == base && (('0' == (*nptr)) && ('x' == nptr[1]))){
    //we have a preceding 0x step over it
    nptr += 2;
  }

  long nLimit1 = (bNegativeNumber ? -(CIEC_DINT::scm_nMinVal / base) : (CIEC_DINT::scm_nMaxVal / base));
  long nLimit2 = (bNegativeNumber ? -(CIEC_DINT::scm_nMinVal % base) : (CIEC_DINT::scm_nMaxVal % base));
  if (nLimit2 < 0) // Rounding direction for negative numbers is implementation defined
  {
    nLimit1--;
    nLimit2 = base-nLimit2;
  }

  for(; *nptr; ++nptr){ //Do until '/0'
    if((!isDigit(*nptr)) && (!((16 == base) && isAtoFChar(*nptr)))){
      //we have an invalid character
      break;
    }
    TForteInt8 nCharVal = charHexDigitToInt(*nptr);
    if(nRetVal > nLimit1 || (nRetVal == nLimit1 && nCharVal > nLimit2)) {
      nRetVal = (bNegativeNumber) ? CIEC_DINT::scm_nMinVal : CIEC_DINT::scm_nMaxVal;
      errno = ERANGE;
      break;
    }
    nRetVal = base * nRetVal + nCharVal;
  }

  if((bNegativeNumber) && (ERANGE != errno)){
    nRetVal = -nRetVal;
  }

  if(0 != endptr){
    *endptr = const_cast<char*>(nptr);
  }
  return nRetVal;
}

unsigned long int forte::core::util::strtoul(const char *nptr, char **endptr, int base){
  unsigned long int unRetVal = 0;
  unsigned long int unLimit1 = (CIEC_UDINT::scm_nMaxVal / base);
  unsigned long int unLimit2 = (CIEC_UDINT::scm_nMaxVal % base);
  errno = 0;

  if((16 == base) && ('0' == (*nptr)) && ('x' == nptr[1])){
    //we have a preceding 0x step over it
    nptr += 2;
  }

  for(; *nptr; ++nptr){ //Do until '/0'
    if((!isDigit(*nptr)) && (!((16 == base) && isAtoFChar(*nptr)))){
      //we have an invalid character
      break;
    }
    TForteInt8 nCharVal = charHexDigitToInt(*nptr);

    if(unRetVal > unLimit1 || (unRetVal == unLimit1 && (unsigned long int) nCharVal > unLimit2)){
      //in this round we would exceed the limit of the data type
      unRetVal = CIEC_UDINT::scm_nMaxVal;
      errno = ERANGE;
      break;
    }
    unRetVal = base * unRetVal + nCharVal;
  }
  if(0 != endptr){
    *endptr = const_cast<char*>(nptr);
  }
  return unRetVal;
}

#ifdef FORTE_USE_64BIT_DATATYPES
long long int forte::core::util::strtoll(const char *nptr, char **endptr, int base) {
  long long int nRetVal = 0;
  bool bNegativeNumber = false;
  errno = 0;

  //in difference to IEC 611313-3 literals base 16 literals may have a leading -
  if((*nptr) == '-'){
    bNegativeNumber = true;
    ++nptr;
  }

  if(16 == base && ('0' == (*nptr)) && ('x' == nptr[1])){
    //we have a preceding 0x step over it
    nptr += 2;
  }

  long long nLimit1;
  long long nLimit2;

  if (bNegativeNumber){
    volatile long long nLimMinDiv = CIEC_LINT::scm_nMinVal / base;
    volatile long long nLimMinMod = CIEC_LINT::scm_nMinVal % base;

    nLimit1 = -nLimMinDiv;
    nLimit2 = -nLimMinMod;

    if (nLimit2 < 0) // Rounding direction for negative numbers is implementation defined
    {
      nLimit1--;
      nLimit2 = base-nLimit2;
    }
  }
  else{
    nLimit1 = CIEC_LINT::scm_nMaxVal / base;
    nLimit2 = CIEC_LINT::scm_nMaxVal % base;
  }

  for(; *nptr; ++nptr){ //Do until '/0'
    if((!isDigit(*nptr)) && (!((16 == base) && isAtoFChar(*nptr)))){
      //we have an invalid character
      break;
    }
    TForteInt8 nCharVal = charHexDigitToInt(*nptr);
    if(nRetVal > nLimit1 || (nRetVal == nLimit1 && nCharVal > nLimit2)){
      nRetVal = (bNegativeNumber) ? CIEC_LINT::scm_nMinVal : CIEC_LINT::scm_nMaxVal;
      errno = ERANGE;
      break;
    }
    nRetVal = base * nRetVal + nCharVal;
  }

  if((bNegativeNumber) && (ERANGE != errno)){
    nRetVal = -nRetVal;
  }
  if(0 != endptr){
    *endptr = const_cast<char*>(nptr);
  }
  return nRetVal;
}

unsigned long long int forte::core::util::strtoull(const char *nptr, char **endptr, int base){
  unsigned long long int unRetVal = 0;
  unsigned long long int unLimit1 = (CIEC_ULINT::scm_nMaxVal / base);
  unsigned long long int unLimit2 = (CIEC_ULINT::scm_nMaxVal % base);
  errno = 0;

  if((16 == base) && ('0' == (*nptr)) && ('x' == nptr[1])){
    //we have a preceding 0x step over it
    nptr += 2;
  }

  for(; *nptr; ++nptr){ //Do until '/0'
    if((!isDigit(*nptr)) && (!((16 == base) && isAtoFChar(*nptr)))){
      //we have an invalid character
      break;
    }
    TForteInt8 nCharVal = charHexDigitToInt(*nptr);
    if(unRetVal > unLimit1 || (unRetVal == unLimit1 && (unsigned long long int) nCharVal > unLimit2)){
      //in this round we would exceed the limit of the data type
      unRetVal = CIEC_ULINT::scm_nMaxVal;
      errno = ERANGE;
      break;
    }
    unRetVal = base * unRetVal + nCharVal;
  }
  if(0 != endptr){
    *endptr = const_cast<char*>(nptr);
  }
  return unRetVal;
}

#endif

size_t forte::core::util::getExtraSizeForEscapedChars(const char* paString){
  size_t retVal = 0;
  while(0 != *paString){
    for(size_t i = 0; i < sizeof(forte::core::util::scReplacementForEscapedCharacters) / sizeof(const char* const); i++){
      if(forte::core::util::scEscapedCharacters[i] == *paString){
        retVal += strlen(forte::core::util::scReplacementForEscapedCharacters[i]) - 1;
        break;
      }
    }
    paString++;
  }
  return retVal;
}

size_t forte::core::util::transformNonEscapedToEscapedXMLText(char* const paString){
  size_t retVal = 0;
  char* runner = strchr(paString, '\0');
  char* originalEnd = runner;
  runner--;
  while(paString <= runner){
    const char* toCopy = 0;

    for(size_t i = 0; i < sizeof(forte::core::util::scReplacementForEscapedCharacters) / sizeof(const char* const ); i++){
      if(forte::core::util::scEscapedCharacters[i] == *runner){
        toCopy = forte::core::util::scReplacementForEscapedCharacters[i];
        break;
      }
    }

    if(0 != toCopy){
      size_t toMove = strlen(toCopy);
      memmove(&runner[toMove], runner + 1, originalEnd - runner + retVal);
      memcpy(runner, toCopy, toMove);
      retVal += toMove - 1;
    }
    runner--;
  }
  return retVal;
}

size_t forte::core::util::transformEscapedXMLToNonEscapedText(char* const paString){
  char* runner = paString;
  char *endRunner = strchr(paString, '\0');
  size_t retVal = 0;
  while(runner <= endRunner){
    if('&' == *runner){
      char toCopy = 0;
      size_t toMove = 0;

      for(size_t i = 0; i < sizeof(forte::core::util::scReplacementForEscapedCharacters) / sizeof(const char* const ); i++){
        if(0 == strncmp(runner, forte::core::util::scReplacementForEscapedCharacters[i], strlen(forte::core::util::scReplacementForEscapedCharacters[i]))){
          toCopy = forte::core::util::scEscapedCharacters[i];
          toMove = strlen(forte::core::util::scReplacementForEscapedCharacters[i]);
          break;
        }
      }

      if(0 != toCopy){
        *runner = toCopy;
        memmove(runner + 1, &runner[toMove], (endRunner - &runner[toMove]) + 1);
        endRunner -= toMove - 1;
        retVal += toMove - 1;
      }
      else{
        DEVLOG_ERROR("[XML Transformer] The given XML text has & character but it's none of the known escaped characters");
      }
    }
    runner++;
  }
  return retVal;
}
