/********************************************************************************
 * 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 "ps_cpp_generator.h"
#include "../exception.h"
#include <cassert>
#include <cctype>
#include <fstream>
#include <algorithm>
#include <set>
#include <mutex>

CPsCppGenerator::CPsCppGenerator(sdv::IInterfaceAccess* pParser) : CGenContext(pParser), m_mtx("SDV_IDL_COMPILER_GENERATE_PS")
{}

CPsCppGenerator::~CPsCppGenerator()
{}

bool CPsCppGenerator::Generate()
{
    // Synchronize proxy/stub code generation among processes.
    std::unique_lock<ipc::named_mutex> lock(m_mtx);

    // Create "proxy" directory
    std::filesystem::path pathPSTarget = GetOutputDir() / "ps";
    if (!std::filesystem::exists(pathPSTarget) && !std::filesystem::create_directory(pathPSTarget))
        throw CCompileException("Cannot create proxy/stub directory: ", pathPSTarget.generic_u8string());

    // The source string
    std::string ssSource;

    // File with "CMakeLists.txt" function; read completely if existing
    std::filesystem::path pathFile = pathPSTarget / "proxystub.cpp";
    if (std::filesystem::exists(pathFile))
    {
        std::ifstream stream;
        stream.open(pathFile);
        if (!stream.is_open()) throw CCompileException("Failed to open the proxystub.cpp file for reading.");

        // Read the complete source
        std::stringstream sstream;
        sstream << stream.rdbuf();
        ssSource = std::move(sstream.str());
    }

    // Create the file in memory
    if (ssSource.empty())
    {
        std::stringstream sstream;

        // Add file header
        sstream << "/**" << std::endl;
        sstream << " * @file proxstub.cpp" << 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" << std::endl;
        sstream << " *" << std::endl;
        sstream << " * Allow including all proxy-stub source code files to be included through this file." << std::endl;
        sstream << " */" << std::endl;
        sstream << std::endl;
        ssSource = std::move(sstream.str());
    }

    // Search for each #include preprocessor directive
    // And build a map of text includes
    struct SChunkPos { size_t nPos = 0; size_t nLen = 0; };
    std::set<std::string> setFiles;
    size_t nPos = 0;
    while (nPos != std::string::npos)
    {
        // Find the directive
        nPos = ssSource.find("#include", nPos);
        if (nPos == std::string::npos) continue;
        nPos += 8;

        // Skip whitespace
        while (nPos < ssSource.size() && std::isspace(ssSource[nPos])) nPos++;
        if (nPos >= ssSource.size()) continue;

        // Check for quote
        if (ssSource[nPos] != '\"') continue;
        nPos++;

        // Extract the include file and add to the set
        size_t nFilePos = nPos;
        while (nPos < ssSource.size() && ssSource[nPos] != '\"') nPos++;
        if (nPos >= ssSource.size()) continue;
        setFiles.insert(ssSource.substr(nFilePos, nPos - nFilePos));
    }

    // Insert additional files if needed
    size_t nSourceSize = ssSource.size();
    std::filesystem::path pathPSFileBase = GetSource().filename();
    pathPSFileBase.replace_extension("");
    std::string ssFileBase = pathPSFileBase.generic_u8string();
    if (setFiles.find(ssFileBase + "_stub.cpp") == setFiles.end())
    {
        std::stringstream sstream;
        sstream << std::endl;
        sstream << "// Adding proxy and stub code for " << GetSource().filename().generic_u8string() << std::endl;
        sstream << "#include \"" << ssFileBase << "_stub.cpp" << "\"" << std::endl;
        sstream << "#include \"" << ssFileBase << "_proxy.cpp" << "\"" << std::endl;
        ssSource += sstream.str();
    }

    // Write the file again if needed
    if (nSourceSize != ssSource.size())
    {
        std::ofstream stream;
        stream.open(pathFile, std::ofstream::trunc);
        if (!stream.is_open()) throw CCompileException("Failed to open the proxystub.cpp file for writing.");

        // Write the complete source
        stream << ssSource;
    }

    // Done!
    return true;
}