function model = dsPropagateParameters(model,varargin)
%PROPAGATEPARAMETERS - substitute parameter values or prepend parameter names with prefix across all model equations.
%
% Usage:
%   model = SubstituteParameters(model)
%
% Input:
%   - model: DynaSim model structure
%   - options:
%     'action': {'substitute','prepend','postpend'} (default: substitute)
%     'prop_prefix': string prepended to all parameter names if action is 'prepend'
%     'prop_suffix': string postpended to all parameter names if action is 'postpend'
%
% Output: DynaSim model structure with updated parameter in all equations
%
% See also: dsPropagateFunctions, dsWriteDynaSimSolver
% 
% Author: Jason Sherfey, PhD <jssherfey@gmail.com>
% Copyright (C) 2016 Jason Sherfey, Boston University, USA

%% localfn output
if ~nargin
  model = localfunctions; % output var name specific to this fn
  return
end

% Check inputs
model=dsCheckModel(model, varargin{:});
if ~isstruct(model.parameters)
  % nothing to do
  return;
end

% Check inputs
options=dsCheckOptions(varargin,{...
  'action','substitute',{'substitute','prepend','postpend'},...
  'prop_prefix','pset.p.',[],...
  'prop_suffix','',[],...
  'param_type','parameters',{'parameters', 'fixed_variables'},...
  'auto_gen_test_data_flag',0,{0,1},...
  },false);

%% auto_gen_test_data_flag argin
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

%% Finding parameters.
parameters=model.(options.param_type);
if isempty(parameters)
  return
end

%% 1.0 Propagate through sub-structures
target_types={'fixed_variables','functions','monitors','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
    for i=1:length(expressions)
      if isempty(expressions{i})
        continue;
      end
      
      % update expressions of this type
      switch options.action
        case 'substitute'
          expressions{i}=insert_parameters(expressions{i},parameters, [],[], varargin{:});
        case 'prepend'
          expressions{i}=insert_parameters(expressions{i},parameters, 'prop_prefix',options.prop_prefix, varargin{:});
        case 'postpend'
          expressions{i}=insert_parameters(expressions{i},parameters, 'prop_suffix',options.prop_suffix, varargin{:});
      end
    end
    
    % update model with expressions that have parameter values in them
    model.(type)=cell2struct(expressions,update_these,1);
  end
end

%% 2.0 Propagate parameters through structure arrays (conditionals)
if ~isempty(model.conditionals)
  target_types={'condition','action','else'};
  
  for type_index=1:length(target_types)
    type=target_types{type_index};
    expressions={model.conditionals.(type)};
    
    % loop over conditional expressions from which to eliminate internal function calls
    for i=1:length(expressions)
      if isempty(expressions{i})
        continue;
      end
      
      % update expressions of this type
      switch options.action
        case 'substitute'
          expressions{i}=insert_parameters(expressions{i},parameters, [],[], varargin{:});
        case 'prepend'
          expressions{i}=insert_parameters(expressions{i},parameters, 'prop_prefix',options.prop_prefix, varargin{:});
        case 'postpend'
          expressions{i}=insert_parameters(expressions{i},parameters, 'prop_suffix',options.prop_suffix, varargin{:});
      end
    end
    [model.conditionals(1:length(model.conditionals)).(type)]=deal(expressions{:});
  end
end


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

end % main fn


%% Local Fn
function expression=insert_parameters(expression,parameters,attachType,attachStr, varargin)

%% 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 = [{expression}, {parameters}, {attachType}, {attachStr}, varargs]; % specific to this function
end

if isnumeric(expression)
  % convert to string and return string
  expression=toString(expression);
  return;
end

allwords=regexp(expression,'[a-zA-Z]+\w*','match');
words=unique(allwords);
found_parameters=words(ismember(words,fieldnames(parameters)));

if ~isempty(found_parameters)
  % substitute those found into this target expression
  for ff=1:length(found_parameters)
    % name of found parameter
    found_parameter=found_parameters{ff};
    
    if isempty(attachType) % no prefix given, substitute value instead
      % found value to replace found parameter name in target
      found_value=parameters.(found_parameter);
      
      % convert found value into string
      if isnumeric(found_value)
        if length(found_value)>1
          found_value=sprintf('[%s]',num2str(found_value));
        else
          found_value=num2str(found_value);
        end
      elseif iscell(found_value)
        if iscellstr(found_value)
          tmp=cellfun(@(x)['''' x '''' ','] ,found_value,'uni',0);
        else
          tmp=cellfun(@(x)[num2str(x) ','] ,found_value,'uni',0);
        end
        tmp=[tmp{:}];
        found_value=sprintf('{%s}',tmp(1:end-1));
      elseif isa(found_value,'function_handle')
        found_value=func2str(found_value);
      end
    elseif strcmp(attachType, 'prop_prefix') % prefix provided, substitute prefix_name
      prefix = attachStr;
      found_value=[prefix found_parameter];
    elseif strcmp(attachType, 'prop_suffix') % suffix provided, substitute suffix_name
      suffix = attachStr;
      found_value=[found_parameter suffix];
    end
    
    if ~ischar(found_value)
      warning('failed to convert parameter ''%s'' to string and substitute into model equations:',found_parameter);
      found_value % TODO: check this
    else
      % update expression
      num_found = length(find(ismember(allwords,found_parameter)));
      for iter=1:num_found
        if ~strcmp(attachType, 'suffix')
          expression=dsStrrep(expression,found_parameter,found_value, '', '', varargin{:});
        else
          expression=dsStrrep2(expression,found_parameter,found_value, '', '', varargin{:});
        end
      end
    end
  end
end

%% auto_gen_test_data_flag argout
if options.auto_gen_test_data_flag
  argout = {expression}; % specific to this function
  
  dsUnitSaveAutoGenTestDataLocalFn(argin, argout); % localfn

end

end