// tango.zh
// Beta 2


// Functions involved in loading text into __Tango_Buffer[].

// Text-producing functions need a bit of padding to overwrite.
const int __TANGO_PAD_NUMBER = 12;
const int __TANGO_PAD_ORDINAL = 9;
const int __TANGO_PAD_SAVENAME = 8;
// @string's padding is customizable, so it's in the main tango.zh file.


// Loads a ZScript string into a text slot.
void Tango_LoadString(int slot, int string)
{
    __Tango_LoadString(slot, string, false, 0);
}

void Tango_LoadString(int slot, int string, int startChar)
{
    __Tango_LoadString(slot, string, false, startChar);
}

void Tango_AppendString(int slot, int string)
{
    __Tango_LoadString(slot, string, true, 0);
}

void Tango_AppendString(int slot, int string, int startChar)
{
    __Tango_LoadString(slot, string, true, startChar);
}

// Loads a ZC message into a text slot.
void Tango_LoadMessage(int slot, int messageID)
{
    __Tango_LoadMessage(slot, messageID, false, 0);
}

void Tango_LoadMessage(int slot, int messageID, int startChar)
{
    __Tango_LoadMessage(slot, messageID, false, startChar);
}

void Tango_AppendMessage(int slot, int messageID)
{
    __Tango_LoadMessage(slot, messageID, true, 0);
}

void Tango_AppendMessage(int slot, int messageID, int startChar)
{
    __Tango_LoadMessage(slot, messageID, true, startChar);
}

// Implementation of the above
void __Tango_LoadString(int slot, int str, bool append, int startChar)
{
    if(!__Tango_ValidateSlot(slot))
        return;
    
    __Tango_SetCurrentSlot(slot);
    int destPtr=__Tango_FindDestStart(append);
        
    if(!__Tango_ValidateString(str))
    {
        __Tango_LogError(__TANGO_ERROR_LOAD_INVALID_STRING, str);
        __Tango_Buffer[destPtr]=NULL;
        return;
    }
    
    int strStart=__Tango_FindSrcStart(str, startChar);
    
    __Tango_LoadIntoBuffer(str, strStart, destPtr);
}

void __Tango_LoadMessage(int slot, int msg, bool append, int startChar)
{
    if(!__Tango_ValidateSlot(slot))
        return;
    
    __Tango_SetCurrentSlot(slot);
    int destPtr=__Tango_FindDestStart(append);
        
    if(!__Tango_ValidateMessage(msg))
    {
        __Tango_LogError(__TANGO_ERROR_LOAD_INVALID_MESSAGE, msg);
        __Tango_Buffer[destPtr]=NULL;
        return;
    }
    
    int buffer[256];
    Game->GetMessage(msg, buffer);
    int srcPtr=__Tango_FindSrcStart(buffer, startChar);
    
    __Tango_LoadIntoBuffer(buffer, srcPtr, destPtr);
}

// Find the position after the start character or 0 if it's not in the string.
int __Tango_FindSrcStart(int str, int startChar)
{
    if(startChar<=0)
        return 0;
    
    for(int pos=1; str[pos]!=NULL; pos++)
    {
        if(str[pos-1]==startChar)
            return pos;
    }
    
    return 0;
}

// Find the start of the slot or the end of its current text,
// depending on append.
int __Tango_FindDestStart(bool append)
{
    int pos=__Tango_Data[__TCS_START];
    
    if(!append)
        return pos;
    
    int last=__Tango_Data[__TCS_END]-1;
    
    while(pos<last && __Tango_Buffer[pos]!=0)
        pos++;
    
    return pos;
}

