////////////////////////////////////////////////////////////////////////////////
/// Copyright (c) 2010 - 2026 by Tech Soft 3D, Inc.
///
/// The information contained herein is confidential and proprietary to
/// Tech Soft 3D, Inc., and considered a trade secret as defined under civil
/// and criminal statutes. Tech Soft 3D shall pursue its civil and criminal
/// remedies in the event of unauthorized use or misappropriation of its trade
/// secrets. Use of this information by anyone other than authorized employees
/// of Tech Soft 3D, Inc. is granted only under a written non-disclosure
/// agreement, expressly prescribing the scope and manner of such use.
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// \example ImportExport
/// CAD file format converter using HOOPS Exchange.
///
/// 1. initialize_exchange() - Load library and apply license
/// 2. import_cad_file() - Read CAD file into A3DAsmModelFile
/// 3. export_cad_file() - Write to output format
/// 4. terminate_exchange() - Cleanup
////////////////////////////////////////////////////////////////////////////////

#define INITIALIZE_A3D_API
#include <A3DSDKIncludes.h>
#include <hoops_license.h>

// Sample tools
#include <Sample/ArgParser.hpp>
#include <Sample/Defaults.h>
#include <Sample/ExchangeAPI.hpp>  // operator<< for A3DStatus, used by REQUIRE
#include <Sample/Logging.hpp>
#include <Sample/Main.h>
#include <Sample/Require.hpp>

// Standard libraries
#include <algorithm>      // For get_file_format()
#include <string>         // std::string
#include <unordered_map>  // For get_file_format()

////////////////////////////////////////////////////////////////////////////////
/// Load Exchange library and initialize.
///
/// Uses:
/// - A3DDllInitialize
/// - A3DLicPutUnifiedLicense
/// - A3DSDKLoadLibraryA
/// - A3DDllSetCallbacksReport
static void initialize_exchange(void) {
    const A3DBool loaded = A3DSDKLoadLibraryA(HOOPS_BINARY_DIRECTORY);
    REQUIRE(loaded);

    A3DStatus status = A3DLicPutUnifiedLicense(HOOPS_LICENSE);
    REQUIRE(status == A3D_SUCCESS);

    status = A3DDllInitialize(A3D_DLL_MAJORVERSION, A3D_DLL_MINORVERSION);
    REQUIRE(status == A3D_SUCCESS);

    status = A3DDllSetCallbacksReport(Sample::PrintMessage, Sample::PrintWarning, Sample::PrintError);
    REQUIRE(status == A3D_SUCCESS);
}
/// End of initialize_exchange()

////////////////////////////////////////////////////////////////////////////////
/// Import a CAD file.
///
/// \param file_path Input file path.
/// \return Loaded model file handle.
///
/// Uses:
/// - A3DAsmModelFileLoadFromFile
/// - A3DRWParamsGeneralData
/// - A3DRWParamsLoadData
/// - A3DRWParamsTessellationData
static A3DAsmModelFile* import_cad_file(const std::string& file_path) {
    A3DAsmModelFile* model_file = A3D_NULL_HANDLE;

    // Configure import parameters
    A3DRWParamsLoadData read_params                          = A3D_MAKE_DATA(A3DRWParamsLoadData);
    read_params.m_sGeneral.m_bReadSolids                     = A3D_TRUE;
    read_params.m_sGeneral.m_bReadSurfaces                   = A3D_TRUE;
    read_params.m_sGeneral.m_bReadWireframes                 = A3D_TRUE;
    read_params.m_sGeneral.m_bReadPmis                       = A3D_TRUE;
    read_params.m_sGeneral.m_bReadAttributes                 = A3D_TRUE;
    read_params.m_sGeneral.m_bReadHiddenObjects              = A3D_TRUE;
    read_params.m_sGeneral.m_bReadConstructionAndReferences  = A3D_FALSE;
    read_params.m_sGeneral.m_bReadActiveFilter               = A3D_TRUE;
    read_params.m_sGeneral.m_eReadingMode2D3D                = kA3DRead_3D;
    read_params.m_sGeneral.m_eReadGeomTessMode               = kA3DReadGeomAndTess;
    read_params.m_sGeneral.m_eDefaultUnit                    = kA3DUnitUnknown;
    read_params.m_sGeneral.m_dLongWireframeThreshold         = 24690.0;
    read_params.m_sTessellation.m_eTessellationLevelOfDetail = kA3DTessLODMedium;
    read_params.m_sAssembly.m_bUseRootDirectory              = A3D_TRUE;
    read_params.m_sMultiEntries.m_bLoadDefault               = A3D_TRUE;
    read_params.m_sPmi.m_bAlwaysSubstituteFont               = A3D_FALSE;
    read_params.m_sPmi.m_pcSubstitutionFont                  = const_cast<char*>("Myriad CAD");

    // Load the file
    const A3DStatus status = A3DAsmModelFileLoadFromFile(
        file_path.c_str(),
        &read_params,
        &model_file
    );

    REQUIRE_ANY_OF(status, A3D_SUCCESS, A3D_LOAD_MISSING_COMPONENTS);

    return model_file;
}
// End of import_cad_file()

