/*
  labcomm2006_private.h -- semi private declarations for handling encoding and 
                       decoding of labcomm samples.

  Copyright 2006-2013 Anders Blomdell <anders.blomdell@control.lth.se>

  This file is part of LabComm.

  LabComm is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  LabComm is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef __LABCOMM2006_PRIVATE_H__
#define __LABCOMM2006_PRIVATE_H__

#ifdef LABCOMM_COMPAT
  #include LABCOMM_COMPAT
#else
  #include <endian.h>
  #include <stdio.h>
  #include <stdint.h>
  #include <unistd.h>
#endif

//#include <stdlib.h>
#include <string.h>
#include "labcomm2006.h"

/*
 * Predeclared aggregate type indices
 */
#define LABCOMM_SAMPLE   0x02
#define LABCOMM_ARRAY    0x10
#define LABCOMM_STRUCT   0x11
#define LABCOMM_USER     0x40   /* ..0xffffffff */

/*
 * Predefined primitive type indices
 */
#define LABCOMM_BOOLEAN  0x20 
#define LABCOMM_BYTE     0x21
#define LABCOMM_SHORT    0x22
#define LABCOMM_INT      0x23
#define LABCOMM_LONG     0x24
#define LABCOMM_FLOAT    0x25
#define LABCOMM_DOUBLE   0x26
#define LABCOMM_STRING   0x27

/*
 * Macro to automagically call constructors in modules compiled 
 * with the labcomm compiler. If __attribute__((constructor)) is
 * not supported, these calls has to be done first in main program.
 */
#ifndef LABCOMM_CONSTRUCTOR
#define LABCOMM_CONSTRUCTOR __attribute__((constructor))
#endif

/*
 * Semi private dynamic memory declarations
 */

struct labcomm2006_memory {
  void *(*alloc)(struct labcomm2006_memory *m, int lifetime, size_t size);
  void *(*realloc)(struct labcomm2006_memory *m, int lifetime, 
		   void *ptr, size_t size);
  void (*free)(struct labcomm2006_memory *m, int lifetime, void *ptr);
  void *context;
};

/*
 * Semi private decoder declarations
 */
typedef void (*labcomm2006_handler_function)(void *value, void *context);

typedef void (*labcomm2006_decoder_function)(
  struct labcomm2006_reader *r,
  labcomm2006_handler_function handler,
  void *context);

struct labcomm2006_reader_action_context;

struct labcomm2006_reader_action {
  /* 'alloc' is called at the first invocation of 'labcomm2006_decoder_decode_one' 
     on the decoder containing the reader.

     Returned value:
       >  0    Number of bytes allocated for buffering
       <= 0    Error
  */
  int (*alloc)(struct labcomm2006_reader *r, 
	       struct labcomm2006_reader_action_context *action_context);
  /* 'free' returns the resources claimed by 'alloc' and might have other
     reader specific side-effects as well.

     Returned value:
       == 0    Success
       != 0    Error
  */
  int (*free)(struct labcomm2006_reader *r, 
	      struct labcomm2006_reader_action_context *action_context);
  /* 'start' is called at the following instances:
     1. When a sample is registered 
          (local_index != 0, remote_index == 0, value == NULL)
     2. When a sample definition is received 
          (local_index != 0, remote_index != 0, value == NULL)
     3. When a sample is received
          (local_index != 0, remote_index != 0, value != NULL)
   */
  int (*start)(struct labcomm2006_reader *r, 
	       struct labcomm2006_reader_action_context *action_context,
	       int local_index, int remote_index,
	       const struct labcomm2006_signature *signature,
	       void *value);
  int (*end)(struct labcomm2006_reader *r, 
	     struct labcomm2006_reader_action_context *action_context);
  int (*fill)(struct labcomm2006_reader *r, 
	      struct labcomm2006_reader_action_context *action_context);
  int (*ioctl)(struct labcomm2006_reader *r, 
	       struct labcomm2006_reader_action_context *action_context,
	       int local_index, int remote_index,
	       const struct labcomm2006_signature *signature, 
	       uint32_t ioctl_action, va_list args);
};

struct labcomm2006_reader_action_context {
  struct labcomm2006_reader_action_context *next;
  const struct labcomm2006_reader_action *action;
  void *context;  
};