// Copies a string into __Tango_Buffer and compiles any code it contains.
void __Tango_LoadIntoBuffer(int src, int srcPtr, int destPtr)
{
    __Tango_LogMessage(__TANGO_MSG_LOAD_START, src);
    int end=__Tango_GetStringEnd(src);
    int newPos[2]; // Pointer output array for code parsing functions
    
    while(srcPtr<=end)
    {
        if(src[srcPtr]==__TANGO_CODE_DELIMITER)
        {
            // Check if it's just @@ first.
            if(src[srcPtr+1]==__TANGO_CODE_DELIMITER)
            {
                __Tango_SetChar(destPtr, __TANGO_CODE_DELIMITER);
                srcPtr+=2;
                destPtr++;
            }
            
            else
            {
                if(__Tango_ParseTopLevelCode(src, srcPtr+1, destPtr,  newPos))
                {
                    // Error; stop here.
                    __Tango_SetChar(destPtr, NULL);
                    break;
                }
                
                srcPtr=newPos[0];
                destPtr=newPos[1];
                
                // Stop here if it was @0.
                if(__Tango_Buffer[destPtr-1]==NULL)
                {
                    if(destPtr<2)
                        break;
                    // Make sure it wasn't actually an arument of 0...
                    else if(__Tango_Buffer[destPtr-2]!=__TANGO_NUM_MARKER)
                        break;
                }
            }
        }
        else
        {
            __Tango_SetChar(destPtr, src[srcPtr]);
            
            if(src[srcPtr]==NULL)
                break;
            
            srcPtr++;
            destPtr++;
        }
    }
    
    // Make certain the string is terminated. This may be necessary if it
    // had trailing spaces or overflowed.
    destPtr=Min(destPtr, __Tango_Data[__TCS_END]-1);
    __Tango_Buffer[destPtr]=NULL;
    
    __Tango_LogMessage(__TANGO_MSG_LOAD_END, 0);
}

// Parses functions and ASCII values at the top level in src and writes them
// to the buffer. srcPtr points to just after '@'. Returns true if there was
// an error, false if not.
bool __Tango_ParseTopLevelCode(int src, int srcPtr, int destPtr, int ptrOut)
{
    int fnStart=destPtr;
    int character=src[srcPtr];
    
    // At the top level, ASCII codes and functions are legal;
    // variables are not. Not all functions are valid here,
    // but that isn't checked.
    if(character>='0' && character<='9')
    {
        // Character value
        int num=__Tango_ReadNumber(src, srcPtr, true, ptrOut);
        srcPtr=ptrOut[0];
        __Tango_SetChar(destPtr, num);
        destPtr++;
    }
    else if(character=='(')
    {
        // Character value in parentheses
        srcPtr=__Tango_EatSpaces(src, srcPtr+1);
        
        // Make sure it's actually a number.
        character=src[srcPtr];
        if(!(character>='0' && character<='9'))
        {
            __Tango_LogError(__TANGO_ERROR_INVALID_CHARACTER_CODE, srcPtr);
            return true;
        }
        
        int num=__Tango_ReadNumber(src, srcPtr, true, ptrOut);
        srcPtr=__Tango_EatSpaces(src, ptrOut[0]);
        
        // Make sure it's followed by ')'; if not, that's an error.
        if(src[srcPtr]==')')
        {
            __Tango_SetChar(destPtr, num);
            srcPtr++;
            destPtr++;
        }
        else
        {
            __Tango_LogError(__TANGO_ERROR_INVALID_CHARACTER_CODE, srcPtr);
            return true;
        }
    }
    else if(character>='a' && character<='z')
    {
        // Function
        float id=__Tango_ReadIdentifier(src, srcPtr, ptrOut);
        srcPtr=__Tango_EatSpaces(src, ptrOut[0]);
        
        // If the next character is '(', place the correct marker and
        // read arguments.
        if(src[srcPtr]=='(')
        {
            __Tango_SetStartMarker(destPtr, id);
            __Tango_SetChar(destPtr+1, id);
            destPtr+=2;
            
            if(__Tango_ParseArguments(src, srcPtr+1, destPtr, ptrOut))
                return true; // Error
            
            srcPtr=ptrOut[0];
            destPtr=ptrOut[1];
            destPtr=__Tango_SetEndMarker(destPtr, id);
            destPtr=__Tango_AddPadding(id, fnStart, destPtr);
        }
        // Not '('; that's an error.
        else
        {
            __Tango_LogError(__TANGO_ERROR_INVALID_FUNCTION, srcPtr);
            return true;
        }
    }
    else
    {
        // Something else. Invalid.
        __Tango_LogError(__TANGO_ERROR_INVALID_FUNCTION, srcPtr);
        return true;
    }
    
    // Finally, check if overflow occurred.
    if(destPtr>=__Tango_Data[__TCS_END])
        return true;
    
    ptrOut[0]=srcPtr;
    ptrOut[1]=destPtr;
    return false;
}

