-module(tfte_server).

-behaviour(gen_statem).


%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-include_lib("kernel/include/logger.hrl").


%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% API functions
-export([start_link/0]).
-export([context_ready/1]).
-export([context_event/1]).
-export([topology_ready/1]).
-export([topology_event/1]).
-export([request_lsp/1]).
-export([delete_lsp/1]).

% Behaviour gen_statem functions
-export([init/1]).
-export([callback_mode/0]).
-export([handle_event/4]).
-export([terminate/3]).
-export([code_change/4]).


%%% Records %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-record(data, {
    services = #{}
}).


%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

start_link() ->
    gen_statem:start_link(?MODULE, [], []).

context_ready(Context) ->
    gen_statem:cast(?MODULE, {context_ready, Context}).

context_event(Event) ->
    gen_statem:cast(?MODULE, {context_event, Event}).

topology_ready(Topology) ->
    gen_statem:cast(?MODULE, {topology_ready, Topology}).

topology_event(Event) ->
    gen_statem:cast(?MODULE, {topology_event, Event}).

request_lsp(ServiceMap) ->
    gen_statem:cast(?MODULE, {request_lsp, ServiceMap}).

delete_lsp(ServiceId) ->
    gen_statem:cast(?MODULE, {delete_lsp, ServiceId}).


%%% BEHAVIOUR gen_statem FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

init([]) ->
    ?LOG_INFO("Starting server...", []),
    {ok, wait_context, #data{}}.

callback_mode() -> [handle_event_function, state_enter].

%-- WAIT_CONTEXT STATE ---------------------------------------------------------
handle_event(enter, _, wait_context, _Data) ->
    keep_state_and_data;
handle_event(cast, {context_ready, _Context}, wait_contex, Data) ->
    ?LOG_DEBUG("Teraflow context initialized: ~p", [_Context]),
    tfte_topology:context_updated(),
    {next_state, ready, Data};
%-- READY STATE ----------------------------------------------------------------
handle_event(enter, _, ready, _Data) ->
    keep_state_and_data;
handle_event(cast, {context_ready, _Context}, ready, _Data) ->
    ?LOG_DEBUG("Teraflow context updated: ~p", [_Context]),
    tfte_topology:context_updated(),
    keep_state_and_data;
handle_event(cast, {context_event, _Event}, ready, _Data) ->
    ?LOG_DEBUG("Teraflow context event: ~p", [_Event]),
    keep_state_and_data;
handle_event(cast, {topology_ready, _Topology}, ready, _Data) ->
    ?LOG_DEBUG("Teraflow topology updated: ~p", [_Topology]),
    keep_state_and_data;
handle_event(cast, {topology_event, _Event}, ready, _Data) ->
    ?LOG_DEBUG("Teraflow topology event: ~p", [_Event]),
    keep_state_and_data;
handle_event({call, _From}, {request_lsp, ServiceMap}, ready, Data) ->
    #{service_id := ServiceId} = ServiceMap,
    ?LOG_DEBUG("Teraflow service ~s requested its LSPs",
               [format_service_id(ServiceId)]),
    {Result, Data2} = do_request_lsp(Data, ServiceMap),
    {keep_state, Data2, [{reply, Result}]};
handle_event(cast, {delete_lsp, ServiceId}, ready, Data) ->
    ?LOG_DEBUG("Teraflow service ~s delete its LSPs",
              [format_service_id(ServiceId)]),
    {Result, Data2} = do_delete_lsp(Data, ServiceId),
    {keep_state, Data2, [{reply, Result}]};
%-- ANY STATE ------------------------------------------------------------------
handle_event(EventType, EventContent, State, Data) ->
    ?LOG_WARNING(Data, "Unexpected ~w event in state ~w: ~w",
                 [EventType, State, EventContent]),
    keep_state_and_data.

terminate(Reason, _State, _Data) ->
    ?LOG_INFO("Server terminated: ~p", [Reason]),
    ok.

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


%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

format_service_id(#{context_id := #{context_uuid := #{uuid := ContextName}},
                    service_uuid := #{uuid := ServiceUUID}}) ->
    iolist_to_binary(io_lib:format("~s:~s", [ContextName, ServiceUUID])).

do_request_lsp(#data{services = Services} = Data,
               #{service_type := 'SERVICETYPE_TE'} = ServiceMap) ->
    #{service_config := Config,
      service_endpoint_ids := Endpoints,
      service_id := ServiceId} = ServiceMap,
    #{binding_label := BindingLabel1, symbolic_name := SymbolicName1}
        = tfte_util:custom_config(Config, <<"/lsp-fw">>),
    #{binding_label := BindingLabel2, symbolic_name := SymbolicName2}
        = tfte_util:custom_config(Config, <<"/lsp-bw">>),
    [#{device_id := #{device_uuid := #{uuid := Id1}}},
     #{device_id := #{device_uuid := #{uuid := Id2}}}] = Endpoints,
    case epce_server:initiate_flow(SymbolicName1, Id1, Id2, BindingLabel1) of
        {error, Reason} ->
            ?LOG_ERROR("Error while setting up service ~s forward LSP: ~p",
                       [format_service_id(ServiceId), Reason]),
            {{error, Reason}, Data};
        {ok, ForwardFlow} ->
            case epce_server:initiate_flow(SymbolicName2, Id2, Id1, BindingLabel2) of
                {error, Reason} ->
                    ?LOG_ERROR("Error while setting up service ~s backward LSP: ~p",
                               [format_service_id(ServiceId), Reason]),
                    %TODO: Cleanup forward flow ?
                    {{error, Reason}, Data};
                {ok, BackwardFlow} ->
                    ServiceData = {ServiceMap, ForwardFlow, BackwardFlow},
                    Services2 = Services#{ServiceId => ServiceData},
                    Data2 = Data#data{services = Services2},
                    {{ok, 'SERVICESTATUS_ACTIVE'}, Data2}
            end
    end.

do_delete_lsp(Data, ServiceId) ->
    ?LOG_INFO("LSP DELETION REQUESTED ~p", [ServiceId]),
    {{error, not_implemented}, Data}.