/********************************************************************************
 * 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 "context.h"
#include "../exception.h"
#include <cassert>
#include <cctype>
#include <fstream>
#include <ctime>
#include <chrono>
#include <iomanip>
#include <functional>
#include <vector>

CGenContext::CGenContext(sdv::IInterfaceAccess* pParser) : m_pParser(pParser)
{
    if (!m_pParser) throw CCompileException("Internal error: no valid parser pointer supplied to generator.");
    m_pCompilerInfo = m_pParser->GetInterface<sdv::idl::ICompilerInfo>();
    if (!m_pCompilerInfo) throw  CCompileException("Internal error: compiler info is not available.");
    m_pOption = m_pParser->GetInterface<sdv::idl::ICompilerOption>();
    if (!m_pOption) throw  CCompileException("Internal error: cannot access options interface.");

}

CGenContext::~CGenContext()
{}

std::filesystem::path CGenContext::GetSource() const
{
    if (!m_pCompilerInfo) return std::filesystem::path();
    std::filesystem::path pathSource = static_cast<std::string>(m_pCompilerInfo->GetFilePath());
    if (pathSource.empty()) throw CCompileException("Internal error: file path is not available.");
    return pathSource;
}

std::filesystem::path CGenContext::GetOutputDir() const
{
    if (!m_pCompilerInfo) return std::filesystem::path();
    std::filesystem::path pathOutputDir = static_cast<std::string>(m_pCompilerInfo->GetOutputDir());
    if (pathOutputDir.empty())
        pathOutputDir = GetSource().parent_path();
    return pathOutputDir;
}

std::string CGenContext::Header(const std::filesystem::path& rpathFile,
    const std::string& rssDescription /*= std::string()*/) const
{
    std::stringstream sstream;

    // Add file header
    sstream << "/**" << std::endl;
    sstream << " * @file " << rpathFile.filename().generic_u8string() << std::endl;
    auto now = std::chrono::system_clock::now();
    auto in_time_t = std::chrono::system_clock::to_time_t(now);
    sstream << " * @date " << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %X") << std::endl;
    sstream << " * This file was generated by the SDV IDL compiler from '" << GetSource().filename().generic_u8string() << "'" <<
        std::endl;
    if (!rssDescription.empty())
    {
        // Insert the JavaDoc marks before each line
        size_t nPos = 0;
        while (nPos < rssDescription.size())
        {
            size_t nEnd = rssDescription.find_first_of("\r\n", nPos);
            sstream << " * " << rssDescription.substr(nPos, nEnd == std::string::npos ? nEnd : nEnd - nPos) << std::endl;
            nPos = nEnd;
            if (nPos < rssDescription.size() && rssDescription[nPos] == '\r')
                nPos++;
            if (nPos < rssDescription.size() && rssDescription[nPos] == '\n')
                nPos++;
        }
    }
    sstream << " */" << std::endl;
    sstream << std::endl;

    return sstream.str();
}

std::string CGenContext::Safeguard(const std::filesystem::path& rpathFile, bool bInitial)
{
    // Safeguards start with "__IDL_GENERATED__", add the file name, add the date and time and end with "__"
    std::stringstream sstreamSafeguard;
    sstreamSafeguard << "__IDL_GENERATED__";
    std::string ssFile = rpathFile.filename().generic_u8string();
    for (char c : ssFile)
    {
        if ((c < '0' || c > '9') &&
            (c < 'a' || c > 'z') &&
            (c < 'A' || c > 'Z'))
            sstreamSafeguard << '_';
        else
            sstreamSafeguard << static_cast<char>(std::toupper(c));
    }
    sstreamSafeguard << "__";

    auto now = std::chrono::system_clock::now();
    auto in_time_t = std::chrono::system_clock::to_time_t(now);
    sstreamSafeguard << std::put_time(std::localtime(&in_time_t), "%Y%m%d_%H%M%S") << "_";
    sstreamSafeguard << std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count() % 1000;

    sstreamSafeguard << "__";
    // Return the safeguard code
    std::stringstream sstream;
    if (bInitial)
        sstream << "#ifndef " << sstreamSafeguard.str() << std::endl << "#define " << sstreamSafeguard.str() << std::endl << std::endl;
    else
        sstream << std::endl << "#endif // !defined(" << sstreamSafeguard.str() << ")" << std::endl;

    return sstream.str();
}