// Parse the arguments to a function and writes them to __Tango_Buffer[].
// srcPtr points to the first character after '('; the closing parenthesis
// will be handled. Returns true if there was an error, false if not.
bool __Tango_ParseArguments(int src, int srcPtr, int destPtr, int ptrOut)
{
    int character;
    bool negative=false;
    
    while(true)
    {
        srcPtr=__Tango_EatSpaces(src, srcPtr);
        character=src[srcPtr];
        
        if(character==')')
        {
            // End of the function
            srcPtr++;
            
            if(negative)
            {
                // "-)" isn't valid...
                __Tango_LogError(__TANGO_ERROR_INVALID_ARGUMENT, srcPtr);
                return true;
            }
            
            break;
        }
        else if(character==__TANGO_CODE_DELIMITER)
        {
            // Variable or function
            srcPtr++;
            character=src[srcPtr];
            
            if(!(character>='a' && character<='z'))
            {
                // Not a valid identifier
                __Tango_LogError(__TANGO_ERROR_INVALID_ARGUMENT, srcPtr);
                return true;
            }
            
            if(__Tango_ParseArgumentCode(src, srcPtr, destPtr, negative,
                                         ptrOut))
                return true; // Error
            
            srcPtr=__Tango_EatSpaces(src, ptrOut[0]);
            destPtr=ptrOut[1];
            negative=false;
        }
        else if(character>='0' && character<='9')
        {
            // Number
            float num=__Tango_ReadNumber(src, srcPtr, false, ptrOut);
            srcPtr=__Tango_EatSpaces(src, ptrOut[0]);
            
            __Tango_SetChar(destPtr, __TANGO_NUM_MARKER);
            if(negative)
            {
                __Tango_SetChar(destPtr+1, -num);
                negative=false;
            }
            else
                __Tango_SetChar(destPtr+1, num);
            destPtr+=2;
        }
        else if(character=='-')
        {
            // "--" isn't valid
            if(negative)
            {
                __Tango_LogError(__TANGO_ERROR_INVALID_ARGUMENT, srcPtr);
                return true;
            }
            
            // Next argument is negative
            negative=true;
            srcPtr++;
        }
        else
        {
            // Something invalid.
            __Tango_LogError(__TANGO_ERROR_INVALID_FUNCTION, srcPtr);
            return true;
        }
    }
    
    ptrOut[0]=srcPtr;
    ptrOut[1]=destPtr;
    return false;
}

// Reads a code argument from src[srcPtr] and writes it to the buffer.
// srcPtr points to just after the '@'. Returns true if there was an error,
// false if not.
bool __Tango_ParseArgumentCode(int src, int srcPtr, int destPtr, bool negative,
                               int ptrOut)
{
    int fnStart=destPtr;
    
    // The first letter's already been checked for validity.
    float id=__Tango_ReadIdentifier(src, srcPtr, ptrOut);
    srcPtr=__Tango_EatSpaces(src, ptrOut[0]);
    
    // If the next character is '(', this is a function. If not,
    // it's a variable (or something invalid, but that won't
    // be caught here).
    if(src[srcPtr]=='(')
    {
        __Tango_SetStartMarker(destPtr, id);
        if(negative)
            __Tango_SetChar(destPtr+1, -id);
        else
            __Tango_SetChar(destPtr+1, id);
        destPtr+=2;
        
        if(__Tango_ParseArguments(src, srcPtr+1, destPtr, ptrOut))
            return true; // Error
        
        srcPtr=ptrOut[0];
        destPtr=ptrOut[1];
        destPtr=__Tango_SetEndMarker(destPtr, id);
        destPtr=__Tango_AddPadding(id, fnStart, destPtr);
    }
    else
    {
        __Tango_SetChar(destPtr, __TANGO_VAR_MARKER);
        if(negative)
            __Tango_SetChar(destPtr+1, -id);
        else
            __Tango_SetChar(destPtr+1, id);
        destPtr+=2;
    }
    
    ptrOut[0]=srcPtr;
    ptrOut[1]=destPtr;
    return false;
}

// Reads and returns a number starting from src[srcPtr].
float __Tango_ReadNumber(int src, int srcPtr, bool integer, int ptrOut)
{
    int accumulator=0;
    int character;
    bool fraction=false;
    int fractionPlace=0.1;
    
    while(true)
    {
        character=src[srcPtr];
        
        if(character>='0' && character<='9')
        {
            if(!fraction)
            {
                accumulator*=10;
                accumulator+=character-'0';
            }
            else
            {
                accumulator+=(character-'0')*fractionPlace;
                if(fractionPlace==0.0001)
                    break;
                else
                    fractionPlace/=10;
            }
        }
        else if(character=='.')
        {
            if(integer || fraction)
                break;
            else
                fraction=true;
        }
        else
            break;
        
        srcPtr++;
    }
    
    ptrOut[0]=srcPtr;
    return accumulator;
}

