/*
    _____               __  ___          __            ____        _      __
   / ___/__ ___ _  ___ /  |/  /__  ___  / /_____ __ __/ __/_______(_)__  / /_
  / (_ / _ `/  ' \/ -_) /|_/ / _ \/ _ \/  '_/ -_) // /\ \/ __/ __/ / _ \/ __/
  \___/\_,_/_/_/_/\__/_/  /_/\___/_//_/_/\_\\__/\_, /___/\__/_/ /_/ .__/\__/
                                               /___/             /_/
                                             
  See Copyright Notice in gmMachine.h

*/

#include "gmConfig.h"
#include "gmStringLib.h"
#include "gmThread.h"
#include "gmMachine.h"
#include "gmHelpers.h"


//
// String
//


#define GM_WHITE_SPACE " \t\v\r\n"


static int GM_CDECL gmfStringLeft(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);
  GM_CHECK_INT_PARAM(count, 0);

  const gmVariable * var = a_thread->GetThis();

  GM_ASSERT(var->m_type == GM_STRING);

  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  
  int length = strObj->GetLength();
  count = gmClamp(0, count, length);

  char * buffer = (char *) alloca(count + 1);
  memcpy(buffer, str, count);
  buffer[count] = 0;

  a_thread->PushNewString(buffer);

  return GM_OK;
}


static int GM_CDECL gmfStringRight(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);
  GM_CHECK_INT_PARAM(count, 0);

  const gmVariable * var = a_thread->GetThis();
  
  GM_ASSERT(var->m_type == GM_STRING);

  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  
  int length = strObj->GetLength();
  count = gmClamp(0, count, length);

  char * buffer = (char *) alloca(count + 1);
  memcpy(buffer, str + length - count, count);
  buffer[count] = 0;

  a_thread->PushNewString(buffer);

  return GM_OK;
}


static int GM_CDECL gmfStringRightAt(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);
  GM_CHECK_INT_PARAM(index, 0);

  const gmVariable * var = a_thread->GetThis();
  
  GM_ASSERT(var->m_type == GM_STRING);

  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  
  int length = strObj->GetLength();
  index = gmClamp(0, index, length);
  int count = (length - index);
  char * buffer = (char *) alloca(count + 1);
  memcpy(buffer, str + index, count);
  buffer[count] = 0;

  a_thread->PushNewString(buffer, count);

  return GM_OK;
}


static int GM_CDECL gmfStringMid(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(2);

  int first = 0, count = 0;

  if(!gmGetFloatOrIntParamAsInt(a_thread, 0, first)) {return GM_EXCEPTION;}
  if(!gmGetFloatOrIntParamAsInt(a_thread, 1, count)) {return GM_EXCEPTION;}

  const gmVariable * var = a_thread->GetThis();
  
  GM_ASSERT(var->m_type == GM_STRING);

  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  
  int length = strObj->GetLength();

  //Check bounds
  if (first < 0)
  {
    first = 0;
  }
  if (count < 0)
  {
    count = 0;
  }
  if (first + count > length)
  {
    count = length - first;
  }
  if (first > length)
  {
    count = 0;
  }

  char * buffer = (char *) alloca(count + 1);
  memcpy(buffer, str + first, count);
  buffer[count] = 0;

  a_thread->PushNewString(buffer);

  return GM_OK;
}


static int GM_CDECL gmfStringLength(gmThread * a_thread)
{
  const gmVariable * var = a_thread->GetThis();
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);

  a_thread->PushInt(strObj->GetLength());
  
  return GM_OK;
}


static int GM_CDECL gmfStringIsEmpty(gmThread * a_thread)
{
  const gmVariable * var = a_thread->GetThis();
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);

  a_thread->PushInt( (strObj->GetLength() == 0) );
  
  return GM_OK;
}


static int GM_CDECL gmfStringCompare(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);

  if(a_thread->ParamType(0) == GM_STRING)
  {
    const gmVariable * var = a_thread->GetThis();
  
    GM_ASSERT(var->m_type == GM_STRING);

    gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
    const char* thisStr = (const char *) *strObj;
    const char* otherStr = a_thread->ParamString(0);

    a_thread->PushInt(strcmp(thisStr, otherStr));
  
    return GM_OK;
  }

  return GM_EXCEPTION;
}


