/********************************************************************************
 * 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:
 *   Thomas Pfleiderer - initial API and implementation
 ********************************************************************************/


/**
 * @brief Build description template. File 'buildDescription.xml'. Code chunks are inserted at the keywords surrounded by %%.
 */
const char szBuildDescriptionTemplate[] = R"code(<!--
@file %buiddescription_path%
@date %date%
This file is the fmi Build Description xml.
This file was generated by the DBC utility from:
  %dbc_sources%
  %dbc_version%
-->
%buildDescription_xml%
)code";


/**
* @brief CMake build project template. File 'CMakeLists.txt'. Code chunks are inserted at the keywords surrounded by %%.
*/
const char szMappingCMakeFileListsTemplate[] = R"code(
# @file %cmakefilelists_path%
# This file is cmake project file.
# This file was generated by the DBC utility from:
#   %dbc_sources%
#   %dbc_version%


# Based on CMakeLists.txt from https://github.com/modelica/Reference-FMUs
# only FMI 2.0, only CoSimulation
# without fumsim


# Only valid for Windows
if ( WIN32 )

   # Enforce CMake version 3.20 or newer needed for path function
   cmake_minimum_required (VERSION 3.20)

   # Use new policy for project version settings and default warning level
   cmake_policy(SET CMP0048 NEW)   # requires CMake 3.14
   cmake_policy(SET CMP0092 NEW)   # requires CMake 3.15

   project (%model_Identifier%Project)

   # Use C++17 support
   set(CMAKE_CXX_STANDARD 17)

   # Library symbols are hidden by default
   set(CMAKE_CXX_VISIBILITY_PRESET hidden)

   # Set the SDV_FRAMEWORK_DEV_INCLUDE if not defined yet
   if (NOT DEFINED SDV_FRAMEWORK_DEV_INCLUDE)
       if (NOT DEFINED ENV{SDV_FRAMEWORK_DEV_INCLUDE})
           message( FATAL_ERROR "The environment variable SDV_FRAMEWORK_DEV_INCLUDE needs to be pointing to the SDV V-API development include files location!")
       endif()
       set (SDV_FRAMEWORK_DEV_INCLUDE "$ENV{SDV_FRAMEWORK_DEV_INCLUDE}")
   endif()

   # Include link to export directory of SDV V-API development include files location
   include_directories(${SDV_FRAMEWORK_DEV_INCLUDE})
   set(VAPI_CORE_SDV_BINARY_DIR ${CMAKE_BINARY_DIR}/bin)
   set(MODEL_NAME %model_Identifier%)
   set(TARGET_NAME ${MODEL_NAME})
   set(FMU_FULL_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/fmus/${MODEL_NAME}.fmu")    

   FUNCTION(cat IN_FILE OUT_FILE)
       file(READ ${IN_FILE} CONTENTS)
       file(APPEND ${OUT_FILE} "${CONTENTS}")
   ENDFUNCTION()

   set(FMI_VERSION 2 CACHE STRING "FMI Version")
   set_property(CACHE FMI_VERSION PROPERTY STRINGS 2)

   set(FMI_TYPE CS CACHE STRING "FMI Version")
   set_property(CACHE FMI_TYPE PROPERTY STRINGS CS)
   set(FMI_TYPE "")

   set (FMI_PLATFORM win32)
   if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
       set (FMI_PLATFORM win64)
   endif ()

   SET(HEADERS
       ${MODEL_NAME}/config.h
       include/cosimulation.h
       include/model.h
   )

   SET(HEADERS
       ${HEADERS}
       include/fmi2Functions.h
       include/fmi2FunctionTypes.h
       include/fmi2TypesPlatform.h
   )

   SET(SOURCES
       ${MODEL_NAME}/model.cpp
       src/fmi${FMI_VERSION}Functions.c
       src/cosimulation.c
   )

   add_library(${TARGET_NAME} SHARED
       ${HEADERS}
       ${SOURCES}
       ${MODEL_NAME}/FMI${FMI_VERSION}${FMI_TYPE}.xml
       ${MODEL_NAME}/buildDescription.xml
   )

   file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/fmus)

   set(FMU_BUILD_DIR temp/${MODEL_NAME})
   target_compile_definitions(${TARGET_NAME} PRIVATE
       FMI_VERSION=${FMI_VERSION}
       DISABLE_PREFIX
   )

   #[[
   if (MSVC)
       set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
   else()
       set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libstdc++ -static-libgcc")
       set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++ -static-libgcc")
   endif()
   ]]

   target_compile_definitions(${TARGET_NAME} PRIVATE FMI_COSIMULATION)
   target_include_directories(${TARGET_NAME} PRIVATE include ${MODEL_NAME})
   target_link_libraries(${TARGET_NAME} Winmm Ws2_32 Rpcrt4.lib)

   set_target_properties(${TARGET_NAME} PROPERTIES
       RUNTIME_OUTPUT_DIRECTORY         "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}"
       RUNTIME_OUTPUT_DIRECTORY_DEBUG   "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}"
       RUNTIME_OUTPUT_DIRECTORY_RELEASE "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}"
       LIBRARY_OUTPUT_DIRECTORY         "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}"
       LIBRARY_OUTPUT_DIRECTORY_DEBUG   "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}"
       LIBRARY_OUTPUT_DIRECTORY_RELEASE "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}"
       ARCHIVE_OUTPUT_DIRECTORY         "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}"
       ARCHIVE_OUTPUT_DIRECTORY_DEBUG   "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}"
       ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${FMU_BUILD_DIR}/binaries/${FMI_PLATFORM}"
   )

   set_target_properties(${TARGET_NAME} PROPERTIES PREFIX "")
   set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME ${MODEL_NAME})

   # modelDescription.xml
   add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
       ${CMAKE_CURRENT_SOURCE_DIR}/${MODEL_NAME}/FMI${FMI_VERSION}${FMI_TYPE}.xml
       "${FMU_BUILD_DIR}/modelDescription.xml"
   )
   
   set(ARCHIVE_FILES "modelDescription.xml" "binaries")
   if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${MODEL_NAME}/resources")
       add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory
           "${CMAKE_CURRENT_SOURCE_DIR}/${MODEL_NAME}/resources"
           "${FMU_BUILD_DIR}/resources/"
       )
       set(ARCHIVE_FILES ${ARCHIVE_FILES} "resources")
   endif()

   # When windows robocopy command (using cmd.exe) is used to copy files its important to set the dependencies
   # to assure that the copy command is finished before the next custom action to avoid copy/file access failures

   # Copy sdv binaries of this FMU 
   set(DEST_DIR "${FMU_BUILD_DIR}/resources")
   set(SOURCE_DIR_EXAMPLES_BIN "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
   add_custom_target(copy_function_sdv_files_${TARGET_NAME} DEPENDS ${TARGET_NAME}) 
   add_custom_command(TARGET copy_function_sdv_files_${TARGET_NAME} 
       COMMAND cmd /C "robocopy \"${SOURCE_DIR_EXAMPLES_BIN}\" \"${DEST_DIR}\" *.pdb *.sdv /NP /R:3 /W:5 || exit /b 0"
       COMMENT "Copying contents from ${SOURCE_DIR_EXAMPLES_BIN} to ${DEST_DIR}, include only *.pdb *.sdv files"
   )  
   add_dependencies(copy_function_sdv_files_${TARGET_NAME} ${TARGET_NAME})



   # Copy framework sdv binaries
   set(SOURCE_DIR_CORE_BIN "${SDV_FRAMEWORK_RUNTIME}")
   add_custom_target(copy_framework_sdv_files_${TARGET_NAME} DEPENDS copy_function_sdv_files_${TARGET_NAME}) 
   add_custom_command(TARGET copy_framework_sdv_files_${TARGET_NAME}
       COMMAND cmd /C "robocopy \"${SOURCE_DIR_CORE_BIN}\" \"${DEST_DIR}\" *.pdb *.sdv /NP /R:3 /W:5 || exit /b 0"
       COMMENT "Copying contents from ${SOURCE_DIR_CORE_BIN} to ${DEST_DIR}, include only *.pdb *.sdv files"
   )  
   add_dependencies(copy_framework_sdv_files_${TARGET_NAME} copy_function_sdv_files_${TARGET_NAME})



   # FMU content created, all files copied
   # to zip the files create a new target 'create_zip' which is build after all files have been copied
   add_custom_target(create_zip_${TARGET_NAME} ALL DEPENDS copy_framework_sdv_files_${TARGET_NAME} ) 
   add_custom_command(TARGET create_zip_${TARGET_NAME} POST_BUILD
       COMMAND ${CMAKE_COMMAND} -E tar "cfv" ${FMU_FULL_FILE_NAME} --format=zip ${ARCHIVE_FILES}
       WORKING_DIRECTORY ${FMU_BUILD_DIR}
       COMMENT "Creating ZIP ${FMU_FULL_FILE_NAME}"
   )  
   add_dependencies(create_zip_${TARGET_NAME} copy_framework_sdv_files_${TARGET_NAME}) 

#TODO
   #add_dependencies(${TARGET_NAME} <add_cmake_target_this_depends_on>)
endif ()

)code";


