%%--------------------------------------------------------------------
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1999-2016. 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%
%%
%%
%%----------------------------------------------------------------------
%% File    : CosNotifyFilter_MappingFilter_impl.erl
%% Purpose : 
%%----------------------------------------------------------------------

-module('CosNotifyFilter_MappingFilter_impl').

%%--------------- INCLUDES -----------------------------------
%% Application files
-include_lib("orber/include/corba.hrl").
-include_lib("orber/include/ifr_types.hrl").
%% Application files
-include("CosNotification.hrl").
-include("CosNotifyChannelAdmin.hrl").
-include("CosNotifyComm.hrl").
-include("CosNotifyFilter.hrl").
-include("CosNotification_Definitions.hrl").

%%--------------- IMPORTS ------------------------------------

%%--------------- EXPORTS ------------------------------------
%% External Attributes
-export(['_get_constraint_grammar'/2, 
	 '_get_value_type'/2, 
	 '_get_default_value'/2]).
%% External Functions
-export([add_mapping_constraints/3, 
	 modify_mapping_constraints/4, 
	 get_mapping_constraints/3,
	 get_all_mapping_constraints/2, 
	 remove_all_mapping_constraints/2, 
	 destroy/2,
	 match/3, 
	 match_structured/3, 
	 match_typed/3]).

%%--------------- gen_server specific exports ----------------
-export([handle_info/2, code_change/3]).
-export([init/1, terminate/2]).

%%--------------- LOCAL DEFINITIONS --------------------------
%%######### MISC ##########
-define(create_MappingInfo(_Types, _Con, _ID, _A), 
	#'CosNotifyFilter_MappingConstraintInfo'{
				      constraint_expression = 
				      #'CosNotifyFilter_ConstraintExp'{
					event_types = _Types,
					constraint_expr = _Con},
				      constraint_id = _ID,
				      value = _A
				     }).
%%#### Data structures ####
-record(state, {constraint_grammar,
		value,
		typeC,
		constraints = [],
		filters = [],
		idCounter = 0,
		filterFactory,
		factoryPid,
		etsR}).

%% Data structures constructors
-define(get_InitState(Gr, DVal, FF, FP), 
	#state{constraint_grammar=Gr,
	       value = DVal,
	       typeC = any:get_typecode(DVal),
	       filterFactory = FF,
	       factoryPid = FP,
	       etsR = ets:new(oe_ets, [bag, protected])}).