static int GM_CDECL gmfStringCompareNoCase(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);

  if(a_thread->ParamType(0) == GM_STRING)
  {
    const gmVariable * var = a_thread->GetThis();
  
    GM_ASSERT(var->m_type == GM_STRING);

    gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
    const char* thisStr = (const char *) *strObj;
    const char* otherStr = a_thread->ParamString(0);

    a_thread->PushInt(_gmstricmp(thisStr, otherStr));
  
    return GM_OK;
  }

  return GM_EXCEPTION;
}


static int GM_CDECL gmfStringLower(gmThread * a_thread)
{
  const gmVariable * var = a_thread->GetThis();
  
  GM_ASSERT(var->m_type == GM_STRING);

  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  
  int length = strObj->GetLength();
  char * buffer = (char *) alloca(length + 1);
  memcpy(buffer, str, length + 1);

  strlwr(buffer);

  a_thread->PushNewString(buffer, length);

  return GM_OK;
}


static int GM_CDECL gmfStringUpper(gmThread * a_thread)
{
  const gmVariable * var = a_thread->GetThis();
  
  GM_ASSERT(var->m_type == GM_STRING);

  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  
  int length = strObj->GetLength();
  char * buffer = (char *) alloca(length + 1);
  memcpy(buffer, str, length + 1);
  
  strupr(buffer);

  a_thread->PushNewString(buffer, length);

  return GM_OK;
}


static int GM_CDECL gmfStringSpanIncluding(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);

  if(a_thread->ParamType(0) == GM_STRING)
  {
    const gmVariable * var = a_thread->GetThis();
  
    GM_ASSERT(var->m_type == GM_STRING);

    gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
    const char * thisStr = (const char *) *strObj;
    const char * otherStr = a_thread->ParamString(0);
    
    int offset = (int)strspn(thisStr, otherStr);
    char * buffer = (char *) alloca(offset + 1);
    memcpy(buffer, thisStr, offset);
    buffer[offset] = 0;

    a_thread->PushNewString(buffer, offset);

    return GM_OK;
  }

  return GM_EXCEPTION;
}


static int GM_CDECL gmfStringSpanExcluding(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);

  if(a_thread->ParamType(0) == GM_STRING)
  {
    const gmVariable * var = a_thread->GetThis();
  
    GM_ASSERT(var->m_type == GM_STRING);

    gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
    const char * thisStr = (const char *) *strObj;
    const char * otherStr = a_thread->ParamString(0);
    
    int offset = (int)strcspn(thisStr, otherStr);
    char * buffer = (char *) alloca(offset + 1);
    memcpy(buffer, thisStr, offset);
    buffer[offset] = 0;

    a_thread->PushNewString(buffer, offset);

    return GM_OK;
  }

  return GM_EXCEPTION;
}


// string.AppendPath(a_appendString, a_endWithSlash);
// Append this string with another string, fixing for slashes
// a_endWithSlash is optional, default is false, removing trailing slashes.
static int GM_CDECL gmfStringAppendPath(gmThread * a_thread)
{
  //Need at least 1 parameter
  if(a_thread->GetNumParams() < 1)
  {
    return GM_EXCEPTION;
  }

  //Optional trailing slash flag
  int PutTrailingSlash = a_thread->ParamInt(1, false);

  if(a_thread->ParamType(0) == GM_STRING)
  {
    const gmVariable * varA = a_thread->GetThis();
  
    GM_ASSERT(varA->m_type == GM_STRING);

    gmStringObject * strObjA = (gmStringObject *) GM_OBJECT(varA->m_value.m_ref);
    gmStringObject * strObjB = a_thread->ParamStringObject(0);
    const char* cStrA = strObjA->GetString();
    const char* cStrB = strObjB->GetString();
    int lenA = strObjA->GetLength();
    int lenB = strObjB->GetLength();

    int curLength = lenA;
    int appLength = lenB;

    //Alloc buffer on stack is fine, path strings cannot be long
    char * buffer = (char *) alloca(curLength + appLength + 2);

    if(lenA > 0)
    {
      memcpy(buffer, cStrA, lenA);


      //Make sure first part has a slash
      if(buffer[curLength-1] != '\\' && buffer[curLength-1] != '/')
      {
        buffer[curLength] = '\\';
        curLength += 1;
      }
    }

    if(lenB > 0)
    {
      //Remove slash from start of append string
      const char* startOfAppend = cStrB;
      if((startOfAppend[0] == '\\') || (startOfAppend[0] == '/'))
      {
        startOfAppend += 1;
        appLength -= 1;
      }

      //Append the string
      memcpy(&buffer[curLength], startOfAppend, appLength);
    }

    int newLength = curLength + appLength;
  
    if(PutTrailingSlash && (newLength > 0)) //Only add slash if string is not empty
    {
      //Make sure path ends with slash
      if(buffer[newLength-1] != '\\' && buffer[newLength-1] != '/')
      {
        buffer[newLength] = '\\';
        newLength += 1;
      }
    }
    else
    {
      //Make sure path does not end with slash
      if(buffer[newLength-1] == '\\' || buffer[newLength-1] == '/')
      {
        newLength -= 1;
      }
    }

    //Make sure it is terminated
    buffer[newLength] = 0;

    a_thread->PushNewString(buffer, newLength);
  
    return GM_OK;
  }

  return GM_EXCEPTION;
}