/**
*@brief Config Header template. File 'config.h'. Code chunks are inserted at the keywords surrounded by %%.
*/
const char szConfigHTemplate[] = R"code(/**
* @file %configH_path%
* @date %date%
* This file defines the data link object between CAN and the V-API devices.
* This file was generated by the DBC utility from:
*   %dbc_sources%
*   %dbc_version%
*/
#ifndef %safeguardconfig%
#define %safeguardconfig%

#include <stdint.h>

// define class name and unique id
#define MODEL_IDENTIFIER %model_Identifier%
#define INSTANTIATION_TOKEN "%model_guid%"
#define FMI_VERSION 2

#define CO_SIMULATION

// define model size
#define NX 0
#define NZ 0

#define GET_INT32
#define SET_INT32
#define GET_FLAOT64
#define SET_FLOAT64
#define EVENT_UPDATE
#define CLEAN_UP

#define FIXED_SOLVER_STEP 0.04
#define DEFAULT_STOP_TIME 10

typedef enum {
%value_reference%

} ValueReference;

typedef struct {
%model_data%

} ModelData;

#endif // !defined %safeguardconfig%

)code";


/**
*@brief FMI2 xml template. File 'FMI2.XML'. Code chunks are inserted at the keywords surrounded by %%.
*/
const char szFMI2XMLTemplate[] = R"code(<?xml version="1.0" encoding="UTF-8"?>
<fmiModelDescription
 fmiVersion                = "2.0"
 modelName                 = "%model_Identifier%"
 guid                      = "%model_guid%"
 description               = "TargetLink FMU for %model_Identifier%"
 generationTool            = "TargetLink was generated by the DBC utility: %dbc_sources%"
 generationDateAndTime     = "%date%"
 variableNamingConvention  = "structured"
 numberOfEventIndicators   = "0">
 <CoSimulation
     modelIdentifier                         = "%model_Identifier%"
     canHandleVariableCommunicationStepSize  = "false"
     canGetAndSetFMUstate                    = "false"
     canSerializeFMUstate                    = "false"
     providesDirectionalDerivative           = "false"
     canBeInstantiatedOnlyOncePerProcess     = "true"
     canInterpolateInputs                    = "false"
     canRunAsynchronuously                   = "false">
   <SourceFiles>
     <File name="all.c"/>
   </SourceFiles>
 </CoSimulation>
 <DefaultExperiment startTime="0" stepSize="0.01"/>
 <ModelVariables>