struct labcomm2006_reader {
  struct labcomm2006_reader_action_context *action_context;
  struct labcomm2006_memory *memory;
  /* The following fields are initialized by labcomm2006_decoder_new */
  struct labcomm2006_decoder *decoder;
  unsigned char *data;
  int data_size;
  int count;
  int pos;
  int error;
};

int labcomm2006_reader_alloc(struct labcomm2006_reader *r, 
                             struct labcomm2006_reader_action_context *action_context);
int labcomm2006_reader_free(struct labcomm2006_reader *r, 
                            struct labcomm2006_reader_action_context *action_context);
int labcomm2006_reader_start(struct labcomm2006_reader *r, 
                             struct labcomm2006_reader_action_context *action_context,
                             int local_index, int remote_index,
                             const struct labcomm2006_signature *signature,
                             void *value);
int labcomm2006_reader_end(struct labcomm2006_reader *r, 
                           struct labcomm2006_reader_action_context *action_context);
int labcomm2006_reader_fill(struct labcomm2006_reader *r, 
                            struct labcomm2006_reader_action_context *action_context);
int labcomm2006_reader_ioctl(struct labcomm2006_reader *r, 
                             struct labcomm2006_reader_action_context *action_context,
                             int local_index, int remote_index,
                             const struct labcomm2006_signature *signature, 
                             uint32_t ioctl_action, va_list args);

/*
 * Non typesafe registration function to be called from
 * generated labcomm2006_decoder_register_* functions.
 */
int labcomm2006_internal_decoder_register(
  struct labcomm2006_decoder *d, 
  const struct labcomm2006_signature *s, 
  labcomm2006_decoder_function decoder,
  labcomm2006_handler_function handler,
  void *context);

int labcomm2006_internal_decoder_ioctl(struct labcomm2006_decoder *decoder, 
                                       const struct labcomm2006_signature *signature,
                                       uint32_t ioctl_action, va_list args);

#if __BYTE_ORDER == __LITTLE_ENDIAN

#define LABCOMM_DECODE(name, type)					\
  static inline type labcomm2006_read_##name(struct labcomm2006_reader *r) {	\
    type result; int i;							\
    for (i = sizeof(type) - 1 ; i >= 0 ; i--) {				\
      if (r->pos >= r->count) {						\
	labcomm2006_reader_fill(r, r->action_context);			\
	if (r->error < 0) {						\
	  return 0;							\
	}								\
      }									\
      ((unsigned char*)(&result))[i] = r->data[r->pos];			\
      r->pos++;								\
    }									\
    return result;							\
  }

#else

#define LABCOMM_DECODE(name, type)					\
  static inline type labcomm2006_read_##name(struct labcomm2006_reader *r) {	\
    type result; int i;							\
    for (i = 0 ; i < sizeof(type) ; i++) {				\
      if (r->pos >= r->count) {						\
	labcomm2006_reader_fille(r, r->action_context);			\
	if (r->error < 0) {						\
	  return 0;							\
	}								\
      }									\
      ((unsigned char*)(&result))[i] = r->data[r->pos];			\
      r->pos++;								\
    }									\
    return result;							\
  }

#endif

LABCOMM_DECODE(boolean, unsigned char)
LABCOMM_DECODE(byte, unsigned char)
LABCOMM_DECODE(short, short)
LABCOMM_DECODE(int, int)
LABCOMM_DECODE(long, long long)
LABCOMM_DECODE(float, float)
LABCOMM_DECODE(double, double)

// compatibility with 2014 version
#define labcomm2006_read_packed32 labcomm2006_read_int

static inline char *labcomm2006_read_string(struct labcomm2006_reader *r)
{
  char *result = NULL;
  int length, pos; 
  
  length = labcomm2006_read_packed32(r);
  result = labcomm2006_memory_alloc(r->memory, 1, length + 1);
  if (!result) {
    labcomm2006_on_error_fprintf(LABCOMM_ERROR_MEMORY, 4, "%d byte at %s:%d",
                     length+1, __FUNCTION__, __LINE__);
    return NULL;
  }
  for (pos = 0 ; pos < length ; pos++) {
    if (r->pos >= r->count) {	
      labcomm2006_reader_fill(r, r->action_context);
      if (r->error < 0) {
	goto out;
      }
    }
    result[pos] = r->data[r->pos];
    r->pos++;
  }
out:
  result[pos] = 0;
  return result;
}

/*
 * Semi private encoder declarations
 */