// string.RemoveInvalidChars(a_replaceChar, a_invalidSet)
// eg. "File Name#1.tga".RemoveInvalidChars("_","# ") returns "File_Name_1.tga"
// Note: Parameters are optional.
static int GM_CDECL gmfStringReplaceCharsInSet(gmThread * a_thread)
{
  GM_INT_PARAM(repCharInt, 0, '_');
  GM_STRING_PARAM(invalidCharSet, 1, " \\/:-+");

  char repChar = (char)repCharInt; //Convert full int to char
  const gmVariable * varA = a_thread->GetThis();
  
  GM_ASSERT(varA->m_type == GM_STRING);

  gmStringObject * strObjA = (gmStringObject *) GM_OBJECT(varA->m_value.m_ref);
  const char* cStrA = strObjA->GetString();
  int lenA = strObjA->GetLength();

  //Alloc buffer on stack is fine, path strings cannot be long
  char * buffer = (char *) alloca(lenA + 1);
  memcpy(buffer, cStrA, lenA + 1);

  int validPos;

  //Check that replacement char is NOT in invalid set, otherwise endless loop...
  if(strchr(invalidCharSet, repChar))
  {
    return GM_EXCEPTION;
  }

  for(;;)
  {
    validPos = (int)strcspn(buffer, invalidCharSet);
    if(validPos != lenA)
    {
      buffer[validPos] = repChar;
    }
    else
    {
      break;
    }
  }

  a_thread->PushNewString(buffer, lenA);
      
  return GM_OK;
}


// string.AppendPath(a_appendString, a_endWithSlash);
// Append this string with another string, fixing for slashes
// a_endWithSlash is optional, default is false, removing trailing slashes.
static void GM_CDECL gmStringOpAppendPath(gmThread * a_thread, gmVariable * a_operands)
{
  // Both types must be strings
  if(a_operands[0].m_type != GM_STRING ||
     a_operands[1].m_type != GM_STRING)
  {
    a_operands[0].m_type = GM_NULL;
    a_operands[0].m_value.m_ref = 0;
    return;
  }

  gmStringObject * strObjA = (gmStringObject *) GM_OBJECT(a_operands[0].m_value.m_ref);
  gmStringObject * strObjB = (gmStringObject *) GM_OBJECT(a_operands[1].m_value.m_ref);
  const char* cStrA = strObjA->GetString();
  const char* cStrB = strObjB->GetString();
  int lenA = strObjA->GetLength();
  int lenB = strObjB->GetLength();

  int curLength = lenA;
  int appLength = lenB;

  //Alloc buffer on stack is fine, path strings cannot be long
  char * buffer = (char *) alloca(curLength + appLength + 2);

  if(lenA <= 0)
  {
    a_operands[0] = a_operands[1];
  }

  if(lenB <= 0)
  {
    return;
  }

  memcpy(buffer, cStrA, lenA);

  //Make sure first part has a slash
  if(buffer[curLength-1] != '\\' && buffer[curLength-1] != '/')
  {
    buffer[curLength] = '\\';
    curLength += 1;
  }

  //Remove slash from start of append string
  const char* startOfAppend = cStrB;
  if((startOfAppend[0] == '\\') || (startOfAppend[0] == '/'))
  {
    startOfAppend += 1;
    appLength -= 1;
  }

  //Append the string
  memcpy(&buffer[curLength], startOfAppend, appLength);

  int newLength = curLength + appLength;

  //Make sure it is terminated
  buffer[newLength] = 0;

  a_operands[0].m_type = GM_STRING;
  a_operands[0].m_value.m_ref = a_thread->GetMachine()->AllocStringObject(buffer, newLength)->GetRef();
}


