/*
  labcomm_encoder.c -- handling encoding 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/>.
*/
#define CURRENT_VERSION "LabComm20141009"

#include <errno.h>
#include "labcomm.h"
#include "labcomm_private.h"
#include "labcomm_ioctl.h"

#ifndef WITHOUT_PRAGMA
#include "labcomm_dynamic_buffer_writer.h"
#include "labcomm_bytearray_reader.h"
#endif

struct labcomm_encoder {
  struct labcomm_writer *writer;
  struct labcomm_error_handler *error;
  struct labcomm_memory *memory;
  struct labcomm_scheduler *scheduler;
  LABCOMM_SIGNATURE_ARRAY_DEF(registered, int);
  int context_type; //type tag for context. Currently only LABCOMM_PRAGMA
  void *context; // for, e.g. parent of pragma packet builder 
};

struct labcomm_encoder *labcomm_encoder_new(
  struct labcomm_writer *writer,
  struct labcomm_error_handler *error,
  struct labcomm_memory *memory,
  struct labcomm_scheduler *scheduler)
{
  struct labcomm_encoder *result;

  result = labcomm_memory_alloc(memory, 0, sizeof(*result));
  if (result) {
    int length;

    result->writer = writer;
    result->writer->encoder = result;
    result->writer->data = NULL;
    result->writer->data_size = 0;
    result->writer->count = 0;
    result->writer->pos = 0;
    result->writer->error = 0;
    result->error = error;
    result->memory = memory;
    result->scheduler = scheduler;
    LABCOMM_SIGNATURE_ARRAY_INIT(result->registered, int);
    labcomm_writer_alloc(result->writer,
			 result->writer->action_context);
    labcomm_writer_start(result->writer, 
                         result->writer->action_context, 
                         LABCOMM_VERSION, NULL, CURRENT_VERSION);
    labcomm_write_packed32(result->writer, LABCOMM_VERSION);
    length = (labcomm_size_packed32(LABCOMM_VERSION) +
              labcomm_size_string(CURRENT_VERSION));
    labcomm_write_packed32(result->writer, length);
    labcomm_write_string(result->writer, CURRENT_VERSION);
    labcomm_writer_end(result->writer, result->writer->action_context);
  }
  return result;
}

void labcomm_encoder_free(struct labcomm_encoder* e)
{
  struct labcomm_memory *memory = e->memory;

  labcomm_writer_free(e->writer, e->writer->action_context);
  LABCOMM_SIGNATURE_ARRAY_FREE(e->memory, e->registered, int);
  labcomm_memory_free(memory, 0, e);
}

#undef WITHOUT_PRAGMA
#ifndef WITHOUT_PRAGMA

struct pragma_packet_builder {
	char * pragma_type;
	struct labcomm_encoder* parent;
};

struct labcomm_encoder *labcomm_pragma_builder_new(
                                   struct labcomm_encoder *e,
				   char * pragma_type) {
        struct labcomm_writer *dyn_writer = labcomm_dynamic_buffer_writer_new(
                                                 e->memory);
	struct labcomm_encoder *pb = labcomm_encoder_new(dyn_writer,
                                                          e->error,
                                                          e->memory,
                                                          e->scheduler);
	size_t tlen = 1+strlen(pragma_type);
	char* ptype = labcomm_memory_alloc(
					e->memory,
					1,
					tlen);
	if(ptype) {
          strncpy(ptype, pragma_type, tlen);		
	} //XXX TODO: and else?
					
	struct pragma_packet_builder* ctxt = labcomm_memory_alloc(
					e->memory,
					1,
					sizeof(struct pragma_packet_builder));
					
	if(ctxt){
		ctxt->pragma_type=ptype;
		ctxt->parent=e;
	}
	pb->context_type = LABCOMM_PRAGMA;
	pb->context = ctxt;
	return pb;
}

