function [Res] = update_nowcast(X_old,X_new,Time,Spec,Res,series,period,vintage_old,vintage_new)


if ~isnumeric(vintage_old)
    vintage_old = datenum(vintage_old,'yyyy-mm-dd');
end
if ~isnumeric(vintage_new)
    vintage_new = datenum(vintage_new,'yyyy-mm-dd');
end

% Make sure datasets are the same size
N = size(X_new,2);
T_old = size(X_old,1); T_new = size(X_new,1);
if T_new > T_old
    X_old = [X_old; NaN(T_new-T_old,N)];
end
% append 1 year (12 months) of data to each dataset to allow for
% forecasting at different horizons
X_old = [X_old; NaN(12,N)];
X_new = [X_new; NaN(12,N)];

[y, m, d] = datevec(Time(end));

Time = [Time; datenum(y,(m+1:m+12)',d)];

i_series = find(strcmp(series,Spec.SeriesID));

series_name = Spec.SeriesName{i_series};
freq        = Spec.Frequency{i_series};

switch freq
    case 'm'
        [y,m] = strtok(period,freq);
        y = str2double(y);
        m = str2double(strrep(m,freq,''));
        d = 1;
        t_nowcast = find(Time==datenum(y,m,d));
    case 'q'
        [y,q] = strtok(period,freq);
        y = str2double(y);
        q = str2double(strrep(q,freq,''));
        m = 3*q;
        d = 1;
        t_nowcast = find(Time==datenum(y,m,d));
end

if isempty(t_nowcast)
    error('Period is out of nowcasting horizon (up to one year ahead).');
end



% Update nowcast for target variable 'series' (i) at horizon 'period' (t)
%   > Relate nowcast update into news from data releases:
%     a. Compute the impact from data revisions
%     b. Compute the impact from new data releases


X_rev = X_new;  
X_rev(isnan(X_old)) = NaN;  

% Compute news --------------------------------------------------------

% Compute impact from data revisions
[y_old] = News_DFM(X_old,X_rev,Res,t_nowcast,i_series);

% Compute impact from data releases
[y_rev,y_new,~,actual,forecast,weight] = News_DFM(X_rev,X_new,Res,t_nowcast,i_series);

% Display output
fprintf('\n\n\n');
fprintf('Nowcast Update: %s \n', datestr(vintage_new, 'mmmm dd, yyyy'))
fprintf('Nowcast for %s (%s), %s \n',Spec.SeriesName{i_series},Spec.UnitsTransformed{i_series},datestr(Time(t_nowcast),'YYYY:QQ'));

if(isempty(forecast))  % Only display table output if a forecast is made
    fprintf('\n  No forecast was made.\n')
else

    impact_revisions = y_rev - y_old;      % Impact from revisions
    news = actual - forecast;              % News from releases
    impact_releases = weight .* (news);    % Impact of releases

    % Store results
    news_table = array2table([forecast, actual, weight, impact_releases],...
                            'VariableNames', {'Forecast', 'Actual', 'Weight', 'Impact'},...
                            'RowNames', Spec.SeriesID);

    % Select only series with updates
    data_released = any(isnan(X_old) & ~isnan(X_new), 1);  

    % Display the impact decomposition
    fprintf('\n  Nowcast Impact Decomposition \n')
    fprintf('  Note: The displayed output is subject to rounding error\n\n')
    fprintf('                  %s nowcast:                  %5.2f\n', ...
            datestr(vintage_old, 'mmm dd'), y_old)
    fprintf('      Impact from data revisions:      %5.2f\n', impact_revisions)
    fprintf('       Impact from data releases:      %5.2f\n', sum(news_table.Impact,'omitnan'))
    fprintf('                                     +_________\n')
    fprintf('                    Total impact:      %5.2f\n', ...
            impact_revisions + sum(news_table.Impact,'omitnan'))
    fprintf('                  %s nowcast:                  %5.2f\n\n', ...
        datestr(vintage_new, 'mmm dd'), y_new)

    % Display the table output
    fprintf('\n  Nowcast Detail Table \n\n')
    disp(news_table(data_released, :))


    % Output results to structure
    Res.impact_revisions = impact_revisions;
    Res.weight = weight;
    Res.news_table = news_table;

end

end


function [y_old,y_new,singlenews,actual,forecast,weight,t_miss,v_miss,innov] = News_DFM(X_old,X_new,Res,t_fcst,v_news)
%News_DFM()    Calculates changes in news
%             
%  Syntax:
%  [y_old, y_new, singlenews, actual, fore, weight ,t_miss, v_miss, innov] = ...
%    News_DFM(X_old, X_new, Q, t_fcst, v_news) 
%
%  Description:
%   News DFM() inputs two datasets, DFM parameters, target time index, and 
%   target variable index. The function then produces Nowcast updates and
%   decomposes the changes into news.
%
%  Input Arguments:
%    X_old:  Old data matrix (old vintage)
%    X_new:  New data matrix (new vintage)
%    Res:    DFM() output results (see DFM for more details)
%    t_fcst: Index for target time
%    v_news: Index for target variable 
%
%  Output Arguments:
%    y_old:       Old nowcast
%    y_new:       New nowcast
%    single_news: News for each data series
%    actual:      Observed series release values
%    fore:        Forecasted series values
%    weight:      News weight
%    t_miss:      Time index for data releases
%    v_miss:      Series index for data releases
%    innov:       Difference between observed and predicted series values ("innovation")

%% Initialize variables

r = size(Res.C,2);
[~, N] = size(X_new);
singlenews = zeros(1,N);  % Initialize news vector (will store news for each series)

%% NO FORECAST CASE: Already values for variables v_news at time t_fcst

if ~isnan(X_new(t_fcst,v_news)) 
    Res_old = para_const(X_old, Res, 0);  % Apply Kalman filter for old data


    for i=1:size(v_news,2)      % Loop for each target variable
        % (Observed value) - (predicted value)
        singlenews(:,v_news(i)) = X_new(t_fcst,v_news(i)) ...
                                    - Res_old.X_sm(t_fcst,v_news(i));
        
        % Set predicted and observed y values
        y_old(1,i) = Res_old.X_sm(t_fcst,v_news(i));
        y_new(1,i) = X_new(t_fcst,v_news(i));
    end

    % Forecast-related output set to empty
    actual=[];forecast=[];weight=[];t_miss=[];v_miss=[];innov=[];


else
    %% FORECAST CASE (these are broken down into (A) and (B))

    % Initialize series mean/standard deviation respectively
    Mx = Res.Mx;
    Wx = Res.Wx;

    % Calculate indicators for missing values (1 if missing, 0 otherwise)
    miss_old=isnan(X_old);
    miss_new=isnan(X_new);

    % Indicator for missing--combine above information to single matrix where:
    % (i) -1: Value is in the old data, but missing in new data
    % (ii) 1: Value is in the new data, but missing in old data 
    % (iii) 0: Values are missing from/available in both datasets
    i_miss = miss_old - miss_new;

    % Time/variable indicies where case (b) is true
    [t_miss,v_miss]=find(i_miss==1);

    %% FORECAST SUBCASE (A): NO NEW INFORMATION

    if isempty(v_miss)
        % Fill in missing variables using a Kalman filter
        Res_old = para_const(X_old, Res, 0);
        Res_new = para_const(X_new, Res, 0);
        
        % Set predicted and observed y values. New y value is set to old
        y_old = Res_old.X_sm(t_fcst,v_news);
        y_new = y_old;
        % y_new = Res_new.X_sm(t_fcst,v_news);
        
        % No news, so nothing returned for news-related output
        groupnews=[];singlenews=[];gain=[];gainSer=[];
        actual=[];forecast=[];weight=[];t_miss=[];v_miss=[];innov=[];
    else
    %----------------------------------------------------------------------
    %     v_miss=[1:size(X_new,2)]';
    %     t_miss=t_miss(1)*ones(size(X_new,2),1);
    %----------------------------------------------------------------------
    %% FORECAST SUBCASE (B): NEW INFORMATION
   
    % Difference between forecast time and new data time
    lag = t_fcst-t_miss;

    % Gives biggest time interval between forecast and new data
    k = max([abs(lag); max(lag)-min(lag)]);

    C = Res.C;   % Observation matrix
    R = Res.R';  % Covariance for observation matrix residuals

    % Number of new events
    n_news = size(lag,1);

    % Smooth old dataset
    Res_old = para_const(X_old, Res, k);
    Plag = Res_old.Plag;

    % Smooth new dataset
    Res_new = para_const(X_new, Res, 0);

    % Subset for target variable and forecast time
    y_old = Res_old.X_sm(t_fcst,v_news);
    y_new = Res_new.X_sm(t_fcst,v_news);



    P = Res_old.P(:,:,2:end);
    P1=[];  % Initialize projection onto updates

    % Cycle through total number of updates
    for i=1:n_news
        h = abs(t_fcst-t_miss(i));
        m = max([t_miss(i) t_fcst]);
        
        % If location of update is later than the forecasting date
        if t_miss(i)>t_fcst
            Pp=Plag{h+1}(:,:,m);  %P(1:r,h*r+1:h*r+r,m)';
        else
            Pp=Plag{h+1}(:,:,m)';  %P(1:r,h*r+1:h*r+r,m);
        end
        P1=[P1 Pp*C(v_miss(i),1:r)'];  % Projection on updates
    end

    for i=1:size(t_miss,1)
        % Standardize predicted and observed values
        X_new_norm = (X_new(t_miss(i),v_miss(i)) - Mx(v_miss(i)))./Wx(v_miss(i));
        X_sm_norm = (Res_old.X_sm(t_miss(i),v_miss(i))- Mx(v_miss(i)))./Wx(v_miss(i));
        
        % Innovation: Gives [observed] data - [predicted data]
        innov(i)= X_new_norm - X_sm_norm;          
    end

    ins=size(innov,2);
    P2=[];
    p2=[];

    % Gives non-standardized series weights
    for i=1:size(lag,1)
        for j=1:size(lag,1)
            h=abs(lag(i)-lag(j));
            m=max([t_miss(i),t_miss(j)]);
            
            if t_miss(j)>t_miss(i)
                Pp=Plag{h+1}(:,:,m); %P(1:r,h*r+1:(h+1)*r,m)';
            else
                Pp=Plag{h+1}(:,:,m)'; %P(1:r,h*r+1:(h+1)*r,m);
            end
            if v_miss(i)==v_miss(j) & t_miss(i)~=t_miss(j)
                WW(v_miss(i),v_miss(j))=0;
            else
                WW(v_miss(i),v_miss(j))=R(v_miss(i),v_miss(j));
            end
            p2=[p2 C(v_miss(i),1:r)*Pp*C(v_miss(j),1:r)'+WW(v_miss(i),v_miss(j))];
        end
        P2=[P2;p2];
        p2=[];
    end

    clear temp
    for i=1:size(v_news,2)      % loop on v_news
        % Convert to real units (unstadardized data)
        totnews(1,i) = Wx(v_news(i))*C(v_news(i),1:r)*P1*inv(P2)*innov';
        temp(1,:,i) = Wx(v_news(i))*C(v_news(i),1:r)*P1*inv(P2).*innov;
        gain(:,:,i) = Wx(v_news(i))*C(v_news(i),1:r)*P1*inv(P2);
    end

    % Initialize output objects
    singlenews = NaN(max(t_miss)-min(t_miss)+1,N); %,size(v_news,2)
    actual     = NaN(N,1);  % Actual forecasted values
    forecast   = NaN(N,1);  % Forecasted values
    weight     = NaN(N,1,size(v_news,2));

    % Fill in output values 
    for i=1:size(innov,2)
        actual(v_miss(i),1) = X_new(t_miss(i),v_miss(i));  
        forecast(v_miss(i),1) = Res_old.X_sm(t_miss(i),v_miss(i));
        
        for j=1:size(v_news,2)
            singlenews(t_miss(i)-min(t_miss)+1,v_miss(i),j) = temp(1,i,j);
            weight(v_miss(i),:,j) = gain(:,i,j)/Wx(v_miss(i));
        end
    end

    singlenews = sum(singlenews,1);      % Returns total news


    [v_miss, ~, ~] = unique(v_miss);  

end

end

end

function Res = para_const(X, P, lag)
%para_const()    Implements Kalman filter for "News_DFM.m"
%
%  Syntax:
%    Res = para_const(X,P,lag)
%
%  Description:
%    para_const() implements the Kalman filter for the news calculation
%    step. This procedure smooths and fills in missing data for a given 
%    data matrix X. In contrast to runKF(), this function is used when
%    model parameters are already estimated.
%
%  Input parameters:
%    X: Data matrix. 
%    P: Parameters from the dynamic factor model.
%    lag: Number of lags
%
%  Output parameters:
%    Res [struc]: A structure containing the following:
%      Res.Plag: Smoothed factor covariance for transition matrix
%      Res.P:    Smoothed factor covariance matrix
%      Res.X_sm: Smoothed data matrix
%      Res.F:    Smoothed factors
%    


% Kalman filter with specified paramaters 
% written for 
% "MAXIMUM LIKELIHOOD ESTIMATION OF FACTOR MODELS ON DATA SETS WITH
% ARBITRARY PATTERN OF MISSING DATA."
% by Marta Banbura and Michele Modugno 

%% Set model parameters and data preparation

% Set model parameters
Z_0 = P.Z_0;
V_0 = P.V_0;
A = P.A;
C = P.C;
Q = P.Q;
R = P.R;
Mx = P.Mx;
Wx = P.Wx;

% Prepare data
[T,~] = size(X);

% Standardise x
Y = ((X-repmat(Mx,T,1))./repmat(Wx,T,1))';

%% Apply Kalman filter and smoother
% See runKF() for details about FIS and SKF

Sf = SKF(Y, A, C, Q, R, Z_0, V_0);  % Kalman filter

Ss = FIS(A, Sf);  % Smoothing step

%% Calculate parameter output
Vs = Ss.VmT(:,:,2:end);  % Smoothed factor covariance for transition matrix
Vf = Sf.VmU(:,:,2:end);  % Filtered factor posterior covariance
Zsmooth = Ss.ZmT;        % Smoothed factors
Vsmooth = Ss.VmT;        % Smoothed covariance values

Plag{1} = Vs;

for jk = 1:lag
    for jt = size(Plag{1},3):-1:lag+1
        As = Vf(:,:,jt-jk)*A'*pinv(A*Vf(:,:,jt-jk)*A'+Q);
        Plag{jk+1}(:,:,jt) = As*Plag{jk}(:,:,jt);
    end
end

% Prepare data for output
Zsmooth=Zsmooth';
x_sm = Zsmooth(2:end,:)*C';  % Factors to series representation
X_sm = repmat(Wx,T,1).*x_sm+repmat(Mx,T,1);  % Standardized to unstandardized

%--------------------------------------------------------------------------
%   Loading the structure with the results
%--------------------------------------------------------------------------
Res.Plag = Plag;
Res.P = Vsmooth;
Res.X_sm = X_sm;  
Res.F = Zsmooth(2:end,:); 

end


%______________________________________________________________________
function S = SKF(Y, A, C, Q, R, Z_0, V_0)
%SKF    Applies Kalman filter
%
%  Syntax:
%    S = SKF(Y, A, C, Q, R, Z_0, V_0)
%
%  Description:
%    SKF() applies a Kalman filter. This is a bayesian algorithm. Looping 
%    forward in time, a 'prior' estimate is calculated from the previous 
%    period. Then, using the observed data, a 'posterior' value is obtained.
%
%  Input parameters:
%    y:   Input data.
%    A:   Transition matrix coefficients. 
%    C:   Observation matrix coefficients.
%    Q:   Covariance matrix (factors and idiosyncratic component)
%    R:   Variance-Covariance for observation equation residuals 
%    Z_0: Initial factor values
%    V_0: Initial factor covariance matrix 
%
%  Output parameters:
%    S.Zm:     Prior/predicted factor state vector (Z_t|t-1)  
%    S.ZmU:    Posterior/updated state vector (Z_t|t)  
%    S.Vm:     Prior/predicted covariance of factor state vector (V_t|t-1)  
%    S.VmU:    Posterior/updated covariance of factor state vector (V_t|t)  
%    S.loglik: Value of likelihood function
%    S.k_t:    Kalman gain
%
%  Model:
%   Y_t = C_t Z_t + e_t,     e_t ~ N(0, R)
%   Z_t = A Z_{t-1} + mu_t,  mu_t ~ N(0, Q)
  
%% INSTANTIATE OUTPUT VALUES ---------------------------------------------
  % Output structure & dimensions of state space matrix
  [~, m] = size(C);
  
  % Outputs time for data matrix. "number of observations"
  nobs  = size(Y,2);
  
  % Initialize output
  S.Zm  = NaN(m, nobs);       % Z_t | t-1 (prior)
  S.Vm  = NaN(m, m, nobs);    % V_t | t-1 (prior)
  S.ZmU = NaN(m, nobs+1);     % Z_t | t (posterior/updated)
  S.VmU = NaN(m, m, nobs+1);  % V_t | t (posterior/updated)
  S.loglik = 0;

%% SET INITIAL VALUES ----------------------------------------------------
  Zu = Z_0;  % Z_0|0 (In below loop, Zu gives Z_t | t)
  Vu = V_0;  % V_0|0 (In below loop, Vu guvse V_t | t)
  
  % Store initial values
  S.ZmU(:,1)    = Zu;
  S.VmU(:,:,1)  = Vu;

%% KALMAN FILTER PROCEDURE ----------------------------------------------
  for t = 1:nobs
      %%% CALCULATING PRIOR DISTIBUTION----------------------------------
      
      % Use transition eqn to create prior estimate for factor
      % i.e. Z = Z_t|t-1
      Z   = A * Zu;
      
      % Prior covariance matrix of Z (i.e. V = V_t|t-1)
      %   Var(Z) = Var(A*Z + u_t) = Var(A*Z) + Var(\epsilon) = 
      %   A*Vu*A' + Q
      V   = A * Vu* A' + Q; 
      V   =  0.5 * (V+V');  % Trick to make symmetric
      
      %%% CALCULATING POSTERIOR DISTRIBUTION ----------------------------
       
      % Removes missing series: These are removed from Y, C, and R
      [Y_t, C_t, R_t, ~] = MissData(Y(:,t), C, R); 

      % Check if y_t contains no data. If so, replace Zu and Vu with prior.
      if isempty(Y_t)
          Zu = Z;
          Vu = V;
      else  
          % Steps for variance and population regression coefficients:
          % Var(c_t*Z_t + e_t) = c_t Var(A) c_t' + Var(u) = c_t*V *c_t' + R
          VC  = V * C_t';  
          iF  = inv(C_t * VC + R_t);
          
          % Matrix of population regression coefficients (QuantEcon eqn #4)
          VCF = VC*iF;  

          % Gives difference between actual and predicted observation
          % matrix values
          innov  = Y_t - C_t*Z;
          
          % Update estimate of factor values (posterior)
          Zu  = Z  + VCF * innov;
          
          % Update covariance matrix (posterior) for time t
          Vu  = V  - VCF * VC';
          Vu   =  0.5 * (Vu+Vu'); % Approximation trick to make symmetric
          
          % Update log likelihood 
          S.loglik = S.loglik + 0.5*(log(det(iF))  - innov'*iF*innov);
      end
      
      %%% STORE OUTPUT----------------------------------------------------
      
      % Store covariance and observation values for t-1 (priors)
      S.Zm(:,t)   = Z;
      S.Vm(:,:,t) = V;

      % Store covariance and state values for t (posteriors)
      % i.e. Zu = Z_t|t   & Vu = V_t|t
      S.ZmU(:,t+1)    = Zu;
      S.VmU(:,:,t+1)  = Vu;
  end 
  
  % Store Kalman gain k_t
  if isempty(Y_t)
      S.k_t = zeros(m,m);
  else
      S.k_t = VCF * C_t;
  end
end


  
%______________________________________________________________________
function S = FIS(A, S)
%FIS()    Applies fixed-interval smoother
%
%  Syntax:
%    S = FIS(A, S)
%
%  Description:
%    SKF() applies a fixed-interval smoother, and is used in conjunction 
%    with SKF(). Starting from the final value, this uses a set of 
%    recursions and works backwards. Smoothers are advantageous in that 
%    future values are used for estimation. See  page 154 of 'Forecasting, 
%    structural time series models and the Kalman filter' for more details 
%    (Harvey, 1990).
%
%  Input parameters:
%    A: Transition matrix coefficients. 
%    S: Output from SKF() step. See SKF() for details.
%
%  Output parameters:
%    S: In addition to the output from SKF(), FIS() adds the following
%       smoothed estimates. Note that t = 1...T gives time:
%    - S.ZmT: Smoothed estimate of factor values (Z_t|T) 
%    - S.VmT: Smoothed estimate of factor covariance matrix (V_t|T = Cov(Z_t|T))
%    - S.VmT_1: Smoothed estimate of Lag 1 factor covariance matrix (Cov(Z_tZ_t-1|T))
%
%  Model:
%   Y_t = C_t Z_t + e_t for e_t ~ N(0, R)
%   Z_t = A Z_{t-1} + mu_t for mu_t ~ N(0, Q)

%% ORGANIZE INPUT ---------------------------------------------------------

% Initialize output matrices
  [m, nobs] = size(S.Zm);
  S.ZmT = zeros(m,nobs+1);
  S.VmT = zeros(m,m,nobs+1);
  
  % Fill the final period of ZmT, VmT with SKF() posterior values
  S.ZmT(:,nobs+1)   = squeeze(S.ZmU(:, nobs+1));
  S.VmT(:,:,nobs+1) = squeeze(S.VmU(:,:, nobs+1));

  % Initialize VmT_1 lag 1 covariance matrix for final period
  S.VmT_1(:,:,nobs) = (eye(m)-S.k_t) *A*squeeze(S.VmU(:,:,nobs));
  
  % Used for recursion process. See companion file for details
  J_2 = squeeze(S.VmU(:,:,nobs)) * A' * pinv(squeeze(S.Vm(:,:,nobs)));

  %% RUN SMOOTHING ALGORITHM ----------------------------------------------
  
  % Loop through time reverse-chronologically (starting at final period nobs)
    for t = nobs:-1:1
                
        % Store posterior and prior factor covariance values 
        VmU = squeeze(S.VmU(:,:,t));
        Vm1 = squeeze(S.Vm(:,:,t));
        
        % Store previous period smoothed factor covariance and lag-1 covariance
        V_T = squeeze(S.VmT(:,:,t+1));
        V_T1 = squeeze(S.VmT_1(:,:,t));
      
        J_1 = J_2;
                
        % Update smoothed factor estimate
        S.ZmT(:,t) = S.ZmU(:,t) + J_1 * (S.ZmT(:,t+1) - A * S.ZmU(:,t)) ; 
        
        % Update smoothed factor covariance matrix
        S.VmT(:,:,t) = VmU + J_1 * (V_T - Vm1) * J_1';   
      
        if t>1
            % Update weight
            J_2 = squeeze(S.VmU(:, :, t-1)) * A' * pinv(squeeze(S.Vm(:,:,t-1)));
            
            % Update lag 1 factor covariance matrix 
            S.VmT_1(:,:,t-1) = VmU * J_2'+J_1 * (V_T1 - A * VmU) * J_2';
        end
    end

end


function [y,C,R,L]  = MissData(y,C,R)
%______________________________________________________________________
% PROC missdata                                                        
% PURPOSE: eliminates the rows in y & matrices Z, G that correspond to     
%          missing data (NaN) in y                                                                                  
% INPUT    y             vector of observations at time t    
%          S             KF system matrices (structure)
%                        must contain Z & G
% OUTPUT   y             vector of observations (reduced)     
%          Z G           KF system matrices     (reduced)     
%          L             To restore standard dimensions     
%                        where # is the nr of available data in y
%______________________________________________________________________
  
  % Returns 1 for nonmissing series
  ix = ~isnan(y);
  
  % Index for columns with nonmissing variables
  e  = eye(size(y,1));
  L  = e(:,ix);

  % Removes missing series
  y  = y(ix);
  
  % Removes missing series from observation matrix
  C  =  C(ix,:);  
  
  % Removes missing series from transition matrix
  R  =  R(ix,ix);

end