// int string.Find(char/string a_charOrStringToFind, int a_startOffset == 0);
// Find a character or character string in a string.
// Returns character offset or -1 if not found.
static int GM_CDECL gmStringFind(gmThread * a_thread)
{
  int numParams = GM_THREAD_ARG->GetNumParams();
  int startOffset = 0;
  char* retCharPtr = NULL;
  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char* thisStr = (const char *) *strObj;
  
  if(numParams == 2)
  {
    if(a_thread->ParamType(1) == GM_INT)
    {
      startOffset = a_thread->ParamInt(1); //Optional start offset param
    }
    else
    {
      return GM_EXCEPTION;
    }
  } 
  else if(numParams < 1 || numParams > 2)
  {
    return GM_EXCEPTION;
  }

  //Check if this string is empty, or start offset out of range
  if( (strObj->GetLength() == 0) || 
      (startOffset > strObj->GetLength()) ||
      (startOffset < 0) 
    )
  {
    a_thread->PushInt( -1 ); //return Not Found
    return GM_OK;
  }

  if(a_thread->ParamType(0) == GM_INT)
  {
    const char otherChar = (char)a_thread->ParamInt(0);
    
    //Find character
    retCharPtr = (char*)strchr(thisStr + startOffset, otherChar);
  }
  else if(a_thread->ParamType(0) == GM_STRING)
  {
    const char* otherStr = a_thread->ParamString(0);

    //Find string
    retCharPtr = (char*)strstr(thisStr + startOffset, otherStr);
  }
  else
  {
    return GM_EXCEPTION;
  }

  // return -1 for not found, distance from beginning otherwise
  int retOffset = (retCharPtr == NULL) ? -1 : (int)(retCharPtr - thisStr);
  a_thread->PushInt(retOffset);

  return GM_OK;
}


static int GM_CDECL gmStringReverse(gmThread * a_thread)
{
  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;

  int len = (int)strlen(str);
  if(len > 0)
  {
    char * buffer = (char *) alloca(len + 1); 
    memcpy(buffer, str, len + 1); //Copy old string

    while(len--)
    {
      buffer[len] = *(str++);
    }

    a_thread->PushNewString(buffer);
  }
  return GM_OK;
}


// int string.ReverseFind(char/string a_charOrStringToFind);
// Find the last instance of a specific character in a string.
// Returns character offset or -1 if not found.
static int GM_CDECL gmStringReverseFind(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);

  const char* retCharPtr = NULL;
  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * thisStrObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char* thisStr = (const char *) *thisStrObj;
  
  if(a_thread->ParamType(0) == GM_INT)
  {
    const char otherChar = (char)a_thread->ParamInt(0);
    
    //Find character
    retCharPtr = strrchr(thisStr, otherChar);
  }
  else if(a_thread->ParamType(0) == GM_STRING)
  {
    gmStringObject * otherStrObj = a_thread->ParamStringObject(0);
    const char* otherStr = a_thread->ParamString(0);

    //Find string
    const char* lastFoundPtr = NULL;
    const char* newTestPtr = NULL;
    const char* curTestPtr = thisStr;
    const char* endThisStr = thisStr + thisStrObj->GetLength();
    int searchStrLength = otherStrObj->GetLength();

    //Search through string for last occurence
    //Not very efficient, but very rarely used.
    for(;;)
    {
      newTestPtr = strstr(curTestPtr, otherStr);
      if(!newTestPtr)
      {
        break;
      }
      lastFoundPtr = newTestPtr;
      curTestPtr = newTestPtr + searchStrLength;
      if(curTestPtr > endThisStr)
      {
        break;
      }
    };

    retCharPtr = lastFoundPtr;
  }
  else
  {
    return GM_EXCEPTION;
  }

  // return -1 for not found, distance from beginning otherwise
  int retOffset = (retCharPtr == NULL) ? -1 : (int)(retCharPtr - thisStr);
  a_thread->PushInt(retOffset);

  return GM_OK;
}


