function solve_file = dsGetSolveFile(model,studyinfo,varargin)
%GETSOLVEFILE - helper function that creates or retrieves the desired solver file.
%
% Usage:
%   solve_file = dsGetSolveFile(model,studyinfo,options)
%
% Inputs:
%   - model: DynaSim model structure (see dsGenerateModel)
%   - studyinfo (optional): DynaSim studyinfo structure (see dsCheckStudyinfo)
%   - options (optional): cell array of key/value pairs or Matlab structure with options
%     'solver'      : solver for numerical integration (see dsGetSolveFile)
%                     {'euler','rk2','rk4'} (default: 'rk4')
%     'disk_flag'   : whether to write to disk during simulation instead of
%                     storing in memory {0 or 1} (default: 0)
%     'study_dir'   : relative or absolute path to output directory (default:
%                     current directory)
%     'verbose_flag': whether to display informative messages/logs (default: 0)
%
% Output:
%   - solver_file: full file name of file solving the system in model
%
% See also: dsWriteDynaSimSolver, dsCompareSolveFiles, dsPrepareMEX,
%           dsSimulate, dsCreateBatch
%
% Author: Jason Sherfey, PhD <jssherfey@gmail.com>
% Copyright (C) 2016 Jason Sherfey, Boston University, USA

% Check inputs
opts=[];
if nargin<1
  error('first argument must be a DynaSim model structure.');
end
if nargin<2
  studyinfo=[];
end
if nargin<3
  varargin={};
elseif isstruct(varargin{1}) % user provided an options structure
  opts=varargin{1};
  fields=fieldnames(opts);
  varargin={};
end

options=dsCheckOptions(varargin,{...
  'solver','rk4',{'euler','rk1','rk2','rk4','modified_euler','rungekutta','rk','ode23','ode45',...
    'ode1','ode2','ode3','ode4','ode5','ode8','ode113','ode15s','ode23s','ode23t','ode23tb'},... % DynaSim and built-in Matlab solvers
  'matlab_solver_options',[],[],... % options from odeset for use with built-in Matlab solvers
  'disk_flag',0,{0,1},...            % whether to write to disk during simulation instead of storing in memory
  'solve_file',[],[],... % m- or mex-file solving the system
  'study_dir',[],[],... % study directory
  'verbose_flag',0,{0,1},...
  'parfor_flag',0,{0,1},...     % whether to run simulations in parallel (using parfor)
  'mex_flag',0,{0,1},... % exist('codegen')==6, whether to compile using coder instead of interpreting Matlab
  'mex_dir_flag',1,{0,1},... % Flag to tell whether or not to search in mex_dir for pre-compiled solve files (solve*_mex*).
  'mex_dir',[],[],... % Directory to search for pre-compiled mex files. Can be relative to 'study_dir' or absolute path.
  'one_solve_file_flag',0,{0,1},...
  'auto_gen_test_data_flag',0,{0,1},...
  'unit_test_flag',0,{0,1},...
  },false);

if ~isempty(opts)
  % combine default options and user-supplied options w/ the latter
  % overriding the former
  warning('off','catstruct:DuplicatesFound');
  options=catstruct(options,opts);
  options=orderfields(options,fields);
end

if isempty(options.studyinfo)
  options.studyinfo = studyinfo;
end

if isempty(options.mex_dir)
    options.mex_dir = dsGetConfig('mex_path');
end

if options.verbose_flag
  fprintf('\nPREPARING SOLVER:\n');
end