typedef int (*labcomm2006_encoder_function)(struct labcomm2006_writer *,
                                            void *value);
struct labcomm2006_writer_action_context;

struct labcomm2006_writer_action {
  int (*alloc)(struct labcomm2006_writer *w, 
	       struct labcomm2006_writer_action_context *action_context);
  int (*free)(struct labcomm2006_writer *w, 
	      struct labcomm2006_writer_action_context *action_context);
  /* 'start' is called right before a sample is to be sent. In the 
     case of a sample or typedef, 'value' == NULL.

     Returned value:
       == 0          Success -> continue sending the sample
       == -EALREADY  Success -> silently skip sending the sample,
                                'end' will not be called
       < 0           Error
   */
  int (*start)(struct labcomm2006_writer *w, 
	       struct labcomm2006_writer_action_context *action_context,
	       int index, const struct labcomm2006_signature *signature,
	       void *value);
  int (*end)(struct labcomm2006_writer *w, 
	     struct labcomm2006_writer_action_context *action_context);
  int (*flush)(struct labcomm2006_writer *w, 
	       struct labcomm2006_writer_action_context *action_context); 
  int (*ioctl)(struct labcomm2006_writer *w, 
	       struct labcomm2006_writer_action_context *action_context, 
	       int index, const struct labcomm2006_signature *signature, 
	       uint32_t ioctl_action, va_list args);
};

struct labcomm2006_writer_action_context {
  struct labcomm2006_writer_action_context *next;
  const struct labcomm2006_writer_action *action;
  void *context;  
};

struct labcomm2006_writer {
  struct labcomm2006_writer_action_context *action_context;
  struct labcomm2006_memory *memory;
  /* The following fields are initialized by labcomm2006_encoder_new */
  struct labcomm2006_encoder *encoder;
  unsigned char *data;
  int data_size;
  int count;
  int pos;
  int error;
};

int labcomm2006_writer_alloc(struct labcomm2006_writer *w, 
			 struct labcomm2006_writer_action_context *action_context);
int labcomm2006_writer_free(struct labcomm2006_writer *w, 
			struct labcomm2006_writer_action_context *action_context);
int labcomm2006_writer_start(struct labcomm2006_writer *w, 
			 struct labcomm2006_writer_action_context *action_context,
			 int index, const struct labcomm2006_signature *signature,
			 void *value);
int labcomm2006_writer_end(struct labcomm2006_writer *w, 
		       struct labcomm2006_writer_action_context *action_context);
int labcomm2006_writer_flush(struct labcomm2006_writer *w, 
			 struct labcomm2006_writer_action_context *action_context); 
int labcomm2006_writer_ioctl(struct labcomm2006_writer *w, 
			 struct labcomm2006_writer_action_context *action_context, 
			 int index, const struct labcomm2006_signature *signature, 
			 uint32_t ioctl_action, va_list args);

int labcomm2006_internal_encoder_register(
  struct labcomm2006_encoder *encoder, 
  const struct labcomm2006_signature *signature, 
  labcomm2006_encoder_function encode);

int labcomm2006_internal_encode(
  struct labcomm2006_encoder *encoder, 
  const struct labcomm2006_signature *signature, 
  labcomm2006_encoder_function encode,
  void *value);

int labcomm2006_internal_encoder_ioctl(struct labcomm2006_encoder *encoder, 
                                       const struct labcomm2006_signature *signature,
                                       uint32_t ioctl_action, va_list args);

int labcomm2006_internal_sizeof(const struct labcomm2006_signature *signature,
                                void *v);

#if __BYTE_ORDER == __LITTLE_ENDIAN

#define LABCOMM_ENCODE(name, type)					\
  static inline int labcomm2006_write_##name(struct labcomm2006_writer *w, type data) { \
    int i;								\
    for (i = sizeof(type) - 1 ; i >= 0 ; i--) {				\
      if (w->pos >= w->count) { /*buffer is full*/			\
        int err;							\
	err = labcomm2006_writer_flush(w, w->action_context);		\
	if (err != 0) { return err; }					\
      }									\
      w->data[w->pos] = ((unsigned char*)(&data))[i];			\
      w->pos++;								\
    }									\
    return 0;								\
  }

#else

