classdef stlInterval < contSet
% stlInterval - open/closed time intervals for signal temporal logic specifications
%
% Time intervals are always one dimensional and are subsets of the non-negative reals.
% The boundaries of the interval can be open or closed. The interval can be empty.
%
% Syntax:
%    int = stlInterval()
%    int = stlInterval(x)
%    int = stlInterval(I)
%    int = stlInterval(SI)
%    int = stlInterval(lb,ub)
%    int = stlInterval(lb,ub,closed)
%    int = stlInterval(lb,ub,lc,rc)
%
% Inputs:
%    x - numeric value
%    I - interval object
%    SI - stlInterval object
%    lb - lower bound
%    ub - upper bound
%    closed - boolean for both left and right closed (default: true)
%    lc - is left closed (default: true)
%    rc - is right closed (default: true)
%
% Outputs:
%    int - generated stlInterval object
%
% Example:
%
%    int0 = stlInterval();               % empty interval
%    int1 = stlInterval(1);              % singular interval [1,1]
%    int2 = stlInterval(0,1);            % closed interval [0,1]
%    int3 = stlInterval(0,1,false);      % open interval (0,1)
%    int4 = stlInterval(0,1,true,false); % half open interval [0,1)
%
%
% Other m-files required: none
% Subfunctions: none
% MAT-files required: none
%
% See also: none

% Authors:       Florian Lercher
% Written:       06-February-2024
% Last update:   ---
% Last revision: ---

% ------------------------------ BEGIN CODE -------------------------------

properties (SetAccess = private, GetAccess = public)
    lower {mustBeScalarOrEmpty,mustBeNumeric}
    leftClosed logical = true
    upper {mustBeScalarOrEmpty,mustBeNumeric}
    rightClosed logical = true
end

methods

    % class constructor
    % TODO: maybe add possibility to create from interval and convert all intervals in STL to this class
    function obj = stlInterval(varargin)
        [lb,ub,lc,rc] = aux_parseArgs(varargin{:});
        if ~isempty(lb) && ~isempty(ub)
            if abs(lb) == inf
                lc = false;
            end
            if abs(ub) == inf
                rc = false;
            end
            if lb > ub || (lb == ub && (~lc || ~rc))
                % make empty interval
                [lb,ub,lc,rc] = aux_parseArgs();
            end
        end
        obj.lower = lb;
        obj.upper = ub;
        obj.leftClosed = lc;
        obj.rightClosed = rc;
    end

    % methods in separate files
    n = dim(I)
    [infi,isMin] = infimum(obj)
    res = isequal(obj,other)
    int = leftClosure(obj)
    int = minkDiff(obj,sub,type)
    int = minus(minuend,subtrahend)
    int = plus(summand1,summand2)
    int = rightClosure(obj)
    [sup,isMax] = supremum(obj)
    int = toLeft(obj)
    int = toRight(obj)

    % overload equality and inequality operators using isequal
    function res = eq(obj,other)
        res = isequal(obj,other);
    end
    function res = ne(obj,other)
        res = ~isequal(obj,other);
    end

    function str = toStr(obj)
        if isemptyobject(obj)
            str = '∅';
            return;
        end

        if obj.leftClosed
            lterm = '[';
        else
            lterm = '(';
        end
        if obj.rightClosed
            rterm = ']';
        else
            rterm = ')';
        end
        str = sprintf('%s%d, %d%s',lterm,obj.lower,obj.upper,rterm);
    end
end

methods (Static = true)
    I = empty(n)
end
end


% Auxiliary functions -----------------------------------------------------

function [lb,ub,lc,rc] = aux_parseArgs(varargin)
    switch length(varargin)
        case 0
            % empty interval
            lb = [];
            ub = [];
            lc = true;
            rc = true;
        case 1
            arg = varargin{1};
            if isnumeric(arg)
                % singular interval
                lb = varargin{1};
                ub = lb;
                lc = true;
                rc = true;
            elseif isa(arg,'stlInterval')
                % copy constructor
                lb = arg.lower;
                ub = arg.upper;
                lc = arg.leftClosed;
                rc = arg.rightClosed;
            elseif isa(arg,'interval')
                if ~all(size(arg) == [1,1])
                    throw(CORAerror('CORA:wrongInputInConstructor','Interval must be one-dimensional.'));
                end
                lb = infimum(arg);
                ub = supremum(arg);
                % regular intervals are always closed
                lc = true;
                rc = true;
            end
        case 2
            % closed interval
            lb = varargin{1};
            ub = varargin{2};
            lc = true;
            rc = true;
        case 3
            lb = varargin{1};
            ub = varargin{2};
            lc = varargin{3};
            rc = lc;
        case 4
            lb = varargin{1};
            ub = varargin{2};
            lc = varargin{3};
            rc = varargin{4};
        otherwise
            throw(CORAerror('CORA:tooManyInputArgs',4));
    end
    if ~isempty(lb) && lb < 0
        throw(CORAerror('CORA:wrongInputInConstructor','Lower bound must be non-negative.'));
    end
end

% ------------------------------ END OF CODE ------------------------------
