function [model,map] = dsImportModel(source,varargin)
%IMPORTMODEL - import model from raw equations, other program source, etc.
%
% Usage:
%   [model,map] = dsImportModel(source,'option',value,...)
%
% Inputs:
%   - source: [string]
%     1. file with model equations (DynaSim .mech or .eqns, XPP, ...)
%     2. string with equations
%     3. reference to DB model with equations
%   - options (optional):
%     'namespace'      : namespace to prepend to all parameter, variable, and function names
%     'ic_pop'         : name of population with state variables defined in this model
%         - note: connection mechanisms in target pop can have ic_pop=source
%     'host'           : name of database hosting the model to import
%     'user_parameters': cell array of key/value pairs to override model parameters
%
% Output:
%   DynaSim model structure (see dsGenerateModel)
%
% See also: dsGenerateModel, dsCheckModel
% 
% Author: Jason Sherfey, PhD <jssherfey@gmail.com>
% Copyright (C) 2016 Jason Sherfey, Boston University, USA

% Check inputs
options=dsCheckOptions(varargin,{...
  'host','local',[],... % database, eg: infbrain, modeldb
  'namespace',[],[],... % namespace, eg: E, I
  'ic_pop',[],[],... % eg: E, I
  'user_parameters',[],[],... % eg: {'Cm',1,'gNa',100}
  },false);

% ------------------------------------------------------------------
%% 1.0 Download model if not stored locally
% host:
% check if source string has form HOST:MODEL; update options.host
tmp=regexp(source,':','split');
if numel(tmp)>1
  host=tmp{1};
  ModelID=str2num(tmp{2});
else
  host='local';
end
% download model if source host is known
switch host
 case {'infbrain','infinitebrain','ib'}
   source=downloadModel(ModelID);
end

% ------------------------------------------------------------------
%% 2.0 Convert to DynaSim model structure
% if DynaSim .mech, .eqns, .txt:
  % parse model equations
  [model,map] = dsParseModelEquations(source,'namespace',options.namespace, varargin{:});
  
% if DynaSim .mat: load MAT-file
% ... load(source) ...

% if XPP .ode file: load and convert to DynaSim structure
%  ... xpp2dynasim() ...

% if NEURON .modl file: ...  neuron2dynasim() ...

% if NeuroML: ...  neuroml2dynasim() ...

% if Brian: ...  brian2dynasim() ...

% ------------------------------------------------------------------
%% 3.0 Post-process model
% override default parameter values by user-supplied values
if ~isempty(options.user_parameters)
  model = set_user_parameters(model,options.user_parameters,options.namespace); % set user parameters
  
  model = check_IC_param(model,options.user_parameters,options.namespace);
end

% check initial conditions of state variables defined in this (sub-)model
if ~isempty(options.ic_pop)
  model = add_missing_ICs(model,options.ic_pop); % add missing ICs
end

%% 4.0 cleanup
if ~strcmp(host,'local') && exist(source,'file')
  delete(source);
end