#define LABCOMM_ENCODE(name, type)					\
  static inline int labcomm2006_write_##name(struct labcomm2006_writer *w, type data) { \
    int i;								\
    for (i = 0 ; i < sizeof(type) ; i++) {				\
      if (w->pos >= w->count) {						\
        int err;							\
	err = labcomm2006_writer_flush(w, w->action_context);		\
	if (err != 0) { return err; }					\
      }									\
      w->data[w->pos] = ((unsigned char*)(&data))[i];			\
      w->pos++;								\
    }									\
    return 0;								\
  }

#endif

LABCOMM_ENCODE(boolean, unsigned char)
LABCOMM_ENCODE(byte, unsigned char)
LABCOMM_ENCODE(short, short)
LABCOMM_ENCODE(int, int)
LABCOMM_ENCODE(long, long long)
LABCOMM_ENCODE(float, float)
LABCOMM_ENCODE(double, double)

#define labcomm2006_write_packed32 labcomm2006_write_int

#if 0
static inline int labcomm2006_write_packed32(struct labcomm2006_writer *w, 
					 unsigned int data)
{
  unsigned char tmp[5];
  int i;
  
  for (i = 0 ; i == 0 || data ; i++, data = (data >> 7)) {
    tmp[i] = data & 0x7f;
  }
  for (i = i - 1 ; i >= 0 ; i--) {
    if (w->pos >= w->count) {					
      int err;
      err = labcomm2006_writer_flush(w, w->action_context);	
      if (err != 0) { return err; }
    }
    w->data[w->pos++] = tmp[i] | (i?0x80:0x00);
  }
  return 0;
}
#endif

static inline int labcomm2006_write_string(struct labcomm2006_writer *w, char *s)
{
  int length, i, err; 

  length = strlen((char*)s);
  err = labcomm2006_write_packed32(w, length);
  if (err != 0) { return err; }
  for (i = 0 ; i < length ; i++) {
    if (w->pos >= w->count) {	
      int err;
      err = labcomm2006_writer_flush(w, w->action_context);	
      if (err != 0) { return err; }
    }
    w->data[w->pos] = s[i];
    w->pos++;
  }
  return 0;
}

/* Size of packed32 variable is 4 as we use int*/
static inline int labcomm2006_size_packed32(unsigned int data)
{
  return 4;
}

/*
 * Macros for handling arrays indexed by signature index
 */

#define LABCOMM_SIGNATURE_ARRAY_DEF(name, kind)	\
  struct {					\
    int first;					\
    int last;					\
    kind *data;					\
  } name

#define LABCOMM_SIGNATURE_ARRAY_DEF_INIT(name, kind)		\
  LABCOMM_SIGNATURE_ARRAY_DEF(name, kind) = { 0, 0, NULL }

#define LABCOMM_SIGNATURE_ARRAY_INIT(name, kind)		\
  name.first = 0; name.last = 0; name.data = NULL;		\
  name.data = (kind *)name.data; /* typechecking no-op */

#define LABCOMM_SIGNATURE_ARRAY_FREE(memory, name, kind)	\
  if (name.data) { labcomm2006_memory_free(memory, 0, name.data); }	\
  name.data = (kind *)NULL; /* typechecking */

void *labcomm2006_signature_array_ref(struct labcomm2006_memory * memory,
                                      int *first, int *last, void **data,
                                      int size, int index);
/*
 * NB: the pointer returned by LABCOMM_SIGNATURE_ARRAY_REF might be
 *     rendered invalid by a subsequent call to LABCOMM_SIGNATURE_ARRAY_REF
 *     on the same SIGNATURE_ARRAY, so make sure not to use the result if 
 *     any other code might have made a call to LABCOMM_SIGNATURE_ARRAY_REF
 *     on the same SIGNATURE_ARRAY.
 */
#define LABCOMM_SIGNATURE_ARRAY_REF(memory, name, kind, index)		\
  (name.data = (kind *)name.data, /* typechecking no-op */		\
   (kind *)(labcomm2006_signature_array_ref(memory,				\
					&name.first, &name.last,	\
					(void **)&name.data,		\
					sizeof(kind), index)))

#define LABCOMM_SIGNATURE_ARRAY_FOREACH(name, kind, var)		\
  for (name.data = (kind *)name.data, /* typechecking no-op */		\
       var = name.first ; var < name.last ; var++)

/* Give signature a free local index, this may not be used concurrently */
void labcomm2006_set_local_index(struct labcomm2006_signature *signature);

/* Get the local index for a signature */
int labcomm2006_get_local_index(const struct labcomm2006_signature *s);

#endif