#include "cs104_connection.h"
#include "hal_time.h"
#include "hal_thread.h"

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

#if 0
/* Callback handler to log sent or received messages (optional) */
static void rawMessageHandler (void* parameter, uint8_t* msg, int msgSize, bool sent)
{
    if (sent)
        printf("SEND: ");
    else
        printf("RCVD: ");

    int i;
    for (i = 0; i < msgSize; i++) {
        printf("%02x ", msg[i]);
    }

    printf("\n");
}
#endif

static volatile int loopflag;
static void sigint_handler(int signo)
{
    loopflag = 0;
    fprintf(stdout, "received signal: %d\n", signo);
    fflush(stdout);
}

/* Connection event handler */
static void connectionHandler(void* parameter, CS104_Connection connection, CS104_ConnectionEvent event)
{
    switch (event) {
    case CS104_CONNECTION_OPENED:
        printf("Connection established\n");
        break;
    case CS104_CONNECTION_CLOSED:
        printf("Connection closed\n");
        break;
    case CS104_CONNECTION_STARTDT_CON_RECEIVED:
        printf("Received STARTDT_CON\n");
        break;
    case CS104_CONNECTION_STOPDT_CON_RECEIVED:
        printf("Received STOPDT_CON\n");
        break;
    }
}

/*
 * CS101_ASDUReceivedHandler implementation
 *
 * For CS104 the address parameter has to be ignored
 */
static bool asduReceivedHandler (void* parameter, int address, CS101_ASDU asdu)
{
    int idx, numels, oaddr;
    IEC60870_5_TypeID typeid;
    const char * tidstr, * cotstr;

    oaddr = CS101_ASDU_getOA(asdu);
    typeid = CS101_ASDU_getTypeID(asdu);
    tidstr = TypeID_toString(typeid);
    if (tidstr == NULL)
        tidstr = "unknown";

    cotstr = CS101_CauseOfTransmission_toString(CS101_ASDU_getCOT(asdu));
    if (cotstr == NULL)
        cotstr = "unknown";
    numels = CS101_ASDU_getNumberOfElements(asdu);
    fprintf(stdout, "RECVD ASDU, OA: %d, type: %s(%d) elements: %d, COT: %s\n",
        oaddr, tidstr, (int) typeid, numels, cotstr);
    fflush(stdout);

    if (typeid == M_ME_NB_1) {
        for (idx = 0; idx < numels; ++idx) {
            MeasuredValueScaled io = (MeasuredValueScaled)
                CS101_ASDU_getElement(asdu, idx);
            if (io == NULL) {
                fprintf(stderr, "Failed to get value at index: %d\n", idx);
                fflush(stderr);
                continue;
            }
            fprintf(stdout, "IOA[%d], value: %d\n",
                InformationObject_getObjectAddress((InformationObject) io),
                MeasuredValueScaled_getValue(io));
            MeasuredValueScaled_destroy(io);
        }
    } else if (typeid == M_ME_NC_1) {
        for (idx = 0; idx < numels; ++idx) {
            MeasuredValueShort io = (MeasuredValueShort)
                CS101_ASDU_getElement(asdu, idx);
            if (io == NULL) {
                fprintf(stderr, "CS101_ASDU_getElement(%d) has failed!\n", idx);
                fflush(stderr);
                continue;
            }
            fprintf(stdout, "IOA[%d], value: %.04f\n",
                InformationObject_getObjectAddress((InformationObject) io),
                MeasuredValueShort_getValue(io));
            MeasuredValueShort_destroy(io);
        }
    } else if (typeid == M_ME_TE_1) {
        printf("  measured scaled values with CP56Time2a timestamp:\n");
        for (idx = 0; idx < numels; idx++) {
            MeasuredValueScaledWithCP56Time2a io =
                    (MeasuredValueScaledWithCP56Time2a) CS101_ASDU_getElement(asdu, idx);
            printf("    IOA: %i value: %i\n",
                    InformationObject_getObjectAddress((InformationObject) io),
                    MeasuredValueScaled_getValue((MeasuredValueScaled) io)
            );
            MeasuredValueScaledWithCP56Time2a_destroy(io);
        }
    } else if (typeid == M_SP_NA_1) {
        printf("  single point information:\n");
        for (idx = 0; idx < numels; idx++) {
            SinglePointInformation io =
                    (SinglePointInformation) CS101_ASDU_getElement(asdu, idx);
            printf("    IOA: %i value: %i\n",
                    InformationObject_getObjectAddress((InformationObject) io),
                    SinglePointInformation_getValue((SinglePointInformation) io)
            );
            SinglePointInformation_destroy(io);
        }
    } else if (typeid == C_TS_TA_1) {
        printf("  test command with timestamp\n");
    }

    return true;
}

int main(int argc, char** argv)
{
    bool bval;
    int oaddr = 0;
    CS104_APCIParameters apar;
    const char * ip = "localhost";
    uint16_t port = IEC_60870_5_104_DEFAULT_PORT;

    if (argc > 1)
        ip = argv[1];
    if (argc > 2)
        port = atoi(argv[2]);
    if (argc > 3)
        oaddr = (int) strtol(argv[3], NULL, 0);
    signal(SIGINT, sigint_handler);

    loopflag = -1;
    fprintf(stdout, "Connecting to: %s:%d\n", ip, port);
    fflush(stdout);

    CS104_Connection con = CS104_Connection_create(ip, port);
    CS101_AppLayerParameters alParams = CS104_Connection_getAppLayerParameters(con);
    alParams->originatorAddress = oaddr;
    CS104_Connection_setConnectionHandler(con, connectionHandler, NULL);
    CS104_Connection_setASDUReceivedHandler(con, asduReceivedHandler, NULL);

    /* optional bind to local IP address/interface */
    /* if (localIp)
        CS104_Connection_setLocalAddress(con, localIp, localPort); */

    /* uncomment to log messages */
    /* CS104_Connection_setRawMessageHandler(con, rawMessageHandler, NULL); */

    bval = CS104_Connection_connect(con);
    if (!bval) {
        fprintf(stderr, "Failed to connect to IEC104 server: %s:%d\n",
            ip, port);
        fflush(stderr);
        goto err0;
    }
    fprintf(stdout, "Connected: %s:%d\n", ip, port);
    fflush(stdout);

    apar = CS104_Connection_getAPCIParameters(con);
    apar->w = 0;
    apar->t2 = 0;

    CS104_Connection_sendStartDT(con);
    CS104_Connection_sendInterrogationCommand(con, CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);

    while (loopflag)
        Thread_sleep(1000);
    fputs("Application will now exit!\n", stdout);
    fflush(stdout);
err0:
    CS104_Connection_destroy(con);
    return 0;
}
