function model = dsCheckModel(model, varargin)
%CHECKMODEL - Standardize model structure and auto-populate missing fields
%
% Usage:
%   model=dsCheckModel(model)
%
% Input: DynaSim model structure or equations
%
% Output:
%   - DynaSim model structure (standardized)
%     model.parameters      : substructure with model parameters
%     model.fixed_variables : substructure with fixed variable definitions
%     model.functions       : substructure with function definitions
%     model.monitors        : substructure with monitor definitions
%     model.state_variables : cell array listing state variables
%     model.ODEs            : substructure with one ordinary differential 
%                             equation (ODE) per state variable
%     model.ICs             : substructure with initial conditions (ICs) for 
%                             each state variable
%     model.conditionals(i) : structure array with each element indicating
%                             conditional actions specified in subfields 
%                             "condition","action","else" (see NOTE 1)
%     model.linkers(i)      : structure array with each element indicating
%                             an "expression" that should be inserted 
%                             (according to "operation") into any equations 
%                             where the "target" appears. (see NOTE 2)
%       .target    : string giving the target where expression should be inserted
%       .expression: string giving the expression to insert
%       .operation : string giving the operation to use to insert expression
%     model.comments{i}     : cell array of comments found in model files
%     model.specification   : specification used to generate the model (see dsCheckSpecification)
%     model.namespaces      : (see NOTE 3)
%
%   - NOTE 1: "action" may include multiple statements separated by semicolons.
%       "condition" must be an expression that evaluates to true or false.
%
%   - NOTE 2: "linkers" are used only when a model contains external model files.
%       Equations and state variables defined in external files can be combined with
%       equations in other model files (associated with the same population) or
%       population equations in the specification. Recommended practice is to begin
%       targets with the '@' character.
%     - Example: linking mechanism to equations in specification: TODO
%     - Example: linking mechanism to equations in a different mechanism: TODO
%
%   - NOTE 3: all variables and functions have prefixes added to them that
%     indicate their namespace; a mapping from original names found in equations to
%     the names appearing in the model structure is available in model.namespaces.
%     - Namespaces in the model structure:
%       model.parameters      .([namespace param_name])=expression
%       model.fixed_variables .([namespace var_name])=expression
%       model.functions       .([namespace func_name])=@(variables)expression
%       model.monitors        .([namespace monitor_name])=expression
%       model.state_variables = {namespace_var1,namespace_var2,...}
%       model.ODEs            .([namespace state_variable])=expression
%       model.ICs             .([namespace state_variable])=expression
%       model.conditionals(i) .namespace,condition,action,else
%       model.linkers(i)      .namespace,target,expression,operation
%       model.comments{i}     string
%       .specification,.namespaces
%
% Examples:
% - Example 1: obtain empty model structure with all fields
%     model=dsCheckModel([])
%
% - Example 2: standardize existing model
%     model=dsCheckModel(model)
%
% see also: dsGenerateModel, dsCheckSpecification, dsCheckData
% 
% Author: Jason Sherfey, PhD <jssherfey@gmail.com>
% Copyright (C) 2016 Jason Sherfey, Boston University, USA

%% auto_gen_test_data_flag argin
options = dsCheckOptions(varargin,{'auto_gen_test_data_flag',0,{0,1}},false);
if options.auto_gen_test_data_flag
  varargs = varargin;
  varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0;
  varargs(end+1:end+2) = {'unit_test_flag',1};
  argin = [{model}, varargs]; % specific to this function
end

field_order={'parameters','fixed_variables','functions','monitors',...
  'state_variables','ODEs','ICs','conditionals','linkers','comments',...
  'specification','namespaces'};
field_defaults={struct(''),struct(''),struct(''),struct(''),{},struct(''),...
                struct(''),struct(''),struct(''),{},struct(''),{}};

if isempty(model)
  % prepare empty model structure
  for i=1:length(field_order)
    model.(field_order{i})=field_defaults{i};
  end
end

% % check if input is string with name of file containing model
% if ischar(model) && exist(model,'file')
%   model=dsImportModel(model);
% end

% check if input is string or cell with equations or spec struct and convert to model structure
if ischar(model) || iscell(model) || ~isfield(model,'state_variables')
  model = dsGenerateModel(model);
end

% check back compatibility
model=backward_compatibility(model);

%% auto_gen_test_data_flag argout
if options.auto_gen_test_data_flag
  argout = {model}; % specific to this function
  
  dsUnitSaveAutoGenTestData(argin, argout);
end

% % auto-populate missing data
% for i=1:length(field_order)
%   if ~isfield(model,field_order{i})
%     model.(field_order{i})=field_defaults{i};
%   end
% end
% 
% % standardize field order
% model=orderfields(model,field_order);

% note: auto-populating and standardization of field order may not be
% necessary or beneficial for DynaSim model structures. It only adds extra
% time... if the above is uncommented-out, then dsCombineModels() should also
% be edited by uncommenting-out the call to dsCheckModel() and commenting-out
% the call to orderfields according to first input (at the end of the
% function).


function model=backward_compatibility(model)
% account for change in state variable dimensions:
% cells used to be along rows in a column; now columns across a row.
% replace cols (Npop,1) by rows (1,Npop). similar for Npre,Npost
% do string substitution in ODEs and ICs
target_types={'ODEs','ICs'};
% loop over types of model data
for type_index=1:length(target_types)
  type=target_types{type_index};
  % info for this type
  s=model.(type);
  if isstruct(s)
    update_these=fieldnames(s);
    expressions=struct2cell(s);
    % loop over target expressions from which to eliminate internal function calls
    updated=0;
    for i=1:length(expressions)
      if isempty(expressions{i})
        continue;
      end
      % update expressions of this type
      % note: do single check first b/c will not normally be needed -->
      % reduces 3 conditional checks to 1 in most cases.
      if ~isempty(regexp(expressions{i},'\((\w+_)?(Npop|Npre|Npost),1\)','once'))
        updated=1;
        if ~isempty(regexp(expressions{i},'\((\w+_)?Npop,1\)','once'))
          expressions{i}=regexprep(expressions{i},'\((\w+_)Npop,1\)','\(1,$1Npop\)');
        end
        if ~isempty(regexp(expressions{i},'\((\w+_)?Npre,1\)','once'))
          expressions{i}=regexprep(expressions{i},'\((\w+_)Npre,1\)','\(1,$Npre\)');
        end
        if ~isempty(regexp(expressions{i},'\((\w+_)?Npost,1\)','once'))
          expressions{i}=regexprep(expressions{i},'\((\w+_)Npost,1\)','\(1,$Npost\)');
        end
      end
    end
    if updated
      % update model with expressions that have parameter values in them
      model.(type)=cell2struct(expressions,update_these,1);
    end
  end
end