%model_variables%
 </ModelVariables>
 <ModelStructure>
   <Outputs>
     <Unknown index="%unknown_index%"/>
   </Outputs>
 </ModelStructure>
</fmiModelDescription>
)code";


/**
*@brief Model cpp template. File 'model.cpp'. Code chunks are inserted at the keywords surrounded by %%.
*/
const char szModelCPPTemplate[] = R"code(/**
* @file %modelcpp_path%
* @date %date%
* This file defines the data link object between CAN and the V-API devices.
* This file was generated by the DBC utility from:
*   %dbc_sources%
*   %dbc_version%
*/

#include <memory>
#include <vector>
#include <iostream>
#include <fstream>
#include <support/timer.h>
#include "signal_identifier.h"
#include <support/signal_support.h>
#include <support/app_control.h>

%global_signals%
// in case the simulation timer should be used
sdv::core::ITimerSimulationStep* g_pTimerSimulationStep;

std::unique_ptr<sdv::app::CAppControl> g_appcontrol;

bool InitializeAppControl(const std::string& resource, const std::string& configFileName)
{
    auto bResult = g_appcontrol->AddModuleSearchDir( resource );
    bResult &= g_appcontrol->Startup("");
    g_appcontrol->SetConfigMode();
    bResult &= g_appcontrol->AddConfigSearchDir( resource );

    if (!configFileName.empty())
    {
        bResult &= g_appcontrol->LoadConfig(configFileName.c_str()) == sdv::core::EConfigProcessResult::successful;
    }

    return bResult;
}

