function  [par, logpost, accepted, neval, sampler_options] = posterior_sampler_iteration(objective_function,last_draw, last_posterior, sampler_options,varargin)
% [par, logpost, accepted, neval, sampler_options] = posterior_sampler_iteration(objective_function,last_draw, last_posterior, sampler_options,varargin)
% posterior samplers
%
% INPUTS
%   objective_function:     string storing the objective function (e.g. 'dsge_likelihood.m')
%   last_draw:              parameter vector in last iteration
%   last_posterior:         value of the posterior in last iteration
%   sampler_options:        posterior sampler options
%   varargin, usually containing:
%       dataset_:               the dataset after required transformation
%       dataset_info:           Various information about the dataset (descriptive statistics and missing observations).
%       options_:               structure storing the options
%       M_:                     structure storing the model information
%       estim_params_:          structure storing information about estimated parameters
%       bayestopt_:             structure storing information about priors
%       mh_bounds:              structure containing prior bounds
%       dr                  [structure]     Reduced form model.
%       endo_steady_state   [vector]        steady state value for endogenous variables
%       exo_steady_state    [vector]        steady state value for exogenous variables
%       exo_det_steady_state [vector]       steady state value for exogenous deterministic variables
%       derivatives_info    [structure]     derivative info for identification
% 
% OUTPUTS
%   par:                    last accepted parameter vector
%   logpost:                value of the posterior after current iteration
%   accepted:               share of proposed draws that were accepted
%   neval:                  number of evaluations (>1 only for slice)
%   sampler_options:        posterior sampler options
%
% SPECIAL REQUIREMENTS
%   none

% Copyright © 2015-2025 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/>.


posterior_sampling_method = sampler_options.posterior_sampling_method;
mh_bounds = sampler_options.bounds;