std::string CGenContext::SmartIndent(const std::string& rssStr, const std::string& rssIndent)
{
    // Determine the amount of whitespace at the beginning of the string and replace this whitespace by the current indent. Do
    // this for every line.
    // Use four spaces for every tab (to allow mixed tab and space usage).
    size_t nDetectedIndentation = 0;
    size_t nPos = 0;
    enum class EState {init, skip_indent, code} eState = EState::init;
    while (eState == EState::init && nPos < rssStr.size())
    {
        switch (rssStr[nPos])
        {
        case ' ':
            nDetectedIndentation++;
            nPos++;
            break;
        case '\t':
            nDetectedIndentation += 4;
            nPos++;
            break;
        default:
            eState = EState::code;
            break;
        }
    }

    std::stringstream sstream;
    while (nPos < rssStr.size())
    {
        // Skip indentation
        size_t nSkip = 0;
        while (eState == EState::skip_indent && nSkip < nDetectedIndentation)
        {
            switch (rssStr[nPos])
            {
            case ' ':
                nSkip++;
                nPos++;
                break;
            case '\t':
                nSkip +=4;
                nPos++;
                break;
            default:
                eState = EState::code;
                break;
            }
        }
        eState = EState::code;

        // Find the next newline
        size_t nEnd = rssStr.find_first_of("\r\n", nPos);
        std::string ssSubstr = rssStr.substr(nPos, nEnd == std::string::npos ? nEnd : nEnd - nPos);

        // If the string didn't start with a number character, remove the line concatinating character if it's there.
        if (rssStr[0] != '#' && !ssSubstr.empty() && *ssSubstr.rbegin() == '\\')
            ssSubstr.resize(ssSubstr.size() - 1);

        // Remove whitespace at the end of the string
        while (!ssSubstr.empty() && std::isspace(*ssSubstr.rbegin()))
            ssSubstr.resize(ssSubstr.size() - 1);

        // Stream the sub-string with indentation if the string didn't start with a number character.
        sstream << (rssStr[0] != '#' ? rssIndent : "") << ssSubstr;

        // Stream and skip newline
        nPos = nEnd;
        if (nPos < rssStr.size())
        {
            if (rssStr[nPos] == '\r')
                nPos++;
            if (rssStr[nPos] == '\n')
                nPos++;
            eState = EState::skip_indent;
        }
        sstream << std::endl;
    }
    return sstream.str();
}

std::string CGenContext::QualifyName(const std::string& rssName)
{
    std::stringstream sstream;
    size_t nPos = 0;
    while (nPos < rssName.size())
    {
        size_t nSeparator = rssName.find_first_of(":.[]", nPos);
        sstream << rssName.substr(nPos, nSeparator == std::string::npos ? nSeparator : nSeparator - nPos);
        nPos = nSeparator;
        if (nPos != std::string::npos)
        {
            if (rssName[nPos] != ']')
                sstream << "_";
            nPos++;
        }
    }
    return sstream.str();
}

std::string CGenContext::ReplaceKeywords(const std::string& rssStr, const CKeywordMap& rmapKeywords, char cMarker /*= '%'*/)
{
    std::stringstream sstream;
    size_t nPos = 0;
    while (nPos < rssStr.size())
    {
        // Find the initial separator
        size_t nSeparator = rssStr.find(cMarker, nPos);
        sstream << rssStr.substr(nPos, nSeparator == std::string::npos ? nSeparator : nSeparator - nPos);
        nPos = nSeparator;
        if (nSeparator == std::string::npos) continue;
        nPos++;

        // Find the next separator.
        nSeparator = rssStr.find(cMarker, nPos);
        if (nSeparator == std::string::npos)
            throw CCompileException("Internal error: missing second separator during code generation.");

        // Find the keyword in the keyword map (between the separator and the position).
        CKeywordMap::const_iterator itKeyword = rmapKeywords.find(rssStr.substr(nPos, nSeparator - nPos));
        if (itKeyword == rmapKeywords.end())
        {
            std::stringstream sstreamError;
            sstreamError << "Internal error: invalid keyword \"" << rssStr.substr(nPos, nSeparator - nPos) <<
                "\" during code generation.";
            throw CCompileException(sstreamError.str().c_str());
        } else
            sstream << itKeyword->second;
        nPos = nSeparator + 1;
    }
    return sstream.str();
}

