%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 1996-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(snmpa_set).
-moduledoc false.

-behaviour(snmpa_set_mechanism).

-define(VMODULE,"SET").
-include("snmp_verbosity.hrl").


%%%-----------------------------------------------------------------
%%% This module implements a simple, basic atomic set mechanism.
%%%-----------------------------------------------------------------
%%% Table of contents
%%% =================
%%% 1. SET REQUEST
%%% 1.1 SET phase one
%%% 1.2 SET phase two
%%% 2. Misc functions
%%%-----------------------------------------------------------------

%% External exports
-export([do_set/2, do_subagent_set/1]).

%%%-----------------------------------------------------------------
%%% 1. SET REQUEST
%%%
%%% 1) Perform set_phase_one for all own vars
%%% 2) Perform set_phase_one for all SAs
%%%    IF nok THEN 2.1 ELSE 3
%%% 2.1) Perform set_phase_two(undo) for all SAs that have performed
%%%      set_phase_one.
%%% 3) Perform set_phase_two for all own vars
%%% 4) Perform set_phase_two(set) for all SAs
%%%    IF nok THEN 4.1 ELSE 5
%%% 4.1) Perform set_phase_two(undo) for all SAs that have performed
%%%      set_phase_one but not set_phase_two(set).
%%% 5) noError
%%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% First of all - validate MibView for all varbinds. In this way
%% we don't have to send the MibView to all SAs for validation.
%%-----------------------------------------------------------------
do_set(MibView, UnsortedVarbinds) ->
    ?vtrace("do set with"
	    "~n   MibView: ~p",[MibView]),
    case snmpa_acm:validate_all_mib_view(UnsortedVarbinds, MibView) of
	true ->
	    {MyVarbinds , SubagentVarbinds} = 
		sort_varbindlist(UnsortedVarbinds),
	    case set_phase_one(MyVarbinds, SubagentVarbinds) of
		{noError, 0} -> set_phase_two(MyVarbinds, SubagentVarbinds);
		{Reason, Index} -> {Reason, Index}
	    end;
	{false, Index} ->
	    {noAccess, Index}
    end.

%%-----------------------------------------------------------------
%% This function is called when a subagents receives a message
%% concerning some set_phase.
%% Mandatory messages for all subagents:
%%   [phase_one, UnsortedVarbinds]
%%   [phase_two, set, UnsortedVarbinds]
%%   [phase_two, undo, UnsortedVarbinds]
%%-----------------------------------------------------------------
do_subagent_set([phase_one, UnsortedVarbinds]) ->
    ?vtrace("do subagent set, phase one",[]),
    {MyVarbinds, SubagentVarbinds} = sort_varbindlist(UnsortedVarbinds),
    set_phase_one(MyVarbinds, SubagentVarbinds);
do_subagent_set([phase_two, State, UnsortedVarbinds]) ->
    ?vtrace("do subagent set, phase two",[]),
    {MyVarbinds, SubagentVarbinds} = sort_varbindlist(UnsortedVarbinds),
    set_phase_two(State, MyVarbinds, SubagentVarbinds).
    
%%%-----------------------------------------------------------------
%%% 1.1 SET phase one
%%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: set_phase_one/3
%% Purpose: First, do set_phase_one for my own variables (i.e. 
%%          variables handled by this agent). Then, do set_phase_one
%%          for all subagents. If any SA failed, do set_phase_two
%%          (undo) for all SA that have done set_phase_one.
%% Returns: {noError, 0} | {ErrorStatus, Index}
%%-----------------------------------------------------------------
set_phase_one(MyVarbinds, SubagentVarbinds) ->
    ?vtrace("set phase one: "
	    "~n   MyVarbinds:       ~p"
	    "~n   SubagentVarbinds: ~p",
	    [MyVarbinds, SubagentVarbinds]),
    case set_phase_one_my_variables(MyVarbinds) of
	{noError, 0} ->
	    case set_phase_one_subagents(SubagentVarbinds, []) of
		{noError, 0} ->
		    {noError, 0};
		{{ErrorStatus, Index}, PerformedSubagents} ->
		    case set_phase_two_undo(MyVarbinds, PerformedSubagents) of
			{noError, 0} ->
			    {ErrorStatus, Index};
			{WorseErrorStatus, WorseIndex} ->
			    {WorseErrorStatus, WorseIndex}
		    end
	    end;
	{ErrorStatus, Index} ->
	    {ErrorStatus, Index}
    end.

set_phase_one_my_variables(MyVarbinds) ->
    ?vtrace("my variables set, phase one:"
	    "~n   ~p",[MyVarbinds]),
    case snmpa_set_lib:is_varbinds_ok(MyVarbinds) of
	{noError, 0} ->
	    snmpa_set_lib:consistency_check(MyVarbinds);
	{ErrorStatus, Index} ->
	    {ErrorStatus, Index}
    end.

%%-----------------------------------------------------------------
%% Loop all subagents, and perform set_phase_one for them.
%%-----------------------------------------------------------------
set_phase_one_subagents([{SubAgentPid, SAVbs}|SubagentVarbinds], Done) ->
    {_SAOids, Vbs} = sa_split(SAVbs),
    case (catch snmpa_agent:subagent_set(SubAgentPid, [phase_one, Vbs])) of
	{noError, 0} ->
	    set_phase_one_subagents(SubagentVarbinds, 
				    [{SubAgentPid, SAVbs} | Done]);
	{'EXIT', Reason} ->
	    user_err("Lost contact with subagent (set phase_one)"
		     "~n~w. Using genErr", [Reason]),
	    {{genErr, 0}, Done};
	{ErrorStatus, ErrorIndex} ->
	    {{ErrorStatus, ErrorIndex}, Done}
    end;
