/*
  This is the reference example application code for XIMEA cameras.
  You can use it to simplify development of your camera application.

  Sample name:
    Gentl / capture-10-images-to-file-via-gentl

  Description:
    Minimal GenTL reference sample - discover camera via GenTL producer, capture 10 images and save to TIFF files.

  Workflow:
    1: Load GenTL producer (.cti)
    2: Discover camera interface and device
    3: Open device and read resolution via register map
    4: Announce and queue buffers
    5: Acquire 10 images
    6: Save each image as image_XX.tiff
*/
#include <iostream>
#include <cstdint>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include "GenTL_v1_5.h"
#include <tiffio.h>

#ifdef _WIN32
#include <windows.h>
#include <malloc.h>
typedef HMODULE LIB_HANDLE;
#else
#include <dlfcn.h>
typedef void* LIB_HANDLE;
#endif

#ifdef _MSC_VER
#define snprintf _snprintf
#endif

using namespace GenTL;

#define FGenTL_V2_AcquisitionStart  0x200310
#define FGenTL_V2_AcquisitionStop   0x200320

#define NUM_BUFFERS   10
#define NUM_IMAGES    10
#define BUFFER_ALIGN  4096
#define IMG_TIMEOUT   2000

// --- Platform helpers --------------------------------------------------------

#ifdef _WIN32
static LIB_HANDLE LibOpen(const char* p) { return LoadLibraryA(p); }
static void       LibClose(LIB_HANDLE h) { if (h) FreeLibrary(h); }
static void* LibSym(LIB_HANDLE h, const char* n) { return (void*)GetProcAddress(h, n); }
static void* AlignedMalloc(size_t sz, size_t al) { return _aligned_malloc(sz, al); }
static void       AlignedFree(void* p) { _aligned_free(p); }
#else
static LIB_HANDLE LibOpen(const char* p) { return dlopen(p, RTLD_NOW); }
static void       LibClose(LIB_HANDLE h) { if (h) dlclose(h); }
static void* LibSym(LIB_HANDLE h, const char* n) { return dlsym(h, n); }
static void* AlignedMalloc(size_t sz, size_t al) { void* p = nullptr; return posix_memalign(&p, al, sz) ? nullptr : p; }
static void       AlignedFree(void* p) { free(p); }
#endif

// --- GenTL API ------------------------

#define GENTL_FUNCTIONS(X) \
    X(GCInitLib) X(GCCloseLib) X(TLOpen) X(TLClose) \
    X(TLUpdateInterfaceList) X(TLGetNumInterfaces) X(TLGetInterfaceID) \
    X(TLOpenInterface) X(IFClose) X(IFUpdateDeviceList) \
    X(IFGetNumDevices) X(IFGetDeviceID) X(IFOpenDevice) \
    X(DevClose) X(DevGetDataStreamID) X(DevOpenDataStream) \
    X(DevGetPort) X(DSClose) X(DSAnnounceBuffer) X(DSQueueBuffer) \
    X(DSStartAcquisition) X(DSStopAcquisition) \
    X(DSFlushQueue) X(DSRevokeBuffer) X(DSGetBufferInfo) \
    X(GCRegisterEvent) X(GCUnregisterEvent) X(EventGetData) \
    X(GCReadPort) X(GCWritePort)

struct GenTLApi {
#define FIELD(name) decltype(&::GenTL::name) name;
    GENTL_FUNCTIONS(FIELD)
#undef FIELD
};