std::string CGenContext::GetIndentChars()
{
    // Default indentation is 4 spaces
    return "    ";
}

CGenContext::SCDeclInfo CGenContext::GetCDeclTypeStr(sdv::IInterfaceAccess* pDeclTypeObj, const std::string& rssScope /*= std::string()*/, bool bScopedName /*= false*/) const
{
    std::function<void(sdv::IInterfaceAccess*, CGenContext::SCDeclInfo&)> fnInterpretType =
        [&, this](sdv::IInterfaceAccess* pTypeObj, CGenContext::SCDeclInfo& rsCDeclInfo)
    {
        if (!pTypeObj) throw CCompileException("Internal error: expecting a declaration type.");
        const sdv::idl::IDeclarationType* pDeclType = pTypeObj->GetInterface<sdv::idl::IDeclarationType>();
        if (!pDeclType) throw CCompileException("Internal error: expecting a declaration type.");
        rsCDeclInfo.eBaseType = pDeclType->GetBaseType();

        // Separate between system type and defined type.
        if (pDeclType->GetTypeDefinition())
        {
            // Deal with anonymous definitions
            const sdv::idl::IEntityInfo* pTypeInfo = GetInterface<sdv::idl::IEntityInfo>(pDeclType->GetTypeDefinition());
            if (!pTypeInfo) throw CCompileException("Internal error: the entity doesn't expose information.");
            rsCDeclInfo.ssDeclType = bScopedName ?
                GetRelativeScopedName(pTypeInfo->GetScopedName(), rssScope) :
                static_cast<std::string>(pTypeInfo->GetName());
            if (rsCDeclInfo.ssDeclType.empty()) throw CCompileException("Internal error: the intity doesn't have a name.");
        }
        else
            rsCDeclInfo.ssDeclType = MapDeclType2CType(rsCDeclInfo.eBaseType);

        // If the type is an interface, add a pointer to the type
        // TODO: Check for derived type...
        switch (rsCDeclInfo.eBaseType)
        {
        case sdv::idl::EDeclType::decltype_interface:
            rsCDeclInfo.ssDeclType += "*";
            rsCDeclInfo.bIsInterface = true;
            rsCDeclInfo.bIsPointer	= true;
            break;
        case sdv::idl::EDeclType::decltype_string:
        case sdv::idl::EDeclType::decltype_u8string:
        case sdv::idl::EDeclType::decltype_u16string:
        case sdv::idl::EDeclType::decltype_u32string:
        case sdv::idl::EDeclType::decltype_wstring:
            rsCDeclInfo.bIsString  = true;
            rsCDeclInfo.bIsComplex = true;
            if (pDeclType->GetFixedLength())
            {
                // Insert fixed after sdv:: and before the string name
                rsCDeclInfo.ssDeclType.insert(5, "fixed_");
                rsCDeclInfo.ssDeclType += "<" + std::to_string(pDeclType->GetFixedLength()) + ">";
                rsCDeclInfo.bTemplated = true;
            }
            break;
        case sdv::idl::EDeclType::decltype_sequence:
            rsCDeclInfo.bIsComplex = true;
            if (!pDeclType->GetValueType())
                throw CCompileException("Internal error: expecting value type for template parameter.");
            else
            {
                CGenContext::SCDeclInfo sCDeclInfoValueType;
                fnInterpretType(pDeclType->GetValueType(), sCDeclInfoValueType);
                rsCDeclInfo.ssDeclType += "<" + sCDeclInfoValueType.ssDeclType;
                if (pDeclType->GetFixedLength())
                    rsCDeclInfo.ssDeclType += ", " + std::to_string(pDeclType->GetFixedLength());
                rsCDeclInfo.ssDeclType += ">";
                rsCDeclInfo.bTemplated = true;
            }
            break;
        case sdv::idl::EDeclType::decltype_pointer:
            rsCDeclInfo.bIsComplex = true;
            if (!pDeclType->GetValueType())
                throw CCompileException("Internal error: expecting value type for template parameter.");
            else
            {
                CGenContext::SCDeclInfo sCDeclInfoValueType;
                fnInterpretType(pDeclType->GetValueType(), sCDeclInfoValueType);
                rsCDeclInfo.ssDeclType += "<" + sCDeclInfoValueType.ssDeclType;
                if (pDeclType->GetFixedLength())
                    rsCDeclInfo.ssDeclType += ", " + std::to_string(pDeclType->GetFixedLength());
                rsCDeclInfo.ssDeclType += ">";
                rsCDeclInfo.bTemplated = true;
            }
            break;
        case sdv::idl::EDeclType::decltype_struct:
        case sdv::idl::EDeclType::decltype_union:
            rsCDeclInfo.bIsComplex = true;
            break;
        default:
            break;
        }
    };

    // Fill the C++ type structure
    CGenContext::SCDeclInfo sCDeclInfo;
    if (!pDeclTypeObj) return sCDeclInfo;
    fnInterpretType(pDeclTypeObj, sCDeclInfo);

    // Exclude void as valid type
    if (sCDeclInfo.ssDeclType != "void")
        sCDeclInfo.bValidType = true;

    return sCDeclInfo;
}