%% Nested fn
  function modl=set_user_parameters(modl,params,namespace)
    precision=8; % number of digits allowed for user-supplied values
    if isempty(params) || isempty(modl.parameters)
      return;
    end
    
    % prepend namespace to user-supplied params
    user_keys=cellfun(@(x)[namespace '_' x],params(1:2:end),'uni',0);
    user_vals=params(2:2:end);
    
    % check for mechanism-specific parameters
    if any(~cellfun(@isempty,regexp(user_keys,'\.')))
      % at least one key has MECH.PARAM
      rem_inds=[]; % inds to user_keys not to be updated in this namespace
      key_inds=find(~cellfun(@isempty,regexp(user_keys,'\.'))); % indices into user_keys with .
      par_inds=2*key_inds-1; % indices into params for keys with .
      
      % check whether MECH is in this namespace
      for i=1:length(key_inds)
        % split params key to obtain MECH and PARAM names
        o=regexp(params{par_inds(i)},'\.','split');
        MECH=o{1};
        PARM=o{2};
        % check that MECH is in namespace (pat='\_MECH$')
        o=regexp(namespace,['\_' MECH '$'],'once');
        if ~isempty(o)
          % yes: set user_keys{key_inds(i)}=[namespace '_' PARAM]
          user_keys{key_inds(i)}=[namespace '_' PARM];
        else
          % store key_inds(i) to remove from user_keys and user_vals
          rem_inds=[rem_inds key_inds(i)];
        end
      end
      
      % exclude parameters not meant for this namespace
      if ~isempty(rem_inds)
        user_keys(rem_inds)=[];
        user_vals(rem_inds)=[];
      end
    end
    
    % HACK
    % remove duplicate namespace from user-supplied params
    %   for iKey = 1:length(user_keys)
    %     locs = regexp(user_keys{iKey}, namespace, 'end');
    %     if length(locs) > 1 %then duplicated namespace
    %       user_keys{iKey}(1:locs(1)+1) = []; %remove duplicate and trailing _
    %     end
    %   end
    
    % get list of parameters in modl
    param_names=fieldnames(modl.parameters);
    
    % find adjusted user-supplied param names in this sub-model
    ind=find(ismember(user_keys,param_names));
    for p=1:length(ind)
      if isnumeric(user_vals{ind(p)}) && size(user_vals{ind(p)},2)>1
        modl.parameters.(user_keys{ind(p)})=user_vals{ind(p)};
      else
        modl.parameters.(user_keys{ind(p)})=toString(user_vals{ind(p)},precision);
      end
    end
    
    % repeat for fixed_variables (e.g., connection matrix)
    if ~isempty(modl.fixed_variables)
      % get list of fixed_variables in modl
      fixvars_names=fieldnames(modl.fixed_variables);
      
      % find adjusted user-supplied param names in this sub-model
      ind=find(ismember(user_keys,fixvars_names));
      for p=1:length(ind)
        if ~ischar(user_vals{ind(p)})
          if isinteger(user_vals{ind(p)})
            modl.fixed_variables.(user_keys{ind(p)})=toString(user_vals{ind(p)},precision);
          else
            modl.fixed_variables.(user_keys{ind(p)})=toString(user_vals{ind(p)});
          end
        else
          modl.fixed_variables.(user_keys{ind(p)})=user_vals{ind(p)};
        end
      end
    end
  end
  % ----------------------------------

  
  function modl = check_IC_param(modl, params, namespace)
    if isempty(modl.state_variables)
      return;
    end

    % prepend namespace to all user-supplied params
    user_keys = cellfun(@(x)[namespace '_' x],params(1:2:end),'uni',0);
    user_vals = params(2:2:end);
        % note: many of these likely wont exist for this mechanism
    
    % IC check: x(0)
    pattern='^\w+\(0\)';
    
    ic_param_inds = ~cellfun(@isempty, regexp(user_keys,pattern,'once'));
    
    if any(ic_param_inds)
      % take subset of key/values that are ICs
      user_keys = user_keys(ic_param_inds);
      user_vals = user_vals(ic_param_inds);
      
      for iKey = 1:length(user_keys)
        thisKey = user_keys{iKey};
        thisVal = user_vals{iKey};
        
        var = regexp(thisKey,'^(\w+)\(','tokens','once');
        state_variable = strtrim(var{1});
        
        % check if this state var exists
        if ~any(strcmp(modl.state_variables, state_variable))
          continue % skip since doesnt exist
        end
        
        expression = num2str(thisVal);

        % convert scalars to vectors for when npop > 1 to permit compiler
        if ~isnan(str2double(expression))
          expression = [expression ' * ones(1,Npop)'];
        end
        
        modl.ICs(1).(state_variable)=expression;
      end
    end
    