//HERE BE DRAGONS! Copied from decoder.c
//Should this be moved to private_h?
static int writer_ioctl(struct labcomm_writer *writer,
			uint32_t action,
			...)
{
  int result;
  va_list va;

  if (LABCOMM_IOC_SIG(action) != LABCOMM_IOC_NOSIG) {
    result = -EINVAL;
    goto out;
  }

  va_start(va, action);
  result = labcomm_writer_ioctl(writer, writer->action_context,
				0, NULL, action, va);
  va_end(va);
out:
  return result;
}
int labcomm_pragma_send(struct labcomm_encoder* e)
{
//HERE BE DRAGONS!
//We assume that the writer is a dynamic_buffer_writer
  if(e->context_type != LABCOMM_PRAGMA) {
	  printf("context type != PRAGMA, bailing out\n");
	  return 1;
  }
  if(!e->context) {
	  printf("context == NULL, bailing out\n");
	  return 2;
  }
  struct pragma_packet_builder* ctx = e->context;	
  struct labcomm_encoder *p = ctx->parent;
  char * pragma_type = ctx->pragma_type;
  char*  pragma_data;
  int err,len;
  labcomm_writer_end(e->writer, e->writer->action_context);
  err = writer_ioctl(e->writer,
		     LABCOMM_IOCTL_WRITER_GET_BYTES_WRITTEN,
		     &len);
  if (err < 0) {

// HERE BE DRAGONS! 
// What is the difference between error_handler (where is it defined?)
// and error_handler_callback. And why is the latter only in 
// the decoder struct?
//
//    e->on_error(LABCOMM_ERROR_BAD_WRITER, 2,
//		"Failed to get size: %s\n", strerror(-err));
    fprintf(stderr, "BAD WRITER, Failed to get size> %s\n", strerror(-err));
    err = -ENOENT;
    goto free_encoder;
  }
  err = writer_ioctl(e->writer,
        	     LABCOMM_IOCTL_WRITER_GET_BYTE_POINTER,
        	     &pragma_data);
  if (err < 0) {
//    e->on_error(LABCOMM_ERROR_BAD_WRITER, 2,
//        	"Failed to get pointer: %s\n", strerror(-err));
    fprintf(stderr, "BAD WRITER, Failed to get pointer> %s\n", strerror(-err));
    err = -ENOENT;
    goto free_encoder;
  }
  { 
	  int data_len =  labcomm_size_string(pragma_type) + len;
	  int i;
	  labcomm_write_packed32(p->writer, LABCOMM_PRAGMA);
	  labcomm_write_packed32(p->writer, data_len);
	  labcomm_write_string(p->writer, pragma_type);
	  for(i=0; i<len;i++){
		  labcomm_write_byte(p->writer, pragma_data[i]);
	  }
	  labcomm_writer_end(p->writer, p->writer->action_context);
	  err = p->writer->error;
  }
free_encoder:
  //XXX are these needed, or is that done in encoder_free?
  labcomm_memory_free(e->memory, 1, ctx->pragma_type);  
  labcomm_memory_free(e->memory, 1, ctx);  
  labcomm_encoder_free(e);
  return err;
}

#endif


int labcomm_internal_encoder_register(
  struct labcomm_encoder *e,
  struct labcomm_signature *signature,
  labcomm_encoder_function encode)
{
  int result = -EINVAL;
  int index, *done, err, i, length;

  index = labcomm_get_local_index(signature);
  labcomm_scheduler_writer_lock(e->scheduler);
  if (index <= 0) { goto out; }
  done = LABCOMM_SIGNATURE_ARRAY_REF(e->memory, e->registered, int, index);
  if (*done) { goto out; }
  *done = 1;	
  err = labcomm_writer_start(e->writer, e->writer->action_context, 
			     index, signature, NULL);
  if (err == -EALREADY) { result = 0; goto out; }
  if (err != 0) { result = err; goto out; }
  labcomm_write_packed32(e->writer, LABCOMM_SAMPLE_DEF);
  length = (labcomm_size_packed32(index) +
            labcomm_size_string(signature->name) +
            labcomm_size_packed32(signature->size) +
            signature->size);
  labcomm_write_packed32(e->writer, length);
  labcomm_write_packed32(e->writer, index);
  labcomm_write_string(e->writer, signature->name);
  labcomm_write_packed32(e->writer, signature->size);
  for (i = 0 ; i < signature->size ; i++) {
    if (e->writer->pos >= e->writer->count) {
      labcomm_writer_flush(e->writer, e->writer->action_context);
    }
    e->writer->data[e->writer->pos] = signature->signature[i];
    e->writer->pos++;
  }
  labcomm_writer_end(e->writer, e->writer->action_context);
  result = e->writer->error;
out:
  labcomm_scheduler_writer_unlock(e->scheduler);
  return result;
}

int labcomm_internal_encode(
  struct labcomm_encoder *e,
  struct labcomm_signature *signature,
  labcomm_encoder_function encode,
  void *value)
{
  int result, index, length;

  index = labcomm_get_local_index(signature);
  length = (signature->encoded_size(value));
  labcomm_scheduler_writer_lock(e->scheduler);
  result = labcomm_writer_start(e->writer, e->writer->action_context, 
				index, signature, value);
  if (result == -EALREADY) { result = 0; goto no_end; }
  if (result != 0) { goto out; }
  result = labcomm_write_packed32(e->writer, index);
  result = labcomm_write_packed32(e->writer, length);
  if (result != 0) { goto out; }
  result = encode(e->writer, value);
out:
  labcomm_writer_end(e->writer, e->writer->action_context);
no_end:
  labcomm_scheduler_writer_unlock(e->scheduler);
  return result;
}

int labcomm_encoder_ioctl(struct labcomm_encoder *encoder, 
			  uint32_t action,
			  ...)
{
  int result;
  va_list va;
  
  if (LABCOMM_IOC_SIG(action) != LABCOMM_IOC_NOSIG) {
    result = -EINVAL;
    goto out;
  }
  
  va_start(va, action);
  result = labcomm_writer_ioctl(encoder->writer, 
			       encoder->writer->action_context,
			       0, NULL, action, va);
  va_end(va);

out:
  return result;
}

int labcomm_internal_encoder_ioctl(struct labcomm_encoder *encoder, 
				   struct labcomm_signature *signature,
				   uint32_t action, va_list va)
{
  int result = -ENOTSUP;
  int index;

  index = labcomm_get_local_index(signature);
  result = labcomm_writer_ioctl(encoder->writer, 
				encoder->writer->action_context, 
				index, signature, action, va);
  return result;
}