function info = perfect_foresight_simulation(compute_linear_solution,steady_state)
% Performs deterministic simulations with lead or lag on one period
%
% INPUTS
%   endo_simul                  [double]     n*T matrix, where n is the number of endogenous variables.
%   exo_simul                   [double]     q*T matrix, where q is the number of shocks.
%   compute_linear_solution     [integer]    scalar equal to zero or one.
%
% OUTPUTS
%   none
%
% ALGORITHM
%   Laffargue, Boucekkine, Juillard (LBJ)
%   see Juillard (1996) Dynare: A program for the resolution and
%   simulation of dynamic models with forward variables through the use
%   of a relaxation algorithm. CEPREMAP. Couverture Orange. 9602.
%
% SPECIAL REQUIREMENTS
%   None.

% Copyright (C) 2009-2015 Dynare Team
%
% This file is part of Dynare.
%
% Dynare is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% Dynare is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with Dynare.  If not, see <https://www.gnu.org/licenses/>.

global M_ options_ it_ oo_

persistent lead_lag_incidence dynamic_model ny nyp nyf nrs nrc iyf iyp isp is isf isf1 iz icf ghx iflag

if ~nargin && isempty(iflag)% Initialization of the persistent variables.
    lead_lag_incidence = M_.lead_lag_incidence;
    dynamic_model = [M_.fname '.dynamic'];
    ny   = size(oo_.endo_simul,1);
    nyp  = nnz(lead_lag_incidence(1,:));% number of lagged variables.
    nyf  = nnz(lead_lag_incidence(3,:));% number of leaded variables.
    nrs  = ny+nyp+nyf+1;
    nrc  = nyf+1;
    iyf  = find(lead_lag_incidence(3,:)>0);% indices for leaded variables.
    iyp  = find(lead_lag_incidence(1,:)>0);% indices for lagged variables.
    isp  = 1:nyp;
    is   = (nyp+1):(nyp+ny); % Indices for contemporaneaous variables.
    isf  = iyf+nyp;
    isf1 = (nyp+ny+1):(nyf+nyp+ny+1);
    iz   = 1:(ny+nyp+nyf);
    icf  = 1:size(iyf,2);
    info = [];
    iflag = 1;
    return
end

initial_endo_simul = oo_.endo_simul;

warning_old_state = warning;
warning off all

if nargin<1
    compute_linear_solution = 0;
else
    if nargin<2
        error('The steady state (second input argument) is missing!');
    end
end

if ~isstruct(compute_linear_solution) && compute_linear_solution
    [dr,info,M_,oo_] = resol(0,M_,options_,oo_);
elseif isstruct(compute_linear_solution)
    dr = compute_linear_solution;
    compute_linear_solution = 1;
end

if compute_linear_solution
    ghx(dr.order_var,:) = dr.ghx;
    ghx = ghx(iyf,:);
end

periods = options_.periods;

stop    = 0 ;
it_init = M_.maximum_lag+1;

info.convergence = 1;
info.time  = 0;
info.error = 0;
info.iterations.time  = zeros(options_.simul.maxit,1);
info.iterations.error = info.iterations.time;

last_line = options_.simul.maxit;
error_growth = 0;

h1 = clock;
for iter = 1:options_.simul.maxit
    h2 = clock;
    if options_.terminal_condition
        c = zeros(ny*(periods+1),nrc);
    else
        c = zeros(ny*periods,nrc);
    end
    it_ = it_init;
    z = [ oo_.endo_simul(iyp,it_-1) ; oo_.endo_simul(:,it_) ; oo_.endo_simul(iyf,it_+1) ];
    [d1,jacobian] = feval(dynamic_model,z,oo_.exo_simul, M_.params, it_);
    jacobian = [jacobian(:,iz) , -d1];
    ic = 1:ny;
    icp = iyp;
    c(ic,:) = jacobian(:,is)\jacobian(:,isf1) ;
    for it_ = it_init+(1:periods-1-(options_.terminal_condition==2))
        z = [ oo_.endo_simul(iyp,it_-1) ; oo_.endo_simul(:,it_) ; oo_.endo_simul(iyf,it_+1)];
        [d1,jacobian] = feval(dynamic_model,z,oo_.exo_simul, M_.params, it_);
        jacobian = [jacobian(:,iz) , -d1];
        jacobian(:,[isf nrs]) = jacobian(:,[isf nrs])-jacobian(:,isp)*c(icp,:);
        ic = ic + ny;
        icp = icp + ny;
        c(ic,:) = jacobian(:,is)\jacobian(:,isf1);
    end
    if options_.terminal_condition
        if options_.terminal_condition==1% Terminal condition is Y_{T} = Y_{T+1}
            s = eye(ny);
            s(:,isf) = s(:,isf)+c(ic,1:nyf);
            ic = ic + ny;
            c(ic,nrc) = s\c(ic,nrc);
        else% Terminal condition is Y_{T+1}-Y^{\star} = TransitionMatrix*(Y_{T}-Y^{\star})
            it_ = it_+1;
            z = [ oo_.endo_simul(iyp,it_-1) ; oo_.endo_simul(:,it_) ; oo_.endo_simul(iyf,it_+1) ] ;
            [d1,jacobian] = feval(dynamic_model,z,oo_.exo_simul, M_.params, it_);
            jacobian = [jacobian(:,iz) -d1];
            jacobian(:,[isf nrs]) = jacobian(:,[isf nrs])-jacobian(:,isp)*c(icp,:) ;
            ic = ic + ny;
            icp = icp + ny;
            s = jacobian(:,is);
            s(:,iyp) = s(:,iyp)+jacobian(:,isf)*ghx;
            c (ic,:) = s\jacobian(:,isf1);
        end
        c = bksup0(c,ny,nrc,iyf,icf,periods);
        c = reshape(c,ny,periods+1);
        oo_.endo_simul(:,it_init+(0:periods)) = oo_.endo_simul(:,it_init+(0:periods))+options_.slowc*c;
    else% Terminal condition is Y_{T}=Y^{\star}
        c = bksup0(c,ny,nrc,iyf,icf,periods);
        c = reshape(c,ny,periods);
        oo_.endo_simul(:,it_init+(0:periods-1)) = oo_.endo_simul(:,it_init+(0:periods-1))+options_.slowc*c;
    end
    err = max(max(abs(c)));
    info.iterations.time(iter)  = etime(clock,h2);
    info.iterations.error(iter) = err;
    if iter>1
        error_growth = error_growth + (info.iterations.error(iter)>info.iterations.error(iter-1));
    end
    if isnan(err) || error_growth>3
        last_line = iter;
        break
    end
    if err < options_.dynatol.f
        stop = 1;
        info.time  = etime(clock,h1);
        info.error = err;
        info.iterations.time = info.iterations.time(1:iter);
        info.iterations.error  = info.iterations.error(1:iter);
        break
    end
end

if stop && options_.terminal_condition==2
    % Compute the distance to the deterministic steady state (for the subset of endogenous variables with a non zero
    % steady state) at the last perdiod.
    idx = find(abs(oo_.steady_state)>0);
    distance_to_steady_state = abs(((oo_.endo_simul(idx,end)-oo_.steady_state(idx))./oo_.steady_state(idx)))*100;
    disp(['(max) Distance to steady state at the end is (in percentage):' num2str(max(distance_to_steady_state))])
end

if ~stop
    info.time  = etime(clock,h1);
    info.error = err;
    info.convergence = 0;
    info.iterations.time  = info.iterations.time(1:last_line);
    info.iterations.error = info.iterations.error(1:last_line);
    oo_.endo_simul = initial_endo_simul;
end

warning(warning_old_state);
