module arch.node.engines.naming_behaviour;

import prelude open;
import arch.node.types.messages open;
import arch.node.types.engine open;
import arch.node.types.identities open;
import arch.node.engines.naming_messages open;
import arch.node.engines.naming_config open;
import arch.node.engines.naming_environment open;
import arch.node.types.anoma as Anoma open;

type ReplyTo :=
  mkReplyTo@{
    whoAsked : Option EngineID;
    mailbox : Option MailboxID;
  };

type NamingActionArgument := | NamingActionArgumentReplyTo ReplyTo;

NamingActionArguments : Type := List NamingActionArgument;

NamingAction : Type :=
  Action
    NamingCfg
    NamingLocalState
    NamingMailboxState
    NamingTimerHandle
    NamingActionArguments
    Anoma.Msg
    Anoma.Cfg
    Anoma.Env;

NamingActionInput : Type :=
  ActionInput
    NamingCfg
    NamingLocalState
    NamingMailboxState
    NamingTimerHandle
    NamingActionArguments
    Anoma.Msg;

NamingActionEffect : Type :=
  ActionEffect
    NamingLocalState
    NamingMailboxState
    NamingTimerHandle
    Anoma.Msg
    Anoma.Cfg
    Anoma.Env;

NamingActionExec : Type :=
  ActionExec
    NamingCfg
    NamingLocalState
    NamingMailboxState
    NamingTimerHandle
    NamingActionArguments
    Anoma.Msg
    Anoma.Cfg
    Anoma.Env;

resolveNameAction (input : NamingActionInput) : Option NamingActionEffect :=
  let
    env := ActionInput.env input;
    cfg := ActionInput.cfg input;
    tt := ActionInput.trigger input;
    localState := EngineEnv.localState env;
    identityName :=
      case getEngineMsgFromTimestampedTrigger tt of
        | some mkEngineMsg@{
                 msg := Anoma.MsgNaming (MsgNamingResolveNameRequest req);
               } := some (RequestResolveName.identityName req)
        | _ := none;
  in case identityName of
       | some name :=
         let
           matchingEvidence :=
             AVLTree.filter
               \{evidence :=
                 isEqual
                   (Ord.cmp (IdentityNameEvidence.identityName evidence) name)}
               (NamingLocalState.evidenceStore localState);
           identities :=
             Set.fromList
               (map
                 \{evidence := IdentityNameEvidence.externalIdentity evidence}
                 (Set.toList matchingEvidence));
           responseMsg :=
             mkReplyResolveName@{
               externalIdentities := identities;
               err := none;
             };
         in case getEngineMsgFromTimestampedTrigger tt of {
              | some emsg :=
                some
                  mkActionEffect@{
                    env := env;
                    msgs :=
                      [
                        mkEngineMsg@{
                          sender := getEngineIDFromEngineCfg cfg;
                          target := EngineMsg.sender emsg;
                          mailbox := some 0;
                          msg :=
                            Anoma.MsgNaming
                              (MsgNamingResolveNameReply responseMsg);
                        };
                      ];
                    timers := [];
                    engines := [];
                  }
              | _ := none
            }
       | _ := none;

submitNameEvidenceAction
  (input : NamingActionInput) : Option NamingActionEffect :=
  let
    env := ActionInput.env input;
    cfg := ActionInput.cfg input;
    tt := ActionInput.trigger input;
    localState := EngineEnv.localState env;
    evidence :=
      case getEngineMsgFromTimestampedTrigger tt of
        | some mkEngineMsg@{
                 msg := Anoma.MsgNaming (MsgNamingSubmitNameEvidenceRequest req);
               } := some (RequestSubmitNameEvidence.evidence req)
        | _ := none;
  in case evidence of
       | some ev :=
         let
           isValid := verifyEvidence ev;
           alreadyExists :=
             case isValid of
               | true :=
                 isElement
                   \{a b := a && b}
                   true
                   (map
                     \{e := isEqual (Ord.cmp e ev)}
                     (Set.toList (NamingLocalState.evidenceStore localState)))
               | false := false;
           newEnv :=
             case isValid && not alreadyExists of
               | true :=
                 env@EngineEnv{localState := localState@NamingLocalState{evidenceStore := Set.insert
                   ev
                   (NamingLocalState.evidenceStore localState)}}
               | false := env;
           responseMsg :=
             mkReplySubmitNameEvidence@{
               err :=
                 case isValid of
                   | false := some "Invalid evidence"
                   | true :=
                     case alreadyExists of
                       | true := some "Evidence already exists"
                       | false := none;
             };
         in case getEngineMsgFromTimestampedTrigger tt of {
              | some emsg :=
                some
                  mkActionEffect@{
                    env := newEnv;
                    msgs :=
                      [
                        mkEngineMsg@{
                          sender := getEngineIDFromEngineCfg cfg;
                          target := EngineMsg.sender emsg;
                          mailbox := some 0;
                          msg :=
                            Anoma.MsgNaming
                              (MsgNamingSubmitNameEvidenceReply responseMsg);
                        };
                      ];
                    timers := [];
                    engines := [];
                  }
              | _ := none
            }
       | _ := none;

