%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2002-2025. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(asn1ct_gen_ber_bin_v2).
-moduledoc false.

%% Generate erlang module which handles (PER) encode and decode for
%% all types in an ASN.1 module

-include("asn1_records.hrl").

-export([decode_class/1]).
-export([gen_encode/2,gen_encode/3,gen_decode/2,gen_decode/3]).
-export([gen_encode_prim/4]).
-export([gen_dec_prim/3]).
-export([gen_objectset_code/2, gen_obj_code/3]).
-export([encode_tag_val/3,tag_to_integer/1]).
-export([gen_inc_decode/2,gen_decode_selected/3]).
-export([extaddgroup2sequence/1]).
-export([dialyzer_suppressions/1]).

-import(asn1ct_gen, [emit/1]).

%% The encoding of class of tag bits 8 and 7
-define(UNIVERSAL,   0).
-define(APPLICATION, 16#40).
-define(CONTEXT,     16#80).
-define(PRIVATE,     16#C0).

%% Primitive or constructed encoding % bit 6
-define(PRIMITIVE,   0).
-define(CONSTRUCTED, 2#00100000).


-define(T_ObjectDescriptor, ?UNIVERSAL bor ?PRIMITIVE bor 7).
%% Restricted character string types
-define(T_NumericString,    ?UNIVERSAL bor ?PRIMITIVE bor 18). %can be constructed
-define(T_PrintableString,  ?UNIVERSAL bor ?PRIMITIVE bor 19). %can be constructed
-define(T_TeletexString,    ?UNIVERSAL bor ?PRIMITIVE bor 20). %can be constructed
-define(T_VideotexString,   ?UNIVERSAL bor ?PRIMITIVE bor 21). %can be constructed
-define(T_IA5String,        ?UNIVERSAL bor ?PRIMITIVE bor 22). %can be constructed
-define(T_GraphicString,    ?UNIVERSAL bor ?PRIMITIVE bor 25). %can be constructed
-define(T_VisibleString,    ?UNIVERSAL bor ?PRIMITIVE bor 26). %can be constructed
-define(T_GeneralString,    ?UNIVERSAL bor ?PRIMITIVE bor 27). %can be constructed

%%===============================================================================
%%===============================================================================
%%===============================================================================
%% Generate ENCODING
%%===============================================================================
%%===============================================================================
%%===============================================================================

dialyzer_suppressions(_) ->
    case asn1ct:use_legacy_types() of
	false -> ok;
	true -> suppress({ber,encode_bit_string,4})
    end,

    %% The `no_match` option for dialyzer will suppress clauses whose
    %% patterns will never match. Here we must ensure that dialyzer
    %% sees calls to all helper functions to avoid warnings for
    %% functions that will never be called.
    %%
    %% We provide argument lists that avoid dialyzer warnings,
    %% but stills allows the compiler to do some optimizations.

    Args1 = ["element(5, Arg)", "[skip,{skip_optional,<<0>>},{chosen,<<0>>}]"],
    suppress({ber,decode_selective,2}, Args1),

    Args2 = ["[{undecoded,0},{alt_parts,0}]", "element(6, Arg)"],
    suppress({ber,decode_primitive_incomplete,2}, Args2),

    emit(["    ok.",nl]).

suppress({_,_,A}=MFA) ->
    Args = [lists:concat(["element(",I,", Arg)"]) || I <- lists:seq(1, A)],
    suppress(MFA, Args).

suppress({M,F,_}=MFA, Args) ->
    case asn1ct_func:is_used(MFA) of
	false ->
	    ok;
	true ->
	    emit(["    _ = ",{call,M,F,Args},com,nl])
    end.

%%===============================================================================
%% encode #{typedef, {pos, name, typespec}}
%%===============================================================================

gen_encode(Erules, #typedef{}=D) ->
    gen_encode_user(Erules, #typedef{}=D, true).

%%===============================================================================
%% encode #{type, {tag, def, constraint}}
%%===============================================================================

gen_encode(Erules,Typename,Type) when is_record(Type,type) ->
    InnerType = asn1ct_gen:get_inner(Type#type.def),
    ObjFun =
	case lists:keysearch(objfun,1,Type#type.tablecinf) of
	    {value,{_,_Name}} ->
		", ObjFun";
	    false ->
		""
	end,

    case asn1ct_gen:type(InnerType) of
	{constructed,bif} ->
            Func = {asis,enc_func(asn1ct_gen:list2name(Typename))},
	    emit([nl,nl,nl,"%%================================",nl,
                  "%%  ",asn1ct_gen:list2name(Typename),nl,
                  "%%================================",nl,
                  Func,"(Val, TagIn",ObjFun,") ->",nl,
                  "   "]),
	    asn1ct_gen:gen_encode_constructed(Erules,Typename,InnerType,Type);
	_ ->
	    true
    end;

%%===============================================================================
%% encode ComponentType
%%===============================================================================

gen_encode(Erules,Tname,#'ComponentType'{name=Cname,typespec=Type}) ->
    NewTname = [Cname|Tname],
    %% The tag is set to [] to avoid that it is
    %% taken into account twice, both as a component/alternative (passed as
    %% argument to the encode decode function and within the encode decode
    %% function it self.
    NewType = Type#type{tag=[]},
    gen_encode(Erules,NewTname,NewType).

gen_encode_user(Erules, #typedef{}=D, Wrapper) ->
    Typename = [D#typedef.name],
    Type = D#typedef.typespec,
    InnerType = asn1ct_gen:get_inner(Type#type.def),
    emit([nl,nl,"%%================================"]),
    emit([nl,"%%  ",Typename]),
    emit([nl,"%%================================",nl]),
    FuncName = {asis,enc_func(asn1ct_gen:list2name(Typename))},
    case Wrapper of
	true ->
	    %% This is a top-level type. Generate an 'enc_Type'/1
	    %% wrapper.
	    OTag = Type#type.tag,
	    Tag0 = [encode_tag_val(decode_class(Class), Form, Number) ||
		       #tag{class=Class,form=Form,number=Number} <- OTag],
	    Tag = lists:reverse(Tag0),
	    emit([FuncName,"(Val) ->",nl,
		  "    ",FuncName,"(Val, ",{asis,Tag},").",nl,nl]);
	false ->
	    ok
    end,
    emit([FuncName,"(Val, TagIn) ->",nl]),
    CurrentMod = get(currmod),
    case asn1ct_gen:type(InnerType) of
	{constructed,bif} ->
	    asn1ct_gen:gen_encode_constructed(Erules,Typename,InnerType,D);
	{primitive,bif} ->
	    gen_encode_prim(ber,Type,"TagIn","Val"),
	    emit([".",nl]);
	#'Externaltypereference'{module=CurrentMod,type=Etype} ->
	    emit(["   ",{asis,enc_func(Etype)},"(Val, TagIn).",nl]);
	#'Externaltypereference'{module=Emod,type=Etype} ->
	    emit(["   ",{asis,Emod},":",{asis,enc_func(Etype)},
                  "(Val, TagIn).",nl]);
	'ASN1_OPEN_TYPE' ->
	    emit(["%% OPEN TYPE",nl]),
	    gen_encode_prim(ber,
			    Type#type{def='ASN1_OPEN_TYPE'},
			    "TagIn","Val"),
	    emit([".",nl])
    end.

gen_encode_prim(_Erules, #type{}=D, DoTag, Value) ->
    BitStringConstraint = get_size_constraint(D#type.constraint),
    MaxBitStrSize = case BitStringConstraint of
			[] -> none;
			{_,'MAX'} -> none;
			{_,Max} -> Max;
			Max when is_integer(Max) -> Max
		    end,
    asn1ct_name:new(enumval),
    Type = case D#type.def of
	       'OCTET STRING'    -> restricted_string;
	       'ObjectDescriptor'-> restricted_string;
	       'NumericString'   -> restricted_string;
	       'TeletexString'   -> restricted_string;
	       'T61String'       -> restricted_string;
	       'VideotexString'  -> restricted_string;
	       'GraphicString'   -> restricted_string;
	       'VisibleString'   -> restricted_string;
	       'GeneralString'   -> restricted_string;
	       'PrintableString' -> restricted_string;
	       'IA5String'       -> restricted_string;
	       'UTCTime'         -> restricted_string;
	       'GeneralizedTime' -> restricted_string;
	       Other             -> Other
	   end,
    case Type of
	restricted_string ->
	    call(encode_restricted_string, [Value,DoTag]);
	'BOOLEAN' ->
	    call(encode_boolean, [Value,DoTag]);
	'INTEGER' ->
	    call(encode_integer, [Value,DoTag]);
	{'INTEGER',NamedNumberList} ->
	    call(encode_integer, [Value,{asis,NamedNumberList}, DoTag]);
	{'ENUMERATED',NamedNumberList={_,_}} ->
	    emit(["case ",Value," of",nl]),
	    emit_enc_enumerated_cases(NamedNumberList,DoTag);
	{'ENUMERATED',NamedNumberList} ->
	    emit(["case ",Value," of",nl]),
	    emit_enc_enumerated_cases(NamedNumberList,DoTag);
	'REAL' ->
	    asn1ct_name:new(realval),
	    asn1ct_name:new(realsize),
	    emit(["begin",nl,
		  {curr,realval}," = ",
                  {call,real_common,encode_real,[Value]},com,nl,
		  {curr,realsize}," = ",
		  {call,erlang,byte_size,[{curr,realval}]},com,nl,
		  {call,ber,encode_tags,
		   [DoTag,{curr,realval},{curr,realsize}]},nl,
		  "end"]);
	{'BIT STRING',[]} ->
	    case asn1ct:use_legacy_types() of
		false when MaxBitStrSize =:= none ->
		    call(encode_unnamed_bit_string, [Value,DoTag]);
		false ->
		    call(encode_unnamed_bit_string,
			 [{asis,MaxBitStrSize},Value,DoTag]);
		true ->
		    call(encode_bit_string,
			 [{asis,BitStringConstraint},Value,
			  {asis,[]},DoTag])
	    end;
	{'BIT STRING',NamedNumberList} ->
	    case asn1ct:use_legacy_types() of
		false when MaxBitStrSize =:= none ->
		    call(encode_named_bit_string,
			 [Value,{asis,NamedNumberList},DoTag]);
		false ->
		    call(encode_named_bit_string,
			 [{asis,MaxBitStrSize},Value,
			  {asis,NamedNumberList},DoTag]);
		true ->
		    call(encode_bit_string,
			 [{asis,BitStringConstraint},Value,
			  {asis,NamedNumberList},DoTag])
	    end;
	'NULL' ->
	    call(encode_null, [Value,DoTag]);
	'OBJECT IDENTIFIER' ->
	    call(encode_object_identifier, [Value,DoTag]);
	'RELATIVE-OID' ->
	    call(encode_relative_oid, [Value,DoTag]);
	'UniversalString' ->
	    call(encode_universal_string, [Value,DoTag]);
	'UTF8String' ->
	    call(encode_UTF8_string, [Value,DoTag]);
	'BMPString' ->
	    call(encode_BMP_string, [Value,DoTag]);
	'ASN1_OPEN_TYPE' ->
	    call(encode_open_type, [Value,DoTag])
    end.

emit_enc_enumerated_cases({L1,L2}, Tags) ->
    emit_enc_enumerated_cases(L1++L2, Tags, ext);
emit_enc_enumerated_cases(L, Tags) ->
    emit_enc_enumerated_cases(L, Tags, noext).

emit_enc_enumerated_cases([{EnumName,EnumVal}|T], Tags, Ext) ->
    {Bytes,Len} = encode_integer(EnumVal),
    emit([{asis,EnumName}," -> ",
	  {call,ber,encode_tags,[Tags,{asis,Bytes},Len]},";",nl]),
    emit_enc_enumerated_cases(T, Tags, Ext);
emit_enc_enumerated_cases([], _Tags, _Ext) ->
    %% FIXME: Should extension be handled?
    emit([{curr,enumval}," -> exit({error,{asn1, {enumerated_not_in_range,",{curr, enumval},"}}})"]),
    emit([nl,"end"]).

encode_integer(Val) ->
    Bytes =
	if
	    Val >= 0 ->
		encode_integer_pos(Val, []);
	    true ->
		encode_integer_neg(Val, [])
	end,
    {Bytes,length(Bytes)}.

encode_integer_pos(0, [B|_Acc]=L) when B < 128 ->
    L;
encode_integer_pos(N, Acc) ->
    encode_integer_pos((N bsr 8), [N band 16#ff| Acc]).

encode_integer_neg(-1, [B1|_T]=L) when B1 > 127 ->
    L;
encode_integer_neg(N, Acc) ->
    encode_integer_neg(N bsr 8, [N band 16#ff|Acc]).

%%===============================================================================
%%===============================================================================
%%===============================================================================
%% Generate DECODING
%%===============================================================================
%%===============================================================================
%%===============================================================================

%%===============================================================================
%% decode #{typedef, {pos, name, typespec}}
%%===============================================================================

gen_decode(Erules,Type) when is_record(Type,typedef) ->
    Def = Type#typedef.typespec,
    InnerTag = Def#type.tag ,

    Tag = [(decode_class(X#tag.class) bsl 10) + X#tag.number || X <- InnerTag],

    FuncName0 =
	case {asn1ct:get_gen_state_field(active),
	      asn1ct:get_gen_state_field(prefix)} of
	    {true,Pref} -> 
		%% prevent duplicated function definitions
		case asn1ct:current_sindex() of
		    I when is_integer(I), I > 0 ->
			[Pref,Type#typedef.name,"_",I];
		    _->
			[Pref,Type#typedef.name]
		end;
	    {_,_} ->
                ["dec_",Type#typedef.name]
	end,
    FuncName = {asis,list_to_atom(lists:concat(FuncName0))},
    emit([nl,nl,
          FuncName,"(Tlv) ->",nl,
          "   ",FuncName,"(Tlv, ",{asis,Tag},").",nl,nl,
          FuncName,"(Tlv, TagIn) ->",nl]),
    gen_decode_user(Erules,Type).

gen_inc_decode(Erules,Type) when is_record(Type,typedef) ->
    Prefix = asn1ct:get_gen_state_field(prefix),
    Suffix = asn1ct_gen:index2suffix(asn1ct:current_sindex()),
    FuncName0 = [Prefix,Type#typedef.name,Suffix],
    FuncName = {asis,list_to_atom(lists:concat(FuncName0))},
    emit([nl,nl,
          FuncName,"(Tlv, TagIn) ->",nl]),
    gen_decode_user(Erules,Type).

%% gen_decode_selected exported function for selected decode
gen_decode_selected(Erules,Type,FuncName) ->
    emit([FuncName,"(Bin) ->",nl]),
    Patterns = asn1ct:read_config_data(partial_decode),
    {_,Pattern} = lists:keyfind(FuncName, 1, Patterns),
    emit(["  case ",{call,ber,decode_selective,
		     [{asis,Pattern},"Bin"]}," of",nl,
	  "    {ok,Bin2} when is_binary(Bin2) ->",nl,
	  "      {Tlv,_} = ", {call,ber,ber_decode_nif,["Bin2"]},com,nl]),
    NoOkWrapper = proplists:get_bool(no_ok_wrapper, Erules#gen.options),
    case NoOkWrapper of
        true -> ok;
        false -> emit("{ok,")
    end,
    gen_decode_selected_type(Erules, Type),
    case NoOkWrapper of
        true ->
            ok;
        false ->
            emit(["};",nl,
                  "    Err -> exit({error,{selective_decode,Err}})"])
    end,
    emit(["  end.",nl]).

gen_decode_selected_type(_Erules,TypeDef) ->
    Def = TypeDef#typedef.typespec,
    InnerType = asn1ct_gen:get_inner(Def#type.def),
    BytesVar = "Tlv",
    Tag = [(decode_class(X#tag.class) bsl 10) + X#tag.number ||
	    X <- Def#type.tag],
    case asn1ct_gen:type(InnerType) of
	'ASN1_OPEN_TYPE' ->
	    asn1ct_name:new(len),
	    gen_dec_prim(Def#type{def='ASN1_OPEN_TYPE'},
			 BytesVar, Tag);
	{primitive,bif} ->
	    asn1ct_name:new(len),
	    gen_dec_prim(Def, BytesVar, Tag);
	{constructed,bif} ->
	    TopType = TypeDef#typedef.name,
            DecFunName = dec_func(asn1ct_gen:list2name(TopType)),
            emit([DecFunName,"(",BytesVar,", ",{asis,Tag},")"]);
	TheType ->
	    DecFunName = mkfuncname(TheType,dec),
	    emit([DecFunName,"(",BytesVar,
		  ", ",{asis,Tag},")"])
    end.

%%===============================================================================
%% decode #{type, {tag, def, constraint}}
%%===============================================================================

%% This gen_decode is called by the gen_decode/3 that decodes
%% ComponentType and the type of a SEQUENCE OF/SET OF for an inner
%% type of an exclusive decode top type..
gen_decode(Erules,Typename,Type) when is_record(Type,type) ->
    InnerType = asn1ct_gen:get_inner(Type#type.def),
    FunctionName =
	case asn1ct:get_gen_state_field(active) of
	    true -> 
		Pattern = asn1ct:get_gen_state_field(namelist),
		Suffix = 
		    case asn1ct:maybe_saved_sindex(Typename,Pattern) of
			I when is_integer(I),I>0 ->
			    lists:concat(["_",I]);
			_ -> ""
		    end,
		lists:concat(["'dec-inc-",
			      asn1ct_gen:list2name(Typename),Suffix]);
	    _ -> 
		lists:concat(["'dec_",asn1ct_gen:list2name(Typename)])
	end,
    case asn1ct_gen:type(InnerType) of
	{constructed,bif} ->
	    ObjFun =
		case Type#type.tablecinf of
		    [{objfun,_}|_R] ->
			", ObjFun";
		    _ ->
			""
		end,
	    emit([FunctionName,"'(Tlv, TagIn",ObjFun,") ->",nl]),
	    asn1ct_gen:gen_decode_constructed(Erules,Typename,InnerType,Type);
	Rec when is_record(Rec,'Externaltypereference') ->
	    case {Typename,asn1ct:get_gen_state_field(namelist)} of
		{[Cname|_],[{Cname,_}|_]} -> %% 
		    %% This referenced type must only be generated
		    %% once as incomplete partial decode. Therefore we
		    %% have to check whether this function already is
		    %% generated.
		    case asn1ct:is_function_generated(Typename) of
			true ->
			    ok;
			_ ->
			    asn1ct:generated_refed_func(Typename),
			    #'Externaltypereference'{module=M,type=Name}=Rec,
			    TypeDef = asn1_db:dbget(M,Name),
			    gen_decode(Erules,TypeDef)
		    end;
		_ ->
		    true
	    end;
	_ ->
	    true
    end;


%%===============================================================================
%% decode ComponentType
%%===============================================================================

gen_decode(Erules,Tname,#'ComponentType'{name=Cname,typespec=Type}) ->
    NewTname = [Cname|Tname],
    %% The tag is set to [] to avoid that it is taken into account
    %% twice, both as a component/alternative (passed as argument to
    %% the encode/decode function), and within the encode decode
    %% function itself.
    NewType = Type#type{tag=[]},
    case {asn1ct:get_gen_state_field(active),
	  asn1ct:get_tobe_refed_func(NewTname)} of
	{true,{_,NameList}} ->
	    asn1ct:update_gen_state(namelist,NameList),
	    %% remove to gen_refed_funcs list from tobe_refed_funcs later
	    gen_decode(Erules,NewTname,NewType);
	{No,_} when No == false; No == undefined ->
	    gen_decode(Erules,NewTname,NewType);
	_ ->
	    ok
    end.


gen_decode_user(Erules,D) when is_record(D,typedef) ->
    Typename = [D#typedef.name],
    Def = D#typedef.typespec,
    InnerType = asn1ct_gen:get_inner(Def#type.def),
    BytesVar = "Tlv",
    case asn1ct_gen:type(InnerType) of
	'ASN1_OPEN_TYPE' ->
	    asn1ct_name:new(len),
	    gen_dec_prim(Def#type{def='ASN1_OPEN_TYPE'},
			 BytesVar, {string,"TagIn"}),
	    emit([".",nl,nl]);
	{primitive,bif} ->
	    asn1ct_name:new(len),
	    gen_dec_prim(Def, BytesVar, {string,"TagIn"}),
	    emit([".",nl,nl]);
	{constructed,bif} ->
	    asn1ct:update_namelist(D#typedef.name),
	    asn1ct_gen:gen_decode_constructed(Erules,Typename,InnerType,D);
	TheType ->
	    DecFunName = mkfuncname(TheType,dec),
	    emit([DecFunName,"(",BytesVar,
		  ", TagIn).",nl,nl])
    end.


gen_dec_prim(Att, BytesVar, DoTag) ->
    Typename = Att#type.def,
    Constraint = get_size_constraint(Att#type.constraint),
    IntConstr = int_constr(Att#type.constraint),
    NewTypeName = case Typename of
		      'NumericString'   -> restricted_string;
		      'TeletexString'   -> restricted_string;
		      'T61String'       -> restricted_string;
		      'VideotexString'  -> restricted_string;
		      'GraphicString'   -> restricted_string;
		      'VisibleString'   -> restricted_string;
		      'GeneralString'   -> restricted_string;
		      'PrintableString' -> restricted_string;
		      'IA5String'       -> restricted_string;
		      'ObjectDescriptor'-> restricted_string;
		      'UTCTime'         -> restricted_string;
		      'GeneralizedTime' -> restricted_string;
		      'OCTET STRING'    ->
			  case asn1ct:use_legacy_types() of
			      true -> restricted_string;
			      false -> Typename
			  end;
		      _                 -> Typename
		  end,
    TagStr = case DoTag of
		 {string,Tag1} -> Tag1;
		 _ when is_list(DoTag) -> {asis,DoTag}
	     end,
    case NewTypeName of
	'BOOLEAN'->
	    call(decode_boolean, [BytesVar,TagStr]);
	'INTEGER' ->
	    check_constraint(decode_integer, [BytesVar,TagStr],
			     IntConstr,
			     identity,
			     identity);
	{'INTEGER',NNL} ->
	    check_constraint(decode_integer,
			     [BytesVar,TagStr],
			     IntConstr,
			     identity,
			     fun(Val) ->
				     asn1ct_name:new(val),
				     emit([{curr,val}," = "]),
				     Val(),
				     emit([com,nl,
					   {call,ber,number2name,
					    [{curr,val},{asis,NNL}]}])
			     end);
	{'ENUMERATED',NNL} ->
	    gen_dec_enumerated(BytesVar, NNL, TagStr);
	'REAL' ->
	    asn1ct_name:new(tmpbuf),
	    emit(["begin",nl,
		  {curr,tmpbuf}," = ",
		  {call,ber,match_tags,[BytesVar,TagStr]},com,nl,
		  {call,real_common,decode_real,[{curr,tmpbuf}]},nl,
		  "end",nl]);
	{'BIT STRING',NNL} ->
	    gen_dec_bit_string(BytesVar, Constraint, NNL, TagStr);
	'NULL' ->
	    call(decode_null, [BytesVar,TagStr]);
	'OBJECT IDENTIFIER' ->
	    call(decode_object_identifier, [BytesVar,TagStr]);
	'RELATIVE-OID' ->
	    call(decode_relative_oid, [BytesVar,TagStr]);
	'OCTET STRING' ->
	    check_constraint(decode_octet_string, [BytesVar,TagStr],
			     Constraint, {erlang,byte_size}, identity);
	restricted_string ->
	    check_constraint(decode_restricted_string, [BytesVar,TagStr],
			     Constraint,
			     {erlang,byte_size},
			     fun(Val) ->
				     emit("binary_to_list("),
				     Val(),
				     emit(")")
			     end);
	'UniversalString' ->
	    check_constraint(decode_universal_string, [BytesVar,TagStr],
			     Constraint, {erlang,length}, identity);
	'UTF8String' ->
	    call(decode_UTF8_string, [BytesVar,TagStr]);
	'BMPString' ->
	    check_constraint(decode_BMP_string, [BytesVar,TagStr],
			     Constraint, {erlang,length}, identity);
	'ASN1_OPEN_TYPE' ->
	    call(decode_open_type_as_binary, [BytesVar,TagStr])
    end.

%% Simplify an integer constraint so that we can efficiently test it.
-spec int_constr(term()) -> [] | {integer(),integer()|'MAX'}.
int_constr(C) ->
    case asn1ct_imm:effective_constraint(integer, C) of
	[{_,[]}] ->
	    %% Extension - ignore constraint.
	    [];
	[{'ValueRange',{'MIN',_}}] ->
	    %% Tricky to implement efficiently - ignore it.
	    [];
	[{'ValueRange',{_,_}=Range}] ->
	    Range;
	[{'SingleValue',Sv}] ->
	    Sv;
	[] ->
	    []
    end.

gen_dec_bit_string(BytesVar, _Constraint, [_|_]=NNL, TagStr) ->
    call(decode_named_bit_string,
	 [BytesVar,{asis,NNL},TagStr]);
gen_dec_bit_string(BytesVar, Constraint, [], TagStr) ->
    case asn1ct:get_bit_string_format() of
	compact ->
	    check_constraint(decode_compact_bit_string,
			     [BytesVar,TagStr],
			     Constraint,
			     {ber,compact_bit_string_size},
			     identity);
	legacy ->
	    check_constraint(decode_native_bit_string,
			     [BytesVar,TagStr],
			     Constraint,
			     {erlang,bit_size},
			     fun(Val) ->
				     asn1ct_name:new(val),
				     emit([{curr,val}," = "]),
				     Val(),
				     emit([com,nl,
					   {call,ber,native_to_legacy_bit_string,
					    [{curr,val}]}])
			     end);
	bitstring ->
	    check_constraint(decode_native_bit_string,
			     [BytesVar,TagStr],
			     Constraint,
			     {erlang,bit_size},
			     identity)
    end.

check_constraint(F, Args, Constr, PreConstr0, ReturnVal0) ->
    PreConstr = case PreConstr0 of
		    identity ->
			fun(V) -> V end;
		    {Mod,Name} ->
			fun(V) ->
				asn1ct_name:new(c),
				emit([{curr,c}," = ",
				      {call,Mod,Name,[V]},com,nl]),
				{curr,c}
			end
		end,
    ReturnVal = case ReturnVal0 of
		    identity ->	fun(Val) -> Val() end;
		    _ -> ReturnVal0
		end,
    case Constr of
	[] when ReturnVal0 =:= identity ->
	    %% No constraint, no complications.
	    call(F, Args);
	[] ->
	    %% No constraint, but the return value could consist
	    %% of more than one statement.
	    emit(["begin",nl]),
	    ReturnVal(fun() -> call(F, Args) end),
	    emit([nl,
		  "end",nl]);
	_ ->
	    %% There is a constraint.
	    asn1ct_name:new(val),
	    emit(["begin",nl,
		  {curr,val}," = ",{call,ber,F,Args},com,nl]),
	    PreVal0 = asn1ct_gen:mk_var(asn1ct_name:curr(val)),
	    PreVal = PreConstr(PreVal0),
	    emit("if "),
	    case Constr of
		{Min,Max} ->
		    emit([{asis,Min}," =< ",PreVal,", ",
			  PreVal," =< ",{asis,Max}]);
		Sv when is_integer(Sv) ->
		    emit([PreVal," =:= ",{asis,Sv}])
	    end,
	    emit([" ->",nl]),
	    ReturnVal(fun() -> emit(PreVal0) end),
	    emit([";",nl,
		  "true ->",nl,
		  "exit({error,{asn1,bad_range}})",nl,
		  "end",nl,
		 "end"])
    end.

gen_dec_enumerated(BytesVar, NNL0, TagStr) ->
    asn1ct_name:new(enum),
    emit(["case ",
	  {call,ber,decode_integer,[BytesVar,TagStr]},
	  " of",nl]),
    NNL = case NNL0 of
	      {L1,L2} ->
		  L1 ++ L2 ++ [accept];
	      [_|_] ->
		  NNL0 ++ [error]
	  end,
    gen_dec_enumerated_1(NNL),
    emit("end").

gen_dec_enumerated_1([accept]) ->
    asn1ct_name:new(default),
    emit([{curr,default}," -> {asn1_enum,",{curr,default},"}",nl]);
gen_dec_enumerated_1([error]) ->
    asn1ct_name:new(default),
    emit([{curr,default}," -> exit({error,{asn1,{illegal_enumerated,",
	  {curr,default},"}}})",nl]);
gen_dec_enumerated_1([{V,K}|T]) ->
    emit([{asis,K}," -> ",{asis,V},";",nl]),
    gen_dec_enumerated_1(T).

    
%% Object code generating for encoding and decoding
%% ------------------------------------------------

gen_obj_code(Erules,_Module,Obj) when is_record(Obj,typedef) ->
    ObjName = Obj#typedef.name,
    Def = Obj#typedef.typespec,
    #'Externaltypereference'{module=M,type=ClName} = Def#'Object'.classname,
    Class = asn1_db:dbget(M,ClName),
    {object,_,Fields} = Def#'Object'.def,
    emit([nl,nl,nl,
          "%%================================",nl,
          "%%  ",ObjName,nl,
          "%%================================",nl]),
    EncConstructed =
	gen_encode_objectfields(ClName,get_class_fields(Class),
				ObjName,Fields,[]),
    emit(nl),
    gen_encode_constr_type(Erules,EncConstructed),
    emit(nl),
    DecConstructed =
	gen_decode_objectfields(ClName,get_class_fields(Class),
				ObjName,Fields,[]),
    emit(nl),
    gen_decode_constr_type(Erules,DecConstructed),
    emit_tlv_format_function().

gen_encode_objectfields(ClassName,[{typefield,Name,OptOrMand}|Rest],
			ObjName,ObjectFields,ConstrAcc) ->
    EmitFuncClause =
	fun(Arg) ->
		emit([{asis,enc_func(ObjName)},"(",{asis,Name},
                      ", ",Arg,", _RestPrimFieldName) ->",nl])
	end,
    MaybeConstr=
	case {get_object_field(Name,ObjectFields),OptOrMand} of
	    {false,'OPTIONAL'} ->
		EmitFuncClause("Val"),
		emit(["   {Val,0}"]),
		[];
	    {false,{'DEFAULT',DefaultType}} ->
		EmitFuncClause("Val"),
		gen_encode_default_call(ClassName,Name,DefaultType);
	    {{Name,TypeSpec},_} ->
		%% A specified field overwrites any 'DEFAULT' or
		%% 'OPTIONAL' field in the class
		EmitFuncClause("Val"),
		gen_encode_field_call(ObjName,Name,TypeSpec)
	end,
    case more_genfields(Rest) of
	true ->
	    emit([";",nl]);
	false ->
	    emit([".",nl])
    end,
    gen_encode_objectfields(ClassName,Rest,ObjName,ObjectFields,
			    MaybeConstr++ConstrAcc);
gen_encode_objectfields(ClassName,[{objectfield,Name,_,_,OptOrMand}|Rest],
			ObjName,ObjectFields,ConstrAcc) ->
    CurrentMod = get(currmod),
    EmitFuncClause =
	fun(Args) ->
		emit([{asis,enc_func(ObjName)},"(",{asis,Name},
		      ", ",Args,") ->",nl])
	end,
    case {get_object_field(Name,ObjectFields),OptOrMand} of
	{false,'OPTIONAL'} ->
	    EmitFuncClause("_,_"),
	    emit(["  exit({error,{'use of missing field in object', ",{asis,Name},
		  "}})"]);
	{false,{'DEFAULT',_DefaultObject}} ->
	    exit({error,{asn1,{"not implemented yet",Name}}});
	{{Name,#'Externalvaluereference'{module=CurrentMod,
					 value=TypeName}},_} ->
	    EmitFuncClause(" Val, [H|T]"),
	    emit([indent(3),{asis,enc_func(TypeName)},"(H, Val, T)"]);
	{{Name,#'Externalvaluereference'{module=M,value=TypeName}},_} ->
	    EmitFuncClause(" Val, [H|T]"),
	    emit([indent(3),{asis,M},":",{asis,enc_func(TypeName)},
                  "(H, Val, T)"]);
	{{Name,#typedef{name=TypeName}},_} when is_atom(TypeName) ->
	    EmitFuncClause(" Val, [H|T]"),
            emit([indent(3),{asis,enc_func(TypeName)},"(H, Val, T)"])
    end,
    case more_genfields(Rest) of
	true ->
	    emit([";",nl]);
	false ->
	    emit([".",nl])
    end,
    gen_encode_objectfields(ClassName,Rest,ObjName,ObjectFields,ConstrAcc);
    

gen_encode_objectfields(ClassName,[_C|Cs],O,OF,Acc) ->
    gen_encode_objectfields(ClassName,Cs,O,OF,Acc);
gen_encode_objectfields(_,[],_,_,Acc) ->
    Acc.

gen_encode_constr_type(Erules,[TypeDef|Rest]) when is_record(TypeDef,typedef) ->
    case is_already_generated(enc,TypeDef#typedef.name) of
	true -> ok;
	false -> gen_encode_user(Erules, TypeDef, false)
    end,
    gen_encode_constr_type(Erules,Rest);
gen_encode_constr_type(_,[]) ->
    ok.

gen_encode_field_call(_ObjName,_FieldName,
		      #'Externaltypereference'{module=M,type=T}) ->
    CurrentMod = get(currmod),
    TDef = asn1_db:dbget(M,T),
    Def = TDef#typedef.typespec,
    OTag = Def#type.tag,
    Tag = [encode_tag_val(decode_class(X#tag.class),
			  X#tag.form,X#tag.number)||
	      X <- OTag],
    if
	M == CurrentMod ->
	    emit(["   ",{asis,enc_func(T)},"(Val, ",{asis,Tag},")"]),
	    [];
	true ->
	    emit(["   ",{asis,M},":",{asis,enc_func(T)},
                  "(Val, ",{asis,Tag},")"]),
	    []
    end;
gen_encode_field_call(ObjName,FieldName,Type) ->
    Def = Type#typedef.typespec,
    OTag = Def#type.tag,
    Tag = [encode_tag_val(decode_class(X#tag.class),
			  X#tag.form,X#tag.number)||
	      X <- OTag],
    case Type#typedef.name of
	{primitive,bif} ->            %tag should be the primitive tag
	    gen_encode_prim(ber,Def,{asis,lists:reverse(Tag)},
			    "Val"),
	    [];
	{constructed,bif} ->
            Name = lists:concat([ObjName,'_',FieldName]),
	    emit(["   ",{asis,enc_func(Name)},"(Val,",{asis,Tag},")"]),
            [Type#typedef{name=list_to_atom(Name)}];
	{ExtMod,TypeName} ->
	    emit(["   ",{asis,ExtMod},":",{asis,enc_func(TypeName)},
		  "(Val,",{asis,Tag},")"]),
	    [];
	TypeName ->
	    emit(["   ",{asis,enc_func(TypeName)},
                  "(Val,",{asis,Tag},")"]),
	    []
    end.

gen_encode_default_call(ClassName,FieldName,Type) ->
    CurrentMod = get(currmod),
    InnerType = asn1ct_gen:get_inner(Type#type.def),
    OTag = Type#type.tag,
    Tag = [encode_tag_val(decode_class(X#tag.class),X#tag.form,X#tag.number)|| X <- OTag],
    case asn1ct_gen:type(InnerType) of
    	{constructed,bif} ->
            Name = lists:concat([ClassName,'_',FieldName]),
	    emit(["   ",{asis,enc_func(Name)},
		  "(Val, ",{asis,Tag},")"]),
	    [#typedef{name=list_to_atom(Name),typespec=Type}];
	{primitive,bif} ->
	    gen_encode_prim(ber,Type,{asis,lists:reverse(Tag)},"Val"),
	    [];
	#'Externaltypereference'{module=CurrentMod,type=Etype} ->
	    emit(["   'enc_",Etype,"'(Val, ",{asis,Tag},")",nl]),
	    [];
	#'Externaltypereference'{module=Emod,type=Etype} ->
	    emit(["   '",Emod,"':'enc_",Etype,"'(Val, ",{asis,Tag},")",nl]),
	    []
    end.
    
%%%%%%%%%%%%%%%%

gen_decode_objectfields(ClassName,[{typefield,Name,OptOrMand}|Rest],
			ObjName,ObjectFields,ConstrAcc) ->
    EmitFuncClause =
	fun(Arg) ->
		emit([{asis,dec_func(ObjName)},"(",{asis,Name},
		      ", ",Arg,",_) ->",nl])
	end,
    MaybeConstr=
	case {get_object_field(Name,ObjectFields),OptOrMand} of
	    {false,'OPTIONAL'} ->
		EmitFuncClause(" Bytes"),
		emit(["   Bytes"]),
		[];
	    {false,{'DEFAULT',DefaultType}} ->
		EmitFuncClause("Bytes"),
		emit_tlv_format("Bytes"),
		gen_decode_default_call(ClassName,Name,"Tlv",DefaultType);
	    {{Name,TypeSpec},_} ->
		%% A specified field overwrites any 'DEFAULT' or
		%% 'OPTIONAL' field in the class
		EmitFuncClause("Bytes"),
		emit_tlv_format("Bytes"),
		gen_decode_field_call(ObjName,Name,"Tlv",TypeSpec)
	end,
    case more_genfields(Rest) of
	true ->
	    emit([";",nl]);
	false ->
	    emit([".",nl])
    end,
    gen_decode_objectfields(ClassName,Rest,ObjName,ObjectFields,MaybeConstr++ConstrAcc);
gen_decode_objectfields(ClassName,[{objectfield,Name,_,_,OptOrMand}|Rest],
			ObjName,ObjectFields,ConstrAcc) ->
    CurrentMod = get(currmod),
    EmitFuncClause =
	fun(Args) ->
		emit([{asis,dec_func(ObjName)},"(",{asis,Name},
		      ", ",Args,") ->",nl])
	end,
    case {get_object_field(Name,ObjectFields),OptOrMand} of
	{false,'OPTIONAL'} ->
	    EmitFuncClause("_,_"),
	    emit(["  exit({error,{'illegal use of missing field in object', ",{asis,Name},
		  "}})"]);
	{false,{'DEFAULT',_DefaultObject}} ->
	    exit({error,{asn1,{"not implemented yet",Name}}});
	{{Name,#'Externalvaluereference'{module=CurrentMod,
					 value=TypeName}},_} ->
	    EmitFuncClause("Bytes,[H|T]"),
	    emit([indent(3),{asis,dec_func(TypeName)},"(H, Bytes, T)"]);
	{{Name,#'Externalvaluereference'{module=M,value=TypeName}},_} ->
	    EmitFuncClause("Bytes,[H|T]"),
	    emit([indent(3),{asis,M},":",{asis,dec_func(TypeName)},
		  "(H, Bytes, T)"]);
	{{Name,#typedef{name=TypeName}},_} when is_atom(TypeName) ->
            EmitFuncClause("Bytes,[H|T]"),
            emit([indent(3),{asis,dec_func(TypeName)},"(H, Bytes, T)"])
    end,
    case more_genfields(Rest) of
	true ->
	    emit([";",nl]);
	false ->
	    emit([".",nl])
    end,
    gen_decode_objectfields(ClassName,Rest,ObjName,ObjectFields,ConstrAcc);
gen_decode_objectfields(CN,[_C|Cs],O,OF,CAcc) ->
    gen_decode_objectfields(CN,Cs,O,OF,CAcc);
gen_decode_objectfields(_,[],_,_,CAcc) ->
    CAcc.

emit_tlv_format(Bytes) ->
    notice_tlv_format_gen(), % notice for generating of tlv_format/1
    emit(["  Tlv = tlv_format(",Bytes,"),",nl]).

notice_tlv_format_gen() ->
    Module = get(currmod),
    case get(tlv_format) of
	{done,Module} ->
	    ok;
	_ ->                                    % true or undefined
	    put(tlv_format,true)
    end.

emit_tlv_format_function() ->
    Module = get(currmod),
    case get(tlv_format) of
	true ->
	    emit_tlv_format_function1(),
	    put(tlv_format,{done,Module});
	_ ->
	    ok
    end.
emit_tlv_format_function1() ->
    emit(["tlv_format(Bytes) when is_binary(Bytes) ->",nl,
	  "  {Tlv,_} = ",{call,ber,ber_decode_nif,["Bytes"]},com,nl,
	  "  Tlv;",nl,
	  "tlv_format(Bytes) ->",nl,
	  "  Bytes.",nl]).


gen_decode_constr_type(Erules,[TypeDef|Rest]) when is_record(TypeDef,typedef) ->
    case is_already_generated(dec,TypeDef#typedef.name) of
	true -> ok;
	_ ->
	    emit([nl,nl,
		  "'dec_",TypeDef#typedef.name,
		  "'(Tlv, TagIn) ->",nl]),
	    gen_decode_user(Erules, TypeDef)
    end,
    gen_decode_constr_type(Erules,Rest);
gen_decode_constr_type(_,[]) ->
    ok.

%%%%%%%%%%%
gen_decode_field_call(_ObjName,_FieldName,Bytes,
		      #'Externaltypereference'{module=M,type=T}) ->
    CurrentMod = get(currmod),
    TDef = asn1_db:dbget(M,T),
    Def = TDef#typedef.typespec,
    OTag = Def#type.tag,
    Tag = [(decode_class(X#tag.class) bsl 10) + X#tag.number || 
	      X <- OTag],
    if
	M == CurrentMod ->
	    emit(["   ",{asis,dec_func(T)},"(",Bytes,
		  ", ",{asis,Tag},")"]),
	    [];
	true ->
	    emit(["   ",{asis,M},":",{asis,dec_func(T)},
		  "(",Bytes,", ",{asis,Tag},")"]),
	    []
    end;
gen_decode_field_call(ObjName,FieldName,Bytes,Type) ->
    Def = Type#typedef.typespec,
    OTag = Def#type.tag,
    Tag = [(decode_class(X#tag.class) bsl 10) + X#tag.number || 
	      X <- OTag],
    case Type#typedef.name of
	{primitive,bif} ->
	    gen_dec_prim(Def, Bytes, Tag),
	    [];
	{constructed,bif} ->
            Name = lists:concat([ObjName,"_",FieldName]),
	    emit(["   ",{asis,dec_func(Name)},
                  "(",Bytes,",",{asis,Tag},")"]),
	    [Type#typedef{name=list_to_atom(Name)}];
	{ExtMod,TypeName} ->
	    emit(["   ",{asis,ExtMod},":",{asis,dec_func(TypeName)},
		  "(",Bytes,",",{asis,Tag},")"]),
	    [];
	TypeName ->
	    emit(["   ",{asis,dec_func(TypeName)},
                  "(",Bytes,",",{asis,Tag},")"]),
	    []
    end.

gen_decode_default_call(ClassName,FieldName,Bytes,Type) ->
    CurrentMod = get(currmod),
    InnerType = asn1ct_gen:get_inner(Type#type.def),
    OTag = Type#type.tag,
    Tag = [(decode_class(X#tag.class) bsl 10) + X#tag.number || X <- OTag],
    case asn1ct_gen:type(InnerType) of
    	{constructed,bif} ->
	    emit(["   'dec_",ClassName,'_',FieldName,"'(",Bytes,",",
		  {asis,Tag},")"]),
	    [#typedef{name=list_to_atom(lists:concat([ClassName,'_',
						      FieldName])),
		      typespec=Type}];
	{primitive,bif} ->
	    gen_dec_prim(Type, Bytes, Tag),
	    [];
	#'Externaltypereference'{module=CurrentMod,type=Etype} ->
	    emit(["   'dec_",Etype,"'(",Bytes, " ,",{asis,Tag},")",nl]),
	    [];
	#'Externaltypereference'{module=Emod,type=Etype} ->
	    emit(["   '",Emod,"':'dec_",Etype,"'(",Bytes,", ",
		  {asis,Tag},")",nl]),
	    []
    end.
%%%%%%%%%%%

is_already_generated(Operation,Name) ->
    case get(class_default_type) of
	undefined ->
	    put(class_default_type,[{Operation,Name}]),
	    false;
	GeneratedList ->
	    case lists:member({Operation,Name},GeneratedList) of
		true ->
		    true;
		false ->
		    put(class_default_type,[{Operation,Name}|GeneratedList]),
		    false
	    end
    end.

more_genfields([]) ->
    false;
more_genfields([Field|Fields]) ->
    case element(1,Field) of
	typefield ->
	    true;
	objectfield ->
	    true;
	_ ->
	    more_genfields(Fields)
    end.




%% Object Set code generating for encoding and decoding
%% ----------------------------------------------------
gen_objectset_code(Erules,ObjSet) ->
    ObjSetName = ObjSet#typedef.name,
    Def = ObjSet#typedef.typespec,
    #'Externaltypereference'{module=ClassModule,
			     type=ClassName} = Def#'ObjectSet'.class,
    ClassDef = asn1_db:dbget(ClassModule,ClassName),
    UniqueFName = Def#'ObjectSet'.uniquefname,
    Set = Def#'ObjectSet'.set,
    emit([nl,nl,nl,
          "%%================================",nl,
          "%%  ",ObjSetName,nl,
          "%%================================",nl]),
    case ClassName of
	{_Module,ExtClassName} ->
	    gen_objset_code(Erules,ObjSetName,UniqueFName,Set,ExtClassName,ClassDef);
	_ ->
	    gen_objset_code(Erules,ObjSetName,UniqueFName,Set,ClassName,ClassDef)
    end,
    emit(nl).

gen_objset_code(Erules,ObjSetName,UniqueFName,Set,ClassName,ClassDef)->
    ClassFields = get_class_fields(ClassDef),
    InternalFuncs=gen_objset_enc(Erules,ObjSetName,UniqueFName,Set,
				 ClassName,ClassFields,1,[]),
    gen_objset_dec(Erules,ObjSetName,UniqueFName,Set,ClassName,ClassFields,1),
    gen_internal_funcs(Erules,InternalFuncs).

%% gen_objset_enc iterates over the objects of the object set
gen_objset_enc(_,_,{unique,undefined},_,_,_,_,_) ->
    %% There is no unique field in the class of this object set
    %% don't bother about the constraint
    [];
gen_objset_enc(Erules, ObjSetName, UniqueName,
	       [{ObjName,Val,Fields}|T], ClName, ClFields,
	       NthObj,Acc)->
    CurrMod = get(currmod),
    {InternalFunc,NewNthObj}=
	case ObjName of
	    {no_mod,no_name} ->
		gen_inlined_enc_funs(Fields, ClFields, ObjSetName, Val, NthObj);
	    {CurrMod,Name} ->
		emit([asis_atom(["getenc_",ObjSetName]),
                      "(Id) when Id =:= ",{asis,Val}," ->",nl,
		      "    fun ",asis_atom(["enc_",Name]),"/3;",nl]),
		{[],NthObj};
	    {ModuleName,Name} ->
		emit([asis_atom(["getenc_",ObjSetName]),
                      "(Id) when Id =:= ",{asis,Val}," ->",nl]),
		emit_ext_fun(enc,ModuleName,Name),
		emit([";",nl]),
		{[],NthObj};
	    _ ->
		emit([asis_atom(["getenc_",ObjSetName]),
                      "(",{asis,Val},") ->",nl,
		      "  fun ",asis_atom(["enc_",ObjName]),"/3;",nl]),
		{[],NthObj}
	end,
    gen_objset_enc(Erules, ObjSetName, UniqueName, T, ClName, ClFields,
		   NewNthObj, InternalFunc ++ Acc);
%% See X.681 Annex E for the following case
gen_objset_enc(_,ObjSetName,_UniqueName,['EXTENSIONMARK'],_ClName,
	       _ClFields,_NthObj,Acc) ->
    emit([asis_atom(["getenc_",ObjSetName]),"(_) ->",nl,
	  indent(2),"fun(_, Val, _RestPrimFieldName) ->",nl]),
    emit_enc_open_type(4),
    emit([nl,
	  indent(2),"end.",nl,nl]),
    Acc;
gen_objset_enc(_, ObjSetName, UniqueName, [], _, _, _, Acc) ->
    emit_default_getenc(ObjSetName, UniqueName),
    emit([".",nl,nl]),
    Acc.

emit_ext_fun(EncDec,ModuleName,Name) ->
    emit([indent(3),"fun(T,V,O) -> '",ModuleName,"':'",EncDec,"_",
	  Name,"'(T,V,O) end"]).
    
emit_default_getenc(ObjSetName,UniqueName) ->
    emit([asis_atom(["getenc_",ObjSetName]),"(ErrV) ->",nl,
          indent(3),"fun(C,V,_) ->",nl,
          "exit({'Type not compatible with table constraint',{component,C},{value,V}, {unique_name_and_value,",{asis,UniqueName},", ErrV}}) end"]).

%% gen_inlined_enc_funs for each object iterates over all fields of a
%% class, and for each typefield it checks if the object has that
%% field and emits the proper code.
gen_inlined_enc_funs(Fields, [{typefield,_,_}|_]=T, ObjSetName, Val, NthObj) ->
    emit([asis_atom(["getenc_",ObjSetName]),"(",{asis,Val},") ->",nl,
	  indent(3),"fun(Type, Val, _RestPrimFieldName) ->",nl,
	  indent(6),"case Type of",nl]),
    gen_inlined_enc_funs1(Fields, T, ObjSetName, [], NthObj, []);
gen_inlined_enc_funs(Fields, [_|Rest], ObjSetName, Val, NthObj) ->
    gen_inlined_enc_funs(Fields, Rest, ObjSetName, Val, NthObj);
gen_inlined_enc_funs(_, [], _, _, NthObj) ->
    {[],NthObj}.

gen_inlined_enc_funs1(Fields, [{typefield,Name,_}|Rest], ObjSetName,
		      Sep0, NthObj, Acc0) ->
    emit(Sep0),
    Sep = [";",nl],
    CurrMod = get(currmod),
    InternalDefFunName = asn1ct_gen:list2name([NthObj,Name,ObjSetName]),
    {Acc,NAdd} =
	case lists:keyfind(Name,1,Fields) of
	    {_,#type{}=Type} ->
		{Ret,N} = emit_inner_of_fun(Type,InternalDefFunName),
		{Ret++Acc0,N};
	    {_,#typedef{}=Type} ->
		emit([indent(9),{asis,Name}," ->",nl]),
		{Ret,N} = emit_inner_of_fun(Type, InternalDefFunName),
		{Ret++Acc0,N};
	    {_,#'Externaltypereference'{module=M,type=T}} ->
		emit([indent(9),{asis,Name}," ->",nl]),
		if
		    M =:= CurrMod ->
			emit([indent(12),"'enc_",T,"'(Val)"]);
		    true ->
			#typedef{typespec=Type} = asn1_db:dbget(M,T),
			OTag = Type#type.tag,
			Tag = [encode_tag_val(decode_class(X#tag.class), 
					      X#tag.form,X#tag.number) ||
				  X <- OTag],
			emit([indent(12),"'",M,"':'enc_",T,"'(Val, ",
			      {asis,Tag},")"])
		end,
		{Acc0,0};
	    false ->
		%% This field was not present in the object; thus, there
		%% was no type in the table and we therefore generate
		%% code that returns the input for application
		%% treatment.
		emit([indent(9),{asis,Name}," ->",nl]),
		emit_enc_open_type(11),
		{Acc0,0}
	end,
    gen_inlined_enc_funs1(Fields, Rest, ObjSetName, Sep, NthObj+NAdd, Acc);
gen_inlined_enc_funs1(Fields,[_|Rest], ObjSetName, Sep, NthObj, Acc)->
    gen_inlined_enc_funs1(Fields, Rest, ObjSetName, Sep, NthObj, Acc);
gen_inlined_enc_funs1(_, [], _, _, NthObj, Acc) ->
    emit([nl,indent(6),"end",nl,
	  indent(3),"end;",nl]),
    {Acc,NthObj}.

emit_enc_open_type(I) ->
    Indent = indent(I),
    S = [Indent,          "case Val of",nl,
	 Indent,indent(2),"{asn1_OPENTYPE,Bin} when is_binary(Bin) ->",nl,
	 Indent,indent(4),"{Bin,byte_size(Bin)}"|
	 case asn1ct:use_legacy_types() of
	     false ->
		 [nl,
		  Indent,"end"];
	     true ->
		 [";",nl,
		  Indent,indent(2),"Bin when is_binary(Bin) ->",nl,
		  Indent,indent(4),"{Bin,byte_size(Bin)};",nl,
		  Indent,indent(2),"_ ->",nl,
		  Indent,indent(4),"{Val,length(Val)}",nl,
		  Indent,          "end"]
	 end],
    emit(S).

emit_inner_of_fun(TDef=#typedef{name={ExtMod,Name},typespec=Type},
		  InternalDefFunName) ->
    OTag = Type#type.tag,
    Tag = [encode_tag_val(decode_class(X#tag.class),X#tag.form,X#tag.number)|| X <- OTag],
    case {ExtMod,Name} of
	{primitive,bif} ->
	    emit(indent(12)),
	    gen_encode_prim(ber,Type,[{asis,lists:reverse(Tag)}],"Val"),
	    {[],0};
	{constructed,bif} ->
	    emit([indent(12),"'enc_",
		  InternalDefFunName,"'(Val, ",{asis,Tag},")"]),
	    {[TDef#typedef{name=InternalDefFunName}],1};
	_ ->
	    emit([indent(12),"'",ExtMod,"':'enc_",Name,"'(Val",{asis,Tag},")"]),
	    {[],0}
    end;
emit_inner_of_fun(#typedef{name=Name},_) ->
    emit([indent(12),"'enc_",Name,"'(Val)"]),
    {[],0};
emit_inner_of_fun(Type,_) when is_record(Type,type) ->
    CurrMod = get(currmod),
    case Type#type.def of
	Def when is_atom(Def) ->
	    OTag = Type#type.tag,
	    Tag = [encode_tag_val(decode_class(X#tag.class),
				  X#tag.form,X#tag.number)||X <- OTag],
	    emit([indent(9),Def," ->",nl,indent(12)]),
	    gen_encode_prim(ber,Type,{asis,lists:reverse(Tag)},"Val");
	#'Externaltypereference'{module=CurrMod,type=T} ->
	    emit([indent(9),T," ->",nl,indent(12),"'enc_",T,
		  "'(Val)"]);
	#'Externaltypereference'{module=ExtMod,type=T} ->
	    #typedef{typespec=ExtType} = asn1_db:dbget(ExtMod,T),
	    OTag = ExtType#type.tag,
	    Tag = [encode_tag_val(decode_class(X#tag.class),
				  X#tag.form,X#tag.number) ||
		      X <- OTag],
	    emit([indent(9),T," ->",nl,indent(12),ExtMod,":'enc_",
		  T,"'(Val, ",{asis,Tag},")"])
    end,
    {[],0}.

indent(N) ->
    lists:duplicate(N,32). % 32 = space


gen_objset_dec(_,_,{unique,undefined},_,_,_,_) ->
    %% There is no unique field in the class of this object set
    %% don't bother about the constraint
    ok;
gen_objset_dec(Erules, ObjSName, UniqueName, [{ObjName,Val,Fields}|T],
	       ClName, ClFields, NthObj)->
    CurrMod = get(currmod),
    NewNthObj=
	case ObjName of
	    {no_mod,no_name} ->
		gen_inlined_dec_funs(Fields,ClFields,ObjSName,Val,NthObj);
	    {CurrMod,Name} ->
		emit([asis_atom(["getdec_",ObjSName]),
                      "(Id) when Id =:= ",{asis,Val}," ->",nl,
		      "    fun 'dec_",Name,"'/3;", nl]),
		NthObj;
	    {ModuleName,Name} ->
		emit([asis_atom(["getdec_",ObjSName]),
                      "(Id) when Id =:= ",{asis,Val}," ->",nl]),
		emit_ext_fun(dec,ModuleName,Name),
		emit([";",nl]),
		NthObj;
	    _ ->
		emit([asis_atom(["getdec_",ObjSName]),
                      "(",{asis,Val},") ->",nl,
		      "    fun 'dec_",ObjName,"'/3;", nl]),
		NthObj
	end,
    gen_objset_dec(Erules, ObjSName, UniqueName, T, ClName,
		   ClFields, NewNthObj);
gen_objset_dec(_,ObjSetName,_UniqueName,['EXTENSIONMARK'],_ClName,
	       _ClFields,_NthObj) ->
    emit([asis_atom(["getdec_",ObjSetName]),"(_) ->",nl,
          indent(2),"fun(_,Bytes, _RestPrimFieldName) ->",nl]),
    emit_dec_open_type(4),
    emit([nl,
	  indent(2),"end.",nl,nl]),
    ok;
gen_objset_dec(_, ObjSetName, UniqueName, [], _, _, _) ->
    emit_default_getdec(ObjSetName, UniqueName),
    emit([".",nl,nl]),
    ok.

emit_default_getdec(ObjSetName,UniqueName) ->
    emit(["'getdec_",ObjSetName,"'(ErrV) ->",nl]),
    emit([indent(2), "fun(C,V,_) -> exit({{component,C},{value,V},{unique_name_and_value,",{asis,UniqueName},", ErrV}}) end"]).

gen_inlined_dec_funs(Fields, [{typefield,_,_}|_]=ClFields, ObjSetName, Val, NthObj) ->
    emit(["'getdec_",ObjSetName,"'(",{asis,Val},") ->",nl]),
    emit([indent(3),"fun(Type, Bytes, _RestPrimFieldName) ->",nl,
	  indent(6),"case Type of",nl]),
    gen_inlined_dec_funs1(Fields, ClFields, ObjSetName, "", NthObj);
gen_inlined_dec_funs(Fields, [_|ClFields], ObjSetName, Val, NthObj) ->
    gen_inlined_dec_funs(Fields, ClFields, ObjSetName, Val, NthObj);
gen_inlined_dec_funs(_, _, _, _,NthObj) ->
    NthObj.

gen_inlined_dec_funs1(Fields, [{typefield,Name,Prop}|Rest],
		      ObjSetName, Sep0, NthObj) ->
    emit(Sep0),
    Sep = [";",nl],
    DecProp = case Prop of
		  'OPTIONAL' -> opt_or_default;
		  {'DEFAULT',_} -> opt_or_default;
		  _ -> mandatory
	      end,
    InternalDefFunName = [NthObj,Name,ObjSetName],
    N = case lists:keyfind(Name, 1, Fields) of
	    {_,#type{}=Type} ->
		emit_inner_of_decfun(Type,DecProp,InternalDefFunName);
	    {_,#typedef{}=Type} ->
		emit([indent(9),{asis,Name}," ->",nl]),
		emit_inner_of_decfun(Type,DecProp,InternalDefFunName);
	    {_,#'Externaltypereference'{module=M,type=T}} ->
		emit([indent(9),{asis,Name}," ->",nl]),
		CurrMod = get(currmod),
		if
		    M =:= CurrMod ->
			emit([indent(12),"'dec_",T,"'(Bytes)"]);
		    true ->
			#typedef{typespec=Type} = asn1_db:dbget(M,T),
			OTag = Type#type.tag,
			Tag = [(decode_class(X#tag.class) bsl 10) + 
			       X#tag.number || X <- OTag],
			emit([indent(12),"'",M,"':'dec_",T,"'(Bytes, ",{asis,Tag},")"])
		end,
		0;
	    false ->
		emit([indent(9),{asis,Name}," ->",nl]),
		emit_dec_open_type(11),
		0
    end,
    gen_inlined_dec_funs1(Fields, Rest, ObjSetName, Sep, NthObj+N);
gen_inlined_dec_funs1(Fields, [_|Rest], ObjSetName, Sep, NthObj)->
    gen_inlined_dec_funs1(Fields, Rest, ObjSetName, Sep, NthObj);
gen_inlined_dec_funs1(_, [], _, _, NthObj) ->
    emit([nl,indent(6),"end",nl,
	  indent(3),"end;",nl]),
    NthObj.

emit_dec_open_type(I) ->
    Indent = indent(I),
    S = case asn1ct:use_legacy_types() of
	    false ->
		[Indent,          "case Bytes of",nl,
		 Indent,indent(2),"Bin when is_binary(Bin) -> ",nl,
		 Indent,indent(4),"{asn1_OPENTYPE,Bin};",nl,
		 Indent,indent(2),"_ ->",nl,
		 Indent,indent(4),"{asn1_OPENTYPE,",
		 {call,ber,ber_encode,["Bytes"]},"}",nl,
		 Indent,          "end"];
	    true ->
		[Indent,          "case Bytes of",nl,
		 Indent,indent(2),"Bin when is_binary(Bin) -> ",nl,
		 Indent,indent(4),"Bin;",nl,
		 Indent,indent(2),"_ ->",nl,
		 Indent,indent(4),{call,ber,ber_encode,["Bytes"]},nl,
		 Indent,          "end"]
	end,
    emit(S).

emit_inner_of_decfun(#typedef{name={ExtName,Name},typespec=Type}, _Prop,
		     InternalDefFunName) ->
    OTag = Type#type.tag,
    Tag = [(decode_class(X#tag.class) bsl 10) + X#tag.number || X <- OTag],
    case {ExtName,Name} of
	{primitive,bif} ->
	    emit(indent(12)),
	    gen_dec_prim(Type, "Bytes", Tag),
	    0;
	{constructed,bif} ->
	    emit([indent(12),"'dec_",
 		  asn1ct_gen:list2name(InternalDefFunName),"'(Bytes, ",
		  {asis,Tag},")"]),
	    1;
	_ ->
	    emit([indent(12),"'",ExtName,"':'dec_",Name,"'(Bytes, ",
		  {asis,Tag},")"]),
	    0
    end;
emit_inner_of_decfun(#typedef{name=Name},_Prop,_) ->
    emit([indent(12),"'dec_",Name,"'(Bytes)"]),
    0;
emit_inner_of_decfun(#type{}=Type, _Prop, _) ->
    OTag = Type#type.tag,
    Tag = [(decode_class(X#tag.class) bsl 10) + X#tag.number || X <- OTag],
    CurrMod = get(currmod),
    Def = Type#type.def,
    InnerType = asn1ct_gen:get_inner(Def),
    WhatKind = asn1ct_gen:type(InnerType),
    case WhatKind of
	{primitive,bif} -> 
	    emit([indent(9),Def," ->",nl,indent(12)]),
	    gen_dec_prim(Type, "Bytes", Tag);
	#'Externaltypereference'{module=CurrMod,type=T} ->
	    emit([indent(9),T," ->",nl,indent(12),"'dec_",T,
		  "'(Bytes)"]);
	#'Externaltypereference'{module=ExtMod,type=T} ->
	    emit([indent(9),T," ->",nl,indent(12),ExtMod,":'dec_",
		  T,"'(Bytes, ",{asis,Tag},")"])
    end,
    0.

gen_internal_funcs(_,[]) ->
    ok;
gen_internal_funcs(Erules,[TypeDef|Rest]) ->
    gen_encode_user(Erules, TypeDef, false),
    emit([nl,nl,
	  "'dec_",TypeDef#typedef.name,"'(Tlv, TagIn) ->",nl]),
    gen_decode_user(Erules,TypeDef),
    gen_internal_funcs(Erules,Rest).


decode_class('UNIVERSAL') ->
    ?UNIVERSAL;
decode_class('APPLICATION') ->
    ?APPLICATION;
decode_class('CONTEXT') ->
    ?CONTEXT;
decode_class('PRIVATE') ->
    ?PRIVATE.

mkfuncname(#'Externaltypereference'{module=Mod,type=EType}, DecOrEnc) ->
    CurrMod = get(currmod),
    case CurrMod of
	Mod ->
	    lists:concat(["'",DecOrEnc,"_",EType,"'"]);
	_ ->
	    lists:concat(["'",Mod,"':'",DecOrEnc,"_",EType,"'"])
    end.

get_size_constraint(C) ->
    case lists:keyfind('SizeConstraint', 1, C) of
	false -> [];
	{_,{_,[]}} -> [];			%Extensible.
	{_,{Sv,Sv}} -> Sv;
	{_,{_,_}=Tc} -> Tc
    end.

get_class_fields(#classdef{typespec=ObjClass}) ->
    ObjClass#objectclass.fields;
get_class_fields(#objectclass{fields=Fields}) ->
    Fields;
get_class_fields(_) ->
    [].

get_object_field(Name,ObjectFields) ->
    case lists:keysearch(Name,1,ObjectFields) of
	{value,Field} -> Field;
	false -> false
    end.

tag_to_integer(#tag{class=Class,number=N})
  when is_integer(N), 0 =< N, N =< 1 bsl 16 ->
    decode_class(Class) bsl 10 bor N.

%%encode_tag(TagClass(?UNI, APP etc), Form (?PRIM etx), TagInteger) ->  
%%     8bit Int | binary 
encode_tag_val(Class, Form, TagNo) when (TagNo =< 30) -> 
    <<(Class bsr 6):2,(Form bsr 5):1,TagNo:5>>;

encode_tag_val(Class, Form, TagNo) -> 
    {Octets,_Len} = mk_object_val(TagNo),
    BinOct = list_to_binary(Octets),
    <<(Class bsr 6):2, (Form bsr 5):1, 31:5,BinOct/binary>>.

%%%%%%%%%%% 
%% mk_object_val(Value) -> {OctetList, Len} 
%% returns a Val as a list of octets, the 8 bit is always set to one except
%% for the last octet, where its 0 
%% 

 
mk_object_val(Val) when Val =< 127 -> 
    {[255 band Val], 1}; 
mk_object_val(Val) -> 
    mk_object_val(Val bsr 7, [Val band 127], 1).  
mk_object_val(0, Ack, Len) -> 
    {Ack, Len}; 
mk_object_val(Val, Ack, Len) -> 
    mk_object_val(Val bsr 7, [((Val band 127) bor 128) | Ack], Len + 1). 

%% For BER the ExtensionAdditionGroup notation has no impact on the
%% encoding/decoding. Therefore we can filter away the
%% ExtensionAdditionGroup start and end markers.
extaddgroup2sequence(ExtList) when is_list(ExtList) ->
    lists:filter(fun(#'ExtensionAdditionGroup'{}) ->
			 false;
		    ('ExtensionAdditionGroupEnd') ->
			 false;
		    (_) ->
			 true
		 end, ExtList).

call(F, Args) ->
    asn1ct_func:call(ber, F, Args).

enc_func(Tname) ->
    list_to_atom(lists:concat(["enc_",Tname])).

dec_func(Tname) ->
    list_to_atom(lists:concat(["dec_",Tname])).

asis_atom(List) ->
    {asis,list_to_atom(lists:concat(List))}.
