%-------------------------------------------------------------------------%
% Copyright (c) 2019 Modenese L., Ceseracciu, E., Reggiani M., Lloyd, D.G.%
%                                                                         %
% Licensed under the Apache License, Version 2.0 (the "License");         %
% you may not use this file except in compliance with the License.        %
% You may obtain a copy of the License at                                 %
% http://www.apache.org/licenses/LICENSE-2.0.                             %
%                                                                         % 
% Unless required by applicable law or agreed to in writing, software     %
% distributed under the License is distributed on an "AS IS" BASIS,       %
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or         %
% implied. See the License for the specific language governing            %
% permissions and limitations under the License.                          %
%                                                                         %
%    Author: Luca Modenese, March 2013                                    %
%                        revised July 2014 (improved management of joints)%
%                        modified May 2015 (management of max resolution) %
%    email:    l.modenese@imperial.ac.uk                                  % 
% ----------------------------------------------------------------------- %
%
% Given as INPUT an OpenSim muscle OSModel, OSMuscle, a muscle variable and a nr
% of evaluation points this function returns as
% musOutput a vector of the muscle variable of interest
% obtained by sampling the ROM of the joint spanned by the muscle in
% N_EvalPoints evaluation points.
% For multidof joint the combinations of ROMs are considered.
% For multiarticular muscles the combination of ROM are considered.
% The script is totally general because based on generating strings of code 
% correspondent to the encountered code. The strings are evaluated at the end.
%
% IMPORTANT 1
% The function can decrease the N_EvalPoints if there are too many dof 
% involved (ASSUMPTION is that a better sampling will be vanified by the 
% huge amount of data generated). This is an option that can be controlled
% by the user by deciding if to use it or not (setting limit_discr = 0/1)
% and, in case the sampling is limited, by setting a lower limit to the
% discretization.
%
% IMPORTANT 2
% Another check is done on the dofs: only INDEPENDENT coordinate are
% considered. This is fundamental for patellofemoral joint that both in
% LLLM and Arnold's model are constrained dof, dependent on the knee
% flexion angle. This function assumes to be used with Arnold's model.
% currentState is initialState;
%
% IMPORTANT 3
% At the purposes of the muscle optimizer it is important that here the
% model is initialize every time. So there are no risk of working with an
% old state (important for Schutte muscles for instance, where we observed
% that it was necessary to re-initialize the muscle after updating the
% muscle parameters).

function musOutput = sampleMuscleQuantities(osimModel,OSMuscle,muscleQuant, N_EvalPoints)  %#ok<STOUT>

%======= SETTINGS ======
% limit (1) or not (0) the discretization of the joint space sampling
limit_discr = 0; 
% minimum angular discretization
%min_increm_in_deg = 2.5;
min_increm_in_deg = 1;


%=======================

% initialize the model
currentState = osimModel.initSystem();

% getting the joint crossed by a muscle
muscleCrossedJointSet = getJointsSpannedByMuscle(osimModel, OSMuscle);

% index f+or effective dofs
n_dof = 1;
DOF_Index = [];
for n_joint = 1:size(muscleCrossedJointSet,2)
    
    % current joint
    curr_joint = muscleCrossedJointSet{n_joint};
    
    % get CoordinateSet for the crossed joint
    %curr_joint_CoordinateSet = osimModel.getJointSet().get(curr_joint).getCoordinateSet();
    
    % Initial estimation of the nr of Dof of the CoordinateSet for that
    % joint before checking for locked and constraint dofs.
    %nDOF = osimModel.getJointSet().get(curr_joint).getCoordinateSet().getSize();
    nDOF = osimModel.getJointSet().get(curr_joint).numCoordinates();
    % skip welded joint and removes welded joint from muscleCrossedJointSet
    if nDOF == 0;
        continue;
    end
    
    % calculating effective dof for that joint 
    effect_DOF = nDOF;
    for n_coord = 0:nDOF-1
        
        % get coordinate
        %curr_coord = curr_joint_CoordinateSet.get(n_coord);
        curr_coord = osimModel.getJointSet().get(curr_joint).get_coordinates(n_coord);
        
        curr_coord_name = char(curr_coord.getName());
        
        % skip dof if locked
        if curr_coord.getLocked(currentState)
            continue;
        end
        
        % if coordinate is constrained then the independent coordinate and
        % associated joint will be listed in the sampling "map"
        if curr_coord.isConstrained(currentState) && ~curr_coord.getLocked(currentState)
            constraint_coord_name = curr_coord_name;
            % finding the independent coordinate
            [ind_coord_name, ind_coord_joint_name] = getIndipCoordAndJoint(osimModel, constraint_coord_name); %#ok<NASGU>
            % updating the coordinate name to be saved in the list
            curr_coord_name = ind_coord_name;
            effect_DOF = effect_DOF-1;
            % ignoring constrained dof if they point to an independent
            % coordinate that has already been stored
            if sum(ismember(DOF_Index, osimModel.getCoordinateSet.getIndex(curr_coord_name)))>0
                continue
            end
            % skip dof if independent coordinate locked (the coord
            % correspondent to the name needs to be extracted)
            if osimModel.getCoordinateSet.get(curr_coord_name).getLocked(currentState)
                continue;
            end
        end
                
        % NB: DOF_Index is used later in the string generated code.
        % CRUCIAL: the index of dof now is model based ("global") and
        % different from the joint based used until now.
        DOF_Index(n_dof) = osimModel.getCoordinateSet.getIndex(curr_coord_name);

        % necessary update/reload the curr_coord to avoid problems with
        % dependent coordinates
        curr_coord = osimModel.getCoordinateSet.get(DOF_Index(n_dof));
        
        % Getting the values defining the range
        jointRange(1) = curr_coord.getRangeMin();
        jointRange(2) = curr_coord.getRangeMax();
        
        % Storing range of motion conveniently
        CoordinateBoundaries(n_dof) = {jointRange}; %#ok<NASGU>
        
        % increments in the variables when sampling the mtl space. 
        % Increments are different for each dof and based on N_eval.
        %Defining the increments
        degIncrem(n_dof) = (jointRange(2)-jointRange(1))/(N_EvalPoints-1);
        
        % limit or not the discretization of the joint space sampling
        if limit_discr==1
            % a limit to the increase can be set though
            if degIncrem(n_dof)<(min_increm_in_deg/180*pi)
                degIncrem(n_dof)=(min_increm_in_deg/180*pi); %#ok<*AGROW>
            end
        end
        
        % updating index of list of dof
        n_dof = n_dof+1;
    end