std::string CGenContext::MapEntityType2CType(sdv::idl::EEntityType eEntityType)
{
    switch (eEntityType)
    {
    case sdv::idl::EEntityType::type_enum:                return "enum class";
    case sdv::idl::EEntityType::type_struct:              return "struct";
    case sdv::idl::EEntityType::type_union:               return "union";
    case sdv::idl::EEntityType::type_module:              return "namespace";
    case sdv::idl::EEntityType::type_interface:           return "interface";
    case sdv::idl::EEntityType::type_exception:           return "except";
    case sdv::idl::EEntityType::type_typedef:             return "typedef";
    case sdv::idl::EEntityType::type_attribute:           return "";
    case sdv::idl::EEntityType::type_operation:           return "";
    case sdv::idl::EEntityType::type_parameter:           return "";
    case sdv::idl::EEntityType::type_enum_entry:          return "";
    case sdv::idl::EEntityType::type_case_entry:          return "";
    default:
        return "invalid";
    }
}

std::string CGenContext::MapDeclType2CType(sdv::idl::EDeclType eDeclType)
{
    switch (eDeclType)
    {
    case sdv::idl::EDeclType::decltype_short:               return "int16_t";
    case sdv::idl::EDeclType::decltype_long:                return "int32_t";
    case sdv::idl::EDeclType::decltype_long_long:           return "int64_t";
    case sdv::idl::EDeclType::decltype_unsigned_short:      return "uint16_t";
    case sdv::idl::EDeclType::decltype_unsigned_long:       return "uint32_t";
    case sdv::idl::EDeclType::decltype_unsigned_long_long:  return "uint64_t";
    case sdv::idl::EDeclType::decltype_float:               return "float";
    case sdv::idl::EDeclType::decltype_double:              return "double";
    case sdv::idl::EDeclType::decltype_long_double:         return "long double";
    case sdv::idl::EDeclType::decltype_fixed:               return "uint32_t";        // TODO: Not implemented!
    case sdv::idl::EDeclType::decltype_char:                return "char";
    case sdv::idl::EDeclType::decltype_char16:              return "char16_t";
    case sdv::idl::EDeclType::decltype_char32:              return "char32_t";
    case sdv::idl::EDeclType::decltype_wchar:               return "wchar_t";
    case sdv::idl::EDeclType::decltype_boolean:             return "bool";
    case sdv::idl::EDeclType::decltype_native:              return "size_t";
    case sdv::idl::EDeclType::decltype_octet:               return "uint8_t";
    case sdv::idl::EDeclType::decltype_string:              return "sdv::string";
    case sdv::idl::EDeclType::decltype_u8string:            return "sdv::u8string";
    case sdv::idl::EDeclType::decltype_u16string:           return "sdv::u16string";
    case sdv::idl::EDeclType::decltype_u32string:           return "sdv::u32string";
    case sdv::idl::EDeclType::decltype_wstring:             return "sdv::wstring";
    case sdv::idl::EDeclType::decltype_enum:                return "enum class";
    case sdv::idl::EDeclType::decltype_struct:              return "struct";
    case sdv::idl::EDeclType::decltype_union:               return "union";
    case sdv::idl::EDeclType::decltype_module:              return "namespace";
    case sdv::idl::EDeclType::decltype_interface:           return "interface";
    case sdv::idl::EDeclType::decltype_exception:           return "struct";
    case sdv::idl::EDeclType::decltype_attribute:           return "";
    case sdv::idl::EDeclType::decltype_operation:           return "";
    case sdv::idl::EDeclType::decltype_parameter:           return "";
    case sdv::idl::EDeclType::decltype_enum_entry:          return "";
    case sdv::idl::EDeclType::decltype_case_entry:          return "";
    case sdv::idl::EDeclType::decltype_typedef:             return "typedef";
    case sdv::idl::EDeclType::decltype_sequence:            return "sdv::sequence";
    case sdv::idl::EDeclType::decltype_pointer:             return "sdv::pointer";
    case sdv::idl::EDeclType::decltype_map:                 return "uint32_t";        // TODO: Not implemented!
    case sdv::idl::EDeclType::decltype_bitset:              return "uint32_t";        // TODO: Not implemented!
    case sdv::idl::EDeclType::decltype_bitfield:            return "uint32_t";        // TODO: Not implemented!
    case sdv::idl::EDeclType::decltype_bitmask:             return "uint32_t";        // TODO: Not implemented!
    case sdv::idl::EDeclType::decltype_any:                 return "sdv::any_t";
    case sdv::idl::EDeclType::decltype_interface_id:        return "sdv::interface_id";
    case sdv::idl::EDeclType::decltype_interface_type:      return "sdv::interface_t";
    case sdv::idl::EDeclType::decltype_exception_id:        return "sdv::exception_id";
    case sdv::idl::EDeclType::decltype_void:                return "void";
    case sdv::idl::EDeclType::decltype_unknown:
    default:
        return "invalid";
    }
}