switch posterior_sampling_method
  case 'slice'
      if ~sampler_options.maximize

          if isfield(sampler_options,'use_prior_draws') && sampler_options.use_prior_draws.status
              sampler_options.last_posterior=last_posterior;
          end
          [par, logpost, neval, sampler_options] = slice_sampler(objective_function,last_draw, [mh_bounds.lb mh_bounds.ub], sampler_options,varargin{:});
          accepted = 1;
      else
          options_=varargin{3};
          bayestopt_=varargin{6};
          options_.mode_compute = sampler_options.mode_compute;
          if options_.mode_compute==5
              if options_.analytic_derivation
                  options_.analytic_derivation = -1;
              end
          end
          options_.newrat.Save_files = sampler_options.curr_block;
          [par, fval] = ...
              dynare_minimize_objective(objective_function,last_draw(:),options_.mode_compute,options_,[mh_bounds.lb mh_bounds.ub],bayestopt_.name,bayestopt_,[], ...
              varargin{:}); %inputs for objective
          logpost = -fval;
          accepted = 1;
          neval = 1;
      end

  case 'random_walk_metropolis_hastings'
    neval = 1;
    ProposalFun = sampler_options.proposal_distribution;
    proposal_covariance_Cholesky_decomposition = sampler_options.proposal_covariance_Cholesky_decomposition;
    n = sampler_options.n;

    par = feval(ProposalFun, last_draw, proposal_covariance_Cholesky_decomposition, n);
    if all( par(:) > mh_bounds.lb ) && all( par(:) < mh_bounds.ub )
        try
            logpost = - feval(objective_function, par(:),varargin{:});
        catch
            logpost = -inf;
        end
    else
        logpost = -inf;
    end
    r = logpost-last_posterior;
    if (logpost > -inf) && (log(rand) < r)
        accepted = 1;
    else
        accepted = 0;
        par = last_draw;
        logpost = last_posterior;
    end
  case 'tailored_random_block_metropolis_hastings'
    options_=varargin{3};
    bayestopt_=varargin{6};
    npar=length(last_draw);
    %% randomize indices for blocking in this iteration
    indices=randperm(npar)';
    blocks=[1; (1+cumsum((rand(length(indices)-1,1)>(1-sampler_options.new_block_probability))))];
    nblocks=blocks(end,1); %get number of blocks this iteration
    current_draw=last_draw'; %get starting point for current draw for updating
    blocked_draws_counter=0;
    accepted_draws_counter=0;
    for block_iter=1:nblocks
        blocked_draws_counter=blocked_draws_counter+1;
        nxopt=length(indices(blocks==block_iter,1)); %get size of current block
        par_start_current_block=current_draw(indices(blocks==block_iter,1));
        [xopt_current_block] = dynare_minimize_objective(@TaRB_optimizer_wrapper,par_start_current_block,sampler_options.mode_compute,options_,[mh_bounds.lb(indices(blocks==block_iter,1),1) mh_bounds.ub(indices(blocks==block_iter,1),1)],bayestopt_.name,bayestopt_,[],...
                                                                            current_draw,indices(blocks==block_iter,1),objective_function,...% inputs for wrapper
                                                                            varargin{:}); %inputs for objective
        %% covariance for proposal density
        hessian_mat = reshape(hessian('TaRB_optimizer_wrapper',xopt_current_block, ...
                                      options_.gstep,...
                                      current_draw,indices(blocks==block_iter,1),objective_function,...% inputs for wrapper
                                      varargin{:}),nxopt,nxopt);

        if any(any(isnan(hessian_mat))) || any(any(isinf(hessian_mat)))
            inverse_hessian_mat=eye(nxopt)*1e-4; %use diagonal
        else
            inverse_hessian_mat=inv(hessian_mat); %get inverse Hessian
            if any(any((isnan(inverse_hessian_mat)))) || any(any((isinf(inverse_hessian_mat))))
                inverse_hessian_mat=eye(nxopt)*1e-4; %use diagonal
            end
        end
        [proposal_covariance_Cholesky_decomposition_upper,negeigenvalues]=chol(inverse_hessian_mat);
        %if not positive definite, use generalized Cholesky of Eskow/Schnabel
        if negeigenvalues~=0
            proposal_covariance_Cholesky_decomposition_upper=chol_SE(inverse_hessian_mat,0);
        end
        proposal_covariance_Cholesky_decomposition_upper=proposal_covariance_Cholesky_decomposition_upper*diag(bayestopt_.jscale(indices(blocks==block_iter,1),:));
        %get proposal draw
        if strcmpi(sampler_options.proposal_distribution,'rand_multivariate_normal')
            n = nxopt;
        elseif strcmpi(sampler_options.proposal_distribution,'rand_multivariate_student')
            n = sampler_options.student_degrees_of_freedom;
        end

        proposed_par = feval(sampler_options.proposal_distribution, xopt_current_block', proposal_covariance_Cholesky_decomposition_upper, n);
        % check whether draw is valid and compute posterior
        if all( proposed_par(:) > mh_bounds.lb(indices(blocks==block_iter,1),:) ) && all( proposed_par(:) < mh_bounds.ub(indices(blocks==block_iter,1),:) )
            try
                logpost = - TaRB_optimizer_wrapper(proposed_par(:),...
                                  current_draw,indices(blocks==block_iter,1),objective_function,...% inputs for wrapper
                                  varargin{:});
            catch
                logpost = -inf;
            end
        else
            logpost = -inf;
        end

        if (logpost > -inf)
            %get ratio of proposal densities, required because proposal depends
            %on current mode via Hessian and is thus not symmetric anymore
            if strcmpi(sampler_options.proposal_distribution,'rand_multivariate_normal')
                proposal_density_proposed_move_forward=multivariate_normal_pdf(proposed_par,xopt_current_block',proposal_covariance_Cholesky_decomposition_upper,n);
                proposal_density_proposed_move_backward=multivariate_normal_pdf(par_start_current_block',xopt_current_block',proposal_covariance_Cholesky_decomposition_upper,n);
            elseif strcmpi(sampler_options.proposal_distribution,'rand_multivariate_student')
                proposal_density_proposed_move_forward=multivariate_student_pdf(proposed_par,xopt_current_block',proposal_covariance_Cholesky_decomposition_upper,n);
                proposal_density_proposed_move_backward=multivariate_student_pdf(par_start_current_block',xopt_current_block',proposal_covariance_Cholesky_decomposition_upper,n);
            end
            accprob=logpost-last_posterior+ log(proposal_density_proposed_move_backward)-log(proposal_density_proposed_move_forward); %Formula (6), Chib/Ramamurthy
            if  (log(rand) < accprob)
                current_draw(indices(blocks==block_iter,1))=proposed_par;
                last_posterior=logpost;
                accepted_draws_counter =accepted_draws_counter +1;
            else %no updating
                 %do nothing, keep old value
            end
        end
    end
    accepted=accepted_draws_counter/blocked_draws_counter;
    par = current_draw;
    neval=1;
    logpost = last_posterior; %make sure not a temporary draw is returned;
  case 'independent_metropolis_hastings'
    neval = 1;
    ProposalFun = sampler_options.proposal_distribution;
    ProposalDensity = sampler_options.ProposalDensity;
    proposal_covariance_Cholesky_decomposition = sampler_options.proposal_covariance_Cholesky_decomposition;
    n = sampler_options.n;
    xparam1 = sampler_options.xparam1';
    par = feval(ProposalFun, xparam1, proposal_covariance_Cholesky_decomposition, n);
    if all( par(:) > mh_bounds.lb ) && all( par(:) < mh_bounds.ub )
        try
            logpost = - feval(objective_function, par(:),varargin{:});
        catch
            logpost = -inf;
        end
    else
        logpost = -inf;
    end
    r = logpost - last_posterior + ...
        log(feval(ProposalDensity, last_draw, xparam1, proposal_covariance_Cholesky_decomposition, n)) - ...
        log(feval(ProposalDensity, par, xparam1, proposal_covariance_Cholesky_decomposition, n));
    if (logpost > -inf) && (log(rand) < r)
        accepted = 1;
    else
        accepted = 0;
        par = last_draw;
        logpost = last_posterior;
    end
end
