%% ``The contents of this file are subject to the Erlang Public License, 
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%%
-module(ic_cclient).

%% This module implements generation of C client code, where the
%% client acts as an Erlang C-node, and where the communication thus
%% is according to the Erlang distribution protocol.
%%
%% TODO:
%% 1. ic_cbe:extract_info/3 is a silly name. 
%% 2. ParList, ParTypes, TypeList etc. what are they?
%% 3. Zip is merging two lists (of the same length).
%%

-export([do_gen/3]).

%%------------------------------------------------------------
%% IMPLEMENTATION CONVENTIONS
%%------------------------------------------------------------
%% Functions:
%%
%% mk_*       returns things to be used. No side effects.
%% emit_*     Writes to file. Has Fd in arguments.
%% gen_*      Same, but has no Fd. Usually for larger things.
%%
%% Terminology for generating C:
%%
%% par_list   list of identifiers with types, types only, or with 
%%            parameters (arguments) only. 
%% arg_list   list of identifiers only (for function calls)
%%

%%------------------------------------------------------------
%% Internal stuff
%%------------------------------------------------------------

-import(lists, [foreach/2, foldl/3, foldr/3]).
-import(ic_codegen, [emit/2, emit/3, emit/4, emit_c_enc_rpt/4, emit_c_dec_rpt/4]).

-include("icforms.hrl").
-include("ic.hrl").
-include_lib("stdlib/include/erl_compile.hrl").

-define(IC_HEADER, "ic.h").
-define(ERL_INTERFACEHEADER, "erl_interface.h").
-define(EICONVHEADER, "ei.h").
-define(ERLANGATOMLENGTH, "256").


%%------------------------------------------------------------
%% ENTRY POINT
%%------------------------------------------------------------
do_gen(G, File, Form) -> 
    OeName = ic_util:mk_oe_name(G, remove_ext(ic_util:to_list(File))), 
    G2 = ic_file:filename_push(G, [], OeName, c), 
    gen_headers(G2, [], Form), 
    R = gen(G2, [], Form), 
    ic_file:filename_pop(G2, c), 
    R.

remove_ext(File) ->
    filename:rootname(filename:basename(File)).

%%------------------------------------------------------------
%%
%% Generate client side C stubs. 
%%
%% - each module definition results in a separate file.
%% - each interface definition results in a separate file.
%%
%%  G = record(genobj) (see ic.hrl)
%%  N = scoped names in reverse
%%  X = current form to consider. 
%%------------------------------------------------------------

gen(G, N, [X| Xs]) when record(X, preproc) ->
    G1 = change_file_stack(G, N, X), 
    gen(G1, N, Xs);

gen(G, N, [X| Xs]) when record(X, module) ->
    CD = ic_code:codeDirective(G, X), 
    G2 = ic_file:filename_push(G, N, X, CD), 
    N2 = [ic_forms:get_id2(X)| N], 
    gen_headers(G2, N2, X), 
    gen(G2, N2, ic_forms:get_body(X)), 
    G3 = ic_file:filename_pop(G2, CD), 
    gen(G3, N, Xs);