queryNameEvidenceAction
  (input : NamingActionInput) : Option NamingActionEffect :=
  let
    env := ActionInput.env input;
    cfg := ActionInput.cfg input;
    tt := ActionInput.trigger input;
    localState := EngineEnv.localState env;
    externalIdentity :=
      case getEngineMsgFromTimestampedTrigger tt of
        | some mkEngineMsg@{
                 msg := Anoma.MsgNaming (MsgNamingQueryNameEvidenceRequest req);
               } := some (RequestQueryNameEvidence.externalIdentity req)
        | _ := none;
  in case externalIdentity of
       | some extId :=
         let
           relevantEvidence :=
             AVLTree.filter
               \{evidence :=
                 isEqual
                   (Ord.cmp
                     (IdentityNameEvidence.externalIdentity evidence)
                     extId)}
               (NamingLocalState.evidenceStore localState);
           responseMsg :=
             mkReplyQueryNameEvidence@{
               externalIdentity := extId;
               evidence := relevantEvidence;
               err := none;
             };
         in case getEngineMsgFromTimestampedTrigger tt of {
              | some emsg :=
                some
                  mkActionEffect@{
                    env := env;
                    msgs :=
                      [
                        mkEngineMsg@{
                          sender := getEngineIDFromEngineCfg cfg;
                          target := EngineMsg.sender emsg;
                          mailbox := some 0;
                          msg :=
                            Anoma.MsgNaming
                              (MsgNamingQueryNameEvidenceReply responseMsg);
                        };
                      ];
                    timers := [];
                    engines := [];
                  }
              | _ := none
            }
       | _ := none;

resolveNameActionLabel : NamingActionExec := Seq [resolveNameAction];

submitNameEvidenceActionLabel : NamingActionExec :=
  Seq [submitNameEvidenceAction];

queryNameEvidenceActionLabel : NamingActionExec :=
  Seq [queryNameEvidenceAction];

NamingGuard : Type :=
  Guard
    NamingCfg
    NamingLocalState
    NamingMailboxState
    NamingTimerHandle
    NamingActionArguments
    Anoma.Msg
    Anoma.Cfg
    Anoma.Env;

NamingGuardOutput : Type :=
  GuardOutput
    NamingCfg
    NamingLocalState
    NamingMailboxState
    NamingTimerHandle
    NamingActionArguments
    Anoma.Msg
    Anoma.Cfg
    Anoma.Env;

NamingGuardEval : Type :=
  GuardEval
    NamingCfg
    NamingLocalState
    NamingMailboxState
    NamingTimerHandle
    NamingActionArguments
    Anoma.Msg
    Anoma.Cfg
    Anoma.Env;

resolveNameGuard
  (tt : TimestampedTrigger NamingTimerHandle Anoma.Msg)
  (cfg : EngineCfg NamingCfg)
  (env : NamingEnv)
  : Option NamingGuardOutput :=
  case getEngineMsgFromTimestampedTrigger tt of
    | some mkEngineMsg@{
             msg := Anoma.MsgNaming (MsgNamingResolveNameRequest _);
           } :=
      some
        mkGuardOutput@{
          action := resolveNameActionLabel;
          args := [];
        }
    | _ := none;

submitNameEvidenceGuard
  (tt : TimestampedTrigger NamingTimerHandle Anoma.Msg)
  (cfg : EngineCfg NamingCfg)
  (env : NamingEnv)
  : Option NamingGuardOutput :=
  case getEngineMsgFromTimestampedTrigger tt of
    | some mkEngineMsg@{
             msg := Anoma.MsgNaming (MsgNamingSubmitNameEvidenceRequest _);
           } :=
      some
        mkGuardOutput@{
          action := submitNameEvidenceActionLabel;
          args := [];
        }
    | _ := none;

queryNameEvidenceGuard
  (tt : TimestampedTrigger NamingTimerHandle Anoma.Msg)
  (cfg : EngineCfg NamingCfg)
  (env : NamingEnv)
  : Option NamingGuardOutput :=
  case getEngineMsgFromTimestampedTrigger tt of
    | some mkEngineMsg@{
             msg := Anoma.MsgNaming (MsgNamingQueryNameEvidenceRequest _);
           } :=
      some
        mkGuardOutput@{
          action := queryNameEvidenceActionLabel;
          args := [];
        }
    | _ := none;

NamingBehaviour : Type :=
  EngineBehaviour
    NamingCfg
    NamingLocalState
    NamingMailboxState
    NamingTimerHandle
    NamingActionArguments
    Anoma.Msg
    Anoma.Cfg
    Anoma.Env;

namingBehaviour : NamingBehaviour :=
  mkEngineBehaviour@{
    guards :=
      First [resolveNameGuard; submitNameEvidenceGuard; queryNameEvidenceGuard];
  };