// Reads the name of a variable or function starting from src[srcPtr] and
// returns the compiled form.
float __Tango_ReadIdentifier(int src, int srcPtr, int ptrOut)
{
    float accumulator=0;
    int character;
    
    while(true)
    {
        character=src[srcPtr];
        
        if(character>='a' && character<='z')
        {
            accumulator*=__TANGO_CODE_FACTOR;
            accumulator+=(character+1-'a')/10000;
        }
        else if(character>='0' && character<='9')
        {
            accumulator*=__TANGO_CODE_FACTOR;
            accumulator+=(character+27-'0')/10000;
        }
        else
            break;
        
        srcPtr++;
    }
    
    ptrOut[0]=srcPtr;
    return accumulator;
}

// Set the appropriate start marker for the given function.
void __Tango_SetStartMarker(int destPtr, int func)
{
    if(func==__TANGO_FLOW_IF ||
       func==__TANGO_FLOW_ELSE ||
       func==__TANGO_FLOW_ELSEIF ||
       func==__TANGO_FLOW_WHILE ||
       func==__TANGO_FLOW_WAITUNTIL)
        __Tango_SetChar(destPtr, __TANGO_FLOW_MARKER);
    else if(func==__TANGO_SETTER_SET ||
            func==__TANGO_SETTER_INC)
        __Tango_SetChar(destPtr, __TANGO_SETTER_MARKER);
    else
        __Tango_SetChar(destPtr, __TANGO_FUNC_MARKER);
}

// Set the appropriate end marker for the given function.
// Returns the new value of destPtr.
int __Tango_SetEndMarker(int destPtr, int func)
{
    if(func==__TANGO_FLOW_IF ||
       func==__TANGO_FLOW_ELSE ||
       func==__TANGO_FLOW_ELSEIF ||
       func==__TANGO_FLOW_WHILE ||
       func==__TANGO_FLOW_WAITUNTIL)
    {
        __Tango_SetChar(destPtr, __TANGO_FLOW_END_MARKER);
        return destPtr+1;
    }
    else if(func==__TANGO_SETTER_SET ||
            func==__TANGO_SETTER_INC)
        return destPtr; // None needed for setters
    else
    {
        __Tango_SetChar(destPtr, __TANGO_FUNC_END_MARKER);
        return destPtr+1;
    }
}

// Advances srcPtr beyond any spaces in src.
int __Tango_EatSpaces(int src, int srcPtr)
{
    while(src[srcPtr]==' ')
        srcPtr++;
    return srcPtr;
}

// Adds filler characters for text functions to overwrite.
// Returns the new value of destPtr.
int __Tango_AddPadding(int function, int fnStart, int destPtr)
{
    int needed=0;
    
    if(function==__TANGO_FUNC_NUMBER)
        needed=__TANGO_PAD_NUMBER;
    else if(function==__TANGO_FUNC_SAVENAME)
        needed=__TANGO_PAD_SAVENAME;
    else if(function==__TANGO_FUNC_ORDINAL)
        needed=__TANGO_PAD_ORDINAL;
    else if(function==__TANGO_FUNC_STRING)
        needed=__TANGO_MAX_STRING_FUNC_LENGTH;
    
    needed-=destPtr-fnStart;
    
    for(; needed>0; needed--)
    {
        __Tango_SetChar(destPtr, __TANGO_CHAR_FILLER);
        destPtr++;
    }
    
    return destPtr;
}


void __Tango_SetChar(int pos, int character)
{
    if(pos>=__Tango_Data[__TCS_END])
    {
        __Tango_LogError(__TANGO_ERROR_OVERFLOW, 0);
        return;
    }
    
    __Tango_Buffer[pos]=character;
}

// Finds the position of the last character before the null terminator or
// any trailing space. This function will not detect @0.
int __Tango_GetStringEnd(int string)
{
    int lastNonSpace=0;
    int pos=0;
    
    while(string[pos]!=0)
    {
        if(string[pos]!=' ')
            lastNonSpace=pos;
        pos++;
    }
    
    return lastNonSpace;
}