std::string CGenContext::GetRelativeScopedName(const std::string& ssScopedName, const std::string& rssScope)
{
    if (rssScope.empty()) return ssScopedName;

    // Splitting function
    using CScopeVector = std::vector<std::string>;
    auto fnSplitScopedName = [](const std::string& rssName) -> CScopeVector
    {
        CScopeVector vecSplittedName;
        size_t nPos = 0;
        while (nPos != std::string::npos)
        {
            size_t nStart = nPos;
            nPos = rssName.find("::", nStart);
            vecSplittedName.push_back(rssName.substr(nStart, nPos - nStart));
            if (nPos != std::string::npos)
                nPos += 2;
        }
        return vecSplittedName;
    };

    // Split the scoped name
    CScopeVector vecScopedName = fnSplitScopedName(ssScopedName);
    if (vecScopedName.empty()) return ssScopedName;

    // Split the scope
    CScopeVector vecScope = fnSplitScopedName(rssScope);
    if (vecScope.empty()) return ssScopedName;

    // Reverse find the starting point
    auto itScope = vecScope.end();
    auto itScopedName = vecScopedName.begin();
    while (itScope != vecScope.begin())
    {
        itScope--;
        if (*itScope == *itScopedName) break;
    }

    // As long as both iterators have identical scope names, increase the iterators.
    auto itSavedScopedName = itScopedName;
    while (itScope != vecScope.end() && itScopedName != vecScopedName.end() && *itScope == *itScopedName)
    {
        itScope++;
        itSavedScopedName = itScopedName;
        itScopedName++;
    }

    // If the next name scope is anywhere in the rest of the scope, use the save scope name instead. For example:
    //  Name = a::b::c::d
    //  Scope = a::b::x::c
    // The iterator is pointing to:
    //  Name iterator = c::d
    //  Scope iterator = x::c
    // If returning the relative name (which is "c::d") it will be relative to the scope (which is "a::b::x::c") and will then be
    // a scoped name "a::b::x::c::d", which might not even exist. To solve this, insert the last scoped name part to the returned
    // name if the name is used in the rest of the scope as well (so insert "b" to the name which leads to a relative name of
    // "b::c::d").
    while (itScope != vecScope.end())
    {
        if (*itScope == *itScopedName)
        {
            itScopedName = itSavedScopedName;
            break;
        }
        itScope++;
    }

    // Create a new scoped name from the left over scope names in the scoped name vector.
    std::string ssScopedNameNew;
    while (itScopedName != vecScopedName.end())
    {
        if (!ssScopedNameNew.empty()) ssScopedNameNew += "::";
        ssScopedNameNew += *itScopedName;
        itScopedName++;
    }

    return ssScopedNameNew;
}