%%------------------- Data structures selectors -------------------
%% Attributes
-define(get_Grammar(S),         S#state.constraint_grammar).
-define(get_DefVal(S),          S#state.value).
-define(get_DefTC(S),           S#state.typeC).
-define(get_DefAny(S),          S#state.value).

%% ID:s
-define(get_IdCounter(S),       S#state.idCounter).

%% Constraints
-define(get_Constraint(S,I),    find_obj(lists:keysearch(I, 1, S#state.constraints),
					 constraint)).
-define(get_AllConstraints(S),  lists:map(fun({I, C, _W, _WC, _K, T, A}) -> 
						  ?create_MappingInfo(T, C, I, A) 
					  end, 
					  S#state.constraints)).
-define(get_ConstraintAllData(S), S#state.constraints).
-define(get_ConstraintData(S,I), lists:keysearch(I, 1, S#state.constraints)).
-define(match_Type(S,I,ET),     ets:lookup(S#state.etsR, {I, ET})).
%% Parse Tree
-define(get_ParseTree(S,K),     find_obj(ets:lookup(S#state.etsR, K), tree)).

%%------------------- Data structures modifiers -------------------
%% ID:s
-define(set_IdCounter(S,V),     S#state{idCounter=V}).
-define(new_Id(S),              'CosNotification_Common':create_id(S#state.idCounter)).

%% Constraints
-define(del_Constraint(S, I),   match_delete(S, S#state.constraints, I)).
-define(del_AllConstraints(S),  clear_DB(S)).
-define(add_Constraint(S,I,C,W,_WC,K,T,A), S#state{constraints = 
					  [{I, C, W, _WC, K, T, A}|S#state.constraints]}).
-define(set_Constraints(S,C),   S#state{constraints = C}).

-define(del_AllTypes(S),        ets:match_delete(S#state.etsR, {'_','_',types})).
-define(del_Type(S,I),          ets:match_delete(S#state.etsR, {{I, '_'}, '_', types})).
-define(add_Type(S,I,ET,K),     ets:insert(S#state.etsR, {{I, ET}, K, types})).

%% Parse Tree
-define(add_ParseTree(S,K,T),   ets:insert(S#state.etsR, {K, T, tree})).
-define(del_ParseTree(S,K),     ets:delete(S#state.etsR, K)).
-define(del_AllParseTress(S),   ets:match_delete(S#state.etsR, {'_','_',tree})).

%%------------------- MISC ----------------------------------------
-define(is_EmptyFilter(S),     S#state.constraints==[]).
-define(is_EqualType(S,T),     S#state.typeC==any:get_typecode(T)).

%%-----------------------------------------------------------%
%% function : handle_info, code_change
%% Arguments: See gen_server documentation.
%% Effect   : Functions demanded by the gen_server module. 
%%------------------------------------------------------------

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

handle_info(Info, State) ->
    ?debug_print("INFO: ~p  DATA: ~p~n", [State, Info]),
    case Info of
        {'EXIT', _Pid, _Reason} ->
            {noreply, State};
        _ ->
            {noreply, State}
    end.

%%----------------------------------------------------------%
%% function : init, terminate
%% Arguments: 
%%-----------------------------------------------------------

init([FiFac, FacPid, InitGr, DefVal]) ->
    process_flag(trap_exit, true),
    {ok, ?get_InitState(InitGr, DefVal, FiFac, FacPid)}.

terminate(_Reason, _State) ->
    ok.

%%-----------------------------------------------------------
%%------- Exported external attributes ----------------------
%%-----------------------------------------------------------
%%----------------------------------------------------------%
%% Function : '_get_constraint_grammar'/2
%% Type     : readonly
%% Returns  : string()
%%-----------------------------------------------------------
'_get_constraint_grammar'(_OE_THIS, State) ->
    {reply, ?get_Grammar(State), State}.
%%----------------------------------------------------------%
%% Function : '_get_value_type'/2
%% Type     : readonly
%% Returns  : CORBA::TypeCode
%%-----------------------------------------------------------
'_get_value_type'(_OE_THIS, State) ->
    {reply, ?get_DefTC(State), State}.
%%----------------------------------------------------------%
%% Function : '_get_default_value'/2
%% Type     : readonly
%% Returns  : #any{}
%%-----------------------------------------------------------
'_get_default_value'(_OE_THIS, State) ->
    {reply, ?get_DefVal(State), State}.

%%-----------------------------------------------------------
%%------- Exported external functions -----------------------
%%-----------------------------------------------------------
%%----------------------------------------------------------%
%% Function : add_mapping_constraints/3
%% Arguments: Pairs - CosNotifyFilter::MappingConstraintPairSeq
%% Returns  : CosNotifyFilter::MappingConstraintInfoSeq |
%%            {'EXCEPTION', CosNotifyFilter::InvalidConstraint} |
%%            {'EXCEPTION', CosNotifyFilter::InvalidValue}
%%-----------------------------------------------------------
add_mapping_constraints(_OE_THIS, State, Pairs) ->
    {NewState, Filters, Info} = try_create_filters(State, Pairs),
    NewState2=store_filters(NewState, Filters),
    {reply, Info, NewState2}.

%%----------------------------------------------------------%
%% Function : modify_mapping_constraints/4
%% Arguments: IDs - CosNotifyFilter::ConstraintIDSeq
%%            Info - CosNotifyFilter::MappingConstraintInfoSeq
%% Returns  : ok |
%%            {'EXCEPTION', CosNotifyFilter::InvalidConstraint} |
%%            {'EXCEPTION', CosNotifyFilter::InvalidValue} |
%%            {'EXCEPTION', CosNotifyFilter::ConstraintNotFound}
%%-----------------------------------------------------------
modify_mapping_constraints(_OE_THIS, State, IDs, InfoSeq) ->
    lookup_constraints(IDs, State),
    lookup_constraints(InfoSeq, State),
    {NewState, Filters, _Info} = try_create_filters(State, InfoSeq),

    %% We cannot change anything before our checks (see above). Hence,
    %% do NOT move the following lines above this point.

    NewState2  = delete_constraints(IDs, NewState),
    NewState3  = delete_constraints(InfoSeq, NewState2),
    NewState4  = store_filters(NewState3, Filters),
    {reply, ok, NewState4}.

%%----------------------------------------------------------%
%% Function : get_mapping_constraints/3
%% Arguments: IDs - CosNotifyFilter::ConstraintIDSeq
%% Returns  : CosNotifyFilter::MappingConstraintInfoSeq |
%%            {'EXCEPTION', CosNotifyFilter::ConstraintNotFound}
%%-----------------------------------------------------------
get_mapping_constraints(_OE_THIS, State, IDs) ->
    {reply, lookup_constraints(IDs, State), State}.

%%----------------------------------------------------------%
%% Function : get_all_mapping_constraints/2
%% Arguments: -
%% Returns  : CosNotifyFilter::MappingConstraintInfoSeq 
%%-----------------------------------------------------------
get_all_mapping_constraints(_OE_THIS, State) ->
    {reply, ?get_AllConstraints(State), State}.

%%----------------------------------------------------------%
%% Function : remove_all_mapping_constraints/2
%% Arguments: -
%% Returns  : ok
%%-----------------------------------------------------------
remove_all_mapping_constraints(_OE_THIS, State) ->
    {reply, ok, ?del_AllConstraints(State)}.

%%----------------------------------------------------------%
%% Function : destroy/2
%% Arguments: -
%% Returns  : ok
%%-----------------------------------------------------------
destroy(_OE_THIS, State) ->
    {stop, normal, ok, State}.

%%----------------------------------------------------------%
%% Function : match/3
%% Arguments: Event - #any{}
%% Returns  : boolean(), #any{} (out-type) |
%%            {'EXCEPTION', CosNotifyFilter::UnsupportedFilterableData}
%%-----------------------------------------------------------
match(_OE_THIS, State, Event) when is_record(Event,'any') andalso ?is_EmptyFilter(State) ->
    {reply, {false, ?get_DefAny(State)}, State};
match(_OE_THIS, State, Event) when is_record(Event,'any') ->
    match_any_event(State, Event, ?get_ConstraintAllData(State));
match(_,_,_) ->
    corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO}).


%%----------------------------------------------------------%
%% Function : match_structured/3
%% Arguments: Event - CosNotification::StructuredEvent
%% Returns  : boolean(), #any{} (out-type) |
%%            {'EXCEPTION', CosNotifyFilter::UnsupportedFilterableData}
%%-----------------------------------------------------------
match_structured(_OE_THIS, State, Event) when 
  is_record(Event,'CosNotification_StructuredEvent') andalso ?is_EmptyFilter(State) ->
    {reply, {false, ?get_DefAny(State)}, State};
match_structured(_OE_THIS, State, Event) when 
  is_record(Event,'CosNotification_StructuredEvent') ->
    match_str_event(State, Event, ?get_ConstraintAllData(State));
match_structured(_,_,_) ->
    corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO}).

%%----------------------------------------------------------*
%% Function : match_typed/3
%% Arguments: Data - CosNotification::PropertySeq
%% Returns  : boolean() , #any{} (out-type) |
%%            {'EXCEPTION', CosNotifyFilter::UnsupportedFilterableData}
%%-----------------------------------------------------------
-spec match_typed(_, _, _) -> no_return().
match_typed(_OE_THIS, _State, _Data) ->
    corba:raise(#'NO_IMPLEMENT'{completion_status=?COMPLETED_NO}).
	 
%%--------------- LOCAL FUNCTIONS ----------------------------
%% To match constraints
find_obj({value, {Id, Con, _, _, _, Types, Any}}, _) -> 
    ?create_MappingInfo(Types, Con, Id, Any);
find_obj([{_, Tree, tree}|_], tree) -> Tree;
find_obj(_,tree) -> undefined;
find_obj(_,constraint) -> error.

%% Delete given object from list and all related objects in DB (parse tree and types).
match_delete(State, Constraints, ID) ->
    match_delete(State, Constraints, ID, []).
match_delete(_, [], _, _) ->
    error;
match_delete(State, [{ID, _Con, _Which, _WC, Key, _Types, _Any}|T], ID, Acc) ->
    ?del_Type(State, ID),
    ?del_ParseTree(State, Key),
    {ok, ?set_Constraints(State, Acc++T)};
match_delete(State, [H|T], ID, Acc) ->
    match_delete(State, T, ID, [H|Acc]).

%% Remove all data related with constraints; for now, since no other data 
%% stored in DB, we do in a rather brutal way.
clear_DB(State) ->
    catch ets:delete(State#state.etsR),
    State#state{etsR = ets:new(oe_ets, [bag, protected]), constraints=[]}.

%% Given a list of Constrain IDs we want to find the related constraints.
%% !!!!!! This function may not alter any data in DB in any way !!!!!!!!!!
lookup_constraints(IDs, State) ->
    lookup_constraints(IDs, State, []).
lookup_constraints([], _State, Accum) ->
    Accum;
lookup_constraints([H|T], State, Accum) 
  when is_record(H, 'CosNotifyFilter_MappingConstraintInfo') ->
    case ?get_Constraint(State, H#'CosNotifyFilter_MappingConstraintInfo'.constraint_id) of
	error ->
	    corba:raise(#'CosNotifyFilter_ConstraintNotFound'
			{id = H#'CosNotifyFilter_MappingConstraintInfo'.constraint_id});
	_Con ->
	    %% We don't need to collect the result since the input already is of
	    %% the correct type, i.e., ConstraintInfoSeq
	    lookup_constraints(T, State, Accum)
    end;
lookup_constraints([H|T], State, Accum) when is_integer(H) ->
    case ?get_Constraint(State,H) of
	error ->
	    corba:raise(#'CosNotifyFilter_ConstraintNotFound'{id=H});
	Con ->
	    lookup_constraints(T, State, [Con|Accum])
    end;
lookup_constraints(_, _, _) ->
    corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO}).

%% Given a list of Constrain IDs we want to delet the related constraints.
%% We need also to return the ConstraintInfoSeq described related to the
%% given ID's.
delete_constraints([], State) ->
    State;
delete_constraints([H|T], State) 
  when is_record(H, 'CosNotifyFilter_MappingConstraintInfo') ->
    case catch ?del_Constraint(State,
			       H#'CosNotifyFilter_MappingConstraintInfo'.constraint_id) of
	{ok, NewState} ->
	    delete_constraints(T, NewState);
	Reason ->
	    orber:dbg("[~p] 'CosNotifyFilter_MappingFilter':modify_mapping_constraints().~n"
		      "Unable to remove: ~p~n"
		      "Reason: ~p~n", 
		      [?LINE, H, Reason], ?DEBUG_LEVEL),
	    delete_constraints(T, State)
    end;
delete_constraints([H|T], State) ->
    case catch ?del_Constraint(State,H) of
	{ok, NewState} ->
	    delete_constraints(T, NewState);
	Reason ->
	    orber:dbg("[~p] 'CosNotifyFilter_MappingFilter':modify_mapping_constraints().~n"
		      "Unable to remove: ~p~n"
		      "Reason: ~p~n", 
		      [?LINE, H, Reason], ?DEBUG_LEVEL),
	    delete_constraints(T, State)
    end.
    
%%-----------------------------------------------------------
%% Function : try_create_filters/2
%% Arguments: CL - #'CosNotifyFilter_MappingConstraintPair{
%%                   constraint_expression = 
%%                      #'CosNotifyFilter_ConstraintExp'{
%%                             event_types = 
%%                                   [#'CosNotification_EventType'{
%%                                      domain_name = Str, type_name = Str}]
%%                             constraint_expr = Str},
%%                   result_to_set = Any}
%% Returns  : {State, AccumList}
%%-----------------------------------------------------------
%% !!!!!! This function may not alter any data in DB in any way !!!!!!!!!!
try_create_filters(State, CL) ->
    try_create_filters(State, CL, [], []).
try_create_filters(State, [], Accum, InfoSeq) ->
    {State, Accum, InfoSeq};
try_create_filters(State, [#'CosNotifyFilter_MappingConstraintPair'
			   {constraint_expression = 
			    #'CosNotifyFilter_ConstraintExp'{event_types = Types,
							     constraint_expr = Con},
			    result_to_set=Any}|T], Accum, InfoSeq) ->
    case catch {?is_EqualType(State,Any), cosNotification_Filter:create_filter(Con)} of
	{false, _} ->
	    corba:raise(#'CosNotifyFilter_InvalidValue'
			{constr =  #'CosNotifyFilter_ConstraintExp'
			 {event_types = Types, constraint_expr = Con},
			 value=Any});
	{_, {ok, Tree}} ->
	    case catch cosNotification_Filter:check_types(Types) of
		true ->
		    ID = ?new_Id(State),
		    Key = ?not_CreateDBKey,
		    try_create_filters(?set_IdCounter(State, ID), T, 
				       [{ID, true, [], Key, Types, Con, Tree, Any}|Accum],
				       [?create_MappingInfo(Types, Con, ID, Any)|InfoSeq]);
		{ok, Which, WC} ->
		    ID = ?new_Id(State),
		    Key = ?not_CreateDBKey,
		    try_create_filters(?set_IdCounter(State, ID), T, 
				       [{ID, Which, WC, Key, Types, Con, Tree, Any}|Accum],
				       [?create_MappingInfo(Types, Con, ID, Any)|InfoSeq]);
		_ ->
		    corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO})
	    end;
	_ ->
	    corba:raise(#'CosNotifyFilter_InvalidConstraint'
			{constr =  #'CosNotifyFilter_ConstraintExp'
			 {event_types = Types, constraint_expr = Con}})
    end;
try_create_filters(State, [#'CosNotifyFilter_MappingConstraintInfo'
			   {constraint_expression = #'CosNotifyFilter_ConstraintExp'
			    {event_types = Types, constraint_expr = Con},
			    constraint_id=ID,
			    value=Any}|T], Accum, InfoSeq) ->
    case catch cosNotification_Filter:create_filter(Con) of
	{ok, Tree} ->
	    case catch cosNotification_Filter:check_types(Types) of
		true ->
		    Key = ?not_CreateDBKey,
		    try_create_filters(State, T, 
				       [{ID, true, [], Key, Types, Con, Tree, Any}|Accum],
				       [?create_MappingInfo(Types, Con, ID, Any)|InfoSeq]);
		{ok, Which, WC} ->
		    Key = ?not_CreateDBKey,
		    try_create_filters(State, T, 
				       [{ID, Which, WC, Key, Types, Con, Tree, Any}|Accum],
				       [?create_MappingInfo(Types, Con, ID, Any)|InfoSeq]);
		_ ->
		    corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO})
	    end;
	_ ->
	    corba:raise(#'CosNotifyFilter_InvalidConstraint'
			{constr =  #'CosNotifyFilter_ConstraintExp'
			 {event_types = Types, constraint_expr = Con}})
    end;
try_create_filters(_,_,_,_) ->
    %% The list contained something else but ConstraintExp.
    corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO}).

%%-----------------------------------------------------------
%% Function : store_filters/4
%% Arguments: Filters - a list of filters.
%% Returns  : 
%%-----------------------------------------------------------

store_filters(State, []) ->
    State;
store_filters(State, [{ID, Which, WC, Key, Types, Con, Tree, Any}|T]) ->
    ?add_ParseTree(State, Key, Tree),
    write_types(State, Types, ID, Key),
    store_filters(?add_Constraint(State, ID, Con, Which, WC, Key, Types, Any), T).
    

write_types(_State, [],_, _) ->
    ok;
write_types(State, [EventType|T], ID, Key) ->
    ?add_Type(State, ID, EventType, Key),
    ?debug_print("FILTER:  ~p   ~p  ~p~n", [ID, Key, EventType]),
    write_types(State, T, ID, Key).

%%-----------------------------------------------------------
%% Function : match_any_event
%% Arguments: Event - #any{}
%% Returns  : 
%%-----------------------------------------------------------
match_any_event(State, _Event, []) ->
    ?debug_print("FILTER REJECTED:  ~p~n", [_Event]),
    {reply, {false, ?get_DefAny(State)}, State};
match_any_event(State, Event, [{_, _, _, _, Key, Any}|T]) ->
    case catch cosNotification_Filter:eval(?get_ParseTree(State,Key), Event) of
	true ->
	    ?debug_print("FILTER APPROVED (WC):  ~p~n", [Event]),
	    {reply, {true, Any}, State};
	_ ->
	    match_any_event(State, Event, T)
    end.
    

%%-----------------------------------------------------------
%% Function : match_str_event
%% Arguments: 
%% Returns  : 
%%-----------------------------------------------------------

match_str_event(State, _Event, []) ->
    ?debug_print("FILTER REJECTED:  ~p~n", [_Event]),
    {reply, {false, ?get_DefAny(State)}, State};
match_str_event(State, Event, [{ID, _Con, Which, WC, Key, _Types, Any}|T]) ->
    ET = ((Event#'CosNotification_StructuredEvent'.header)
	  #'CosNotification_EventHeader'.fixed_header)
	#'CosNotification_FixedEventHeader'.event_type,
    CheckList = 
	case Which of
	    both ->
		[ET];
	    domain ->
		[ET, 
		 ET#'CosNotification_EventType'{type_name=""},
		 ET#'CosNotification_EventType'{type_name="*"}];
	    type ->
		[ET, 
		 ET#'CosNotification_EventType'{domain_name=""},
		 ET#'CosNotification_EventType'{domain_name="*"}];
	    _ ->
		[ET,
		 ET#'CosNotification_EventType'{type_name=""},
		 ET#'CosNotification_EventType'{type_name="*"},
		 ET#'CosNotification_EventType'{domain_name=""},
		 ET#'CosNotification_EventType'{domain_name="*"}]
	end,
    case check_DB(State, ID, CheckList) of
	false ->
	    %% No match, may have used wildcards, e.g., "dom*".
	    case catch cosNotification_Filter:match_types(
			 ET#'CosNotification_EventType'.domain_name,
			 ET#'CosNotification_EventType'.type_name, 
			 WC) of
		true ->
		    case catch cosNotification_Filter:eval(?get_ParseTree(State,Key), 
							   Event) of
			true ->
			    ?debug_print("FILTER APPROVED (WC):  ~p~n", [Event]),
			    {reply, {true, Any}, State};
			_ ->
			    match_str_event(State, Event, T)
		    end;
		_->
		    match_str_event(State, Event, T)
	    end;
	Key ->
	    case catch cosNotification_Filter:eval(?get_ParseTree(State,Key),
						   Event) of
		true ->
		    ?debug_print("FILTER APPROVED:  ~p~n", [Event]),
		    {reply, {true, Any}, State};
		_ ->
		    match_str_event(State, Event, T)
	    end
    end.

check_DB(_, _, []) ->
    false;
check_DB(State, ID, [H|T]) ->
    case ?match_Type(State, ID, H) of
	[] ->
	    check_DB(State, ID, T);
	[{_, K, types}|_] ->
	    K
    end.
%%--------------- MISC FUNCTIONS, E.G. DEBUGGING -------------
%%--------------- END OF MODULE ------------------------------