set_phase_one_subagents([], _Done) ->
    {noError, 0}.

%%%-----------------------------------------------------------------
%%% 1.2 SET phase two
%%%-----------------------------------------------------------------
%% returns:  {ErrStatus, ErrIndex}
set_phase_two(MyVarbinds, SubagentVarbinds) ->
    ?vtrace("set phase two: "
	    "~n   MyVarbinds:       ~p"
	    "~n   SubagentVarbinds: ~p",
	    [MyVarbinds, SubagentVarbinds]),
    case snmpa_set_lib:try_set(MyVarbinds) of
	{noError, 0} ->
            ?vtrace("set phase two: (local) varbinds set ok", []),
	    set_phase_two_subagents(SubagentVarbinds);
	{ErrorStatus, ErrorIndex} ->
            ?vlog("set phase two: (local) varbinds set failed"
                  "~n   ErrorStatus: ~p" 
                  "~n   ErrorIndex:  ~p", [ErrorStatus, ErrorIndex]),
	    set_phase_two_undo_subagents(SubagentVarbinds),
	    {ErrorStatus, ErrorIndex}
    end.

%%-----------------------------------------------------------------
%% This function is called for each phase_two state in the
%% subagents. The undo state just pass undo along to each of its
%% subagents.
%%-----------------------------------------------------------------
set_phase_two(set, MyVarbinds, SubagentVarbinds) ->
    set_phase_two(MyVarbinds, SubagentVarbinds);
set_phase_two(undo, MyVarbinds, SubagentVarbinds) ->
    set_phase_two_undo(MyVarbinds, SubagentVarbinds).

%%-----------------------------------------------------------------
%% Loop all subagents, and perform set_phase_two(set) for them.
%% If any fails, perform set_phase_two(undo) for the not yet
%% called SAs.
%%-----------------------------------------------------------------
set_phase_two_subagents([{SubAgentPid, SAVbs} | SubagentVarbinds]) ->
    {_SAOids, Vbs} = sa_split(SAVbs),
    case catch snmpa_agent:subagent_set(SubAgentPid, [phase_two, set, Vbs]) of
	{noError, 0} ->
            ?vtrace("set phase two: subagent ~p varbinds set ok", [SubAgentPid]),
	    set_phase_two_subagents(SubagentVarbinds);
	{'EXIT', Reason} ->
	    user_err("Lost contact with subagent (set)~n~w. Using genErr", 
		     [Reason]),
	    set_phase_two_undo_subagents(SubagentVarbinds),
	    {genErr, 0};
	{ErrorStatus, ErrorIndex} ->
            ?vlog("set phase two: subagent ~p varbinds set failed"
                  "~n   ErrorStatus: ~p"
                  "~n   ErrorIndex:  ~p", [SubAgentPid, ErrorStatus, ErrorIndex]),
	    set_phase_two_undo_subagents(SubagentVarbinds),
	    {ErrorStatus, ErrorIndex}
    end;
set_phase_two_subagents([]) ->
    ?vtrace("set phase two: subagent(s) set ok", []),
    {noError, 0}.

%%-----------------------------------------------------------------
%% This function undos phase_one, own and subagent.
%%-----------------------------------------------------------------
set_phase_two_undo(MyVarbinds, SubagentVarbinds) ->
    case set_phase_two_undo_my_variables(MyVarbinds) of
	{noError, 0} ->
	    set_phase_two_undo_subagents(SubagentVarbinds);
	{ErrorStatus, Index} ->
	    set_phase_two_undo_subagents(SubagentVarbinds),
	    {ErrorStatus, Index}
    end.

set_phase_two_undo_my_variables(MyVarbinds) ->
    snmpa_set_lib:undo_varbinds(MyVarbinds).

set_phase_two_undo_subagents([{SubAgentPid, SAVbs} | SubagentVarbinds]) ->
    {_SAOids, Vbs} = sa_split(SAVbs),
    case catch snmpa_agent:subagent_set(SubAgentPid, [phase_two, undo, Vbs]) of
	{noError, 0} ->
	    set_phase_two_undo_subagents(SubagentVarbinds);
	{'EXIT', Reason} ->
	    user_err("Lost contact with subagent (undo)~n~w. Using genErr", 
		     [Reason]),
	    {genErr, 0};
	{ErrorStatus, ErrorIndex} ->
	    {ErrorStatus, ErrorIndex}
    end;
set_phase_two_undo_subagents([]) ->
    {noError, 0}.

%%%-----------------------------------------------------------------
%%% 2. Misc functions
%%%-----------------------------------------------------------------
sort_varbindlist(Varbinds) ->
    snmpa_svbl:sort_varbindlist(get(mibserver), Varbinds).

sa_split(SubagentVarbinds) ->
    snmpa_svbl:sa_split(SubagentVarbinds).


user_err(F, A) ->
    snmpa_error:user_err(F, A).
