function THIS = instrument(THIS,LIST)
% instrument  Define conditioning instruments for VAR model.
%
% Syntax to add forecast instruments
% ===================================
%
%     V = instrument(V,DEF)
%
% Syntax to remove all forecast instruments
% ==========================================
%
%     V = instrument(V)
%
% Input arguments
% ================
%
% * `V` [ VAR ] - VAR object to which forecast instruments will be added.
%
% * `DEF` [ char | cellstr ] - Definition string for the conditioning
% instruments.
%
% Output arguments
% =================
%
% * `V` [ VAR ] - VAR object with forecast instruments added or removed.
%
% Description
% ============
%
% Conditioning instruments allow you to compute forecasts conditional upon
% a linear combinationi of endogenous variables.
%
% The definition strings must have the following form:
%
%     'name := expression'
%
% where `name` is the name of the new conditioning instrument, and
% `expression` is an expression referrring to existing VAR variable names
% and/or their lags.
%
% The conditioning instruments must be a linear combination (possibly with
% a constant) of the existing endogenous variables. The names of the
% conditioning instruments must be obviously unique (i.e. distinct from the
% names of the exisiting endogenous variables, and from other instruments).
%
% Example
% ========
%
% In the following example, we assume that the VAR object `v` has at least
% three endogenous variables named `x`, `y`, and `z`.
%
%     V = instrument(V,'i1 := x - x{-1}','i2: = (x + y + z)/3');
%
% Note that the above line of code is equivalent to
%
%     V = instrument(V,'i1 := x - x{-1}');
%     V = instrument(V,'i2: = (x + y + z)/3');
%
% The command defines two conditioning instruments named `i1` and `i2`. The
% first instrument is the first difference of the variable `x`. The second
% instrument is the average of the three endogenous variables.
%
% To impose conditions (tunes) on a forecast using these instruments, you
% run [`VAR/forecast`](VAR/forecast) with the fourth input argument
% containing a time series for `i1`, `i2`, or both.
%
%     j = struct();
%     j.i1 = tseries(startdate:startdate+3,0);
%     j.i2 = tseries(startdate:startdate+3,[1;1.5;2]);
%
%     f = forecast(v,d,startdate:startdate+12,j);
%

% -IRIS Toolbox.
% -Copyright (c) 2007-2012 Jaromir Benes.

if exist('LIST','var')
    if ischar(LIST)
        LIST = {LIST};
    end
else
    LIST = {};
end
nname = numel(LIST);

ip = inputParser();
ip.addRequired('V',@(x) isa(x,'VAR'));
ip.addRequired('DEF',@(x) iscellstr(x));
ip.parse(THIS,LIST)

%**************************************************************************

if nname == 0
    % Clear conditioning instruments.
    THIS.inames = {};
    THIS.Zi = [];
    return
end

if isempty(THIS.ynames)
    utils.error('VAR', ...
        'Cannot create conditioning instruments in VAR without named variables.');
end

[ny,p] = sizeof(THIS);

NAME = cell(1,nname);
EXPRSN = cell(1,nname);
dodefine();

xvector = {};
doxvector();

dochknames();

[Z,C] = utils.lincomb2vector(EXPRSN,xvector);

dochkexprsn();

THIS.inames = [THIS.inames,NAME];
THIS.ieqtn = [THIS.ieqtn,LIST];
THIS.Zi = [THIS.Zi;[C,Z]];

% Nested functions.

%**************************************************************************
    function dodefine()
        LIST = regexprep(LIST,'\s+','');
        LIST = regexprep(LIST,';$','','once');
        LIST = regexprep(LIST,'.*','$0;','once');
        LIST = regexprep(LIST,'(?<!:)=',':=','once');
        validdef = true(1,nname);
        for i = 1 : nname
            tok = regexp(LIST{i}, ...
                '^([a-zA-Z]\w+):?=(.*);?$','tokens','once');
            if length(tok) == 2 ...
                    && ~isempty(tok{1}) && ~isempty(tok{2})
                NAME{i} = tok{1};
                EXPRSN{i} = tok{2};
            else
                validdef(i) = false;
            end
        end
        if any(~validdef)
            utils.error('VAR', ...
                ['This is not a valid definition string', ...
                'for conditioning instruments: ''%s''.'], ...
                LIST{~valid});
        end
    end
% dodefine().

%**************************************************************************
    function doxvector()
        xvector = THIS.ynames;
        for i = 2 : p
            time = sprintf('{-%g}',i);
            temp = regexprep(THIS.ynames,'.*',['$0',time]);
            xvector = [xvector,temp]; %#ok<AGROW>
        end
    end
% doxvector().

%**************************************************************************
    function dochknames()
        validname = true(1,nname);
        chklist = [THIS.ynames,NAME];
        for ii = 1 : nname
            validname(ii) = isvarname(NAME{ii}) ...
                && sum(strcmp(NAME{ii},chklist)) == 1;
        end
        if any(~validname)
            utils.error('VAR', ...
                ['This is not a valid name ', ...
                'for conditioning instruments: ''%s''.'], ...
                NAME{~validname});
        end
    end
% dochknames().

%**************************************************************************
    function dochkexprsn()
        validexprsn = all(~isnan([C,Z]),2);
        if any(~validexprsn)
            utils.error('VAR', ...
                ['This is not a valid expression ', ...
                'for conditioning instruments: ''%s''.'], ...
                EXPRSN{~validexprs});
        end
    end
% dochkexprsn().

end