sdv::core::EConfigProcessResult RegisterAllSignals()
{
    std::string msg = "register all signals: ";
    sdv::core::CDispatchService dispatch;

%global_signals_register%
%global_signals_register_check%

    return sdv::core::EConfigProcessResult::failed;
}

bool ResetAllSignals()
{
    sdv::core::CDispatchService dispatch;

%global_signals_reset%
    SDV_LOG_INFO("Reset signals");
    return true;
}


bool CreateCoreServiceTomlFile(const std::string& resources)
{
    std::ofstream tomlFile("sdv_core_reloc.toml");
    if (tomlFile.is_open())
    {
        tomlFile << R"toml(# Location of the SDV binaries and configuration files
[CoreLocation]
Version = 100
Framework = ")toml";
        tomlFile << resources;
        tomlFile << "\"\n";
        tomlFile.close();
        return true;
    }
    return false;
}


bool OpenAPILoad(const std::string& resources)
{
    bool success = CreateCoreServiceTomlFile(resources);
    g_appcontrol =  std::make_unique<sdv::app::CAppControl> ();

//
// TODO: Dispatch service must be loaded first, adjust the correct toml file
//
    success &= InitializeAppControl(resources, "data_dispatch_config_file.toml");
    if (!success)
    {
        std::cout << "Error: InitializeAppControl() failed" << std::endl;
        SDV_LOG_ERROR("Failed InitializeAppControl");
        return false;
    }
    success &= RegisterAllSignals() == sdv::core::EConfigProcessResult::successful;
    if (!success)
    {
        SDV_LOG_ERROR("Signals could not be registered");
    }
%vapi_load_config_files%
    g_appcontrol->SetRunningMode();
    return success;
}

