function model=CheckModel(model)
%% model=CheckModel(model)
% Purpose: standardize model structure and auto-populate missing fields
% Input: DynaSim model structure or equations
% Output: DynaSim model structure (standardized)
% 
% DynaSim model structure:
%   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 CheckSpecification)
%   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:
%     ...
%   Example: linking mechanism to equations in a different mechanism:
%     ...
% 
% 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
% 
% Example 1: obtain empty model structure with all fields
% model=CheckModel([])
% 
% Example 2: standardize existing model
% model=CheckModel(model)
% 
% see also: GenerateModel, CheckSpecification, CheckData

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

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=ImportModel(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=GenerateModel(model);
end

% check back compatibility
model=backward_compatibility(model);

% % 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 CombineModels() should also
% be edited by uncommenting-out the call to CheckModel() 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