/*  Make the necessary includes and set up the variables.  */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <getopt.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>

#include "can_vca.h"
#include "canmond.h"

#define DEF_ADDR "127.0.0.1"

// -------------- log levels ------------------
#define LOG_FATAL   0
#define LOG_ERR     1
#define LOG_MSG     2
#define LOG_INF     3
#define LOG_DEB     4

static int log_is_cont = 0;

void mylog(int level, const char *format, ...)
{
    va_list ap; 
    va_start(ap, format); 
    if(log_is_cont) level |= VCA_LOGL_CONT;
    vca_vlog("testclient", level, format, ap);
    va_end(ap);
}

//--------------------------------------------------------------------
static char help_msg[] =
            "TESTCLIENT - canmond client\n"
            "testclient [OPTION]\n\n"
			"OPTIONS:\n"
			"\t-h\n"
            "\t--help\tthis help screen\n"
			"\t-v\n"
            "\t--verbose\ttverbose\n"
			"\t-a\n"
            "\t--host [n]\tsets  the IP address where the server listens default is "mkstr(DEF_ADDR)"\n"
			"\t-p\n"
            "\t--port [n]\tsets port on which server listens default "mkstr(CANMOND_PORT)"\n"
			"\nCOMMANDS:\n"
			"\tsendmsg id [byte1 byte2 ...]"
            "\t\t- sends CAN message (short version)\n"
			"\tsend \t{CANDTG flags cob timestamp id [byte_1 .. byte_n]}\n"
            "\t\t- sends CAN message (detailed version)\n"
            "\t\t{SDOR UPLOAD server_port client_port node index subindex}\n"
            "\t\t- uploads CANopen object from device object dictionary\n"
            "\t\tserver_port, client_port can be 0 for default values (0x580, 0x600)\n"
            "\t\t{SDOR DOWNLOAD server_port client_port node index subindex [byte_1 ... byte_n]}\n"
            "\t\t- downloads CANopen object to device object dictionary\n"
			"\tq\tquits\n";
int sockfd;

/*--- handler on SIGINT signal : the program quit with CTL-C ---*/
void exithandler(int sig)
{
	close(sockfd);
	printf("Terminated by user\n");
	exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[])
{
    int len;
    struct sockaddr_in address;
    struct hostent *hostinfo;

    fd_set readfds, testfds;
    int result;

    #define RDBUFF_LEN 256
    char rdbuff[RDBUFF_LEN];
    int help_opt = 0;
    int port_opt = CANMOND_PORT;
    char addr_opt[55];

  	struct sigaction act;
    
    /* flush every line in stdin and stderr, rdln needs it  NOT ANY MORE*/
    //setlinebuf(stdout); setlinebuf(stderr);

	/*------- register handler on SIGINT signal -------*/
	act.sa_handler = exithandler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGINT, &act, 0);
    
	/*---------------------------------------*/	
    strcpy(addr_opt, DEF_ADDR);
    // -------------- logging ------------------
    #ifdef DEBUG
    vca_log_cutoff_level = LOG_DEB;
    #else
    vca_log_cutoff_level = LOG_MSG;
    #endif
    {
        /* parse cmd line arguments */
        while(1) {
            static struct option long_options[] = {
                {"host", required_argument, 0, 'a'},
                {"port", required_argument, 0, 'p'},
                {"help", no_argument, 0, 'h'},
                {"verbose", no_argument, 0, 'v'},
                {0, 0, 0, 0}
            };
            int option_index = 0;
            int c, n;
            c = getopt_long(argc, argv, "a:p:hv", long_options, &option_index);
            if (c == -1) break;
            switch (c) {
                case 0:
                    /* If this option set a flag, do nothing else now. */
                    break;
                case 'a':
                    n = snprintf(addr_opt, 50, "%s", optarg);
                    addr_opt[n] = '\0';
                    break;
                case 'p':
                    port_opt = atoi(optarg);
                    break;
                case 'v':
                    vca_log_cutoff_level = LOG_DEB;
                    break;
                case 'h':
                    help_opt = 1;
                    break;
                case '?':
                    /* getopt_long already printed an error message. */
                    break;
                default:
                    fprintf(stderr, "cmd line parsing error\n");
                    abort ();
            }
        }
        if(help_opt) {
            printf(help_msg);
            exit(EXIT_SUCCESS);
        } 
    }

    printf("finding %s:%i ...\n", addr_opt, port_opt);
    hostinfo = gethostbyname(addr_opt);
    if(!hostinfo) {
        perror("host not found");
        exit(EXIT_FAILURE);
    }
    printf("found address: %s - ", hostinfo->h_name);
    {
        char **ppc;
        for(ppc=hostinfo->h_addr_list; *ppc; ppc++)
            printf("%s ", inet_ntoa(*(struct in_addr*)*ppc));
    }
    printf("\n");

    /* Create a socket for the client.  */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    /*  Name the socket, as agreed with the server.  */
    address.sin_family = AF_INET;
    //address.sin_addr.s_addr = inet_addr(addr_opt);
    address.sin_addr = *(struct in_addr*)*(hostinfo->h_addr_list);
    address.sin_port = htons(port_opt);
    len = sizeof(address);