end


% initializes the counter to save the results
iter = 1;  %#ok<NASGU>

% assigns an initial and a final value variable for each dof X
% calling them setAngleStartDofX and setLimitDof respectively.
for n_instr = 1:size(DOF_Index,2);
    % setting up the variables
    eval(['setAngleStartDof',num2str(n_instr),' = CoordinateBoundaries{',num2str(n_instr),'}(1);'])
    eval(['setLimitDof',num2str(n_instr),' = CoordinateBoundaries{',num2str(n_instr),'}(2);'])
end


% setting up for loops in order to explore all the possible combination of
% joint angles (looping on all the dofs of each joint for all the joint
% crossed by the muscle).
% The model pose is updated via: " coordToUpd.setValue(currentState,setAngleDof)"
% The right dof to update is chosen via: "coordToUpd = osimModel.getCoordinateSet.get(n_instr)"
stringToExcute1 = '';
for n_instr = 1:size(DOF_Index,2);
    stringToExcute1 = [stringToExcute1,[' for setAngleDof',num2str(n_instr),'=setAngleStartDof',num2str(n_instr),':degIncrem(',num2str(n_instr),'):setLimitDof',num2str(n_instr),';   coordToUpd = osimModel.getCoordinateSet.get(DOF_Index(',num2str(n_instr),'));    coordToUpd.setValue(currentState,setAngleDof',num2str(n_instr),');']]; %#ok<AGROW>
end

% calculating muscle length for the muscle
switch muscleQuant
    case 'MTL'
            stringToExcute2 = 'musOutput(iter) = OSMuscle.getGeometryPath.getLength(currentState);';
    case 'LfibNorm'
            stringToExcute2 =  ['OSMuscle.setActivation(currentState,1.0);',...
                                ' osimModel.equilibrateMuscles(currentState);',...
                                ' musOutput(iter) = OSMuscle.getNormalizedFiberLength(currentState);'];
    case 'Lten'
            stringToExcute2 =  ['OSMuscle.setActivation(currentState,1.0);',...
                                ' osimModel.equilibrateMuscles(currentState);',...
                                ' musOutput(iter) = OSMuscle.getTendonLength(currentState);'];
    case 'Ffib'
            stringToExcute2 =  ['OSMuscle.setActivation(currentState,1.0);',...
                                ' osimModel.equilibrateMuscles(currentState);',...
                                ' musOutput(iter) = OSMuscle.getActiveFiberForce(currentState);'];
    case 'all'
            stringToExcute2 = [ 'OSMuscle.setActivation(currentState,1.0);',...
                                ' osimModel.equilibrateMuscles(currentState);',...
                                'musOutput(iter,1) = OSMuscle.getGeometryPath.getLength(currentState);',...
                                ' musOutput(iter,2) = OSMuscle.getNormalizedFiberLength(currentState);',...
                                ' musOutput(iter,3) = OSMuscle.getTendonLength(currentState);',...
                                ' musOutput(iter,4) = OSMuscle.getActiveFiberForce(currentState);',...
                                ' musOutput(iter,5) = OSMuscle.getPennationAngle(currentState);'];
end

% updating iteration
stringToExcute3 =['iter = iter+1;',...
%     'angles.colheaders(iter-1) = {char(osimModel.getCoordinateSet.get(DOF_Index(',num2str(n_instr),')).getName)};'...
%     'angles.data(iter-1,1) = setAngleDof',num2str(n_instr),';'...
    ''];

% closing the loops started in stringToExcute1 with appropriate nr of end
stringToExcute4 = '';
for n_instr = 1:size(DOF_Index,2);
    stringToExcute4 =[stringToExcute4,'end;']; %#ok<AGROW>
end

% assembling the code string
Code = [stringToExcute1,stringToExcute2,stringToExcute3,stringToExcute4];

% evaluate the code
eval(Code)

end
