function classes = dsClassifyEquation(string,delimiter, varargin)
%CLASSIFYEQUATION - use regular expressions to classify model expressions in STRING
%
% Usage:
% CLASS=dsClassifyEquation(STRING,DELIMITER)
%
% Inputs:
%   - STRING
%   - DELIMITER (optional character, default=';'): delimit expressions in STRING
%
% Output:
%  -  CLASS (string or cell array of strings for each delimited expression):
%     class:            format:
%     parameter         name=value
%     fixed_variable    name=expression/data
%     function          name(inputs)=expression
%     ODE               dx/dt or x' = expression
%     IC                x(0)=values
%     conditional       if(condition)(action) or if(condition)(action)else(action)
%     monitor           monitor *   (previously: monitor name=expression)
%     linker            {'+=','-=','*=','/='} ('=>'for backwards compatibility) {'>-','>+','>*',or '>\'}
%     comment           % or #
%
% Notes:
% - Output "class" will be a cell array of strings if STRING contains
%   multiple expressions; otherwise it will be a string.
% - This function is designed to be an internal helper function
%   called by user-level functions in DynaSim.
%
% Examples:
%   class=dsClassifyEquation('dx/dt=3*a*x')
%   classes=dsClassifyEquation('dx/dt=3*a*x; x(0)=0')
%   classes=dsClassifyEquation('dx/dt=3*a*x, x(0)=0',',')
%   classes=dsClassifyEquation('a=2; b=2*a; f(x)=b; dx/dt=f(x); x(0)=0; if(x>1)(x=0); current=>f(x); monitor f(x); % comments')
%   classes=dsClassifyEquation('model.eqns');
%
% See also: dsParseModelEquations
%
% Author: Jason Sherfey, PhD <jssherfey@gmail.com>
% Copyright (C) 2016 Jason Sherfey, Boston University, USA

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

%% 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 = [{string},{delimiter}, varargs]; % specific to this function
end


%% check inputs
if nargin==1, delimiter=';'; end % set default delimiter

if ~ischar(string) % error handling
  error('input must be string containing equations');
end

if exist(string,'file')
  % load equations from file and concatenate into a single string
  string=readtext(string);
  string=[string{:}]; % concatenate text from all lines
end

%% split string on delimiter; remove insignificant white space & delimiters
%strings=strtrim(splitstr(string,delimiter));
strings=strtrim(regexp(string,delimiter,'split'));
strings=strrep(strings,delimiter,'');

% classify each delimited expression in string
classes=cell(1,length(strings));
for i=1:length(strings)
  classes{i} = classify(strings{i}, varargin{:});
end

if length(classes)==1 % check for single expression
  classes=classes{1}; % return class label as string
end

%% auto_gen_test_data_flag argout
if options.auto_gen_test_data_flag
  argout = {classes}; % specific to this function

  dsUnitSaveAutoGenTestData(argin, argout);
end

end % main fn


%% local functions

function class = classify(string, varargin)
% input: string containing only one expression
% output: class label (string)

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

class='';

if isempty(string)
  % null check
  class='null';
elseif string(1)=='%' || string(1)=='#'
% comment check
  class='comment';
end

% linker check: % [link ]? target operation expression
                % DynaSim-linker (matlab-incompatible) character combinations
pattern='(link\s*)?((\+=)|(\-=)|(\*=)|(/=)|(=>))';
if isempty(class) && ~isempty(regexp(string,pattern,'once'))
  class='linker';
end

% ODE check: x'=expression or dx/dt=expression
pattern='^((\w+'')|(d\w+/dt))\s*=';
if isempty(class) && ~isempty(regexp(string,pattern,'once'))
  class='ODE';
end

% IC check: x(0)=expression
pattern='^\w+\(0\)\s*=';
if isempty(class) && ~isempty(regexp(string,pattern,'once'))
  class='IC';
end

% parameter check: var=expression (string or numeric)
% patterns: words = string? | inf | digits | scientific, eg e | math with single operator, eg '*','/','^','-','+'
pattern='^(([\w\.]+)|(\[\w+\]))\s*=\s*((''.*'')|(\[?[+\d\.\-(Inf)(inf)]+\]?)|([\-\+]?\d*\.?\d*e[\-\+]?\d+)|([\-\+]?\s*\(?\s*[\-\+]?\s*\d*\.?\d*\s*\)?\s*\.?[\^\/\*\+\-]\s*\(?\s*[\-\+]?\s*\d*\.?\d*\)?\s*)\s*)$';
if isempty(class) && ~isempty(regexp(string,pattern,'once'))
  class='parameter';
end

% conditional check: if(conditions)(actions)(else)
pattern='^if\s*\(.+\)\s*\(.+\)';
if isempty(class) && ~isempty(regexp(string,pattern,'once','ignorecase'))
  class='conditional';
end

% function check: f(vars)=exression
pattern='^\w+\([@a-zA-Z][\w,@]*\)\s*=';
if isempty(class) && ~isempty(regexp(string,pattern,'once'))
  class='function';
end

% monitor check:  monitor f=(expression or function)
pattern='monitor .*';
if isempty(class) && ~isempty(regexp(string,pattern,'once'))
  class='monitor';
end

% fixed_variable (with indexing) check: var(#), var([#]), var([# #]), var([#,#]), var(#:#), var(#:end), var([#:#]), var([#:end])
pattern='^\w+\([\(\[?[\d\s,]+\]?\) | \(\[?\d+:[\(\d+\)|\(end\)]\]?\)]+\)'; % fixed with indexing: var(#), var([#]), var([# #]), var([#,#]), var(#:#), var(#:end), var([#:#]), var([#:end])
if isempty(class) && ~isempty(regexp(string,pattern,'once'))
  class='fixed_variable';
end

% fixed_variable (without indexing) check: var=(expression with grouping or arithmetic)
pattern='^((\w+)|(\[\w+\]))\s*=';
if isempty(class) && ~isempty(regexp(string,pattern,'once'))
  pattern1='(.*[a-z_A-Z,<>(<=)(>=)]+.*)$'; % rhs contains: []{}(),<>*/|   % '=\s*.*[a-z_A-Z,<>(<=)(>=)]+.*'
  pattern2='=\s*\d+e[\-\+]?\d+$'; % scientific notation (should be classified as parameter, not fixed_variable)
  if ~isempty(regexp(string,pattern1,'once')) && ...
      isempty(regexp(string,pattern2,'once'))
    class='fixed_variable';
  end
end

if isempty(class)
  class='unclassified';
end

%% auto_gen_test_data_flag argout
if options.auto_gen_test_data_flag
  argout = {class}; % specific to this function

  dsUnitSaveAutoGenTestDataLocalFn(argin, argout); % localfn
end

end %fn