/********************************************************************************
 * Copyright (c) 2025-2026 ZF Friedrichshafen AG
 *
 * This program and the accompanying materials are made available under the 
 * terms of the Apache License Version 2.0 which is available at
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Contributors: 
 *   Erik Verhoeven - initial API and implementation 
 ********************************************************************************/

#include "codepos.h"
#include "lexer.h"
#include "exception.h"
#include "token.h"

CCodePos::CCodePos()
{}

CCodePos::CCodePos(const char* szCode) : m_szCode(szCode)
{
    if (!szCode)
        throw CCompileException("No code supplied.");
    m_szCurrent = szCode;
    m_uiLine = 1;
    m_uiCol = 1;
}

CCodePos::CCodePos(const CCodePos& rCode) :
    m_szCode(rCode.m_szCode), m_szCurrent(rCode.m_szCurrent), m_szPrependedPos(rCode.m_szPrependedPos),
    m_szPrependedCode(nullptr), m_szPrependedCurrent(nullptr), m_ssPrependedCode(rCode.m_ssPrependedCode),
    m_uiLine(rCode.m_uiLine), m_uiCol(rCode.m_uiCol)
{
    if (rCode.m_szPrependedCurrent && rCode.m_szPrependedCode)
    {
        m_szPrependedCode = m_ssPrependedCode.c_str();
        m_szPrependedCurrent = m_szPrependedCode + (rCode.m_szPrependedCurrent - rCode.m_szPrependedCode);
    }
}

CCodePos::CCodePos(CCodePos&& rCode) noexcept :
    m_szCode(rCode.m_szCode), m_szCurrent(rCode.m_szCurrent), m_szPrependedPos(rCode.m_szPrependedPos),
    m_szPrependedCode(rCode.m_szPrependedCode), m_szPrependedCurrent(rCode.m_szPrependedCurrent),
    m_ssPrependedCode(std::move(rCode.m_ssPrependedCode)), m_lstCodeChunks(std::move(rCode.m_lstCodeChunks)),
    m_uiLine(rCode.m_uiLine), m_uiCol(rCode.m_uiCol)
{
    rCode.m_szCode = nullptr;
    rCode.m_szCurrent = nullptr;
    rCode.m_szPrependedPos = nullptr;
    rCode.m_szPrependedCode = nullptr;
    rCode.m_szPrependedCurrent = nullptr;
    rCode.m_uiLine = 0;
    rCode.m_uiCol = 0;
}

bool CCodePos::IsValid() const
{
    return m_szCode != nullptr &&
        m_szCurrent != nullptr &&
        m_uiLine > 0 &&
        m_uiCol > 0;
}

CCodePos::operator bool() const
{
    return IsValid();
}

void CCodePos::Reset()
{
    m_szCurrent = m_szCode;
    m_szPrependedPos = nullptr;
    m_szPrependedCode = nullptr;
    m_szPrependedCurrent = nullptr;
    m_uiLine = 1;
    m_uiCol = 1;
    m_ssPrependedCode.clear();
}

uint32_t CCodePos::GetLine() const
{
    return m_uiLine;
}

uint32_t CCodePos::GetCol() const
{
    return m_uiCol;
}

bool CCodePos::HasEOF() const
{
    return !m_szCurrent || ((!m_szPrependedCurrent || *m_szPrependedCurrent == '\0') && *m_szCurrent == '\0');
}

bool CCodePos::HasEOL() const
{
    if (HasEOF()) return true;
    if (m_szPrependedCurrent && *m_szPrependedCurrent != '\0')
        return *m_szPrependedCurrent == '\n' || (m_szPrependedCurrent[0] == '\r' && m_szPrependedCurrent[1] == '\n');
    return *m_szCurrent == '\n' || (m_szCurrent[0] == '\r' && m_szCurrent[1] == '\n');
}

CToken CCodePos::GetLocation(ETokenType eTokenType /*= ETokenType::token_none*/) const
{
    CToken token;
    token.m_szCode = (m_szPrependedCurrent && *m_szPrependedCurrent) ? m_szPrependedCurrent : m_szCurrent;
    token.m_uiLine = m_uiLine;
    token.m_uiCol = m_uiCol;
    token.m_eType = eTokenType;
    return token;
}

void CCodePos::UpdateLocation(CToken& rtoken) const
{
    // Check for a valid token.
    if (!rtoken) return;

    // If the token start position is within the prepended start code and the prepended current code, update the location by
    // creating a code chunk.
    if (m_szPrependedCode && rtoken.m_szCode >= m_szPrependedCode && rtoken.m_szCode <= m_szPrependedCurrent)
    {
        // Create a chunk based on the prepended code
        std::string ssChunk = std::string(rtoken.m_szCode, m_szPrependedCurrent - rtoken.m_szCode);

        // If the current position of the prepended code is at the end, add a part of the code.
        if (!*m_szPrependedCurrent)
            ssChunk += std::string(m_szPrependedPos, m_szCurrent - m_szPrependedPos);

        // Add the chunk to the list
        m_lstCodeChunks.emplace_back(std::move(ssChunk));

        // Assign the new start and length infos
        rtoken.m_szCode = m_lstCodeChunks.back().c_str();
        rtoken.m_uiLen = static_cast<uint32_t>(m_lstCodeChunks.back().size());

        return;
    }

    // If the token start position is within the code, update the length
    if (rtoken.m_szCode >= m_szCode && rtoken.m_szCode <= m_szCurrent)
    {
        rtoken.m_uiLen = static_cast<uint32_t>(m_szCurrent - rtoken.m_szCode);
        return;
    }

    // The token start position is invalid. Set the code to nullptr and the length to 0.
    rtoken.m_szCode = nullptr;
    rtoken.m_uiLen = 0;
}