// int string[int index]
// Note: Because strings are constant and in a table, it is impossible to implement SetInd.
static void GM_CDECL gmStringOpGetInd(gmThread * a_thread, gmVariable * a_operands)
{
  if( a_operands[0].m_type != GM_STRING ||
      a_operands[1].m_type != GM_INT )
  {
    a_operands->Nullify();
    return;
  }

  gmStringObject * strObjA = (gmStringObject *) GM_OBJECT(a_operands[0].m_value.m_ref);
  const char* cStrA = strObjA->GetString();
  int index = a_operands[1].m_value.m_int;
  
  if( index < 0 || index > strObjA->GetLength()-1 )
  {
    a_operands->Nullify();
  }
  else
  {
    a_operands->SetInt( (int)cStrA[index] );
  }
}

// int string.GetAt(int a_index);
// Returns character at offset or null if index out of range.
static int GM_CDECL gmStringGetAt(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);
  GM_CHECK_INT_PARAM(index, 0);

  const gmVariable * var = a_thread->GetThis();

  GM_ASSERT(var->m_type == GM_STRING);

  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;

  if(index < 0 || index >= strObj->GetLength())
  {
    a_thread->PushNull(); //Return null if index out of range
    return GM_OK;
  }

  a_thread->PushInt(str[index]);
  return GM_OK;
}


// string string.SetAt(int a_index, int a_char);
// Returns string with modified character at offset, or original string if index out of range.
static int GM_CDECL gmStringSetAt(gmThread * a_thread)
{
  GM_CHECK_NUM_PARAMS(1);
  GM_CHECK_INT_PARAM(index, 0);
  GM_CHECK_INT_PARAM(newChar, 1);

  const gmVariable * var = a_thread->GetThis();

  GM_ASSERT(var->m_type == GM_STRING);

  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  int strLength = strObj->GetLength();

  if(index < 0 || index >= strLength)
  {
    a_thread->PushString(strObj); //Return original string if index out of range
    return GM_OK;
  }

  char * buffer = (char *) alloca(strLength + 1); 
  memcpy(buffer, str, strLength + 1); //Copy old string
  buffer[index] = (char)newChar; //Set character in string

  a_thread->PushNewString(buffer, strLength);
  return GM_OK;
}


static int GM_CDECL gmStringTrimLeft(gmThread * a_thread)
{
  GM_STRING_PARAM(trim, 0, GM_WHITE_SPACE);

  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  if(strlen(str) > 0)
  {
    while(*str && strchr(trim, *str))
      ++str;
    a_thread->PushNewString(str);
  }
  else
    a_thread->PushString(strObj);
  return GM_OK;
}


static int GM_CDECL gmStringTrimRight(gmThread * a_thread)
{
  GM_STRING_PARAM(trim, 0, GM_WHITE_SPACE);

  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  int strLength = strObj->GetLength();
  if(strLength > 0)
  {
    char * buffer = (char *) alloca(strLength + 1);
    memcpy(buffer, str, strLength + 1); //Copy old string

    // Find beginning of trailing matches by starting at end
    char *lpsz = buffer + strLength;
    while (--lpsz >= buffer && strchr(trim, *lpsz) != NULL) {}
    ++lpsz;
    *lpsz = '\0';

    a_thread->PushNewString(buffer);
  }
  else
  {
    a_thread->PushString(strObj);
  }
  return GM_OK;
}


static int GM_CDECL gmStringGetFilenameNoExt(gmThread * a_thread)
{
  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  int strLength = strObj->GetLength();

  char * buffer = (char *) alloca(strLength + 1);
  memcpy(buffer, str, strLength + 1); //Copy old string

  char *lpsz = buffer + strLength;
  while (--lpsz >= buffer && *lpsz != '\\' && *lpsz != '/') {}

  buffer = ++lpsz;
  strLength = (int)strlen(buffer);
  lpsz = buffer + strLength;
  while (--lpsz >= buffer && *lpsz != '.') {}
  if(*lpsz == '.') *lpsz = 0;

  a_thread->PushNewString(buffer);
  return GM_OK;
}


static int GM_CDECL gmStringGetFilename(gmThread * a_thread)
{
  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  int strLength = strObj->GetLength();

  const char *lpsz = str + strLength;
  while (--lpsz >= str && *lpsz != '\\' && *lpsz != '/') {}
  ++lpsz;

  a_thread->PushNewString(lpsz);
  return GM_OK;
}


static int GM_CDECL gmStringGetExtension(gmThread * a_thread)
{
  GM_INT_PARAM(keepDot, 0, 0);

  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  int strLength = strObj->GetLength();

  const char *lpsz = str + strLength;
  while (--lpsz >= str && *lpsz != '.') {}

  if(*lpsz == '.')
  {
    if(!keepDot)
    {
      ++lpsz;
    }
    a_thread->PushNewString(lpsz);
  }
  else
  {
    a_thread->PushNewString("");
  }
  return GM_OK;
}