////////////////////////////////////////////////////////////////////////////////
/// Map file extension to A3DEModellerType
static A3DEModellerType get_file_format(const std::string& filepath);

////////////////////////////////////////////////////////////////////////////////
/// Export model to file (format determined by extension).
///
/// \param model_file Model to export.
/// \param file_path Output file path.
///
/// Uses:
/// - A3DAsmModelFileExportTo*File
/// - A3DRWParamsExport*Data
static void export_cad_file(A3DAsmModelFile* model_file, const std::string& file_path) {
    A3DStatus export_status       = A3D_SUCCESS;
    const char* const c_file_path = file_path.c_str();
    const A3DEModellerType type   = get_file_format(file_path);

    // Each format has its own export params and function
    switch (type) {

        case kA3DModellerIges: {
            A3DRWParamsExportIgesData export_data = A3D_MAKE_DATA(A3DRWParamsExportIgesData);
            export_status = A3DAsmModelFileExportToIgesFile(model_file, &export_data, c_file_path);
            break;
        }

        case kA3DModellerStep: {
            A3DRWParamsExportStepData export_data = A3D_MAKE_DATA(A3DRWParamsExportStepData);
            export_status = A3DAsmModelFileExportToStepFile(model_file, &export_data, c_file_path);
            break;
        }

        case kA3DModellerJt: {
            A3DRWParamsExportJTData export_data = A3D_MAKE_DATA(A3DRWParamsExportJTData);
            export_data.m_eWriteGeomTessMode    = kA3DWriteGeomAndTess;
            export_data.m_bWritePMI             = A3D_TRUE;
            export_status = A3DAsmModelFileExportToJTFile(model_file, &export_data, c_file_path);
            break;
        }

        case kA3DModellerWrl: {
            export_status = A3DAsmModelFileExportToVrmlFile(model_file, c_file_path);
            break;
        }

        case kA3DModellerXML: {
            A3DRWParamsExportXMLData export_data            = A3D_MAKE_DATA(A3DRWParamsExportXMLData);
            export_data.m_bExportMetadata                   = A3D_TRUE;
            export_data.m_bExportTransformations            = A3D_TRUE;
            export_data.m_bExportColorMaterial              = A3D_TRUE;
            export_data.m_bExportProductInformationByFormat = A3D_FALSE;
            export_status = A3DAsmModelFileExportToXMLFile(model_file, &export_data, c_file_path,NULL,NULL,NULL);
            break;
        }

        case kA3DModeller3mf: {
            A3DRWParamsExport3mfData export_data = A3D_MAKE_DATA(A3DRWParamsExport3mfData);
            export_status = A3DAsmModelFileExportTo3mfFile(model_file, &export_data, c_file_path);
            break;
        }

        case kA3DModellerU3D: {
            A3DRWParamsExportU3DData export_data = A3D_MAKE_DATA(A3DRWParamsExportU3DData);
            export_data.m_eU3DVersion            = kA3DECMA3;
            export_status = A3DAsmModelFileExportToU3DFile(model_file, &export_data, c_file_path);
            break;
        }

        case kA3DModellerPrc: {
            A3DRWParamsExportPrcData export_data = A3D_MAKE_DATA(A3DRWParamsExportPrcData);
            export_data.m_eCompressBrepType      = kA3DCompressionMedium;
            export_status = A3DAsmModelFileExportToPrcFile(model_file, &export_data, c_file_path,NULL);
            break;
        }

        case kA3DModellerOBJ: {
            A3DRWParamsExportObjData export_data = A3D_MAKE_DATA(A3DRWParamsExportObjData);
            export_status = A3DAsmModelFileExportToObjFile(model_file, &export_data, c_file_path);
            break;
        }

        case kA3DModellerFBX: {
            A3DRWParamsExportFbxData export_data = A3D_MAKE_DATA(A3DRWParamsExportFbxData);
            export_status = A3DAsmModelFileExportToFbxFile(model_file, &export_data, c_file_path);
            break;
        }

        case kA3DModellerGltf: {
            A3DRWParamsExportGltfData export_data = A3D_MAKE_DATA(A3DRWParamsExportGltfData);
            export_status = A3DAsmModelFileExportToGltfFile(model_file, &export_data, c_file_path);
            break;
        }
#if defined HOOPS_EXCHANGE
        case kA3DModellerParasolid: {
            A3DRWParamsExportParasolidData export_data = A3D_MAKE_DATA(A3DRWParamsExportParasolidData);
            export_status = A3DAsmModelFileExportToParasolidFile(model_file, &export_data, c_file_path);
            break;
        }

        case kA3DModellerAcis: {
            A3DRWParamsExportAcisData export_data = A3D_MAKE_DATA(A3DRWParamsExportAcisData);
            export_status = A3DAsmModelFileExportToAcisFile(model_file, &export_data, c_file_path);
            break;
        }
#endif

#if defined _MSC_VER || defined __linux__
#ifndef TS_PUBLISH_DISABLED
        case kA3DModeller3dHtml: {
            A3DRWParamsExportHtmlData export_data        = A3D_MAKE_DATA(A3DRWParamsExportHtmlData);
            export_data.m_bIncludeMeasurementInformation = A3D_TRUE;
            export_status = A3DAsmModelFileExportToHTMLFile(model_file, &export_data, c_file_path);
            break;
        }
#endif
#endif

        case kA3DModellerStl: {
            A3DRWParamsExportStlData export_data     = A3D_MAKE_DATA(A3DRWParamsExportStlData);
            export_data.m_eTessellationLevelOfDetail = kA3DTessLODMedium;
            export_status = A3DAsmModelFileExportToStlFile(model_file, &export_data, c_file_path);
            break;
        }

#if defined (A3DAPI_EXPERIMENTAL) || defined (A3DAPI_EXPERIMENTAL_WITHOUT_WARNING)
        case experimental_kA3DModellerUSD: {
            A3DRWParamsExportUSDData export_data = A3D_MAKE_DATA(A3DRWParamsExportUSDData);
            export_status = A3DAsmModelFileExportToUSDFile(model_file, &export_data, c_file_path);
            break;
        }
#endif

        default : {
            export_status = A3D_WRITE_WRITER_NOT_IMPLEMENTED;
            break;
        }
    }

    REQUIRE(export_status == A3D_SUCCESS);
}
// End of export_cad_file()