void CCodePos::UpdateLocation(CToken& rtoken, ETokenLiteralType eLiteralType) const
{
    UpdateLocation(rtoken);
    switch (rtoken.GetType())
    {
    case ETokenType::token_literal:
    case ETokenType::token_none:
        rtoken.m_eType = ETokenType::token_literal;
        rtoken.m_eLiteralType = eLiteralType;
        break;
    default:
        throw CCompileException("Internal error: invalid token type during literal type assignment.");
        break;
    }
}

void CCodePos::UpdateLocation(CToken& rtoken, ETokenMetaType eMetaType) const
{
    UpdateLocation(rtoken);
	switch (rtoken.GetType())
	{
	case ETokenType::token_meta:
	case ETokenType::token_none:
		rtoken.m_eType = ETokenType::token_meta;
		rtoken.m_eMetaType = eMetaType;
		break;
	default:
		throw CCompileException("Internal error: invalid token type during meta type assignment.");
		break;
	}
}

void CCodePos::PrependCode(const std::string& rssCode)
{
    if (rssCode.empty()) return;
    std::string ssPrependedCode = rssCode;
    if (m_szPrependedCurrent)
        ssPrependedCode += m_szPrependedCurrent;
    m_ssPrependedCode = std::move(ssPrependedCode);
    m_szPrependedCode = m_ssPrependedCode.c_str();
    m_szPrependedCurrent = m_ssPrependedCode.c_str();
    m_szPrependedPos = m_szCurrent;
}

bool CCodePos::CurrentPositionInMacroExpansion() const
{
    return m_szPrependedCurrent && *m_szPrependedCurrent;
}

CCodePos& CCodePos::operator=(const CCodePos& rCode)
{
    m_szCode = rCode.m_szCode;
    m_szCurrent = rCode.m_szCurrent;
    m_szPrependedPos = rCode.m_szPrependedPos;
    m_ssPrependedCode = rCode.m_ssPrependedCode;
    if (rCode.m_szPrependedCurrent && rCode.m_szPrependedCode)
    {
        m_szPrependedCode = m_ssPrependedCode.c_str();
        m_szPrependedCurrent = m_szPrependedCode + (rCode.m_szPrependedCurrent - rCode.m_szPrependedCode);
    } else
    {
        m_szPrependedCode = nullptr;
        m_szPrependedCurrent = nullptr;
    }
    m_uiLine = rCode.m_uiLine;
    m_uiCol = rCode.m_uiCol;
    return *this;
}

CCodePos& CCodePos::operator=(CCodePos&& rCode) noexcept
{
    m_szCode = rCode.m_szCode;
    m_szCurrent = rCode.m_szCurrent;
    m_szPrependedPos = rCode.m_szPrependedPos;
    m_szPrependedCode = rCode.m_szPrependedCode;
    m_szPrependedCurrent = rCode.m_szPrependedCurrent;
    m_ssPrependedCode = std::move(rCode.m_ssPrependedCode);
    m_lstCodeChunks = std::move(rCode.m_lstCodeChunks);
    m_uiLine = rCode.m_uiLine;
    m_uiCol = rCode.m_uiCol;
    rCode.m_szCode = nullptr;
    rCode.m_szCurrent = nullptr;
    rCode.m_szPrependedPos = nullptr;
    rCode.m_szPrependedCode = nullptr;
    rCode.m_szPrependedCurrent = nullptr;
    rCode.m_uiLine = 0;
    rCode.m_uiCol = 0;
    return *this;
}

char CCodePos::operator[](uint32_t uiOffset /*= 0*/) const
{
    // Handle an offset past the prepended string.
    for (uint32_t uiIndex = 0; uiIndex <= uiOffset; uiIndex++)
    {
        if (!m_szPrependedCurrent || m_szPrependedCurrent[uiIndex] == '\0')
            return m_szCurrent ? m_szCurrent[uiOffset - uiIndex] : '\0';
    }

    // Return a character from the prepended string.
    return m_szPrependedCurrent[uiOffset];
}

char CCodePos::operator*() const
{
    if (m_szPrependedCurrent && m_szPrependedCurrent[0]) return m_szPrependedCurrent[0];
    return m_szCurrent ? *m_szCurrent : '\0';
}

CCodePos::operator const char*() const
{
    if (m_szPrependedCurrent) return m_szPrependedCurrent;
    return m_szCurrent;
}

CCodePos CCodePos::operator++(int)
{
    CCodePos posCopy(*this);
    operator++();
    return posCopy;
}

CCodePos& CCodePos::operator++()
{
    // Check for a valid prepended character.
    if (m_szPrependedCurrent && *m_szPrependedCurrent)
    {
        // Increase current position
        m_szPrependedCurrent++;

        // End of prepended code?
        if (!m_szPrependedCurrent)
        {
            m_szPrependedCurrent = nullptr;
            m_ssPrependedCode.clear();
        }

        return *this;
    }

    // Check for a valid character
    if (m_szCurrent && *m_szCurrent != '\0')
    {
        // Currently a newline?
        if (*m_szCurrent == '\n')
        {
            // Set new position at beginning of ext line
            m_uiCol = 1;
            m_uiLine++;
        }
        else if (*m_szCurrent == '\t')
        {
            // Increase the column
            m_uiCol++;

            // Align with a tab of 4 characters
            while ((m_uiCol - 1) % 4) m_uiCol++;
        }
        else
            m_uiCol++;  // Increase the column

                        // Next character
        m_szCurrent++;
    }

    return *this;
}

CCodePos& CCodePos::operator+=(uint32_t uiOffset)
{
    for (uint32_t uiIndex = 0; uiIndex != uiOffset; uiIndex++)
        operator++(0);

    return *this;
}
