#include "orbit-c-backend.h"
#include <stdlib.h>

static void cbe_output_demarshal_type_integer(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_float(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_char(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_wide_char(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_boolean(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_octet(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_enum(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_fixed(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_string(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_wide_string(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_any(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_object(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_sequence(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_array(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_struct(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_union(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_case_stmt(CBEDemarshalInfo *mi);
static void cbe_output_demarshal_type_typecode(CBEDemarshalInfo *mi);

static int loopvarnames = 0;

void cbe_demarshaller_align(CBEDemarshalInfo *mi)
{
  int boundary;

  boundary = cbe_get_type_head_alignment(cbe_get_typespec(mi->param));

  if(boundary <= 1)
    return;

  if( !mi->previous_param || (cbe_get_type_tail_alignment(cbe_get_typespec(mi->previous_param)) < boundary))
    fprintf(mi->of, "  GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur = ALIGN_ADDRESS(GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur, %d);\n", boundary);
}

void
cbe_output_demarshaller(CBEDemarshalInfo *dmi)
{
  IDL_tree ts;

  loopvarnames++;

  switch(IDL_NODE_TYPE(dmi->param)) {
  case IDLN_CASE_STMT:
    dmi->typespec = ts = dmi->param;
    break;
  default:
    dmi->typespec = ts = cbe_get_typespec(dmi->param);
  }

  switch(IDL_NODE_TYPE(ts)) {
  case IDLN_TYPE_INTEGER:
    cbe_output_demarshal_type_integer(dmi);
    break;

  case IDLN_TYPE_FLOAT:
    cbe_output_demarshal_type_float(dmi);
    break;

  case IDLN_TYPE_CHAR:
    cbe_output_demarshal_type_char(dmi);
    break;

  case IDLN_TYPE_WIDE_CHAR:
    cbe_output_demarshal_type_wide_char(dmi);
    break;

  case IDLN_TYPE_BOOLEAN:
    cbe_output_demarshal_type_boolean(dmi);
    break;

  case IDLN_TYPE_ENUM:
    cbe_output_demarshal_type_enum(dmi);
    break;

  case IDLN_TYPE_OCTET:
    cbe_output_demarshal_type_octet(dmi);
    break;

  case IDLN_TYPE_FIXED:
    cbe_output_demarshal_type_fixed(dmi);
    break;

  case IDLN_TYPE_STRING:
    cbe_output_demarshal_type_string(dmi);
    break;

  case IDLN_TYPE_WIDE_STRING:
    cbe_output_demarshal_type_wide_string(dmi);
    break;

  case IDLN_TYPE_ANY:
    cbe_output_demarshal_type_any(dmi);
    break;

  case IDLN_TYPE_OBJECT:
  case IDLN_INTERFACE:
    cbe_output_demarshal_type_object(dmi);
    break;

  case IDLN_TYPE_SEQUENCE:
    cbe_output_demarshal_type_sequence(dmi);
    break;

  case IDLN_TYPE_ARRAY:
    cbe_output_demarshal_type_array(dmi);
    break;

  case IDLN_TYPE_STRUCT:
  case IDLN_EXCEPT_DCL:
    cbe_output_demarshal_type_struct(dmi);
    break;

  case IDLN_TYPE_UNION:
    cbe_output_demarshal_type_union(dmi);
    break;

  case IDLN_CASE_STMT:
    cbe_output_demarshal_case_stmt(dmi); /* Part of unions */
    break;

  case IDLN_TYPE_TYPECODE:
    cbe_output_demarshal_type_typecode(dmi);
    break;

  default:
    g_warning("NOT producing demarshaller for %s",
	      IDL_tree_type_names[IDL_NODE_TYPE(ts)]);
    break;

  }

  loopvarnames--;
}

static void
cbe_output_demarshal_type_atom(CBEDemarshalInfo *mi)
{
  cbe_demarshaller_align(mi);

  if(mi->byteswap_version)
    fprintf(mi->of, "  GET_ATOM(%s);\n", mi->param_name);
  else {
    fprintf(mi->of, "  %s = *((", mi->param_name);
    orbit_cbe_write_typespec(mi->of, mi->typespec);
    fprintf(mi->of, " *)GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur);\n");
    fprintf(mi->of, "  ((char *)GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur) += sizeof(");
    orbit_cbe_write_typespec(mi->of, mi->typespec);
    fprintf(mi->of, ");\n");
  }

  mi->previous_param = mi->typespec;
}

static void cbe_output_demarshal_type_integer(CBEDemarshalInfo *mi)
{
  cbe_output_demarshal_type_atom(mi);
}

static void cbe_output_demarshal_type_float(CBEDemarshalInfo *mi)
{
  cbe_output_demarshal_type_atom(mi);
}

static void cbe_output_demarshal_type_fixed(CBEDemarshalInfo *mi)
{
  g_assert(!"Not yet implemented\n");
}

static void cbe_output_demarshal_type_char(CBEDemarshalInfo *mi)
{
  cbe_output_demarshal_type_atom(mi);
}

static void cbe_output_demarshal_type_wide_char(CBEDemarshalInfo *mi)
{
  cbe_output_demarshal_type_atom(mi);
}

static void cbe_output_demarshal_type_string(CBEDemarshalInfo *mi)
{
  CBEDemarshalInfo submi = *mi;
  IDL_tree anint = IDL_type_integer_new(0, IDL_INTEGER_TYPE_LONG);

  submi.param_name = "len";
  submi.typespec = anint;
  submi.param = submi.typespec;
  fprintf(mi->of, "  {\n");
  fprintf(mi->of, "    GIOP_unsigned_long len;\n");
  cbe_output_demarshaller(&submi);
  IDL_tree_free(anint);

  if(mi->allocate_memory) {
    fprintf(mi->of, "    %s = CORBA_string_alloc(len);\n", mi->param_name);
    fprintf(mi->of, "    if(len)\n");
    fprintf(mi->of,
	    "    strncpy(%s,\nGIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur,\nlen);\n",
	    mi->param_name);
    fprintf(mi->of, "    else\n(%s)[0] = '\\0';\n", mi->param_name);
    fprintf(mi->of, "    ((char *)GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur) += len;\n");
  } else {
    /* We cheat, and use the 0's in the length as the zero-byte of the zero-length string */
    fprintf(mi->of, "    %s = len?(GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur):(((char *)GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur) - sizeof(CORBA_unsigned_long));", mi->param_name);
    fprintf(mi->of, "    ((char *)GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur) += len;\n");
  }

  fprintf(mi->of, "  }\n");

  mi->previous_param = mi->typespec;
}

static void cbe_output_demarshal_type_wide_string(CBEDemarshalInfo *mi)
{
  g_assert(!"Not yet implemented\n");
}

static void cbe_output_demarshal_type_boolean(CBEDemarshalInfo *mi)
{
  cbe_output_demarshal_type_atom(mi);
}

static void cbe_output_demarshal_type_octet(CBEDemarshalInfo *mi)
{
  cbe_output_demarshal_type_atom(mi);
}

static void cbe_output_demarshal_type_any(CBEDemarshalInfo *mi)
{
  fprintf(mi->of, "ORBit_demarshal_any(_ORBIT_recv_buffer, &(%s), %s, %s);\n",
	  mi->param_name, mi->allocate_memory?"CORBA_TRUE":"CORBA_FALSE",
	  mi->orb_name);
}

static void cbe_output_demarshal_type_object(CBEDemarshalInfo *mi)
{
  fprintf(mi->of, "%s = ORBit_demarshal_object(_ORBIT_recv_buffer, %s);\n",
	  mi->param_name, mi->orb_name);
}

static void cbe_output_demarshal_type_enum(CBEDemarshalInfo *mi)
{
  cbe_output_demarshal_type_atom(mi);
}

static void cbe_output_demarshal_type_sequence(CBEDemarshalInfo *mi)
{
  GString *tmpstr = g_string_new(NULL);
  CBEDemarshalInfo subdmi = *mi;
  IDL_tree anint;

  g_string_sprintf(tmpstr, "(%s)._length", mi->param_name);
  subdmi.param_name = tmpstr->str;
  subdmi.typespec = anint = subdmi.param =
    IDL_type_integer_new(0, IDL_INTEGER_TYPE_LONG);
  IDL_TYPE_INTEGER(anint).f_signed = 0;

  cbe_output_demarshaller(&subdmi);

  subdmi.param = subdmi.typespec = IDL_TYPE_SEQUENCE(mi->typespec).simple_type_spec;

  if(cbe_type_is_fixed_length(subdmi.param)) {
    fprintf(mi->of, "(%s)._release = CORBA_%s;\n", mi->param_name,
	    mi->allocate_memory?"TRUE":"FALSE");

    if(mi->allocate_memory) {
      fprintf(mi->of,
	      "(%s)._buffer = memcpy(ORBit_alloc((%s)._length * sizeof((%s)._buffer[0]), NULL, NULL), GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur, (%s)._length * sizeof((%s)._buffer[0]));\n",
	      mi->param_name, mi->param_name, mi->param_name,
	      mi->param_name, mi->param_name);
    } else {
      fprintf(mi->of,
	      "(%s)._buffer = GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur;\n",
	      mi->param_name);
    }

    fprintf(mi->of, "((char *)GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur) += ((%s)._length) * sizeof((%s)._buffer[0]);\n", mi->param_name, mi->param_name);

    mi->previous_param = subdmi.typespec;
  } else {
    fprintf(mi->of, "(%s)._release = CORBA_TRUE;\n", mi->param_name);

    fprintf(mi->of, "(%s)._buffer = CORBA_sequence_", mi->param_name);
    orbit_cbe_write_typename(mi->of, IDL_TYPE_SEQUENCE(mi->typespec).simple_type_spec);
    fprintf(mi->of, "_allocbuf((%s)._length);\n", mi->param_name);

    if(cbe_get_type_head_alignment(subdmi.param)
       <= cbe_get_type_tail_alignment(subdmi.param)) {
      cbe_demarshaller_align(&subdmi);
      subdmi.previous_param = subdmi.param;
    } else
      subdmi.previous_param = NULL;

    g_string_sprintf(tmpstr, "(%s)._buffer[i%d]", mi->param_name, loopvarnames);
    subdmi.param_name = tmpstr->str;
    fprintf(mi->of, "  {\n    int i%d;\n    for(i%d = 0; i%d < (%s)._length; i%d++) {\n",
	    loopvarnames,
	    loopvarnames, loopvarnames, mi->param_name, loopvarnames);
    cbe_output_demarshaller(&subdmi);
    fprintf(mi->of, "  }\n}\n");
    IDL_tree_free(anint);

    mi->previous_param = subdmi.previous_param;
  }

  g_string_free(tmpstr, TRUE);
}

static void cbe_output_demarshal_type_array(CBEDemarshalInfo *mi)
{
  GString *tmpstr = g_string_new(NULL);
  int dimn;
  IDL_tree curitem, dcl;
  CBEDemarshalInfo subdmi = *mi;
  char *id;
  gboolean needs_per_loop_alignment;
  
  dcl = IDL_get_parent_node(mi->typespec, IDLN_ANY, NULL); /* list */
  dcl = IDL_get_parent_node(dcl, IDLN_ANY, NULL); /* member|type_dcl */

  g_assert(dcl && (IDL_NODE_TYPE(dcl) == IDLN_MEMBER || IDL_NODE_TYPE(dcl) == IDLN_TYPE_DCL));

  if((!cbe_type_is_endian_sensitive(IDL_TYPE_DCL(dcl).type_spec)
      || !mi->byteswap_version)
     && cbe_type_is_fixed_length(IDL_TYPE_DCL(dcl).type_spec)) {

    subdmi.typespec = subdmi.param = IDL_TYPE_DCL(dcl).type_spec;
    cbe_demarshaller_align(&subdmi);

    id = orbit_cbe_get_typename(mi->typespec);
    /* need to test this... :-) */
    fprintf(mi->of,
	    "  memcpy(%s, GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur, sizeof(%s));\n",
	    mi->param_name, (IDL_NODE_TYPE(dcl)==IDLN_MEMBER)?(mi->param_name):id);
    fprintf(mi->of,
	    "  ((char *)GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur) += sizeof(%s);\n",
	    (IDL_NODE_TYPE(dcl)==IDLN_MEMBER)?(mi->param_name):id);

    g_free(id);

    mi->previous_param = IDL_TYPE_DCL(dcl).type_spec;
    return;
  }

  fprintf(mi->of, "  {\n");

  dimn = 0;

  fprintf(mi->of, "  int n%d", loopvarnames + dimn);

  for(dimn = 1, curitem = IDL_LIST(IDL_TYPE_ARRAY(mi->typespec).size_list).next;
      curitem; dimn++, curitem = IDL_LIST(curitem).next) {
    fprintf(mi->of, ", n%d", dimn + loopvarnames);
  }
  fprintf(mi->of, ";\n");

  if(cbe_get_type_head_alignment(IDL_TYPE_DCL(dcl).type_spec)
     <= cbe_get_type_tail_alignment(IDL_TYPE_DCL(dcl).type_spec)) {
    subdmi.param = subdmi.typespec = IDL_TYPE_DCL(dcl).type_spec;
    cbe_demarshaller_align(&subdmi);
    needs_per_loop_alignment = FALSE;
  } else
    needs_per_loop_alignment = TRUE;

  for(dimn = 0, curitem = IDL_TYPE_ARRAY(mi->typespec).size_list;
      curitem; dimn++, curitem = IDL_LIST(curitem).next) {
    fprintf(mi->of, "  for(n%d = 0; n%d < %" IDL_LL "d; n%d++) {\n",
	    dimn + loopvarnames,
	    dimn + loopvarnames,
	    IDL_INTEGER(IDL_LIST(curitem).data).value,
	    dimn + loopvarnames);
  }

  g_string_sprintf(tmpstr, "(%s)", mi->param_name);
  for(dimn = 0, curitem = IDL_TYPE_ARRAY(mi->typespec).size_list;
      curitem; dimn++, curitem = IDL_LIST(curitem).next) {
    g_string_sprintfa(tmpstr, "[n%d]", dimn + loopvarnames);
  }

  subdmi.param = subdmi.typespec = IDL_TYPE_DCL(dcl).type_spec;
  if(!needs_per_loop_alignment)
    subdmi.previous_param = subdmi.param;
  else
    subdmi.previous_param = NULL;

  subdmi.param_name = tmpstr->str;
  loopvarnames += dimn;
  cbe_output_demarshaller(&subdmi);
  loopvarnames -= dimn;

  for(dimn--, curitem = IDL_TYPE_ARRAY(mi->typespec).size_list;
      curitem; dimn--, curitem = IDL_LIST(curitem).next) {
    fprintf(mi->of, "  } /* end loop for n%d */\n", dimn + loopvarnames);
  }

  fprintf(mi->of, "  }\n");

  g_string_free(tmpstr, TRUE);

  mi->previous_param = subdmi.previous_param;
}

static void cbe_output_demarshal_type_struct(CBEDemarshalInfo *mi)
{
  GString *tmpstr = g_string_new(NULL);
  CBEDemarshalInfo subdmi = *mi;
  IDL_tree curitem, curdcl, theid = NULL;

  if(!IDL_TYPE_STRUCT(mi->typespec).member_list)
    return;

  if(!mi->byteswap_version && cbe_type_is_fixed_length(mi->typespec)) {

    subdmi.typespec = subdmi.param = IDL_LIST(IDL_TYPE_STRUCT(mi->typespec).member_list).data;
    cbe_demarshaller_align(&subdmi);

    fprintf(mi->of, "  memcpy(&%s, GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur, sizeof(%s));\n", mi->param_name, mi->param_name);
    fprintf(mi->of,
	    "  ((char *)GIOP_RECV_BUFFER(_ORBIT_recv_buffer)->cur) += sizeof(%s);\n",
	    mi->param_name);

    mi->previous_param = mi->typespec;
    return;
  }

  for(curitem = IDL_TYPE_STRUCT(mi->typespec).member_list;
      curitem; curitem = IDL_LIST(curitem).next) {

    for(curdcl = IDL_MEMBER(IDL_LIST(curitem).data).dcls; curdcl;
	curdcl = IDL_LIST(curdcl).next) {
      subdmi.param = subdmi.typespec =
	cbe_get_typespec(IDL_LIST(curdcl).data);

      switch(IDL_NODE_TYPE(IDL_LIST(curdcl).data)) {
      case IDLN_TYPE_ARRAY:
	theid = IDL_TYPE_ARRAY(IDL_LIST(curdcl).data).ident;
	break;
      case IDLN_IDENT:
	theid = IDL_LIST(curdcl).data;
	break;
      default:
	g_error("Unknown member dcl node type %s",
		IDL_tree_type_names[IDL_NODE_TYPE(IDL_LIST(curdcl).data)]);
      }

      g_string_sprintf(tmpstr, "(%s).%s", mi->param_name,
		       IDL_IDENT(theid).str);
      subdmi.param_name = tmpstr->str;
      cbe_output_demarshaller(&subdmi);
    }
  }
  g_string_free(tmpstr, TRUE);

  mi->previous_param = subdmi.param;
}

static void cbe_output_demarshal_type_union(CBEDemarshalInfo *mi)
{
  GString *tmpstr = g_string_new(NULL);
  CBEDemarshalInfo subdmi = *mi;
  IDL_tree curitem;

  g_string_sprintf(tmpstr, "(%s)._d", mi->param_name);
  subdmi.param_name = tmpstr->str;

  subdmi.param = subdmi.typespec = IDL_TYPE_UNION(mi->typespec).switch_type_spec;

  cbe_output_demarshaller(&subdmi);

  subdmi.had_default = FALSE;

  fprintf(mi->of, "  switch((%s)._d) {\n", mi->param_name);

  g_string_sprintf(tmpstr, "(%s)._u", mi->param_name);
  subdmi.param_name = tmpstr->str;

  for(curitem = IDL_TYPE_UNION(mi->typespec).switch_body;
      curitem; curitem = IDL_LIST(curitem).next) {

    subdmi.param = subdmi.typespec = IDL_LIST(curitem).data;

    cbe_output_demarshaller(&subdmi);
  }
  if(!subdmi.had_default)
    fprintf(mi->of, "default:\n");
  fprintf(mi->of, "}\n\n");

  /* There is no way we can know the exact alignment at compile time,
     but if we put the union as a whole in here, the MINimum possible
     alignment can be calculated */

  mi->previous_param = mi->param;

  g_string_free(tmpstr, TRUE);
}

static void
cbe_output_demarshal_case_stmt(CBEDemarshalInfo *mi)
{
  GString *tmpstr = g_string_new(NULL);
  IDL_tree curitem;
  CBEDemarshalInfo subdmi = *mi;

  for(curitem = IDL_CASE_STMT(mi->typespec).labels;
      curitem;
      curitem = IDL_LIST(curitem).next) {
    if(IDL_LIST(curitem).data) {
      fprintf(mi->of, "  case ");
      orbit_cbe_write_const(mi->of, IDL_LIST(curitem).data);
      fprintf(mi->of, ":\n");
    } else {
      fprintf(mi->of, "  default:\n");
      mi->had_default = TRUE;
    }
  }  

  g_string_sprintf(tmpstr, "(%s).%s", mi->param_name,
		   IDL_IDENT(IDL_LIST(IDL_MEMBER(IDL_CASE_STMT(mi->typespec).element_spec).dcls).data).str);
  subdmi.param_name = tmpstr->str;
  subdmi.param = subdmi.typespec = IDL_MEMBER(IDL_CASE_STMT(mi->typespec).element_spec).type_spec;

  cbe_output_demarshaller(&subdmi);

  mi->previous_param = subdmi.previous_param;

  g_string_free(tmpstr, TRUE);
  fprintf(mi->of, "break;");
}

static void cbe_output_demarshal_type_typecode(CBEDemarshalInfo *mi)
{
  cbe_demarshaller_align(mi);

  fprintf(mi->of, "ORBit_decode_CORBA_TypeCode(&%s, _ORBIT_recv_buffer);\n",
	  mi->param_name);
}
