/******************************************************************
                   CAN slave HW module example
 ******************************************************************
 module simulates getting/setting of industrial process values
 Module just copy changed value of uotput dinfo 
 (it can be ie object 6200:01 (8 bit digital output))
 and copy it with delay to input dinfo 
 (it can be ie object 6000:01 (8 bit digital input))
 (see nascan.eds, nascan.hds)
 ******************************************************************/
// neccessary for getline()
#define _GNU_SOURCE 
 
// pthread.h this should be first include (because of pthread_cancel())
#include <pthread.h> 
#include <unistd.h>
#include <stdio.h>

//#include <vca_hwmod.h>

#include <can_vca.h>
#include <ul_evcbase.h>
#include <sui_event.h>
#include <sui_dinfo.h>
#include <sui_dievc.h>
#include <sui_dtreemem.h>

#define MODULE_ID "nascanhw"

//typedef void* working_thread_fn_t(void *param);

pthread_t working_thread;

static sui_dinfo_t *di_input01 = NULL;
static sui_dinfo_t *di_output01 = NULL;
static unsigned long inval01, outval01;

static sui_dtree_memnode_t memnode, input01node, output01node;
static sui_dtree_memdir_t dinfodir;

static evc_rx_hub_t hub1;

static int initialized = 0;
//======================= logging support =========================
    // -------------- log levels ------------------
    #define LOG_FATAL   0
    #define LOG_ERR     1
    #define LOG_MSG     2
    #define LOG_INF     3
    #define LOG_DEB     4
    #define LOG_TRASH   5
    
    static void myvlog(int level, const char *format, va_list ap)
    {
        vca_vlog("nascanhw", level, format, ap);
    }
    
    static void mylog(int level, const char *format, ...)
    {
        va_list ap; 
        va_start(ap, format); 
        myvlog(level, format, ap);
        va_end(ap);
    }
    /*
    static void mylogcont(int level, const char *format, ...)
    {
        va_list ap; 
        va_start(ap, format); 
        level |= VCA_LOGL_CONT;
        myvlog(level, format, ap);
        va_end(ap);
    }
    */
//=================================================================

static void hub1_hevent(evc_rx_hub_t *hub, sui_event_t *event)
{
    long val = 0;
    sui_dinfo_t *calling_dinfo = (sui_dinfo_t*)(event->u_u.message.ptr);
    sui_rd_long(calling_dinfo, 0, &val);
    printf("HWMODULE: Digital output01 changed. New value: 0x%04x\n", (unsigned)val);
}


static void* working_thread_fn(void *param)
{
    mylog(LOG_DEB, "%s working thread IS RUNNING!\n", MODULE_ID);
    {
        unsigned long old_output = outval01;
        while(1) {
            if(outval01 != old_output) {
                printf("HWMODULE: output value changed from %li to %li\n", old_output, outval01);
                printf("HWMODULE: writing new value to input dinfo");
                // actualy this will call vcaPDOProcessor_t::send_to_can_fnc
                // (send_to_can() in case of canslave)
                // so, it has to be reentrant or synchronized
                // (it is not in case of canslave :))
                if(sui_wr_long(di_input01, 0, &outval01) == SUDI_DATA_OK) printf(" ... OK\n");
                else printf(" ... ERROR\n");
                old_output = outval01;
            }
            sleep(1);
        }
    }
    pthread_exit(0);
}

static int start_working_thread()
{
    int ret;
    mylog(LOG_DEB, "starting working thread\n");
    ret = pthread_create(&working_thread, NULL, working_thread_fn, (void *)NULL);
    if(ret) {
        mylog(LOG_ERR, "ERROR creating working thread, return code: %d\n", ret);
    }
    return ret;
}

static void cancel_working_thread()
{
    mylog(LOG_DEB, "waiting for working thread to terminate\n");
    pthread_cancel(working_thread);
    pthread_join(working_thread, NULL);
    mylog(LOG_DEB, "working thread: JOINED\n");
}

//-------------------- exported symbols --------------------------
int init_module(void)
{
    int ret = -1;
    int ok = 1;
    
    mylog(LOG_DEB, "HW module init\n");
    // create and init dinfos
    di_input01 = sui_create_dinfo_uint(&inval01, 1, sizeof(inval01));
    if(!di_input01) {
        mylog(LOG_ERR, "%s: *** ERROR *** creating input dinfo\n", __PRETTY_FUNCTION__);
        return ret;
    }
    mylog(LOG_DEB, "'input01' dinfo (%p) created.\n", di_input01);
    di_output01 = sui_create_dinfo_uint(&outval01, 1, sizeof(outval01));
    if(!di_output01) { 
        mylog(LOG_ERR, "%s: *** ERROR *** creating output dinfo\n", __PRETTY_FUNCTION__);
        return ret;
    }    
    mylog(LOG_DEB, "'output01' dinfo (%p) created.\n", di_output01);
    // init rx hub
    evc_rx_hub_init(&hub1, (evc_rx_fnc_t*)&hub1_hevent, /*hub context*/NULL);
    sui_dinfo_connect_to_hub(di_output01, &hub1, NULL); // NULL - use default propagation function

    // init root dir node
    sui_dtree_memdir_init(&dinfodir);
    // init dtreemem node
    sui_dtree_memnode_init(&memnode, MODULE_ID, SUI_DTREE_ISDIR);
    // embed root dir node to root memnode
    memnode.ptr.dir = &(dinfodir.dir);
    
    // fill dinfodir
    sui_dtree_memnode_init(&input01node, "input01", SUI_DTREE_ISDINFO);
    sui_dtree_memnode_init(&output01node, "output01", SUI_DTREE_ISDINFO);
    input01node.ptr.datai = di_input01;
    output01node.ptr.datai = di_output01;
    
    if(sui_dtree_mem_insert(&dinfodir, &input01node) <= 0) {
        mylog(LOG_ERR, "%s: *** ERROR *** inserting node '%s'\n", __PRETTY_FUNCTION__, input01node.name);
        ok = 0;
    }    
    if(sui_dtree_mem_insert(&dinfodir, &output01node) <= 0) {
        mylog(LOG_ERR, "%s: *** ERROR *** inserting node '%s'\n", __PRETTY_FUNCTION__, output01node.name);
        ok = 0;
    }    
    
    initialized = 1;
    
    if(ok) {
        ret = start_working_thread();
    }
    else {
        mylog(LOG_ERR, "There were ERRORS dring module init. Working thread will not be started.\n");
    }    
    
    return ret;
}

void cleanup_module(void *clean_up)
{
    // decrementing refcnt frees dinfo, if it is not referenced by someone else
    mylog(LOG_DEB, "HW module cleanup\n");
    cancel_working_thread();
    if(di_input01) sui_dinfo_dec_refcnt(di_input01);
    di_input01 = NULL;
    if(di_output01) sui_dinfo_dec_refcnt(di_output01);
    di_output01 = NULL;
}

sui_dtree_memnode_t* get_dtreemem_node(void)
{
    if(!initialized) 
        mylog(LOG_ERR, "%s(): ERROR module IS NOT initialized, call init_module() first.\n", __PRETTY_FUNCTION__);
    return &memnode;
}
//-------------------------------------------------------------