%     
%     % add default ICs if missing (do not evaluate ICs in dsGenerateModel; do that in dsSimulate before saving params.mat)
%     if isstruct(modl.ICs)
%       missing_ICs=setdiff(modl.state_variables,fieldnames(modl.ICs));
%     else
%       missing_ICs=modl.state_variables;
%     end
%     
%     % add default ICs
%     for ic=1:length(missing_ICs)
%       modl.ICs(1).(missing_ICs{ic})=sprintf('zeros(1,%s)',Npopstr);
%     end
%     
%     % convert scalar ICs to vectors of population size
%     ICfields=fieldnames(modl.ICs);
%     for ic=1:length(ICfields)
%       % check if scalar (scientific notation or decimal)
%       if ~isempty(regexp(modl.ICs.(ICfields{ic}),'^((\d+e[\-\+]?\d+)|([\d.-]+))$','once'))
%         modl.ICs(1).(ICfields{ic})=sprintf('%s*ones(1,%s)',modl.ICs.(ICfields{ic}),Npopstr);
%       end
%     end
  end
  % ----------------------------------
  
  
  function modl = add_missing_ICs(modl,popname)
    if isempty(modl.state_variables)
      return;
    end
    
    Npopstr=[popname '_Npop'];
    
    % add default ICs if missing (do not evaluate ICs in dsGenerateModel; do that in dsSimulate before saving params.mat)
    if isstruct(modl.ICs)
      missing_ICs=setdiff(modl.state_variables,fieldnames(modl.ICs));
    else
      missing_ICs=modl.state_variables;
    end
    
    % add default ICs
    for ic=1:length(missing_ICs)
      modl.ICs(1).(missing_ICs{ic})=sprintf('zeros(1,%s)',Npopstr);
    end
    
    % convert scalar ICs to vectors of population size
    ICfields=fieldnames(modl.ICs);
    for ic=1:length(ICfields)
      % check if scalar (scientific notation or decimal)
      if ~isempty(regexp(modl.ICs.(ICfields{ic}),'^((\d+e[\-\+]?\d+)|([\d.-]+))$','once'))
        modl.ICs(1).(ICfields{ic})=sprintf('%s*ones(1,%s)',modl.ICs.(ICfields{ic}),Npopstr);
      end
    end
  end
  % ----------------------------------

  
  function source = downloadModel(ModelID)
    % Set path to your MySQL Connector/J JAR
    jarfile = '/usr/share/java/mysql-connector-java.jar';
    javaaddpath(jarfile); % WARNING: this might clear global variables
    
    % set connection parameters
    cfg.mysql_connector = 'database';
    cfg.webhost = '104.131.218.171'; % 'infinitebrain.org','104.131.218.171'
    cfg.dbname = 'modulator';
    cfg.dbuser = 'querydb'; % have all users use root to connect to DB and self to transfer files
    cfg.dbpassword = 'publicaccess'; % 'publicaccess'
    cfg.xfruser = 'publicuser';
    cfg.xfrpassword = 'publicaccess';
    cfg.ftp_port=21;
    cfg.MEDIA_PATH = '/project/infinitebrain/media';
    target = pwd; % local directory for temporary files
    
    % Create the database connection object
    jdbcString = sprintf('jdbc:mysql://%s/%s',cfg.webhost,cfg.dbname);
    jdbcDriver = 'com.mysql.jdbc.Driver';
    dbConn = database(cfg.dbname,cfg.dbuser,cfg.dbpassword,jdbcDriver,jdbcString);
    
    % list all mechanism metadata from DB
    %query='select id,name,level,notes,ispublished,project_id from modeldb_model where level=''mechanism'''; %  and privacy='public'
    %data = get(fetch(exec(dbConn,query)), 'Data');
    % get file info associated with this ModelID
    query=sprintf('select file from modeldb_modelspec where model_id=%g',ModelID);
    data = get(fetch(exec(dbConn,query)), 'Data');
    jsonfile=data{1};
    [usermedia,modelfile,ext] = fileparts2(jsonfile); % remote server media directory
    usermedia=fullfile(cfg.MEDIA_PATH,usermedia);
    modelfile=[modelfile ext];%'.json'];
    
    % Open ftp connection and download mechanism file
    f=ftp([cfg.webhost ':' num2str(cfg.ftp_port)],cfg.xfruser,cfg.xfrpassword);
    pasv(f);
    cd(f,usermedia);
    mget(f,modelfile,target);
    
    % parse mechanism file
    tempfile = fullfile(target,modelfile);
    source=tempfile;
    [model,map]=dsParseModelEquations(source, varargin{:});
    % if isequal(ext,'.json')
    %   [spec,jsonspec] = json2spec(tempfile);
    %   spec.model_uid=ModelID;
    % elseif isequal(ext,'.txt')
    %   spec = parse_mech_spec(tempfile,[]);
    % else
    %   spec = [];
    % end
    % delete(tempfile);
    %close ftp connection
    close(f);
  end

end % main fn