#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <linux/types.h>
#include <net/if.h>
#include <avm/event/event.h>
#include "avm_event.h"

#define AVM_EVENT_DEVICE          "/dev/avm_event"

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#undef DEB_ERR
#undef DEB_WARN
#undef DEB_INFO
#undef DEB_TRC

#define DEB_ERR(args...)     fprintf(stderr, args)
#define DEB_WARN(args...)    fprintf(stderr, args)
#define DEB_INFO(args...)    fprintf(stderr, args)
/*--- #define DEB_TRC(args...)     fprintf(stderr, args) ---*/
#if 0
static FILE *debug_stream = NULL;
#define DBG_TRC(args...) { if(debug_stream == NULL) debug_stream = fopen ("/dev/console", "w"); \
                           if(debug_stream) fprintf(debug_stream, args); }
#endif
/*-------------------------------------------------------------------------------------*\
\*-------------------------------------------------------------------------------------*/
static int __avm_event_register(const char* name, struct _avm_event_id_mask *id_mask, unsigned long long mask) {
    int fd, len;
    struct _avm_event_cmd cmd;


    fd = open(AVM_EVENT_DEVICE, O_RDWR);
    if (fd < 0) {
        return AVM_EVENT_UNKNOWN_HANDLE;
    }

    cmd.cmd = avm_event_cmd_register;
#define min(a, b)   ((a) < (b) ? (a) : (b))
    len = min(strlen(name), MAX_EVENT_CLIENT_NAME_LEN);
    strncpy(cmd.param.avm_event_cmd_param_register.Name, name, len);
    cmd.param.avm_event_cmd_param_register.Name[len] = '\0';
#if (AVM_DIST_EVENT_VERSION >= 0x10000)
	/*--- 	Event 2.0: ---*/
	if(id_mask) {
		memcpy(&cmd.param.avm_event_cmd_param_register.mask, id_mask, sizeof(cmd.param.avm_event_cmd_param_register.mask));
	} else {
		memset(&cmd.param.avm_event_cmd_param_register.mask, 0, sizeof(cmd.param.avm_event_cmd_param_register.mask));
		cmd.param.avm_event_cmd_param_register.mask.mask[0] = mask;
	}
#else /*--- #if (AVM_DIST_EVENT_VERSION >= 0x10000) ---*/
	if(id_mask) {
		cmd.param.avm_event_cmd_param_register.mask = id_mask->mask[0];
	} else {
		cmd.param.avm_event_cmd_param_register.mask = mask;
	}
	/*--- 	DBG_TRC("%s: id_mask: %p mask=%Lx -> mask[0]=%Lx\n", __func__, id_mask, mask, cmd.param.avm_event_cmd_param_register.mask.mask[0]); ---*/
#endif /*--- #else ---*/ /*--- #if (AVM_DIST_EVENT_VERSION >= 0x10000) ---*/

    if (write(fd, &cmd, sizeof(cmd)) < 0) {
        close(fd);
        switch (errno) {
            case EBADF:
                return AVM_EVENT_UNKNOWN_HANDLE;
            default:
                return AVM_EVENT_RESOURCE_ERROR;
        }
    }
    avm_event_set_blocking(fd + 1);
    return fd + 1;
}
/*--------------------------------------------------------------------------------*\
\*--------------------------------------------------------------------------------*/
int avm_event_register(const char* name, unsigned long long mask) {
	return __avm_event_register( name, NULL, mask);
}

#define AVM_EVENT_MASK_BITS		 (sizeof(avm_event_mask_fieldentry) * 8)
#define AVM_EVENT_MASK_BITMASK	 (AVM_EVENT_MASK_BITS - 1)

