function y = dsp_cgfc(blk,varargin)
% DSP_CGFC Query the DSP Code Generation Function Call database.
%
% See also DSP_CGFCDB, DSP_CGPREF.

% Author: D. Orofino
% Copyright 1995-2000 The MathWorks, Inc.
% $Revision: 1.7 $ $Date: 2000/05/05 20:16:21 $

% Model database is an array of structures containing
% the following fields for EACH BLOCK INSTANCE (there
% may be multiple instances with the same SFcnName):
%     .Identifier
%     .SFcnName
%     .params
%         .field1 = setting1
%         .field2 = setting2, etc.
%
%   Ex: .params.cplx    = 0  (not complex)
%       .params.inWidth = 8  (input is width 8)

% User preferences database is an array of structures containing
% the following fields FOR EACH S-FCN in the database (there is
% only one database entry per S-fcn block):
%
%   .Inline   = one of 3 possible option strings,
%               'Always', 'Never', or 'If Unique'.
%   .SFcnName = name of S-function
%   .BlkName  = Descriptive name of s-function
%   .Options  = array of structure options
%               .param = string name of option
%               .dtype = Data type of param for C function declaration
%               .value = value of parameter (0=don't split, 1=split on this param)
%               .info  = Description name of param

% Get database:
db = dsp_cgfcdb('query');

% Perform query:
switch varargin{1}
   
% These are used during "initialization" phase:
case 'SetOptsFile'
   y = getUserOpts(-1,blk);  % blk=filename
   
case 'NumPortProps'
   y = NumPortProps(db,blk);
case 'NumFcnVars'
   y = NumFcnVars(db,blk);
case 'GetFcnVarParam'
   y = GetFcnVarParam(db,blk,varargin{2});
case 'GetFcnVarValue'
   y = GetFcnVarValue(db,blk,varargin{2});
case 'GetFcnVarDType'
   y = GetFcnVarDType(db,blk,varargin{2});
case 'GetFcnVarInfo'
   y = GetFcnVarInfo(db,blk,varargin{2});
case 'GetBlockName'
   y = GetBlockName(db,blk);
   
% These are used later in the generation process
case 'need2GenerateFcnCall'
   y = need2GenerateFcnCall(db,blk);
case 'need2CreateFcn'
   y = need2CreateFcn(db,blk);
case 'getPartitionClass'
   y = getPartitionClass(db,blk);
   
% Only used when starting code-gen:
case 'reset'
   y = getUserOpts(-1);
   
otherwise
   error(['Unrecognized query ''' action ''' on code-generation database.']);
end


% -----------------------------------------------------------
function y = getUserOpts(arg,fileName)

% getUserOpts(sfcnName)
%   Return user-defined code-gen options for specified
%   block (sfcnName)
% getUserOpts(-1)
%   Destroy cache of user-defined code-gen options
% getUserOpts(-1, fileName)
%   indicate name of options data file
% getUserOpts
%   return all user-defined code-gen options

persistent userOpts optsBlockNames optsFileName

getAll   = (nargin==0);
resetMem = (nargin==1) & isequal(arg,-1);
getBlock = (nargin==1) & ischar(arg);
loadMem  = (nargin==2);
y=[];

if resetMem,
   % Dump option cache (persistent memory)
   userOpts=[];
   optsBlockNames=[];
   optsFileName='';
   return
end

if loadMem,
   % Load user-defined code-gen options for every
   % block in DSP Blockset:
   
   % Only load once:
   if isempty(userOpts),
      % NOTE: This function resets the code-gen database,
      % since it is called during "block instance setup"
      % initialization and is the FIRST such query called.
      % However, it is called by EVERY block in the model, so we
      % need to know if a non-init fcn was called or not.
      %
      % This can be verified by examining the DSPAddCGFcnArgsToBlock()
      % function in dsp_cgargs.tlc.  DSPAddCGFcnArgsToBlock is called by
      % the BlockInstanceSetup() fcn in every block TLC participating in
      % fcn-call code-gen.  That is the only place DSPAdd... is called.
      % It is also the only place that the NumFcnVars() accessor is called.
      %
      % See findInstance() in this file for more information as to what
      % problems can occur if the block database has not been properly
      % cleared prior to code-gen.
      %
      if nonInitFcnCalled,
         nonInitFcnCalled('reset');
         dsp_cgfcdb('reset');
         dsp_cgfc([],'reset');
      end

      % Add extension if not present:
      [p,f,x]=fileparts(fileName);
      if isempty(x),
         optsFileName=[fileName '.mat'];
      else
         optsFileName = fileName;
      end
      
      % Check for existence:
      if ~exist(optsFileName,'file'),
         error(['Code generation options file "' optsFileName '" not found.']);
      end
      
      s = load(optsFileName);
      userOpts = s.opts;
      
      % cache the block names for faster searching:
      optsBlockNames = char({userOpts.SFcnName});
   end
   
   % Return updated file name:
   y = optsFileName;
   
   return
end

if getAll,
   y=userOpts;  % Return all user options
else
   % Return options for specified block, only.
   % arg is a string containing the mask type
   sfcnName = arg;
   i = strmatch(sfcnName, optsBlockNames);
   if isempty(i),
      error('Block not in options database.');
   elseif (length(i)>1),
      error('Multiple matching blocks found.');
   end
   y = userOpts(i);
end


% -----------------------------------------------------------
function y = GetBlockName(db,blk)

% Return the "descriptive" block name:
sfcnName = blk.ParamSettings.FunctionName;
blkOpts  = getUserOpts(sfcnName);
y = blkOpts.BlkName;

return

% -----------------------------------------------------------
function y = nonInitFcnCalled(action)

persistent init
if nargin<1, action='query'; end

switch action
case 'query'
   y = ~isempty(init);
case 'reset'
   init = [];
case 'set'
   init = 1;
end


% -----------------------------------------------------------
function y = AllFcnVars(db,blk)
% AllFcnVars Return options structure for given block.
%   Structure fields are documented at the top of this file.
%
% NOTE:
%   If the user splits on N, then a different function is
%   generated for each discrete value of N, and N is *not*
%   passed to the function as an argument.  (Instead, the
%   function is constructed explicitly for that one value
%   of N, only.)
%
% Determine if split is requested:
sfcnName = blk.ParamSettings.FunctionName;
blkOpts  = getUserOpts(sfcnName);
y = blkOpts.Options;
return


function y = NumFcnVars(db,blk)
% NumFcnVars Return number of options for given block.
y = length(AllFcnVars(db,blk));

function y = NumPortProps(db,blk)
% NumPortProps Number of port-properties in user opts
y = 4; % xxx hard-coded for now (Yuck!!)

function y = GetFcnVarParam(db,blk,i)
% GetFcnVarParam Return i'th .Option.param field for given block.
y = AllFcnVars(db,blk);
y = y(i).param;

function y = GetFcnVarValue(db,blk,i)
% GetFcnVarValue Return i'th .Option.value field for given block.
y = AllFcnVars(db,blk);
y = y(i).value;

function y = GetFcnVarDType(db,blk,i)
% GetFcnVarDType Return i'th .Option.dtype field for given block.
y = AllFcnVars(db,blk);
y = y(i).dtype;

function y = GetFcnVarInfo(db,blk,i)
% GetFcnVarInfo Return i'th .Option.info field for given block.
y = AllFcnVars(db,blk);
y = y(i).info;


% -----------------------------------------------------------
function y = need2GenerateFcnCall(db,blk)
% need2GenerateFcnCall Determine if this block is one of several
%   instances of the same block.

% 1) if we always inline code, return false
% 2) if we always generate a function call, or
%    if we inline code when unique, then
%      if this is one of several blocks with
%      identical code-gen parameters, return true

nonInitFcnCalled('set');

% If the code-gen preference GUI is open, and has unsaved
% edits in it, save them before proceeding:
dsp_cgpref('SaveDataIfOpen');

blkOpts = getUserOpts(blk.ParamSettings.FunctionName);
if strcmp(blkOpts.Inline,'Always'),
   % Never generate a function call
   y=0;
elseif strcmp(blkOpts.Inline,'Never'),
   % Always generate a function call
   y=1;
else
   % Generate a function call if this block is one of several
   % with identical parameters:
   [j,msg] = findInstance(db,blk);  error(msg);
   idx = findSameParams(db,blk,j);
   y = (length(idx)>1);
end


% -----------------------------------------------------------
function y = need2CreateFcn(db,blk)
% need2CreateFcn
%   Returns 1 if there is at least one other block with parameter set
%   identical to this block, and this block is the first such block
%   recorded in database.  If this is the only block with this parameter
%   set, or this is the 2nd or subsequent instance of blocks with this
%   parameter set, return 0.

% 1) if we always inline code, return false
% 2) if we always generate a function call, or
%    if we inline code when unique, then
%      if this is the first of several blocks with
%      identical code-gen parameters, return true

nonInitFcnCalled('set');

blkOpts = getUserOpts(blk.ParamSettings.FunctionName);
if strcmp(blkOpts.Inline,'Always'),
   % Never create a function, since the code is always inlined:
   y=0;
   
else
   % Create a function if this is the first of (one or more)
   % or (several) blocks with identical parameters, depending
   % on the "inline" setting:
   
   [j,msg] = findInstance(db,blk);  error(msg);
   idx = findSameParams(db,blk,j);
   
   if strcmp(blkOpts.Inline,'Never'),
      % Create a function if this block is the first of one or more
      % with identical parameters:
      y = (idx(1)==j) & (length(idx)>=1);
   else
      % Create a function if this block is the first of several
      % with identical parameters:
      y = (idx(1)==j) & (length(idx)>1);
   end
end


% -----------------------------------------------------------
function y = generateNextPartitionNum(db,block_idx)

% find all blocks with matching s-fcn
% determine next partition # to use
%    - determine highest partition #, i
%         could be -1, 0, or >=1
%    - if i=-1 or 0, next is 1
%    - if i>=1, next is i+1
%
% find all blocks in this subset with same params
% assign 

% Get current S-Function name from parameter structure:
this_sfcnName = db(block_idx).SFcnName;

% Build a list of block indices into the block database
% that have the same S-Function name as the current block:
db_idx = findMatchingSFcnNames(db, this_sfcnName);

% Get partition numbers for all blocks of this S-Fcn type:
pNums = cat(1, db(db_idx).SFcnPartition);
maxPnum = max(pNums);

% Current partition # could be -1 (default)
% In that case, return the 1 (the first partition #)
% Otherwise, return max+1
y = max(1, maxPnum+1);
return


% -----------------------------------------------------------
function y = getPartitionClassOfFirst(db, db_idx, idx)
% Get partition # of first block in class

% All block indices in the partition are in the idx vector
% "This" block has database index db_idx
% The "first" block in the partition has database index idx(1)

% Get the partition number for the first block
%   in the partition class:
y = db(idx(1)).SFcnPartition;

% If the first block has no partition number, assign it one:
if y==-1,
   y = generateNextPartitionNum(db, idx(1));
   
   if (db_idx ~= idx(1)),
      % If this block is not the first block in the partition,
      % then we must update the database for that first block:
      
      % Conceptually: db(idx(1)).SFcnPartition = y;
      newEntry = db(idx(1));
      newEntry.SFcnPartition = y;
      dsp_cgfcdb('set', idx(1), newEntry);
   end
end


% -----------------------------------------------------------
function y = getPartitionClass(db,blk)

% Update the partition class info for block (.SFcnPartition)
% This is a partition # (aka "serial #")
%  - see dsp_cgfcdb.m for details

% get partition number of current block
% if -1, generate partition info (it's still in the default state)
% if >=0, return partition info:
%    0 = no partitions
%   >0 = partition #

nonInitFcnCalled('set');  % for proper error recovery

[db_idx, msg] = findInstance(db,blk);  error(msg);
y = db(db_idx).SFcnPartition;

if y==-1,
   % Partition info not yet computed (default state)
   %
   % Compute partition info for this S-Fcn (not just for this block)
   %
   % find all blocks with matching s-fcn name
   % then, determine next partition # to use
   %    - determine highest partition #, i, in the set
   %         could be -1, 0, or >=1
   %    - if i=-1 or 0, next is 1
   %    - if i>=1, next is i+1
   %
   % NOTE: Once all members of a partition class have been identified,
   %       they are ALL assigned the (new) partition class number.
   %       Not just THIS block, but again, ALL blocks in the partition.
   %       That's not to say that every s-function of this type gets
   %       assigned a class right now ... just those in this partition.
   %
   % find all blocks in this subset with same params
   %
   blkOpts = getUserOpts(blk.ParamSettings.FunctionName);
      
   if strcmp(blkOpts.Inline,'Always'),
      % Never create a function, since the code is always inlined
      % Don't need a partition # (well, yes you do, it's partition zero!)
      y=0;
      
   else
      % Create a function if this is the first of (one or more)
      % or (several) blocks with identical parameters, depending
      % on the "inline" setting:
      idx = findSameParams(db,blk, db_idx);
      
      if strcmp(blkOpts.Inline,'Unique') & (length(idx)<2),
         % 'Unique' setting
         % Only create a partition # if this block is one of several
         % blocks with identical parameters:
         % Only one of its type - no partition class generated
         %
         y=0;
      end
      
      if y==-1  % still unassigned?
         % is this the first block of the partition?
         % if so, assign it a new number
         % otherwise, get number of "first" in partition
         %
         y = getPartitionClassOfFirst(db, db_idx, idx);
      end
   end
   
   % Update database for this one block:
   %
   %   db(j).SFcnPartition = y;
   newEntry = db(db_idx);
   newEntry.SFcnPartition = y;
   dsp_cgfcdb('set',db_idx,newEntry);
end


% -----------------------------------------------------------
function [idx, msg] = findInstance(db,blk)
%findInstance Find index of block instance in the database.
%   If the block is not found, or is found multiple times,
%   return with an appropriate error message string in msg.

idx = strmatch(blk.Identifier, {db.Identifier}, 'exact');
if isempty(idx),
   msg = block_not_in_db;
   
elseif length(idx)>1,
   %
   % When and why a duplicate block entry ever occurs:
   % 1) First, this is due to a code-gen failure, and the
   %    standard "end" code has not been called by the TLC
   % 2) if only "initialization" calls have been made,
   %    then we know that code-gen is just beginning,
   %    and the database should be reset
   % 3) findInstance is the only fcn that detects
   %    the multiple-block condition, and is only called
   %    by the "non-init" accessors:
   %        - need2CreateFcn
   %        - need2GenerateFcnCall
   %
   % How to handle a duplicate block entry:
   % - On the first call to an "init"-type database query
   %   accessor, reset the database!
   % Pitfalls:
   % - If an error occurs during an init-type fcn, we cannot
   %   automatically recover the next time code-gen starts.
   %   In that case, an error message is generated, and the
   %   user is prompted to manually re-attempt code gen.
   %
   msg = db_not_reset;
else
   msg = ''; % no errors
end


% -----------------------------------------------------------
function params = getSelectedCGParams(sfcnName)
% GetSelectedParams  Get code-gen function partitioning param
%    names that have been selected (enabled) for this S-Fcn.
%    Returns a cell-array of strings, may be empty if no
%    parameters were selected.

all_userOpts = getUserOpts(sfcnName);

% Get content of each value field into a vector,
% and find non-zero elements:
v = {all_userOpts.Options.value};
i = find( cat(1,v{:}) );

% Return param field from each structure entry:
if isempty(i),
   params = {};
else
   params = {all_userOpts.Options(i).param};
end
return


% -----------------------------------------------------------
function idx = findMatchingSFcnNames(db,sfcnName)
% findMatchingSFcnNames Return a list of indices into the
%    block database structure that have the same S-Fcn name
%    as sfcnName.

idx = strmatch(sfcnName, {db.SFcnName}, 'exact');
return


% -----------------------------------------------------------
function idx = findSameParams(db,blk, block_idx)
%findSameParams Find data entry with same parameters.
%   Returns the index of the database element with parameters
%   identical to those passed in params argument.  If no
%   corresponding database entry found, returns [].

% NOTE: params = db(block_idx).params,
%   where block_idx is the "current block" entry.

% Check if we've computed this before - it is expensive!
% The default state is "-1", which is an illegal index.
% NOTE: It could be empty, meaning no other blocks have the
%  same params.  Otherwise, it is a vector of database indices,
%  e.g., all entries are > 0.
%
idx = db(block_idx).SameParamsIdx;
if ~isequal(idx, -1),
   return   % Return with cached info
end

% Get current S-Function name from parameter structure:
this_sfcnName = db(block_idx).SFcnName;

if ~strcmp(blk.ParamSettings.FunctionName, this_sfcnName),
   error('Inconsistent data to findSameParams');
end

% Get code-gen parameters that have been selected
% for this s-fcn by the user:
sel_cgparams = getSelectedCGParams(this_sfcnName);

% Build a list of block indices into the block database
% that have the same S-Function name as the current block:
db_idx = findMatchingSFcnNames(db, this_sfcnName);

% Check if the current block params match any other blocks in database.
%
% If there are no parameters to qualify the  match, just return the
% list of matching s-function indices:

if isempty(sel_cgparams),
   idx = db_idx;
   
else
   % We have additional params to match.
   
   % First, cache the partition variable names:
   blk_vals = {};
   params = db(block_idx).params;
   for j=1:length(sel_cgparams),
      blk_vals{j} = eval(['params.' sel_cgparams{j}]);
   end
   %
   % Next, search for blocks with matching parameter sets:
   idx = [];
   for i = 1:length(db_idx),
      for j=1:length(sel_cgparams),
         db_s    = db(db_idx(i)).params;
         db_val  = eval(['db_s.' sel_cgparams{j}]);
         ok      = isequal(db_val, blk_vals{j});
         if ~ok, break; end
      end
      if ok, idx = [idx db_idx(i)]; end
   end
end

% Update database:
newEntry = db(block_idx);
newEntry.SameParamsIdx = idx;
dsp_cgfcdb('set', block_idx, newEntry);

return


% -----------------------------------------------------------
function msg = block_not_in_db
msg = 'Query requested on block that is not in database.';


% -----------------------------------------------------------
function msg = db_not_reset
% DB_NOT_RESET

dsp_cgfcdb('reset');
CR=sprintf('\n');
msg = ['An error most likely occurred the last time RTW generated code.' CR ...
       'The database has now been cleared.  Try to generate code again.'];

% [EOF] dsp_cgfc.m