static int GM_CDECL gmStringSetExtension(gmThread * a_thread)
{
  GM_STRING_PARAM(newExt, 0, "");

  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);

  const char * str = (const char *) *strObj;
  int strLength = strObj->GetLength();
  int extLength = (int)strlen(newExt);

  if (extLength && newExt[0] == '.')
  {
    ++newExt;
    extLength = (int)strlen(newExt);
  }

  char *buffer = (char *) alloca(strLength + 1 + extLength);
  memcpy(buffer, str, strLength + 1);

  char *lpsz = buffer + strLength;
  while (--lpsz >= buffer && *lpsz != '.') {}

  if(*lpsz == '.')
  {
    *lpsz = '\0';
    if (extLength)
      sprintf(buffer, "%s.%s", buffer, newExt);

  }
  else if (extLength)
  {
    sprintf(buffer, "%s.%s", buffer, newExt);
  }

  a_thread->PushNewString(buffer);
  return GM_OK;
}


static int GM_CDECL gmStringGetPath(gmThread * a_thread)
{
  GM_INT_PARAM(keepSlash, 0, 0);

  const gmVariable * var = a_thread->GetThis();
  GM_ASSERT(var->m_type == GM_STRING);
  gmStringObject * strObj = (gmStringObject *) GM_OBJECT(var->m_value.m_ref);
  const char * str = (const char *) *strObj;
  int strLength = strObj->GetLength();

  char * buffer = (char *) alloca(strLength + 1); 
  memcpy(buffer, str, strLength + 1); //Copy old string

  char *lpsz = buffer + strLength;
  while (--lpsz >= buffer && *lpsz != '\\' && *lpsz != '/') {}

  if(*lpsz == '\\' || *lpsz == '/')
  {
    if(keepSlash)
      lpsz[1] = 0;
    else
      lpsz[0] = 0;
    a_thread->PushNewString(buffer);
  }
  else
  {
    a_thread->PushNewString("");
  }
  return GM_OK;
}

extern int GM_CDECL gmfToInt(gmThread * a_thread);
extern int GM_CDECL gmfToFloat(gmThread * a_thread);
extern int GM_CDECL gmfToString(gmThread * a_thread);