/*  Now connect our socket to the server's socket.  */
    printf("connecting %s:%hu ...\n", inet_ntoa(*(struct in_addr*)*(hostinfo->h_addr_list)), ntohs(address.sin_port));
    result = connect(sockfd, (struct sockaddr *)&address, len);
    if(result == -1) {
        perror("oops: ");
        exit(EXIT_FAILURE);
    }
    printf("OK\n");

    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
    FD_SET(0/*stdin*/, &readfds);

    while(1) {
        int result;
        testfds = readfds;
        result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, 0);
        if(result < 0) {
            mylog(LOG_ERR, strerror(errno));
            exit(EXIT_FAILURE);
        }
        if(result == 0) {
            mylog(LOG_DEB, "timeout\n");
            continue;
        }
        
        if(FD_ISSET(0, &testfds)) {
            // stdin activityFD_ISSET
            char *inbuff = rdbuff;
            int n = read(0, inbuff, 99);    
            inbuff[n] = '\0';                
            mylog(LOG_DEB, "STDIN: %s\n", inbuff);
            if((n = vca_strmatch(inbuff, "sendmsg~")) > 0) {
                // send can message
                int i;
                unsigned u;
                struct canmsg_t sendmsg = {0,0,0,0,8,{1,2,3,4,5,6,7,8}};
                inbuff += n;
                inbuff += vca_gethex(inbuff, &u);
                sendmsg.id = (unsigned long)u;
                while(*inbuff != '[' && *inbuff) inbuff++; 
                if(*inbuff == '[') inbuff++; 
                sendmsg.length = 0;
                for(i=0; i<8; i++) {
                    n = vca_gethex(inbuff, &u);
                    sendmsg.data[i] = (unsigned char)u;
                    if(n <= 0) break;
                    sendmsg.length++;
                    inbuff += n;
                }
                n = vca_msg2str(&sendmsg, rdbuff, 100);
                mylog(LOG_INF, "sending message: %s\n", rdbuff);
                write(sockfd, rdbuff, n);
            }
            else if((n = vca_strmatch(inbuff, "send~")) > 0) {
                // send message to the server
                mylog(LOG_INF, "sending to server: %s\n", inbuff + n);
                write(sockfd, inbuff + n, strlen(inbuff + n));
            }
            else if((n = vca_strmatch(inbuff, "q~")) > 0) {
                break;
            }
        }
        else if(FD_ISSET(sockfd, &testfds)) {
            int n; 
            struct canmsg_t canmsg; 
            n = read(sockfd, rdbuff, RDBUFF_LEN - 1);
            if(n < 1) break;
            rdbuff[n++] = '\0';
            printf("got %s\n", rdbuff);
            mylog(LOG_INF, "from server '%s'\n", rdbuff);
            // parse message
            if(vca_str2msg(&canmsg, rdbuff) > 0) {
    //            mylog(LOG_ERR, "%s\ncan't parse the message: invalid format\n", rdbuff);
    //        }
    //        else {
                vca_msg2str(&canmsg, rdbuff, RDBUFF_LEN);
                mylog(LOG_INF, "red message message\n");
                mylog(LOG_INF, "\tCOB:\t%x\n", canmsg.cob);
                mylog(LOG_INF, "\tID:\t%x\n", canmsg.id);
                mylog(LOG_INF, "\tdata:\t[");
                log_is_cont = 1;
                for(n=0; n<canmsg.length; n++) {
                    if(n > 0) mylog(LOG_INF, " ");
                    mylog(LOG_INF, "%02x", canmsg.data[n]);
                }
                mylog(LOG_INF, "]\n");
                log_is_cont = 0;
            }
        }
    }
    close(sockfd);
    printf("bye\n");
    exit(EXIT_SUCCESS);
}
