// DSRemoteCppExample.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include "DSRemoteConnect.h"
#include <string>
#include <vector>

// Report a DX_ERROR_CODE and pass it through, so calls stay one-liners.
static int check(int errCode, const char* what)
{
    if (errCode < 0)
        std::cout << what << " failed, errCode: " << errCode << "\n";
    return errCode;
}

// dsconChannelGetNameUtf8 reports the byte count it needs, so a name of any
// length can be fetched without guessing a buffer size or risking a truncated
// multi-byte character. dsconChannelGetName has neither property.
static std::string channelName(ChannelInstanceHandle channel)
{
    char buffer[64];
    size_t required = 0;

    if (dsconChannelGetNameUtf8(channel, buffer, sizeof(buffer), &required) == DSX_NO_ERROR)
        return std::string(buffer);

    // Name did not fit; `required` includes the NUL terminator.
    std::vector<char> big(required);
    if (dsconChannelGetNameUtf8(channel, big.data(), big.size(), nullptr) == DSX_NO_ERROR)
        return std::string(big.data());

    return std::string();
}

static const char* stateName(int state)
{
    switch (state)
    {
    case DSX_STATE_CHANNEL_SETUP:       return "Channel setup";
    case DSX_STATE_ACQUISITION:         return "Acquisition running";
    case DSX_STATE_ACQUISITION_STOPPED: return "Acquisition stopped";
    case DSX_STATE_ANALYSIS:            return "Analysis";
    default:                            return "Unknown";
    }
}

// dsconGetState reports which screen DewesoftX is on. Over NET it parses the
// GETSTATUS reply, which DewesoftX translates, so a non-English UI reports Unknown.
static void printState(DSXInstanceHandle instance, const char* when)
{
    int state = DSX_STATE_UNKNOWN;
    if (check(dsconGetState(instance, &state), "dsconGetState") == DSX_NO_ERROR)
        std::cout << "State " << when << ": " << stateName(state) << " (" << state << ")\n";
}