static gmFunctionEntry s_stringLib[] = 
{ 
  /*gm
    \lib string
    \brief string operations often store a copy of the string on the stack, so keep string sizes reasonable
  */
  /*gm
    \function IsEmpty
    \brief IsEmpty will test to see if the string is 0 length
    \return non-zero if the string is empty
  */
  {"IsEmpty", gmfStringIsEmpty},
  /*gm
    \function Length
    \brief Length will return the length of the string not including the null terminating character
    \return int length
  */
  {"Length", gmfStringLength},
  /*gm
    \function Left
    \brief Left will return the left count charaters of the string
    \param int count
    \return string
  */
  {"Left", gmfStringLeft},
  /*gm
    \function Right
    \brief Right will return the right count charaters of the string
    \param int count
    \return string
  */
  {"Right", gmfStringRight},
  /*gm
    \function RightAt
    \brief RightAt will return the charaters right of and including the given index
    \param int index
    \return string
  */
  {"RightAt", gmfStringRightAt},
  /*gm
    \function Mid
    \brief Mid will return count characters from the start index
    \param int startIndex
    \param int count
    \return string
  */
  {"Mid", gmfStringMid},
  /*gm
    \function Compare
    \brief Compare will perform a string compare
    \param string to compare
    \return -1 if the this < compare string, 0 if the strings are equal, 1 otherwise
  */
  {"Compare", gmfStringCompare},
  /*gm
    \function CompareNoCase
    \brief CompareNoCase will perform a string compare (case insensitive)
    \param string to compare
    \return -1 if the this < compare string, 0 if the strings are equal, 1 otherwise
  */
  {"CompareNoCase", gmfStringCompareNoCase},
  /*gm
    \function Int
    \brief Int will return the int value of the string
    \return int value
  */
  {"Int", gmfToInt},
  /*gm
    \function Float
    \brief Float will return the float value of the string
    \return float value
  */
  {"Float", gmfToFloat},
  /*gm
    \function String
    \return string
  */
  {"String", gmfToString},
  /*gm
    \function Upper
    \brief Upper will return the string as uppercase
    \return string
  */
  {"Upper", gmfStringUpper},
  /*gm
    \function Lower
    \brief Lower will return the string as lowercase
    \return string
  */
  {"Lower", gmfStringLower},
  /*gm
    \function SpanIncluding
    \brief SpanIncluding will return this string while characters are within the passed string
    \param string charset
    \return string
  */
  {"SpanIncluding", gmfStringSpanIncluding},
  /*gm
    \function SpanExcluding
    \brief SpanExcluding will return this string while characters are not within the passed string
    \param string charset
    \return string
  */
  {"SpanExcluding", gmfStringSpanExcluding},
  /*gm
    \function AppendPath
    \brief AppendPath will append a path make sure one '\' is maintained
    \param string path to append
    \return string
  */
  {"AppendPath", gmfStringAppendPath},
  /*gm
    \function ReplaceCharsInSet
    \brief ReplaceCharsInSet will replace all chars in this that are within the charset with the given int char
    \param int char to replace with
    \param string charset of chars to replace
    \return string
  */
  {"ReplaceCharsInSet", gmfStringReplaceCharsInSet},
  /*gm
    \function Find
    \brief Find will find the first occurance of the passed string within this string
    \param string search string
    \param int start index optional (0)
    \return int index of first occurance, or -1 if the string was not found
  */
  {"Find", gmStringFind},
  /*gm
    \function Reverse
    \brief Reverse characters of a string
    \return string
  */
  {"Reverse", gmStringReverse},
  /*gm
    \function ReverseFind
    \brief ReverseFind will find the first occurance of the passed string within this string starting from the right
    \param string search string
    \return int index of first occurance, or -1 if the string was not found
  */
  {"ReverseFind", gmStringReverseFind},
  /*gm
    \function GetAt
    \brief GetAt will return the char at the given index
    \param int index
    \return int char, or null if index was out of range
  */
  {"GetAt", gmStringGetAt},
  /*gm
    \function SetAt
    \brief SetAt will return the string with the character set at the given position
    \param int index
    \param int char
    \return string
  */
  {"SetAt", gmStringSetAt},
  /*gm
    \function TrimLeft
    \brief TrimLeft will return the string with the chars from the passed char set trimmed from the left
    \param string charset optional (" \r\n\v\t")
    \return string
  */
  {"TrimLeft", gmStringTrimLeft},
  /*gm
    \function TrimRight
    \brief TrimRight will return the string with the chars from the passed char set trimmed from the right
    \param string charset optional (" \r\n\v\t")
    \return string
  */
  {"TrimRight", gmStringTrimRight},

  /*gm
    \function GetFilenameNoExt
    \brief GetFilenameNoExt will return the filename part of a path string
    \return string
  */
  {"GetFilenameNoExt", gmStringGetFilenameNoExt},
  /*gm
    \function GetFilename
    \brief GetFilename will return the filename part of a path string incl. extension
    \return string
  */
  {"GetFilename", gmStringGetFilename},
  /*gm
    \function GetExtension
    \brief GetExtension will return the file extension
    \param int inclDot optional (0) 1 will include '.', 0 won't
    \return string
  */
  {"GetExtension", gmStringGetExtension},
  /*gm
    \function SetExtension
    \brief SetExtension returns a string with the extension change to the given one.
    \param string ext optional (null) the new extension, with or without the dot. null to remove extension.
    \return string
  */
  {"SetExtension", gmStringSetExtension},
  /*gm
    \function GetPath
    \brief GetPath will return the file path from a path string
    \param int inclSlash optional (0) will include a '\' on the end of the path
    \return string
  */
  {"GetPath", gmStringGetPath},
/*
  {"Insert", }, //int Insert(int index, char/string)
  {"Delete", }, //int Delete(int index, int count=1)
  {"Replace", }, //int Replace(char/string,char/string)
  {"Scan", },
*/
};

void gmBindStringLib(gmMachine * a_machine)
{
  a_machine->RegisterTypeOperator(GM_STRING, O_BIT_XOR, NULL, gmStringOpAppendPath);
  a_machine->RegisterTypeOperator(GM_STRING, O_GETIND, NULL, gmStringOpGetInd);
  a_machine->RegisterTypeLibrary(GM_STRING, s_stringLib, sizeof(s_stringLib) / sizeof(s_stringLib[0]));
}