#define AVM_EVENT_MASK_IDX( id)	((id) / AVM_EVENT_MASK_BITS)
#define AVM_EVENT_MASK_BIT( id)	((id) & AVM_EVENT_MASK_BITMASK)
/*--------------------------------------------------------------------------------*\
\*--------------------------------------------------------------------------------*/
int avm_event_register20(const char* name, int num_ids, ...) {
	struct _avm_event_id_mask id_mask;
	int i;
    va_list listPointer;
    va_start(listPointer, num_ids);
	
	memset(&id_mask, 0, sizeof(struct _avm_event_id_mask));
	if(num_ids < 0) {
        va_end(listPointer);
		return -EINVAL;
	}
    for(i = 0; i < num_ids; i++ ) {
        enum _avm_event_id id = va_arg( listPointer, enum _avm_event_id);
		if((unsigned int)id >= avm_event_last) {
			/*--- id out of range ---*/
            va_end(listPointer);
			return -EINVAL;
		}
		id_mask.mask[AVM_EVENT_MASK_IDX(id)] |= (avm_event_mask_fieldentry)1 << AVM_EVENT_MASK_BIT(id);
    }
	va_end(listPointer);
	return __avm_event_register(name, &id_mask, 0ULL);
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int __avm_event_source_register(int handle, const char *name, struct _avm_event_id_mask *id_mask, unsigned long long mask) {
    struct _avm_event_cmd cmd;
    int fd, len;

    fd = handle - 1;

    cmd.cmd = avm_event_cmd_source_register;
    len = min(strlen(name), MAX_EVENT_CLIENT_NAME_LEN);
    strncpy(cmd.param.avm_event_cmd_param_source_register.Name, name, len);
    cmd.param.avm_event_cmd_param_source_register.Name[len] = '\0';
#if (AVM_DIST_EVENT_VERSION >= 0x10000)
	/*--- 	Event 2.0: ---*/
	if(id_mask) {
		memcpy(&cmd.param.avm_event_cmd_param_register.mask, id_mask, sizeof(cmd.param.avm_event_cmd_param_register.mask));
	} else {
		memset(&cmd.param.avm_event_cmd_param_register.mask, 0, sizeof(cmd.param.avm_event_cmd_param_register.mask));
		cmd.param.avm_event_cmd_param_register.mask.mask[0] = mask;
	}
	/*--- 	DBG_TRC("%s: id_mask: %p mask[0]=%Lx -> %Lx\n", __func__, id_mask, mask, cmd.param.avm_event_cmd_param_register.mask.mask[0]); ---*/
#else /*--- #if (AVM_DIST_EVENT_VERSION >= 0x10000) ---*/
	if(id_mask) {
		cmd.param.avm_event_cmd_param_register.mask = id_mask->mask[0];
	} else {
		cmd.param.avm_event_cmd_param_register.mask = mask;
	}
#endif /*--- #else ---*/ /*--- #if (AVM_DIST_EVENT_VERSION >= 0x10000) ---*/

    if (write(fd, &cmd, sizeof(cmd)) < 0) {
        switch (errno) {
            case EBADF:
                return AVM_EVENT_UNKNOWN_HANDLE;
            default:
                return AVM_EVENT_RESOURCE_ERROR;
        }
    }
    return AVM_EVENT_OK;
}
/*--------------------------------------------------------------------------------*\
\*--------------------------------------------------------------------------------*/
int avm_event_source_register(int handle, const char *name, unsigned long long mask) {
	return __avm_event_source_register(handle, name, NULL, mask);
}
/*--------------------------------------------------------------------------------*\
\*--------------------------------------------------------------------------------*/
int avm_event_source_register20(int handle, const char *name,  int num_ids, ...) {
	struct _avm_event_id_mask id_mask;
	int i;
    va_list listPointer;
    va_start(listPointer, num_ids);
	
	memset(&id_mask, 0, sizeof(struct _avm_event_id_mask));
	if(num_ids <= 0) {
        va_end(listPointer);
		return -EINVAL;
	}
    for(i = 0; i < num_ids; i++ ) {
        enum _avm_event_id id = va_arg( listPointer, enum _avm_event_id);
		if((unsigned int)id >= avm_event_last) {
			/*--- id out of range ---*/
            va_end(listPointer);
			return -EINVAL;
		}
		id_mask.mask[AVM_EVENT_MASK_IDX(id)] |= (avm_event_mask_fieldentry)1 << AVM_EVENT_MASK_BIT(id);
    }
	va_end(listPointer);
	return __avm_event_source_register(handle, name, &id_mask, 0ULL);
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int avm_event_source_release(int handle) {
    struct _avm_event_cmd cmd;
    int fd;

    fd = handle - 1;
    cmd.cmd = avm_event_cmd_source_release;
    if (write(fd, &cmd, sizeof(cmd)) < 0) {
        switch (errno) {
            case EBADF:
                return AVM_EVENT_UNKNOWN_HANDLE;
            default:
                return AVM_EVENT_RESOURCE_ERROR;
        }
    }
    return AVM_EVENT_OK;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int avm_event_source_trigger(int handle, unsigned int data_length, struct _avm_event_header *data) {
    struct _avm_event_cmd *cmd;
    int fd;
    int status = AVM_EVENT_OK;

    fd = handle - 1;

    cmd = malloc(sizeof(struct _avm_event_cmd) + data_length);
    if(cmd == NULL) {
        return AVM_EVENT_RESOURCE_ERROR;
    }
    cmd->cmd = avm_event_cmd_source_trigger;
    cmd->param.avm_event_cmd_param_source_trigger.id          = data->id;
    cmd->param.avm_event_cmd_param_source_trigger.data_length = data_length;

    memcpy((unsigned char *)cmd + sizeof(struct _avm_event_cmd), data, data_length);

    if (write(fd, cmd, sizeof(struct _avm_event_cmd) + data_length) < 0) {
        switch (errno) {
            case EBADF:
                status = AVM_EVENT_UNKNOWN_HANDLE;
                break;
            default:
                status = AVM_EVENT_RESOURCE_ERROR;
                break;
        }
    }
    free(cmd);
    return status;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int avm_event_trigger(int handle, unsigned int id) {
    struct _avm_event_cmd cmd;
    int fd;
    int status = AVM_EVENT_OK;

    fd = handle - 1;

    cmd.cmd = avm_event_cmd_trigger;
    cmd.param.avm_event_cmd_param_trigger.id = id;

    if (write(fd, &cmd, sizeof(struct _avm_event_cmd)) < 0) {
        switch (errno) {
            case EBADF:
                status = AVM_EVENT_UNKNOWN_HANDLE;
                break;
            default:
                status = AVM_EVENT_RESOURCE_ERROR;
                break;
        }
    }
    return status;
}

/*-------------------------------------------------------------------------------------*\
\*-------------------------------------------------------------------------------------*/
int avm_event_get_fd(int handle) {
    return handle - 1;
}

/*-------------------------------------------------------------------------------------*\
\*-------------------------------------------------------------------------------------*/
int avm_event_dup(int handle) {
    return dup(handle - 1) + 1;
}

/*-------------------------------------------------------------------------------------*\
\*-------------------------------------------------------------------------------------*/
int avm_event_undup(int handle) {
    if(close(handle - 1))
        return AVM_EVENT_UNKNOWN_HANDLE;
    return AVM_EVENT_OK;
}

/*-------------------------------------------------------------------------------------*\
\*-------------------------------------------------------------------------------------*/
int avm_event_release(int handle) {
    int fd;
    struct _avm_event_cmd cmd;

    fd = handle - 1;

    cmd.cmd = avm_event_cmd_release;
    cmd.param.avm_event_cmd_param_release.Name[0] = '\0';

    if (write(fd, &cmd, sizeof(cmd)) < 0) {
        switch (errno) {
            case EBADF:
                return AVM_EVENT_UNKNOWN_HANDLE;
            default:
                return AVM_EVENT_RESOURCE_ERROR;
        }
    }
    close(fd);
    return AVM_EVENT_OK;
}

/*-------------------------------------------------------------------------------------*\
\*-------------------------------------------------------------------------------------*/
int avm_event_set_non_blocking(int handle) {
    int flags;
    int fd = handle - 1;

    flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        switch (errno) {
            case EBADF:
                return AVM_EVENT_UNKNOWN_HANDLE;
            default:
                return AVM_EVENT_RESOURCE_ERROR;
        }
    }

    if ((flags & O_NONBLOCK)) return AVM_EVENT_OK;
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags)) {
        switch (errno) {
            case EBADF:
                return AVM_EVENT_UNKNOWN_HANDLE;
            default:
                return AVM_EVENT_RESOURCE_ERROR;
        }
    }

    return AVM_EVENT_OK;
}

/*-------------------------------------------------------------------------------------*\
\*-------------------------------------------------------------------------------------*/
int avm_event_set_blocking(int handle) {
    int flags;
    int fd = handle - 1;

    flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        switch (errno) {
            case EBADF:
                return AVM_EVENT_UNKNOWN_HANDLE;
            default:
                return AVM_EVENT_RESOURCE_ERROR;
        }
    }

    if ((flags & O_NONBLOCK) == 0) return AVM_EVENT_OK;

    flags &= ~O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags)) {
        switch (errno) {
            case EBADF:
                return AVM_EVENT_UNKNOWN_HANDLE;
            default:
                return AVM_EVENT_RESOURCE_ERROR;
        }
    }

    return AVM_EVENT_OK;
}

/*------------------------------------------------------------------------------------------*\
 * Paramter: 
 *      handle  
 *      data        Buffer für daten
 *      length      input: maximal datenlaenge, output: gelesene Bytes
\*------------------------------------------------------------------------------------------*/
int avm_event_read(int handle, void *data, unsigned int *length) {
    int fd = handle - 1;
    int ret;

    ret = read(fd, data, *length);
    if(ret >= 0) {
        *length = ret;
        return AVM_EVENT_OK;
    }
    *length = 0;
    return AVM_EVENT_UNKNOWN_HANDLE;
}