void OpenAPIShutdown()
{
    ResetAllSignals();
}
#ifdef __cplusplus
extern "C" {
#endif

#include <float.h>  // for DBL_EPSILON
#include <math.h>   // for fabs()
#include "config.h"
#include "model.h"

Status cleanup(ModelInstance*)
{
    SDV_LOG_INFO("Shutting down...");
    OpenAPIShutdown();
    return OK;
}


bool setStartValues(ModelInstance* comp)
{
    std::string path(comp->resourceLocation);
    std::string resourcePath = path.substr(8);
    std::replace(resourcePath.begin(), resourcePath.end(), '\\', '/');
    if (!OpenAPILoad(resourcePath))
    {
        std::cout << "Error: OpenAPILoad() failed." << std::endl;
        comp->terminateSimulation = true;
        return false;
     }

     // TODO: move this to initialize()?
     comp->nextEventTime = 0;
     comp->nextEventTimeDefined = true;
     return true;
}

Status calculateValues(ModelInstance* comp)
{
    UNUSED(comp);
    // nothing to do
    return OK;
}

Status getFloat64(ModelInstance* comp, [[maybe_unused]] ValueReference vr, [[maybe_unused]] double values[], [[maybe_unused]] size_t nValues, [[maybe_unused]] size_t* index)
{
    ASSERT_NVALUES(1);
%getFloat64%
    return OK;
}

Status getInt32(ModelInstance* comp, [[maybe_unused]] ValueReference vr, [[maybe_unused]] int32_t values[], [[maybe_unused]] size_t nValues, [[maybe_unused]] size_t* index)
{
    ASSERT_NVALUES(1);
%getInt32%
    return OK;
}

Status setFloat64(ModelInstance* comp, [[maybe_unused]] ValueReference vr, [[maybe_unused]] const double values[], [[maybe_unused]] size_t nValues, [[maybe_unused]] size_t* index)
{
    ASSERT_NVALUES(1);
%setFloat64%
    return OK;
}

Status setInt32(ModelInstance* comp, [[maybe_unused]] ValueReference vr, [[maybe_unused]] const int32_t values[], [[maybe_unused]] size_t nValues, [[maybe_unused]] size_t* index)
{
    ASSERT_NVALUES(1);
%setInt32%
    return OK;
}

void eventUpdate(ModelInstance* comp)
{
%event_update%
    double epsilon = (1.0 + fabs(comp->time)) * DBL_EPSILON;

    if (comp->nextEventTimeDefined && comp->time + epsilon >= comp->nextEventTime) {
        comp->nextEventTime += FIXED_SOLVER_STEP;
    }

    comp->valuesOfContinuousStatesChanged = false;
    comp->nominalsOfContinuousStatesChanged = false;
    comp->terminateSimulation = false;
    comp->nextEventTimeDefined = true;
}

#ifdef __cplusplus
} // end of extern "C"
#endif

)code";


/**
*@brief Signal name mapping template. Signal names contain the can names of the signal which must match the names of the vehicle devices. 
* File 'signal_identifier.h'. Code chunks are inserted at the keywords surrounded by %%.
*/
const char szMappingSignalIdentifierTemplate[] = R"code(/**
@file %signalidentifierfile_path%
@date %date%
This file is the signal identifier header.
This file was generated by the DBC utility from:
  %dbc_sources%
  %dbc_version%
*/
#ifndef %safeguardsignalidentifier%
#define %safeguardsignalidentifier%

namespace %object_prefix%
{
   // Data Dispatch Service  signal names  to  dbc variable names                C-type   RX/TX  vss name space
%signals%
} // %object_prefix%

#endif // %safeguardsignalidentifier%
)code";


/**
*@brief Data dispatch service config template. File 'data_dispatch_config_file.toml'. Code chunks are inserted at the keywords surrounded by %%.
*/
const char szDataDispatchServiceTomlFile[] = R"code([Configuration]
Version = 100

[[Component]]
Path = "data_dispatch_service.sdv"
Class = "DataDispatchService"
)code";

/**
*@brief Simulation task timer service config template. File 'simulation_task_timer_config_file.toml'. Code chunks are inserted at the keywords surrounded by %%.
*/
const char szSimulationTaskTimerServiceTomlFile[] = R"code([Configuration]
Version = 100

[[Component]]
Path = "simulation_task_timer.sdv"
Class = "SimulationTaskTimerService"
)code";

/**
*@brief Task timer service config template. File 'task_timer_config_file.toml'. Code chunks are inserted at the keywords surrounded by %%.
*/
const char szTaskTimerhServiceTomlFile[] = R"code([Configuration]
Version = 100

[[Component]]
Path = "task_timer.sdv"
Class = "TaskTimerService"
)code";