int main(int argc, char* argv[])
{
    std::string connectString;
    std::string loadSetupPath;

    if (argc >= 2)
        connectString = argv[1];

    if (argc == 3)
        loadSetupPath = argv[2];

    // argc is at least 1 for a normally launched process, so a default has to
    // key off an empty string, not off argc.
    if (connectString.empty())
        connectString = "localhost:8999:8001";


    
    DSXInstanceHandle dsconInstance = nullptr;
    if (check(dsconCreateInstance(&dsconInstance, DSX_CONNECTION_TYPE_NET), "dsconCreateInstance") != DSX_NO_ERROR)
        return 1;

    // Every call below assumes a live connection, so stop here if it failed
    // rather than dereferencing a half-built instance.
    if (check(dsconConnect(dsconInstance, &connectString[0]), "dsconConnect") != DSX_NO_ERROR)
    {
        std::cout << "Could not connect to " << connectString
                  << ". Is DewesoftX running with the NET module enabled?" << std::endl;
        dsconDestroyInstance(dsconInstance);
        return 1;
    }

    try
    {
        dsconSetAppWindowVisible(dsconInstance, true);
        
        if (!loadSetupPath.empty())
            check(dsconLoadSetup(dsconInstance, &loadSetupPath[0]), "dsconLoadSetup");

        size_t numOfChannels;
        dsconGetChannelCount(dsconInstance, &numOfChannels);
        std::cout << "Number of channels: " << std::to_string(numOfChannels).c_str() << "\n";

        dsconInitChannelIdEnum(dsconInstance);

        int indexOfCh = 0;
        std::vector<ChannelInstanceHandle> chInstanceHandleVector;
        std::vector<ChannelInstanceHandle> controlChannels;

        auto addChannel = [&](char* id) {
            ChannelInstanceHandle chInstanceHandle = nullptr;
            if (check(dsconCreateChannelInstance(dsconInstance, id, &chInstanceHandle),
                      "dsconCreateChannelInstance") != DSX_NO_ERROR)
                return;
            chInstanceHandleVector.push_back(chInstanceHandle);

            std::string name = channelName(chInstanceHandle);
            if (name == "AI 1")
                indexOfCh = static_cast<int>(chInstanceHandleVector.size()) - 1;

            // Control channels accept values written back into DewesoftX.
            bool isControl = false;
            if (dsconIsChannelControl(chInstanceHandle, &isControl) == DSX_NO_ERROR && isControl)
                controlChannels.push_back(chInstanceHandle);

            std::cout << name << (isControl ? " [control]" : "") << std::endl;
        };

        // dsconGetNextChannelId does not bound check, so size the buffer for each
        // id rather than trusting the first one's length for all of them.
        int sizeOfChID = 0;
        std::vector<char> idStr;
        while (dsconGetNextChannelIdSize(dsconInstance, &sizeOfChID) == DSX_NO_ERROR)
        {
            if (idStr.size() < static_cast<size_t>(sizeOfChID))
                idStr.assign(sizeOfChID, 0);

            // The last channel is reported together with DSX_END_OF_ENUM, so use
            // the id first and only then decide whether to keep going.
            int rc = dsconGetNextChannelId(dsconInstance, idStr.data());
            if (rc != DSX_NO_ERROR && rc != DSX_END_OF_ENUM)
                break;

            addChannel(idStr.data());

            if (rc == DSX_END_OF_ENUM)
                break;
        }

        std::cout << "Control channels found: " << controlChannels.size() << "\n";

        // Looking a channel up by its UTF-8 name. The console below prints raw
        // UTF-8 bytes, so run `chcp 65001` first if a name has non-ASCII text.
        ChannelInstanceHandle byName = nullptr;
        if (dsconCreateChannelInstanceByNameUtf8(dsconInstance, u8"AI 1", &byName) == DSX_NO_ERROR)
        {
            std::cout << "Found by UTF-8 name: " << channelName(byName) << "\n";
            dsconFreeChannelInstance(byName);
        }
        else
        {
            std::cout << "No channel named \"AI 1\"\n";
        }

        // Before starting, DewesoftX sits on the Channel setup screen.
        printState(dsconInstance, "before start");

        check(dsconStartMeasurement(dsconInstance), "dsconStartMeasurement");
        printState(dsconInstance, "after start");

        // Storing to a named data file. The name is resolved against DewesoftX's
        // own data directory, so pass a bare file name and no extension.
        // DewesoftX starts acquisition itself if it is not already running.
        if (check(dsconStartStoring(dsconInstance, "DSRemoteCppExample_run"), "dsconStartStoring") == DSX_NO_ERROR)
            std::cout << "Storing to DSRemoteCppExample_run\n";

        double* dataArray = new double[5000];
        double* timeArray = new double[5000];

        size_t countData = 5000;
        for (int loop = 0; loop < 20; loop++)
        {
            countData = 5000;
            dsconChannelReadScalarData_2(chInstanceHandleVector[indexOfCh], dataArray, timeArray, &countData);
            if(countData > 0){
                for (size_t i = 0; i < countData; i++)
                    std::cout << i << "; " << timeArray[i] << "; " << dataArray[i] << "\n";
            }

            // Writing to a control channel: one double per call, applied immediately.
            for (ChannelInstanceHandle control : controlChannels)
                check(dsconControlChannelWriteData(control, static_cast<double>(loop)), "dsconControlChannelWriteData");
        }

        // Closes the data file. Whether acquisition survives is DewesoftX's call
        // - its StopAcqAfterStopStore setting and active cameras both turn this
        // into a full stop, and neither is readable remotely. The Ex form reports
        // what actually happened instead of guessing.
        int stateAfterStop = DSX_STATE_UNKNOWN;
        check(dsconStopStoringEx(dsconInstance, &stateAfterStop), "dsconStopStoringEx");
        std::cout << "After stop storing: " << stateName(stateAfterStop)
                  << (stateAfterStop == DSX_STATE_ACQUISITION
                          ? " - could store again without restarting\n"
                          : " - measurement would need restarting\n");

        check(dsconStopMeasurement(dsconInstance), "dsconStopMeasurement");

        // After Stop, DewesoftX stays in Measure mode with the run stopped.
        printState(dsconInstance, "after stop");

        for (ChannelInstanceHandle ch : chInstanceHandleVector)
            dsconFreeChannelInstance(ch);

        dsconDisconnect(dsconInstance);
        dsconDestroyInstance(dsconInstance);
    }
    catch (const std::exception&)
    {
        dsconDisconnect(dsconInstance);
        dsconDestroyInstance(dsconInstance);
    }
}