////////////////////////////////////////////////////////////////////////////////
/// Cleanup and unload Exchange library.
///
/// Uses:
/// - A3DDllTerminate
/// - A3DSDKUnloadLibrary
static void terminate_exchange(void) {
    A3DStatus status = A3DDllTerminate();
    REQUIRE(status == A3D_SUCCESS);

    A3DSDKUnloadLibrary();
}
// End of terminate_exchange()


////////////////////////////////////////////////////////////////////////////////
/// Function entry point
/// - Parses and set arguments
/// - calls import and export
int main(int argc, char* argv[])
{
    Sample::Arguments args;

    if(!Sample::ParseArguments(argc, argv, args)) {
        return EXIT_FAILURE;
    }

    Sample::StartLogger(args.verbose, args.log_path);

    initialize_exchange();

    A3DAsmModelFile* model_file = import_cad_file(args.input_path);
    export_cad_file(model_file, args.output_path);

    const A3DStatus model_file_deleted = A3DAsmModelFileDelete(model_file);
    REQUIRE(model_file_deleted == A3D_SUCCESS);

    terminate_exchange();

    return EXIT_SUCCESS;
}
// End of main()

static A3DEModellerType get_file_format(const std::string& filepath) {
    auto pos = filepath.find_last_of('.');
    if (pos == std::string::npos || pos + 1 >= filepath.size()) {
        return kA3DModellerUnknown;
    }

    std::string ext = filepath.substr(pos + 1);
    std::transform(ext.begin(), ext.end(), ext.begin(), [](char c) {
        return static_cast<char>(std::toupper(c));
    });

    static const std::unordered_map<std::string, A3DEModellerType> map = {
        {"IGS",     kA3DModellerIges},             {"IGES", kA3DModellerIges},
        {"STP",     kA3DModellerStep},             {"STEP", kA3DModellerStep},
        {"STPZ",    kA3DModellerStep},             {"U3D",  kA3DModellerU3D},
        {"X_T",     kA3DModellerParasolid},        {"X_B",  kA3DModellerParasolid},
        {"XMT_TXT", kA3DModellerParasolid},        {"PRC",  kA3DModellerPrc},
        {"PDF",     kA3DModellerPDF},              {"JT",   kA3DModellerJt},
        {"WRL",     kA3DModellerWrl},              {"VRML", kA3DModellerWrl},
        {"STL",     kA3DModellerStl},              {"SAT",  kA3DModellerAcis},
        {"SAB",     kA3DModellerAcis},             {"XML",  kA3DModellerXML},
        {"3MF",     kA3DModeller3mf},              {"HTML", kA3DModeller3dHtml},
        {"OBJ",     kA3DModellerOBJ},
        {"FBX",     kA3DModellerFBX},              {"GLTF", kA3DModellerGltf},
        {"GLB",     kA3DModellerGltf},             {"IFC",  kA3DModellerIFC},
#if defined (A3DAPI_EXPERIMENTAL) || defined (A3DAPI_EXPERIMENTAL_WITHOUT_WARNING)
        {"USD",     experimental_kA3DModellerUSD}, {"USDA", experimental_kA3DModellerUSD},
#endif
    };

    auto it = map.find(ext);
    if (it != map.end()) {
        return it->second;
    } else {
        return kA3DModellerUnknown;
    }
}