gen(G, N, [X| Xs]) when record(X, interface) ->

    G2 = ic_file:filename_push(G, N, X, c), 
    N2 = [ic_forms:get_id2(X)| N], 

    %% Sets the temporary variable counter.
    put(op_variable_count, 0), 
    put(tmp_declarations, []), 

    gen_headers(G2, N2, X), 

    gen(G2, N2, ic_forms:get_body(X)), 

    lists:foreach(
      fun({_Name, Body}) -> 
	      gen(G2, N2, Body) end, 
      X#interface.inherit_body), 

    %% Generate Prototypes
    gen_prototypes(G2, N2, X), 

    %% Generate generic preparation for decoding
    gen_receive_info(G2, N2, X), 

    G3 = ic_file:filename_pop(G2, c), 

    gen(G3, N, Xs);

gen(G, N, [X| Xs]) when record(X, const) ->
    emit_constant(G, N, X), 
    gen(G, N, Xs);

gen(G, N, [X| Xs]) when record(X, op) ->
    {OpName, ArgNames, RetParTypes} = ic_cbe:extract_info(G, N, X), 
    %% XXX Note: N is the list of scoped ids of the *interface*.
    gen_operation(G, N, X, OpName, ArgNames, RetParTypes), 
    gen_encoder(G, N, X, OpName, ArgNames, RetParTypes), 
    gen_decoder(G, N, X, OpName, ArgNames, RetParTypes), 
    gen(G, N, Xs);

gen(G, N, [X| Xs]) when record(X, attr) ->
    gen(G, N, Xs);

gen(G, N, [X| Xs]) when record(X, except) ->
    icstruct:except_gen(G, N, X, c), 
    gen(G, N, Xs);

gen(G, N, [X| Xs]) when record(X, enum) ->
    icenum:enum_gen(G, N, X, c), 
    gen(G, N, Xs);

gen(G, N, [X| Xs]) when record(X, typedef) ->
    icstruct:struct_gen(G, N, X, c),
    gen(G, N, Xs);

gen(G, N, [X| Xs]) when record(X, struct) ->
    icstruct:struct_gen(G, N, X, c),
    gen(G, N, Xs);

gen(G, N, [X| Xs]) when record(X, union) ->
    icstruct:struct_gen(G, N, X, c),
    gen(G, N, Xs);

gen(G, N, [_X| Xs]) ->
    %% XXX Should have debug message here.
    gen(G, N, Xs);

gen(_G, _N, []) -> 
    ok.

%%------------------------------------------------------------
%% Change file stack
%%------------------------------------------------------------

change_file_stack(G, _N, X) when X#preproc.cat == line_nr ->
    Id = ic_forms:get_id2(X), 
    Flags = X#preproc.aux, 
    case Flags of
	[] -> 
	    ic_genobj:push_file(G, Id);
	_ ->
	    foldr(
	      fun({_, _, "1"}, G1) -> 
		      ic_genobj:push_file(G1, Id);
		 ({_, _, "2"}, G1) -> 
		      ic_genobj:pop_file(G1, Id);
		 ({_, _, "3"}, G1) -> 
		      ic_genobj:sys_file(G1, Id) 
	      end, G, Flags)
    end;
change_file_stack(G, _N, _X) ->
    G.

%%------------------------------------------------------------
%% Generate headers in stubfiles and header files 
%%------------------------------------------------------------

gen_headers(G, N, X) when record(X, interface) ->
    case ic_genobj:is_hrlfile_open(G) of
	true ->
	    %% Set the temporary variable counter
	    put(op_variable_count, 0), 
	    put(tmp_declarations, []), 
	    HFd = ic_genobj:hrlfiled(G), 
	    IncludeFileStack = ic_genobj:include_file_stack(G), 
	    L = length(N), 
	    Filename =
		if
		    L < 2 ->
			lists:nth(L + 1, IncludeFileStack);
		    true ->
			lists:nth(2, IncludeFileStack)
		end, 
	    emit(HFd, "#include \"~s\"\n", [filename:basename(Filename)]), 
	    ic_code:gen_includes(HFd, G, X, c_client), 

	    IfName = ic_util:to_undersc(N), 
	    IfNameUC = ic_util:to_uppercase(IfName), 
	    emit(HFd, "\n#ifndef __~s__\n", [IfNameUC]), 
	    emit(HFd, "#define __~s__\n", [IfNameUC]), 
	    LCmt = io_lib:format("Interface object definition: ~s", [IfName]), 
	    ic_codegen:mcomment_light(HFd, [LCmt], c), 
	    case get_c_timeout(G, "") of
		"" ->
		    ok;
		{SendTmo, RecvTmo} ->
		    emit(HFd, "#define OE_~s_SEND_TIMEOUT  ~s\n", 
			 [IfNameUC, SendTmo]), 
		    emit(HFd, "#define OE_~s_RECV_TIMEOUT  ~s\n", 
			 [IfNameUC, RecvTmo]), 
		    emit(HFd, "#ifndef EI_HAVE_TIMEOUT\n"),
		    emit(HFd, "#error Functions for send and receive with "
			 "timeout not defined in erl_interface\n"),
		    emit(HFd, "#endif\n\n")
	    end,

	    emit(HFd, "typedef CORBA_Object ~s;\n", [IfName]), 
	    emit(HFd, "#endif\n\n");

	false -> ok
    end, 
    case ic_genobj:is_stubfile_open(G) of
	true ->
	    Fd = ic_genobj:stubfiled(G), 
	    ic_codegen:nl(Fd), 
	    emit(Fd, "#include <stdlib.h>\n"), 
	    emit(Fd, "#include <string.h>\n"), 
	    case ic_options:get_opt(G, c_report) of 
		true ->
		    emit(Fd, "#ifndef OE_C_REPORT\n"), 
		    emit(Fd, "#define OE_C_REPORT\n"), 
		    emit(Fd, "#include <stdio.h>\n"), 
		    emit(Fd, "#endif\n");
		_  ->
		    ok
	    end,
	    emit(Fd, "#include \"~s\"\n", [?IC_HEADER]), 
	    emit(Fd, "#include \"~s\"\n", [?ERL_INTERFACEHEADER]), 
	    emit(Fd, "#include \"~s\"\n", [?EICONVHEADER]), 
	    emit(Fd, "#include \"~s\"\n", 
		 [filename:basename(ic_genobj:include_file(G))]), 
	    ic_codegen:nl(Fd), ic_codegen:nl(Fd), 
	    Fd;					% XXX ??
	false ->
	    ok
    end;

%% Some items have extra includes
gen_headers(G, N, X) when record(X, module) ->
    case ic_genobj:is_hrlfile_open(G) of
	true ->
	    HFd = ic_genobj:hrlfiled(G), 
	    IncludeFileStack = ic_genobj:include_file_stack(G), 
	    Filename = lists:nth(length(N) + 1, IncludeFileStack), 
	    emit(HFd, "#include \"~s\"\n", [filename:basename(Filename)]), 
	    ic_code:gen_includes(HFd, G, X, c_client);
	false -> ok
    end;
gen_headers(G, [], _X) -> 
    case ic_genobj:is_hrlfile_open(G) of
	true ->
	    HFd = ic_genobj:hrlfiled(G), 
	    case ic_options:get_opt(G, c_report) of 
		true ->
		    emit(HFd, "#ifndef OE_C_REPORT\n"), 
		    emit(HFd, "#define OE_C_REPORT\n"), 
		    emit(HFd, "#include <stdio.h>\n"), 
		    emit(HFd, "#endif\n");
		_  ->
		    ok
	    end,
	    emit(HFd, "#include \"~s\"\n", [?IC_HEADER]), 
	    emit(HFd, "#include \"~s\"\n", [?ERL_INTERFACEHEADER]), 
	    emit(HFd, "#include \"~s\"\n", [?EICONVHEADER]), 
	    ic_code:gen_includes(HFd, G, c_client);
	false -> ok
    end;
gen_headers(_G, _N, _X) -> 
    ok.


%%------------------------------------------------------------
%% Generate all prototypes (for interface)
%%------------------------------------------------------------
gen_prototypes(G, N, X) ->
    case ic_genobj:is_hrlfile_open(G) of
	false -> 
	    ok;
	true ->
	    HFd = ic_genobj:hrlfiled(G), 
	    IfName = ic_util:to_undersc(N), 

	    %% Emit generated function prototypes
	    emit(HFd, "\n/* Operation functions  */\n"), 
	    lists:foreach(fun({_Name, Body}) ->
				  emit_operation_prototypes(G, HFd, N, Body)
			  end, [{x, ic_forms:get_body(X)}| 
				X#interface.inherit_body]), 

	    UserProto = get_user_proto(G, false),
	    %% Emit generic function prototypes
	    case UserProto of
		false ->
		    ok;
		UserProto ->
		    emit(HFd, 
			 "\n/* Generic user defined encoders */\n"), 
		    emit(HFd, 
			 "int ~s_prepare_notification_encoding("
			 "CORBA_Environment*);"
			 "\n", [UserProto]), 
		    emit(HFd, 
			 "int ~s_prepare_request_encoding(CORBA_Environment*);"
			 "\n", [UserProto])
	    end,
	    %% Emit encoding function prototypes
	    emit(HFd, "\n/* Input encoders */\n"), 
	    lists:foreach(fun({_Name, Body}) ->
				  emit_encoder_prototypes(G, HFd, N, Body) 
			  end, 
			  [{x, ic_forms:get_body(X)}| 
			   X#interface.inherit_body]), 

	    %% Emit generic function prototypes
	    emit(HFd, "\n/* Generic decoders */\n"), 
	    emit(HFd, "int ~s__receive_info(~s, CORBA_Environment*);\n", 
		 [IfName, IfName]), 

	    case UserProto of
		false ->
		    ok;
		UserProto ->
		    emit(HFd, "\n/* Generic user defined decoders */\n"), 
		    emit(HFd, 
			 "int ~s_prepare_reply_decoding(CORBA_Environment*);"
			 "\n", [UserProto]) 
	    end,
	    %% Emit decode function prototypes
	    emit(HFd, "\n/* Result decoders */\n"), 
	    lists:foreach(fun({_Name, Body}) ->
				  emit_decoder_prototypes(G, HFd, N, Body) 
			  end, [{x, ic_forms:get_body(X)}| 
				X#interface.inherit_body]), 
	    case UserProto of
		false ->
		    ok;
		UserProto ->
		    %% Emit generic send and receive_prototypes
		    {Sfx, TmoType} = case get_c_timeout(G, "") of
				     "" ->
					 {"", ""};
				     _ ->
					 {"_tmo", ", unsigned int"} 
			     end,
		    emit(HFd, 
			 "\n/* Generic user defined send and receive "
			 "functions */\n"), 
		    emit(HFd, 
			 "int ~s_send_notification~s(CORBA_Environment*~s);\n",
			 [UserProto, Sfx, TmoType]), 
		    emit(HFd, 
			 "int ~s_send_request_and_receive_reply~s("
			 "CORBA_Environment*~s~s);\n", 
			 [UserProto, Sfx, TmoType, TmoType])
	    end
    end.

%%------------------------------------------------------------
%% Generate receive_info() (generic part for message reception) 
%% (for interface). For backward compatibility only.
%%------------------------------------------------------------

gen_receive_info(G, N, _X) ->
    case ic_genobj:is_stubfile_open(G) of
	false ->
	    ok;
	true ->
	    Fd = ic_genobj:stubfiled(G), 
	    IfName = ic_util:to_undersc(N), 
	    UserProto = get_user_proto(G, oe),
	    Code = 
		"
/*
 *  Generic function, used to return received message information.
 *  Not used by oneways. Always generated. For backward compatibility only.
 */

int ~s__receive_info(~s oe_obj, CORBA_Environment *oe_env)
{
  return  ~s_prepare_reply_decoding(oe_env);
}\n", 
        emit(Fd, Code, [IfName, IfName, UserProto])
end.

%%------------------------------------------------------------
%% Emit constant
%%------------------------------------------------------------

emit_constant(G, N, ConstRecord) ->
    case ic_genobj:is_hrlfile_open(G) of
	false -> ok;
	true ->
	    Fd = ic_genobj:hrlfiled(G), 
	    CName = ic_util:to_undersc(
		      [ic_forms:get_id(ConstRecord#const.id)| N]), 
	    UCName = ic_util:to_uppercase(CName), 

	    emit(Fd, "\n#ifndef __~s__\n", [UCName]), 
	    emit(Fd, "#define __~s__\n", [UCName]), 

	    emit(Fd, "/* Constant: ~s */\n", [CName]), 

	    if record(ConstRecord#const.type, wstring) -> 
		    %% If wstring, add 'L' 
		    emit(Fd, "#define ~s L~p\n", 
			 [CName, ConstRecord#const.val]);
	       true ->
		    emit(Fd, "#define ~s ~p\n", 
			 [CName, ConstRecord#const.val])
	    end, 
	    emit(Fd, "#endif\n\n")
    end.

%%------------------------------------------------------------
%% Generate operation (for interface)
%%------------------------------------------------------------

%% N is the list of scoped ids of the *interface*. 
%% X is the operation
gen_operation(G, N, X, OpName, ArgNames, RetParTypes) ->
    case ic_genobj:is_stubfile_open(G) of
	true ->
	    do_gen_operation(G, N, X, OpName, ArgNames, RetParTypes);
	false ->
	    ok
    end.

do_gen_operation(G, N, X, OpName, ArgNames, RetParTypes) ->
    Fd = ic_genobj:stubfiled(G), 
    IfName = ic_util:to_undersc(N), 
    IfNameUC = ic_util:to_uppercase(IfName), 

    {R, ParTypes, _} = RetParTypes, 

    IsOneway = ic_forms:is_oneway(X),

    emit(Fd, "\n"
	 "/***\n"
	 " ***  Operation function \"~s\" ~s\n"
	 " ***/\n\n", 
	 [OpName, ifelse(IsOneway, "(oneway)", "")]),

    RV = element(1, R), 
    Ret = case IsOneway of
	      false ->
		  if RV /= void -> 
			  mk_ret_type(G, N, R);
		     true -> 
			  "void"
		  end;
	      true ->
		  "void"
	  end, 
    ParListStr = ic_util:chain(mk_par_type_list(G, N, X, [in, out], 
						[types, args], 
						ParTypes, ArgNames), ", "),
    emit(Fd, 
	 "~s ~s(~s, ~sCORBA_Environment *oe_env)\n{\n",
	 [Ret, OpName, [IfName, " ", "oe_obj"], ParListStr]), 

    case IsOneway of
	true ->
	    ok;
	false ->
	    case ictype:isArray(G, N, R) of
		true ->
		    emit(Fd, "  ~s oe_return = NULL;\n\n", 
			 [mk_ret_type(G, N, R)]);
		false ->
		    if RV /= void ->
			    emit(Fd, "  ~s oe_return;\n\n", 
				 [Ret]);
		       true ->
			    ok
		    end
	    end, 
	    emit(Fd, 
		 "  /* Initiating the message reference */\n" 
		 "  ic_init_ref(oe_env, &oe_env->_unique);\n")
    end,

    emit(Fd, 
	 "  /* Initiating exception indicator */ \n"
	 "  oe_env->_major = CORBA_NO_EXCEPTION;\n"),

    %% XXX Add pointer checks: checks of in-parameter
    %% pointers, and non-variable out-parameter pointers.

    emit(Fd,"  /* Creating ~s message */ \n", 
	 [ifelse(IsOneway, "cast", "call")]),

    EncParListStr = ic_util:chain(mk_arg_list_for_encoder(G, N, X, 
							  ParTypes, ArgNames),
				  ", "),
    emit(Fd, 
	 "  if (~s__client_enc(oe_obj, ~s""oe_env) < 0) {\n", 
	 [OpName, EncParListStr]),
    emit(Fd, 
	 "    CORBA_exc_set(oe_env, CORBA_SYSTEM_EXCEPTION, "
	 "DATA_CONVERSION, \"Cannot encode message\");\n"), 

    RetVar = ifelse(RV /= void, " oe_return", ""),
    emit_c_enc_rpt(Fd, "    ", "client operation ~s\\n====\\n", [OpName]),

    emit(Fd, "    return~s;\n  }\n", [RetVar]),

    emit(Fd,"  /* Sending ~s message */ \n", 
	 [ifelse(IsOneway, "cast", "call")]),

    UserProto = get_user_proto(G, oe),
    {Sfx, SendTmo, RecvTmo} = case get_c_timeout(G, "") of
				  "" ->
				      {"", "", ""};
				  _ ->
				      {"_tmo", 
				       [", OE_", IfNameUC, "_SEND_TIMEOUT"], 
				       [", OE_", IfNameUC, "_RECV_TIMEOUT"]} 
			      end,

    case IsOneway of
	true ->
	    emit(Fd, 
		 "  if (~s_send_notification~s(oe_env~s) < 0)\n"
		 "    return~s;\n", [UserProto, Sfx, SendTmo, RetVar]);
	false ->
	    emit(Fd, 
		 "  if (~s_send_request_and_receive_reply~s(oe_env~s~s) < 0)\n"
		 "    return~s;\n", 
		 [UserProto, Sfx, SendTmo, RecvTmo, RetVar]),

	    DecParList0 = mk_arg_list_for_decoder(G, N, X, 
						  ParTypes, ArgNames), 
	    DecParList1 = case mk_ret_type(G, N, R) of
			      "void" ->
				  DecParList0;
			      _ ->
				  ["&oe_return"| DecParList0]
		end, 

	    DecParListStr = ic_util:chain(DecParList1, ", "),
	    %% YYY Extracting results
	    emit(Fd, 
		 "  /* Extracting result value(s) */ \n" 
		 "  if (~s__client_dec(oe_obj, ~s""oe_env) < 0) {\n", 
		 [OpName, DecParListStr]), 
	    emit(Fd, 
		 "    CORBA_exc_set(oe_env, "
		 "CORBA_SYSTEM_EXCEPTION, DATA_CONVERSION, "
		 "\"Bad result value(s)\");\n"),
	    emit_c_dec_rpt(Fd, "    ", "client operation ~s\\n=====\\n", [OpName]),
	    emit(Fd, 
		 "    return~s;\n"
		 "  }\n", [RetVar])
    end,
    emit(Fd, "  return~s;\n", [RetVar]),
    emit(Fd, "}\n\n\n").

%%------------------------------------------------------------
%% Generate encoder 
%%------------------------------------------------------------
%% N is the list of scoped ids of the *interface*. 
%% X is the operation
gen_encoder(G, N, X, OpName, ArgNames, RetParTypes)->
    case ic_genobj:is_stubfile_open(G) of
	true ->
	    Fd = ic_genobj:stubfiled(G), 
	    IfName = ic_util:to_undersc(N), 
	    {_R, ParTypes, _} = RetParTypes, 
	    TypeAttrArgs = mk_type_attr_arg_list(ParTypes, ArgNames), 
	    emit(Fd, "/*\n *  Encode operation input for \"~s\"\n */\n\n", 
		 [OpName]), 
	    ParList = ic_util:chain(
			mk_par_type_list(G, N, X, [in], [types, args], 
					 ParTypes, ArgNames), ", "), 
	    emit(Fd, 
		 "int ~s__client_enc(~s oe_obj, ~s"
		 "CORBA_Environment *oe_env)\n{\n", 
		 [OpName, IfName, ParList]),

	    InTypeAttrArgs = lists:filter(fun({_, in, _}) -> true;
					     ({_, _, _}) -> false
					  end, TypeAttrArgs), 
	    case InTypeAttrArgs of 
		[] ->
		    ok;
		_ ->
		    emit(Fd, 
			 "  int oe_error_code = 0;\n\n")
	    end, 

	    emit_encodings(G, N, Fd, X, InTypeAttrArgs, 
			   ic_forms:is_oneway(X)), 
 	    emit(Fd, "  return 0;\n}\n\n"), 
	    ok;
	
	false -> 
	    ok
    end.

%%------------------------------------------------------------
%% Generate decoder
%%------------------------------------------------------------
%% N is the list of scoped ids of the *interface*. 
%% X is the operation
gen_decoder(G, N, X, OpName, ArgNames, RetParTypes)->
    case ic_forms:is_oneway(X) of
	true ->
	    ok;
	false ->
	    case ic_genobj:is_stubfile_open(G) of
		true ->
		    Fd = ic_genobj:stubfiled(G), 
		    IfName = ic_util:to_undersc(N), 
		    {R, ParTypes, _} = RetParTypes, 
		    TypeAttrArgs = mk_type_attr_arg_list(ParTypes, ArgNames), 
		    emit(Fd, "/*\n *  Decode operation results for "
			 "\"~s\"\n */\n\n", [OpName]), 
		    ParList0 = mk_par_type_list(G, N, X, [out],
						[types, args], 
						ParTypes, ArgNames),
		    PARLIST = case mk_ret_type(G, N, R) of
				  "void" ->
				      ParList0;
				  Else ->    
				      [Else ++ "* oe_return"| ParList0]
			      end, 
		    PLFCD = ic_util:chain(PARLIST, ", "), 
		    emit(Fd, 
			 "int ~s__client_dec(~s oe_obj, ~s"
			 "CORBA_Environment *oe_env)\n{\n", 
			 [OpName, IfName, PLFCD]),
		    emit(Fd, "  int oe_error_code = 0;\n"), 
		    OutTypeAttrArgs = lists:filter(fun({_, out, _}) -> true;
						      ({_, _, _}) -> false
						   end, TypeAttrArgs), 
		    emit_decodings(G, N, Fd, R, OutTypeAttrArgs),
		    emit(Fd, "  return 0;\n}\n\n"), 
		    ok;
		
		false -> 
		    ok
	    end
    end.

%%------------------------------------------------------------
%% EMIT ENCODINGS/DECODINGS
%%------------------------------------------------------------
%%------------------------------------------------------------
%% Emit encodings
%%------------------------------------------------------------
%% N is the list of scoped ids of the *interface*. 
%% X is the operation
%% emit_encodings(G, N, Fd, X, TypeAttrArgs, IsOneWay) 
%%
emit_encodings(G, N, Fd, X, TypeAttrArgs, true) ->
    %% Cast
    UserProto = get_user_proto(G, oe),
    emit(Fd, 
	 "  if (~s_prepare_notification_encoding(oe_env) < 0)\n"
	 "    return -1;\n", [UserProto]),
    emit_encodings_1(G, N, Fd, X, TypeAttrArgs);
emit_encodings(G, N, Fd, X, TypeAttrArgs, false) ->
    %% Call
    UserProto = get_user_proto(G, oe),
    emit(Fd, 
	 "  if (~s_prepare_request_encoding(oe_env) < 0)\n"
	 "    return -1;\n", [UserProto]),
    emit_encodings_1(G, N, Fd, X, TypeAttrArgs).

emit_encodings_1(G, N, Fd, X, TypeAttrArgs) ->
    {ScopedName, _, _} = ic_cbe:extract_info(G, N, X), 
    Name = case ic_options:get_opt(G, scoped_op_calls) of 
	       true -> 
		   ScopedName;
	       false ->
		   ic_forms:get_id2(X)
	   end, 
    if 
	TypeAttrArgs /= [] -> 
	    emit(Fd, "  if (oe_ei_encode_tuple_header(oe_env, ~p) < 0) {\n", 
		 [length(TypeAttrArgs) + 1]), 
	    emit_c_enc_rpt(Fd, "    ", "ei_encode_tuple_header", []),
	    emit(Fd, "    return -1;\n  }\n");
	true ->
	    ok
    end,
    emit(Fd, "  if (oe_ei_encode_atom(oe_env, ~p) < 0) {\n", [Name]), 
    emit_c_enc_rpt(Fd, "    ", "oe_ei_encode_atom", []),
    emit(Fd, "    return -1;\n  }\n"),

    foreach(fun({{'void', _}, _, _}) ->
		    ok;
		({T1, A1, N1}) ->
		    IndOp  = mk_ind_op(A1), 
		    emit_coding_comment(G, N, Fd, "Encode", IndOp, 
					  T1, N1), 
		    ic_cbe:emit_encoding_stmt(G, N, X, Fd, T1, IndOp ++ N1,
					      "oe_env->_outbuf")
	    end, TypeAttrArgs), 
    ok.

%%------------------------------------------------------------
%% Emit dedodings
%%------------------------------------------------------------
%% XXX Unfortunately we have to retain the silly `oe_first' variable,
%% since its name is hardcoded in other modules (icstruct, icunion,
%% etc).
%% N is the list of scoped ids of the *interface*. 
%% X is the operation
emit_decodings(G, N, Fd, RetType, TypeAttrArgs) ->
    if 
	TypeAttrArgs /= [] ->
	    %% Only if there are out parameters
	    emit(Fd, "  if ((oe_error_code = ei_decode_tuple_header("
		 "oe_env->_inbuf, &oe_env->_iin, "
		 "&oe_env->_received)) < 0) {\n"), 
	    emit_c_dec_rpt(Fd, "    ", "ei_decode_tuple_header", []),
	    emit(Fd, "    return oe_error_code;\n    }\n"), 
	    Len = length(TypeAttrArgs) + 1, 
	    emit(Fd, "  if (oe_env->_received != ~p) {\n", [Len]),
	    emit_c_dec_rpt(Fd, "    ", "tuple header size != ~p", [Len]),
	    emit(Fd, "    return -1;\n    }\n"); 
	true  ->
	    ok
    end,

    %% Fetch the return value
    emit_coding_comment(G, N, Fd, "Decode return value", "*", RetType, "oe_return"), 
    APars =
	case ic_cbe:is_variable_size(G, N, RetType) of
	    true ->
		emit(Fd, 
		     "  {\n"
		     "    int oe_size_count_index = oe_env->_iin;\n"
		     "    int oe_malloc_size = 0;\n"
		     "    void *oe_first = NULL;\n"),
		ic_cbe:emit_malloc_size_stmt(G, N, Fd, RetType, 
					     "oe_env->_inbuf", 
					     1, caller), 
		%% XXX Add malloc prefix from option
		emit(Fd, 
		     "    OE_MALLOC_SIZE_CHECK(oe_env, oe_malloc_size);\n" 
		     "    if ((*oe_return = oe_first = "
		     "malloc(oe_malloc_size)) == NULL) {\n"
		     "      CORBA_exc_set(oe_env, CORBA_SYSTEM_EXCEPTION, "
		     "NO_MEMORY, \"Cannot malloc\");\n" 
		     "      return -1;\n"
		     "    }\n"),
		Pars = ["*oe_return"],
		DecType = case ictype:isArray(G, N, RetType) of
			      true -> array_dyn;
			      false -> caller_dyn
			  end,
		ic_cbe:emit_decoding_stmt(G, N, Fd, RetType, 
					  "(*oe_return)", 
					  "", "oe_env->_inbuf", 1, 
					  "&oe_outindex", DecType,
					  Pars), 
		emit(Fd, "  }\n"),
		Pars;
	    false ->
		case ictype:isArray(G, N, RetType) of
		    true ->
			Pars = ["*oe_return"],
			emit(Fd, 
			     "  {\n"
			     "    int oe_size_count_index = oe_env->_iin;\n"
			     "    int oe_malloc_size = 0;\n"
			     "    void *oe_first = NULL;\n"),
			ic_cbe:emit_malloc_size_stmt(G, N, Fd, RetType, 
						     "oe_env->_inbuf", 
						     1, caller), 
			%% XXX Add malloc prefix from option
			emit(Fd, 
			     "    OE_MALLOC_SIZE_CHECK(oe_env, "
			     "oe_malloc_size);\n" 
			     "    if ((*oe_return = oe_first = "
			     "malloc(oe_malloc_size)) == NULL) {\n"
			     "      CORBA_exc_set(oe_env, "
			     "CORBA_SYSTEM_EXCEPTION, NO_MEMORY, "
			     "\"Cannot malloc\");\n" 
			     "        return -1;"
			     "    }\n"),
			ic_cbe:emit_decoding_stmt(G, N, Fd, RetType, 
						  "oe_return", "", 
						  "oe_env->_inbuf", 1, 
						  "&oe_outindex", 
						  array_fix_ret, 
						  Pars), 
			emit(Fd, "  }\n"),
			Pars;
		    false ->
			Pars = [],
			%% The last parameter "oe_outindex" is not interesting 
			%% in the static case.
			ic_cbe:emit_decoding_stmt(G, N, Fd, RetType, 
						  "oe_return", "", 
						  "oe_env->_inbuf", 1, 
						  "&oe_outindex", 
						  caller, Pars), 
			ic_codegen:nl(Fd),
			Pars
		end
	end, 

    foldl(fun({{'void', _}, _, _}, Acc) ->
		  Acc;
	     ({T, A, N1}, Acc) ->
		  emit_one_decoding(G, N, Fd, T, A, N1, Acc)
	  end, APars, TypeAttrArgs), 
    ok.

emit_one_decoding(G, N, Fd, T, A, N1, Acc) ->
    IndOp = mk_ind_op(A), 
    case ic_cbe:is_variable_size(G, N, T) of
	true ->
	    emit_coding_comment(G, N, Fd, "Decode", IndOp, 
				  T, N1), 
	    emit(Fd, 
		 "  {\n"
		 "    int oe_size_count_index = oe_env->_iin;\n"
		 "    int oe_malloc_size = 0;\n"
		 "    void *oe_first = NULL;\n"),
	    ic_cbe:emit_malloc_size_stmt(G, N, Fd, T, 
					 "oe_env->_inbuf", 
					 1, caller), 
	    %% XXX Add malloc prefix from option
	    emit(Fd, 
		 "    OE_MALLOC_SIZE_CHECK(oe_env, oe_malloc_size);\n" 
		 "    if ((~s~s = oe_first = "
		 "malloc(oe_malloc_size)) == NULL) {\n", [IndOp, N1]),
	    ic_cbe:emit_dealloc_stmts(Fd, "      ", Acc),
	    emit(Fd,
		 "      CORBA_exc_set(oe_env, CORBA_SYSTEM_EXCEPTION, "
		 "NO_MEMORY, \"Cannot malloc\");\n" 
		 "      return -1;\n"
		 "    }\n"),
	    NAcc = [IndOp ++ N1| Acc],  
	    DecType = case ictype:isArray(G, N, T) of
			  true ->
			      array_dyn;
			  false ->
			      caller_dyn
		      end,
	    ic_cbe:emit_decoding_stmt(G, N, Fd, T,  
				      "(" ++ IndOp
				      ++ N1 ++ ")", "", 
				      "oe_env->_inbuf", 1, 
				      "&oe_outindex", 
				      DecType, NAcc), 
	    emit(Fd, "  }\n"),
	    NAcc;
	false ->
	    case ictype:isArray(G, N, T) of
		true ->
		    emit_coding_comment(G, N, Fd, "Decode", "", 
					  T, N1), 
		    ic_cbe:emit_decoding_stmt(G, N, Fd, T, N1, 
					      "", "oe_env->_inbuf", 
					      1, "&oe_outindex", 
					      array_fix_out, Acc), 
		    ic_codegen:nl(Fd),
		    [N1| Acc]; 
		false ->
		    %% The last parameter "oe_outindex" is
		    %% not interesting in the static case, but
		    %% must be present anyhow.
		    emit_coding_comment(G, N, Fd, "Decode", 
					  IndOp, T, N1), 
		    ic_cbe:emit_decoding_stmt(G, N, Fd, T,  N1, 
					      "", "oe_env->_inbuf", 
					      1, "&oe_outindex", 
					      caller, Acc), 
		    ic_codegen:nl(Fd),
		    Acc
	    end
    end.

%%------------------------------------------------------------
%% GENERATE PROTOTYPES
%%------------------------------------------------------------
%%------------------------------------------------------------
%% Generate operation prototypes
%%------------------------------------------------------------
emit_operation_prototypes(G, Fd, N, Xs) ->
    lists:foreach(
      fun(X) when record(X, op) ->
	      {ScopedName, ArgNames, RetParTypes} = 
		  ic_cbe:extract_info(G, N, X), 
	      {R, ParTypes, _} = RetParTypes, 
	      IfName = ic_util:to_undersc(N), 
	      RT = mk_ret_type(G, N, R), 
	      ParList = 
		  ic_util:chain(
		    mk_par_type_list(G, N, X, [in, out], [types], 
				     ParTypes, ArgNames), 
		    ", "), 
	      emit(Fd, "~s ~s(~s, ~sCORBA_Environment*);\n", 
		   [RT, ScopedName, IfName, ParList]);
	 (_) ->
	      ok
      end, Xs).

%%------------------------------------------------------------
%% Generate encoder prototypes
%%------------------------------------------------------------
emit_encoder_prototypes(G, Fd, N, Xs) ->
    lists:foreach(
      fun(X) when record(X, op) ->
	      {ScopedName, ArgNames, RetParTypes} = 
		  ic_cbe:extract_info(G, N, X), 
	      {_R, ParTypes, _} = RetParTypes, 
	      IfName = ic_util:to_undersc(N), 
	      ParList = ic_util:chain(
			  mk_par_type_list(G, N, X, [in], [types], 
					   ParTypes, ArgNames), 
			  ", "),
	    emit(Fd, "int ~s__client_enc(~s, ~sCORBA_Environment*);\n", 
		 [ScopedName, IfName, ParList]);
	 (_) ->
	      ok
      end, Xs).

%%------------------------------------------------------------
%% Generate decoder prototypes
%%------------------------------------------------------------
emit_decoder_prototypes(G, Fd, N, Xs) ->
    lists:foreach(
      fun(X) when record(X, op) ->
	      case ic_forms:is_oneway(X) of
		  true ->
		      true;
		  false ->
		      IfName = ic_util:to_undersc(N), 
		      {ScopedName, ArgNames, RetParTypes} = 
			  ic_cbe:extract_info(G, N, X), 
		      {R, ParTypes, _} = RetParTypes, 
		      ParList0 = 
			  mk_par_type_list(G, N, X, [out], [types], 
					   ParTypes, ArgNames), 
		      PARLIST = case mk_ret_type(G, N, R) of
				    "void" ->
					ParList0;
				    Else ->
					[Else ++ "*"| ParList0]
				end, 
		      ParList = ic_util:chain(PARLIST, ", "),
		      emit(Fd, "int ~s__client_dec(~s, ~s"
			   "CORBA_Environment*);\n", 
			   [ScopedName, IfName, ParList])
	      end;
	 (_) ->
	      ok
      end, Xs).

%%------------------------------------------------------------
%% PARAMETER TYPE LISTS
%%------------------------------------------------------------
%%------------------------------------------------------------
%%  Make parameter type list
%%
%%  InOrOut = in | out | [in | out]
%%  TypesOrArgs = types | args | [types | args]
%%------------------------------------------------------------
mk_par_type_list(G, N, X, 
		 InOrOut, TypesOrArgs, Types, Args) when atom(InOrOut) ->
    mk_par_type_list(G, N, X, [InOrOut], TypesOrArgs, Types, Args);
mk_par_type_list(G, N, X, InOrOut, 
		 TypesOrArgs, Types, Args) when atom(TypesOrArgs) ->
    mk_par_type_list(G, N, X, InOrOut, [TypesOrArgs], Types, Args);
mk_par_type_list(G, N, X, InOrOut, TypesOrArgs, Types, Args) ->
    TypeAttrArgs = 
	filterzip(
	  fun(_, {inout, Arg}) ->
		  ic_error:error(G, {inout_spec_for_c, X, Arg}), 
		  false;
	     (Type, {Attr, Arg}) ->
		  case lists:member(Attr, InOrOut) of
		      true ->
			  {true, {Type, Attr, Arg}}; 
		      false ->
			  false
		  end
	  end, Types, Args),
    lists:map(
      fun({Type, Attr, Arg}) ->
	      Ctype = ic_cbe:mk_c_type(G, N, Type), 
	      IsArray = ictype:isArray(G, N, Type), 
	      IsStruct = ictype:isStruct(G, N, Type), 
	      IsUnion = ictype:isUnion(G, N, Type), 
	      Dyn = 
		  case ic_cbe:is_variable_size(G, N, Type) of
		      true ->
			  if 
			      record(Type, string) ->		"";
			      Ctype == "CORBA_char *" -> 	"";
			      record(Type, wstring) ->		"";
			      Ctype == "CORBA_wchar *" ->	"";
			      true ->
				  case IsArray of
				      true ->
					  "_slice*";
				      false ->
					  "*"
				  end
			  end;
		      false ->
			  if 
			      Attr == in, Ctype == "erlang_pid" ->
				  "*";
			      Attr == in, Ctype == "erlang_port" ->
				  "*";
			      Attr == in, Ctype == "erlang_ref" ->
				  "*";
			      Attr == in, IsStruct == true ->
				  "*";
			      Attr == in, IsUnion == true ->
				  "*";
			      Attr == in, IsArray == true ->
				  "_slice*";
			      Attr == out, IsArray == true ->
				  "_slice";
			      true ->
				  ""
			  end
		  end, 
	      IndOp = mk_ind_op(Attr),
	      case {lists:member(types, TypesOrArgs), 
		    lists:member(args, TypesOrArgs)} of
		  {true, true} ->
		      Ctype ++ Dyn ++ IndOp ++ " " ++ Arg; 
		  {true, false} ->
		      Ctype ++ Dyn ++ IndOp;
		  {false, true} ->
		      Arg;
		  {false, false} ->
		      ""
	      end
      end, TypeAttrArgs).

%%------------------------------------------------------------
%% ENCODER ARG LIST
%%------------------------------------------------------------
%%------------------------------------------------------------
%% Make encoder argument list XXX
%%------------------------------------------------------------
mk_arg_list_for_encoder(G, _N, X, Types, Args) ->
    filterzip(
      fun(_, {out, _}) ->
	      false;
	 (_, {inout, Arg}) ->
	      ic_error:error(G, {inout_spec_for_c, X, Arg}), 
	      false;
	 (_Type, {in, Arg}) ->
	      {true, Arg}
      end, Types, Args).

%%------------------------------------------------------------
%% DECODER ARG LIST
%%------------------------------------------------------------
%%------------------------------------------------------------
%% Make decoder argument list XXX
%%------------------------------------------------------------
mk_arg_list_for_decoder(G, _N, X, Types, Args) ->
    filterzip(fun(_, {in, _}) ->
		      false;
		 (_, {inout, Arg}) -> 
		      ic_error:error(G, {inout_spec_for_c, X, Arg}), 
		      false;
		 (_, {out, Arg}) ->
		      {true, Arg}
	      end, Types, Args).

%%------------------------------------------------------------
%% MISC
%%------------------------------------------------------------
%%------------------------------------------------------------
%% Make list of {Type, Attr, Arg}
%%------------------------------------------------------------
mk_type_attr_arg_list(Types, Args) ->
    filterzip(fun(Type, {Attr, Arg}) ->
		      {true, {Type, Attr, Arg}}
	      end, Types, Args).

%%------------------------------------------------------------
%% Make return type
%%------------------------------------------------------------
mk_ret_type(G, N, Type) ->
    Ctype = ic_cbe:mk_c_type(G, N, Type), 
    Dyn = case ic_cbe:is_variable_size(G, N, Type) of
	      true ->
		  if 
		      record(Type, string) ->
			  "";
		      Ctype == "CORBA_char *" ->
			  "";
		      record(Type, wstring) ->  
			  "";
		      Ctype == "CORBA_wchar *" ->  
			  "";
		      true ->
			  case ictype:isArray(G, N, Type) of
			      true ->
				  "_slice*";
			      false ->
				  "*"
			  end
		  end;
	      false ->
		  case ictype:isArray(G, N, Type) of
		      true ->
			  "_slice*";
		      false ->
			  ""
		  end
	  end, 
    Ctype ++ Dyn.


%%------------------------------------------------------------
%% Make indirection operator (to "*" or not to "*").
%%------------------------------------------------------------
mk_ind_op(in) ->
    "";
mk_ind_op(inout) ->
    error;
mk_ind_op(out) ->
    "*".

%%------------------------------------------------------------
%% Emit encoding/decoding comment
%%------------------------------------------------------------
emit_coding_comment(G, N, Fd, String, RefOrVal, Type, Name) ->
    emit(Fd, "  /* ~s parameter: ~s~s ~s */\n", 
	 [String, ic_cbe:mk_c_type(G, N, Type), RefOrVal, Name]).

%%------------------------------------------------------------
%% User protocol prefix for generic functions
%%------------------------------------------------------------
get_user_proto(G, Default) ->
    case ic_options:get_opt(G, user_protocol) of
	false ->
	    Default;
	Pfx ->
	    Pfx
     end.

%%------------------------------------------------------------
%% Timeout. Returns a string (or Default).
%%------------------------------------------------------------
get_c_timeout(G, Default) ->
    case ic_options:get_opt(G, c_timeout) of
	Tmo when integer(Tmo) ->
	    TmoStr = integer_to_list(Tmo),
	    {TmoStr, TmoStr};
	{SendTmo, RecvTmo}  when integer(SendTmo), integer(RecvTmo) ->
	    {integer_to_list(SendTmo), integer_to_list(RecvTmo)};
	false ->
	    Default
    end.

%%------------------------------------------------------------
%% ZIPPERS (merging of successive elements of two lists).
%%------------------------------------------------------------

%% zip([H1| T1], [H2| T2]) ->
%%     [{H1, H2}| zip(T1, T2)];
%% zip([], []) ->
%%     [].

filterzip(F, [H1| T1], [H2| T2]) ->
    case F(H1, H2) of
	false ->
	    filterzip(F, T1, T2);
	{true, Val} ->
	    [Val| filterzip(F, T1, T2)]
    end;
filterzip(_, [], []) ->
    [].
    

ifelse(true, A, _) ->
    A;
ifelse(false, _, B) ->
    B.
