classdef (Sealed=true) odeEvent 
    properties
        EventFcn function_handle % Events are zero crossings of EventFcn components.
        Direction matlab.ode.EventDirection = "both" % Events occur when zero crossings occur while increasing, decreasing, or both.
        Response matlab.ode.EventAction = "proceed" % When events are detected, either stop, proceed, or call the callback function.
        CallbackFcn function_handle % When the Response is "callback", call this function.
    end
    properties (Hidden, Access = {?ode})
        PostponeValidation = false; %#ok<*MCSUP> 
        response_int = 1 % faster equivalent for EventAction.proceed
        direction_int = 0 % faster equivalent for EventDirection.both
        eventfcnnargin = nan
    end
    methods
        function obj = odeEvent(nv)
            arguments
                nv.?odeEvent
            end
            names = fieldnames(nv);
            for k = 1:numel(names)
                obj.(names{k}) = nv.(names{k});
            end
        end
        function obj = set.Direction(obj,d)
            obj.Direction = d;
            obj.direction_int = double(d);
        end
        function obj = set.Response(obj,r)
            obj.Response = r;
            obj.response_int = double(r);
        end
        function obj = set.EventFcn(obj,f)
            obj.EventFcn = f;
            if ~isempty(f)
                obj.eventfcnnargin = nargin(f);
            else
                obj.eventfcnnargin = nan;
            end
        end
    end

    methods (Hidden,Access=?matlab.ode.internal.Solver)
        function [value,terminal,direction] = evaluate(obj,t,y,p)
            enarg = obj.eventfcnnargin;
            if isnan(enarg)
                error(message('MATLAB:ode:NoEventFcn'));
            end
            if nargin == 4 && enarg > 2
                value = obj.EventFcn(t,y,p);
            else
                value = obj.EventFcn(t,y);
            end
            value = value(:);
            if nargout > 1
                nv = numel(value);
                terminal = false(nv,1);
                nr = numel(obj.response_int);
                if nr ~= 1 && nr ~= nv
                    error(message('MATLAB:ode:ResponseSizeMismatch',nv,numel(obj.Response)));
                end
                terminal(:) = obj.response_int ~= 1; % 1 for proceed
                direction = zeros(nv,1);
                nd = numel(obj.direction_int);
                if nd ~= 1 && nd ~= nv
                    error(message('MATLAB:ode:DirectionSizeMismatch',nv,numel(obj.Direction)));
                end
                direction(:) = obj.direction_int;
            end
        end
        function p = isCallback(obj,ie)
            if isscalar(obj.Response)
                p = obj.Response == matlab.ode.EventAction.callback;
            else
                p = obj.Response(ie) == matlab.ode.EventAction.callback;
            end
        end
        function p = isTerminal(obj,ie)
            if isscalar(obj.Response)
                p = obj.Response == matlab.ode.EventAction.stop;
            else
                p = obj.Response(ie) == matlab.ode.EventAction.stop;
            end
        end
        function [stop,teCallback,yeCallback,ieCallback] = getCallbackInfo(obj,te,ye,ie)
            % Sort through the possibilities of multiple events in a step.
            if isempty(ie)
                % Not an event but an integration failure.
                stop = true;
                teCallback = zeros(0,1,'like',te);
                yeCallback = zeros(size(ye,1),0,'like',ye);
                ieCallback = zeros(0,1,'like',ie);
                return
            end
            % If multiple events have occurred in a step, all events prior
            % to the last te value must "proceed" events. Otherwise, the
            % integration would have terminated at the earlier event.
            n = length(ie);
            teCallback = te(end);
            yeCallback = ye(:,end);
            while n >= 2 && te(n - 1) == teCallback
                n = n - 1;
            end
            ieCallback = ie(n:end);
            stop = any(obj.isTerminal(ieCallback));
            ieCallback = ieCallback(obj.isCallback(ieCallback));
        end
        function [varargout] = execCallbackFcn(obj,varargin)
            % Permissively execute the callback function with respect to
            % the number of inputs and outputs.
            % [stop,ye,parameters] = callbackFcn(te,ye,ie,parameters)
            % nargout of this function will always be 3. varargout{3} will
            % be [] when parameters are not used (length(varargin) < 4).
            if isempty(obj.CallbackFcn)
                warning(message('MATLAB:ode:NoCallbackFcn',sprintf('%g',varargin{1})));
                Nout = 0;
            else
                Nin = nargin(obj.CallbackFcn);
                if Nin == -1
                    % Pass all inputs to varargin callback.
                    Nin = nargin - 1;
                end
                Nout = nargout(obj.CallbackFcn);
                [varargout{1:Nout}] = obj.CallbackFcn(varargin{1:Nin});
            end
            if Nout == 0
                % Default value of stop is true.
                varargout{1} = true;
            end
            if Nout < 2
                % ye value passes through unchanged.
                varargout{2} = varargin{2};
            end
            if Nout < 3
                if nargin >= 5
                    % Parameters value passes through unchanged.
                    varargout{3} = varargin{4};
                else
                    % Return [] for unused Parameters value.
                    varargout{3} = [];
                end
            end
        end
    end

    properties(Constant,Hidden)
        % 1.0 : Initial version (R2023b)
        Version = 1.0;
    end

    methods (Hidden)
        function b = saveobj(a)
            % Save all properties to struct. 
            b = ode.assignProps(a,false,"odeEvent");
            % Keep track of versioning information.
            b.CompatibilityHelper.versionSavedFrom = a.Version;
            b.CompatibilityHelper.minCompatibleVersion = 1.0;
        end
    end

    methods (Hidden,Static)
        function b = loadobj(a)
            if odeEvent.Version < a.CompatibilityHelper.minCompatibleVersion
                warning(message("MATLAB:ode:MinVersionIncompat","odeEvent"));
                b = odeEvent;
                return
            end
            if isfield(a,'Direction') && isstruct(a.Direction)
                % Direction enum not available
                warning(message("MATLAB:ode:DefaultIncompat","Direction"));
                a.Direction = matlab.ode.EventDirection(0); % both
            end
            if isfield(a,'Response') && isstruct(a.Response)
                % Response enum not available
                warning(message("MATLAB:ode:DefaultIncompat","Response"));
                a.Response = matlab.ode.EventAction(1); % proceed
            end
            b = ode.assignProps(a,true,"odeEvent");
        end
    end

end

%    Copyright 2023 MathWorks, Inc.