% check solver options
switch options.solver
  case {'euler','rk1','rk2','modified_euler','rk4','rungekutta','rk'}
    solver_type = 'dynasim';
    if ~isempty(options.matlab_solver_options)
      warning('matlab_solver_options are not used by DynaSim solvers. instead try ''ode23'' or ''ode45'' to use those options.');
    end
  case {'ode23','ode45'} % note: only ode23 and ode45 are supported by codegen (see: http://www.mathworks.com/help/coder/ug/functions-supported-for-code-generation--alphabetical-list.html)
    solver_type = 'matlab';
    if options.disk_flag==1
      warning('using disk for real-time storage instead of memory is only available for DynaSim solvers. try using ''euler'',''rk2'', or ''rk4'' for real-time disk usage.');
    end
%   case {'ode1','ode2','ode3','ode4','ode5','ode8','ode113','ode15s','ode23s','ode23t','ode23tb'} % not mex supported
  case {'ode113','ode15s','ode23s','ode23t','ode23tb'} % not mex supported
    solver_type = 'matlab_no_mex';
    if options.disk_flag==1
      warning('using disk for real-time storage instead of memory is only available for DynaSim solvers. try using ''euler'',''rk2'', or ''rk4'' for real-time disk usage.');
    end
  otherwise
    error('unrecognized solver type');
end

%check type of solver against disk_flag
if ~strcmp(solver_type, 'dynasim') && options.disk_flag
  error('Disk_flag not supported with built-in matlab solvers')
end

if ~isempty(options.solve_file)
  % use user-provided solve_file
  solve_file = options.solve_file;
  % note: options.solve_file is used by cluster sim jobs (see dsCreateBatch())
elseif isfield(studyinfo,'solve_file')
  % use study-associated solve_file
  solve_file = studyinfo.solve_file;
elseif options.auto_gen_test_data_flag || options.unit_test_flag
  solve_file = 'solve_ode.m';
else
  % set default solve_file name
  solve_file = ['solve_ode_' datestr(now,'yyyymmddHHMMSS_FFF') '.m'];
end

if ~strcmp(reportUI,'matlab') && ~strcmp(solve_file,'solve_ode.m')
  wrn_fnc = warning('query', 'Octave:function-name-clash');
  if strcmp(wrn_fnc.state,'on')
    fprintf('Switching off ''function-name-clash'' warnings because of solve_ode suffix.\n');
    warning('off', 'Octave:function-name-clash');
  end
end

[fpath,fname,fext]=fileparts2(solve_file);

if isempty(fpath)
  % add path to solve_file name
  if ~isempty(options.sim_id)
    solve_file = fullfile(options.study_dir,'solve',['sim' num2str(options.sim_id)],[fname fext]);
  else
    solve_file = fullfile(options.study_dir,'solve',[fname fext]);
  end

  % convert relative path to absolute path
  solve_file = getAbsolutePath(solve_file);
end
[fpath,fname,fext]=fileparts2(solve_file);

% check that solve file name is less than max function name allowed by matlab
if length(fname) > (namelengthmax-4) % subtract 4 to allow suffix '_mex'
  fname = fname(1:(namelengthmax-4));
  solve_file = fullfile(fpath,[fname fext]);
  warning('Trimming solve_file name to be less than software "namelengthmax". New Name: %s', fname);
end

% create directory for solve_file if it doesn't exist
if ~isdir(fpath)
  if options.verbose_flag
    fprintf('Creating solver directory %s\n',fpath);
  end
  mkdir(fpath);
end
cwd=pwd;

if ~strcmp(cwd,fpath)
  if options.verbose_flag
    fprintf('Changing directory to %s\n',fpath);
  end
  cd(fpath);
end

% create solve_file if it doesn't exist
if ~exist(solve_file,'file')
  keyvals = dsOptions2Keyval(options);
  switch solver_type
    case 'dynasim'  % write DynaSim solver function (solve_ode.m)
      solve_file_m = dsWriteDynaSimSolver(model,keyvals{:},'filename',solve_file); % create DynaSim solver m-file
    case {'matlab', 'matlab_no_mex'} % prepare model function handle etc for built-in solver (@odefun)
      solve_file_m = dsWriteMatlabSolver(model,keyvals{:},'filename',solve_file, 'solver_type',solver_type); % create Matlab solver m-file
                % design: dsWriteMatlabSolver should be very similar to
                % dsWriteDynaSimSolver except have a subfunction with an odefun
                % format variation and main function that calls odeset and
                % feval.
                % DynaSimToOdefun(): a function called outside of
                % dsSimulate. it should evaluate fixed_variables and
                % return @odefun with all substitutions. dsSimulate
                % should be able to handle: dsSimulate(@odefun,'tspan',tspan,'ic',ic)
  end
  solve_file = dsCompareSolveFiles(solve_file_m);               % First search in local solve folder...
  
  if options.mex_flag && options.mex_dir_flag
    solve_file = dsCompareSolveFiles(solve_file, options.mex_dir, options.verbose_flag); % Then search in mex_dir (if it exists and if mex_flag==1).
  end
else
  if options.verbose_flag
    fprintf('Using previous solver file: %s\n',solve_file);
  end
end

%% MEX Compilation
% create MEX file if desired and doesn't exist

% NOTE: if using stiff built-in solver, it should only compile the odefun, not
%   the dynasim solve file. this is called from dsWriteMatlabSolver

if options.mex_flag && ~strcmp(solver_type,'matlab_no_mex') % compile solver function
  if options.one_solve_file_flag
    options.codegen_args = {0};
  end
  solve_file = dsPrepareMEX(solve_file, options);
end

%%
if ~strcmp(cwd,fpath)
  if options.verbose_flag
    fprintf('Changing directory back to %s\n',cwd);
  end
  cd(cwd);
end