bool LoadGenTL(const char* path, LIB_HANDLE* h, GenTLApi* api) {
    *h = LibOpen(path);
    if (!*h) return false;
#define RESOLVE(name) \
    api->name = reinterpret_cast<decltype(&::GenTL::name)>(LibSym(*h, #name)); \
    if (!api->name) { std::cerr << "Missing: " #name << std::endl; return false; }
    GENTL_FUNCTIONS(RESOLVE)
#undef RESOLVE
        return true;
}

// --- Helpers -----------------------------------------------------------------

bool SaveTIFF(const char* path, const void* data, uint32_t w, uint32_t h) {
    TIFF* tif = TIFFOpen(path, "w");
    if (!tif) return false;
    TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, w);
    TIFFSetField(tif, TIFFTAG_IMAGELENGTH, h);
    TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
    TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
    TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
    TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
    TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
    TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, h);
    auto* src = (const uint8_t*)data;
    for (uint32_t y = 0; y < h; y++) {
        if (TIFFWriteScanline(tif, (void*)(src + y * w), y) < 0) { TIFFClose(tif); return false; }
    }
    TIFFClose(tif);
    return true;
}

uint32_t ReadReg(GenTLApi& api, PORT_HANDLE port, uint64_t addr) {
    uint32_t val = 0; size_t sz = sizeof(val);
    api.GCReadPort(port, addr, &val, &sz);
    return val;
}

// --- Acquisition -------------------------------------------------------------

bool RunAcquisition(GenTLApi& api, DEV_HANDLE hDev, PORT_HANDLE hPort,
    DS_HANDLE& hStream, EVENT_HANDLE& hEvent,
    BUFFER_HANDLE* hBuf, void** rawBuf,
    int& nAnnounced, bool& acqStarted, int wrapperVer) {
    bool v1 = (wrapperVer == 1);
    uint32_t w = ReadReg(api, hPort, v1 ? 0x102100 : 0x200218);
    uint32_t h = ReadReg(api, hPort, v1 ? 0x102200 : 0x200220);
    uint32_t payloadSize = ReadReg(api, hPort, v1 ? 0x106600 : 0x200660);
    std::cout << "Resolution: " << w << "x" << h << ", payload: " << payloadSize << " B" << std::endl;
    if (payloadSize == 0) { std::cerr << "Payload size is 0." << std::endl; return false; }

    float expTime = 1000.0f; size_t sz = sizeof(float);
    api.GCWritePort(hPort, v1 ? 0x103400 : 0x200340, &expTime, &sz);
    std::cout << "Exposure: " << expTime << " us" << std::endl;

    char sid[256]; sz = sizeof(sid);
    api.DevGetDataStreamID(hDev, 0, sid, &sz);
    if (api.DevOpenDataStream(hDev, sid, &hStream) != GC_ERR_SUCCESS)
    {
        std::cerr << "Failed to open stream." << std::endl; return false;
    }

    for (int i = 0; i < NUM_BUFFERS; i++) {
        rawBuf[i] = AlignedMalloc(payloadSize, BUFFER_ALIGN);
        if (!rawBuf[i] ||
            api.DSAnnounceBuffer(hStream, rawBuf[i], payloadSize, nullptr, &hBuf[i]) != GC_ERR_SUCCESS ||
            api.DSQueueBuffer(hStream, hBuf[i]) != GC_ERR_SUCCESS)
        {
            std::cerr << "Buffer setup failed." << std::endl; return false;
        }
        nAnnounced = i + 1;
    }

    if (api.GCRegisterEvent(hStream, EVENT_NEW_BUFFER, &hEvent) != GC_ERR_SUCCESS)
    {
        std::cerr << "GCRegisterEvent failed." << std::endl; return false;
    }
    if (api.DSStartAcquisition(hStream, ACQ_START_FLAGS_DEFAULT, GENTL_INFINITE) != GC_ERR_SUCCESS)
    {
        std::cerr << "DSStartAcquisition failed." << std::endl; return false;
    }
    acqStarted = true;

    if (!v1) {
        uint32_t acqCmd = 1; size_t cmdSz = sizeof(size_t);
        api.GCWritePort(hPort, FGenTL_V2_AcquisitionStart, &acqCmd, &cmdSz);
    }

    std::cout << "Acquiring " << NUM_IMAGES << " images..." << std::endl;
    for (int i = 0; i < NUM_IMAGES; i++) {
        EVENT_NEW_BUFFER_DATA ev; sz = sizeof(ev);
        if (api.EventGetData(hEvent, &ev, &sz, IMG_TIMEOUT) != GC_ERR_SUCCESS)
        {
            std::cerr << "Timeout on image " << i << std::endl; continue;
        }
        void* ptr = nullptr; INFO_DATATYPE dt; sz = sizeof(ptr);
        api.DSGetBufferInfo(hStream, ev.BufferHandle, BUFFER_INFO_BASE, &dt, &ptr, &sz);
        char fname[32]; snprintf(fname, sizeof(fname), "image_%02d.tiff", i);
        if (ptr) SaveTIFF(fname, ptr, w, h);
        std::cout << (ptr ? "OK " : "FAIL ") << fname << std::endl;
        api.DSQueueBuffer(hStream, ev.BufferHandle);
    }
    return true;
}

// --- Main --------------------------------------------------------------------

int main() {
    LIB_HANDLE hGenTL = nullptr;
    GenTLApi api;
    TL_HANDLE hSystem = nullptr; IF_HANDLE hIface = nullptr;
    DEV_HANDLE hDev = nullptr; DS_HANDLE hStream = nullptr;
    EVENT_HANDLE hEvent = nullptr; PORT_HANDLE hPort = nullptr;
    BUFFER_HANDLE hBuf[NUM_BUFFERS] = {};
    void* rawBuf[NUM_BUFFERS] = {};
    int nAnnounced = 0;
    bool acqStarted = false;

#ifdef _WIN32
    const char* ctiPaths[] = { "C:\\XIMEA\\GenTL Producer\\ximea.gentl2.cti",
                               "C:\\XIMEA\\GenTL Producer\\ximea.gentl.cti" };
#else
    const char* ctiPaths[] = { "/opt/XIMEA/lib/ximea.gentl2.cti",
                               "/opt/XIMEA/lib/ximea.gentl.cti" };
#endif
    const int ctiVer[] = { 2, 1 };
    int wrapperVer = 0;
    bool found = false;

    for (int v = 0; v < 2 && !found; v++) {
        if (!LoadGenTL(ctiPaths[v], &hGenTL, &api)) continue;
        wrapperVer = ctiVer[v];
        if (api.GCInitLib() != GC_ERR_SUCCESS) { LibClose(hGenTL); continue; }
        if (api.TLOpen(&hSystem) != GC_ERR_SUCCESS) { api.GCCloseLib(); LibClose(hGenTL); continue; }
        api.TLUpdateInterfaceList(hSystem, nullptr, 5000);
        uint32_t nIf = 0; api.TLGetNumInterfaces(hSystem, &nIf);
        for (uint32_t i = 0; i < nIf && !found; i++) {
            char id[256]; size_t sz = sizeof(id);
            if (api.TLGetInterfaceID(hSystem, i, id, &sz) != GC_ERR_SUCCESS) continue;
            if (api.TLOpenInterface(hSystem, id, &hIface) != GC_ERR_SUCCESS) continue;
            api.IFUpdateDeviceList(hIface, nullptr, 5000);
            uint32_t nDev = 0; api.IFGetNumDevices(hIface, &nDev);
            if (nDev > 0) { found = true; break; }
            api.IFClose(hIface); hIface = nullptr;
        }
        if (!found) { api.TLClose(hSystem); api.GCCloseLib(); LibClose(hGenTL); hSystem = nullptr; }
    }
    if (!found) { std::cerr << "No devices found." << std::endl; return -1; }
    std::cout << "Using CTI wrapper v" << wrapperVer << std::endl;

    char devID[256]; size_t sz = sizeof(devID);
    api.IFGetDeviceID(hIface, 0, devID, &sz);
    std::cout << "Opening: " << devID << std::endl;

    bool success = false;
    if (api.IFOpenDevice(hIface, devID, DEVICE_ACCESS_EXCLUSIVE, &hDev) != GC_ERR_SUCCESS)
        std::cerr << "Failed to open device." << std::endl;
    else if (api.DevGetPort(hDev, &hPort) != GC_ERR_SUCCESS)
        std::cerr << "DevGetPort failed." << std::endl;
    else
        success = RunAcquisition(api, hDev, hPort, hStream, hEvent, hBuf, rawBuf, nAnnounced, acqStarted, wrapperVer);

    // Cleanup
    if (acqStarted) {
        if (wrapperVer != 1) {
            uint32_t stop = 0; size_t ssz = sizeof(size_t);
            api.GCWritePort(hPort, FGenTL_V2_AcquisitionStop, &stop, &ssz);
        }
        api.DSStopAcquisition(hStream, ACQ_STOP_FLAGS_DEFAULT);
    }
    if (hStream) {
        api.DSFlushQueue(hStream, ACQ_QUEUE_ALL_DISCARD);
        for (int i = 0; i < nAnnounced; i++) {
            api.DSRevokeBuffer(hStream, hBuf[i], nullptr, nullptr);
            AlignedFree(rawBuf[i]);
        }
    }
    if (hEvent) api.GCUnregisterEvent(hStream, EVENT_NEW_BUFFER);
    if (hStream) api.DSClose(hStream);
    if (hDev) api.DevClose(hDev);
    if (hIface) api.IFClose(hIface);
    if (hSystem) api.TLClose(hSystem);
    api.GCCloseLib();
    LibClose(hGenTL);
    std::cout << (success ? "Done." : "Failed.") << std::endl;
    return success ? 0 : -1;
}