function [T_quantalAbsorptionsNormalized,T_quantalAbsorptions,T_quantalIsomerizations,adjIndDiffParams] = ComputeRawConeFundamentals(params,staticParams)
% [T_quantalAbsorptionsNormalized,T_quantalAbsorptions,T_quantalIsomerizations,adjIndDiffParams] = ComputeRawConeFundamentals(params,staticParams)
%
% Function to compute normalized cone quantal sensitivities from underlying
% pieces and parameters.
%
% Note that this routine returns quantal sensitivities.  You may want
% energy sensitivities.  In that case, use EnergyToQuanta to convert
%   T_energy = EnergyToQuanta(S,T_quantal')'
% and then renormalize.  (You call EnergyToQuanta because you're converting
% sensitivities, which go the opposite direction from spectra.)
%
% The routine also returns two types of quantal sensitivity functions.  The
% first gives the probability that a photon will be absorbed.  These are
% returned in variable T_quantalAbsorptionsNormalized and
% T_quantalAbsorptions, with the first being normalized. The second is the
% probability that the photon will cause a photopigment isomerization. This
% is returned in T_quantalIsomerizations.
%
% It is T_quantalIsomerizations that you want to use to compute
% isomerization rates from retinal illuminance. See note at the end of
% function FillInPhotoreceptors for some information about conventions.  In
% particular, this routine takes pre-retinal absorption into account in its
% computation of probability of absorptions and isomerizations, so that the
% relevant retinal illuminance is one computed without accounting for those
% factors.  This routine does not account for light attenuation due to the
% pupil, however.  The only use of pupil size here is becuase of its slight
% effect on lens density as accounted for in the CIE standard.  Nor does it
% account for the collecting area of a photoreceptor, for cones the inner
% segment diameter.
%
% In the passed params structure, you can either pass the lambdaMax values
% for the photopigment, in which case the absorbance is computed from the
% specified nomogram, or you can pass the absorbance values directly in
% T_xxx format.  A typical choice in this case would be
% 10.^T_log10coneabsorbance_ss for the Stockman-Sharpe/CIE estimates.
%
% The typical use of this function is to be called by
% ComputeCIEConeFundamentals, which sets up the passed structures acording
% to the CIE standard. This routine, however, could in principle be used
% with a wide variety of choices of the component pieces.
%
% The other place this function is used is in attempts to fit a set of cone
% fundamentals by doing parameter search over the pieces.  It is this
% second use that led to the parameters being split over two separate
% structures, one that is held static during the fit and the other which
% contains the parameters that could be searched over by a calling routine.
% For examples, see:
%   FitConeFundamentalsWithNomogram, FitConeFundamentalsTest.
% Looking around today (8/10/13), I (DHB) don't see any examples where this
% routine is called directly.  Rather, it is a subfunction called by
% ComputeCIEConeFundamentals.  The search routines above use
% ComputeCIEConeFundamentals, and only search over lambdaMax values.  I
% think I wrote this with the thought that I might one day search over more
% parameters, but lost motivation to carry it throught.
%
% The computations done here are very similar to those done in routine
% FillInPhotoreceptors.  I (DHB) think that I forgot about what
% FillInPhotoreceptors could do when I wrote this, which has led to some
% redundancy. FillInPhotoreceptors returns a field called
% effectiveAbsorptance, which are the actual quantal efficiencies (not
% normalized) referred to the cornea.  FillInPhotoceptors also computes a
% field isomerizationAbsorptance, which takes the quantal efficiency of
% isomerizations (probability of an isomerization given an absorption into
% acount.
%
% It would probably be clever to unify the two sets of routines a little
% more, but we may never get to it.  The routine ComputeCIEConeFundamentals
% does contain a check that this routine and what is returned by
% FillInPhotoreceptors agree with each other, for cases where the
% parameters match.
%
% See ComputeCIEConeFundamentals for the breakdown of how the Asano et al.
% (2016) individual differences model is specified in params.indDiffParams.
%
% See ComputeCIEConeFundamentals for documentation of the adjIndDiffParams
% output argument.
%
% See also: ComputeCIEConeFundamentals, CIEConeFundamentalsTest,
% FitConeFundamentalsWithNomogram,
%           FitConeFundamentalsTest, DefaultPhotoreceptors,
%           FillInPhotoreceptors.
%
% 8/12/11  dhb  Starting to make this actually work.
% 8/14/11  dhb  Change name, expand comments.
% 8/10/13  dhb  Expand comments.  Return unscaled quantal efficiencies too.
% 2/26/16  dhb, ms  Add in Asano et al. (2016) individual observer adjustments
% 3/30/17  ms   Added output argument returning adjusted ind differences
% 6/4/18   ms   Included absorbance spectrum into adjusted ind diff output arg

% Handle bad value
index = find(params.axialDensity <= 0.0001);
if (~isempty(index))
    params.axialDensity(index) = 0.0001;
end

% Figure out how many receptor classes we're handling
if (isfield(params,'absorbance'))
    nReceptorTypes = size(params.absorbance,1);
else
    nReceptorTypes = length(params.lambdaMax);
end

% Fill in null individual differences parameters if they are not passed
if isempty(params.indDiffParams)
    params.indDiffParams.lambdaMaxShift = zeros(nReceptorTypes,1);
    params.indDiffParams.shiftType = 'linear';
    params.indDiffParams.dlens = 0;
    params.indDiffParams.dmac = 0;
    params.indDiffParams.dphotopigment = zeros(nReceptorTypes,1);
end

% Handle optional values for lens and macular pigment density
% in the parameters structure.  There are two mutually exclusive
% ways to do this.  For historical reasons, we can pass an additive
% density adjustment.  More recently we implemented the Asano et al. (2016)
% parameterization.  We don't allow both to happen at once.
%
% The logic here is a little hairy, because the way that we used to
% adjust lens and mac density was additive, but Asano et al. (2016) do
% it in a multiplicative fashion, so we need a flag to keep track of what
% we're going to do with the numbers down below.
if (~isfield(params,'extraLens'))
    params.extraLens = 0;
end
if (~isfield(params,'extraMac'))
    params.extraMac = 0;
end
if (params.extraLens ~= 0 && params.indDiffParams.dlens ~= 0)
    error('Cannot specify lens density adjustment two ways');
end
if (params.extraMac ~= 0 && params.indDiffParams.dmac ~= 0)
    error('Cannot specify macular pigment density adjustment two ways');
end
OLDLENSWAY = true;
if (params.extraLens == 0)
    OLDLENSWAY = false;
    params.extraLens = params.indDiffParams.dlens/100;
end
OLDMACWAY = true;
if (params.extraMac == 0)
    OLDMACWAY = false;
    params.extraMac = params.indDiffParams.dmac/100;
end

% Prereceptor transmittance.  Check that passed parameters are not so weird
% as to lead to transmittances greater than 1, and throw error if so.
if (OLDLENSWAY)
    fprintf('Using old way of adjusting lens density.  Consider switching to newer implementation via the params.indDiffParams field\n');
    lens = 10.^-(-log10(staticParams.lensTransmittance)+params.extraLens);
else
    lens = 10.^-(-log10(staticParams.lensTransmittance) * (1 + params.extraLens));
end
if (any(lens > 1))
    error('You have passed parameters that make lens transmittance greater than 1');
end
%lens(lens > 1) = 1;
adjIndDiffParams.lens = lens;

if (OLDMACWAY)
    fprintf('Using old way of adjusting macular pigment density.  Consider switching to newer implementation via the params.indDiffParams field\n');
    mac = 10.^-(-log10(staticParams.macularTransmittance)+params.extraMac);
else
    mac = 10.^-(-log10(staticParams.macularTransmittance) * (1 + params.extraMac));
end
if (any(mac > 1))
    error('You have passed parameters that make macular pigment transmittance greater than 1');
end
adjIndDiffParams.mac = mac;
%mac(mac > 1) = 1;

% Compute nomogram if absorbance wasn't passed directly.  We detect
% a direct pass by the existance of params.absorbance.
if (isfield(params,'absorbance'))
    absorbance = params.absorbance;
else
    absorbance = PhotopigmentNomogram(staticParams.S,params.lambdaMax,staticParams.whichNomogram);
end

% Shift absorbance, if desired
if (~isempty(params.indDiffParams.lambdaMaxShift))
    if (length(params.indDiffParams.lambdaMaxShift) ~= size(absorbance,1))
        error('Length of passed lambdaMaxShift does not match number of absorbances available to shift');
    end
    
    absorbance = ShiftPhotopigmentAbsorbance(staticParams.S,absorbance,params.indDiffParams.lambdaMaxShift,params.indDiffParams.shiftType);
end
adjIndDiffParams.absorbance = absorbance;

% Compute absorptance
%
% Handle special case where we deal with ser/ala polymorphism for L cone
%
% We've put in the Asano et al. (2016) multiplicative adjustment.  Since we
% weren't adjusting photopigment density previously (except for
% self-screening), we don't have to deal with two threads here.
%
% Note that density can also get adjusted according to light level to
% account for photopigment bleaching.  That happens in
% FillInPhotoreceptors, which would typically be called before we get here.
% We think this is OK, because both the Asano et al. and the fraction
% bleaching adjustment are multiplicative adjustments of axial density, and
% multiplication commutes so it doesn't matter what order we do things in.
if (size(absorbance,1) == 4)
    if (any(params.indDiffParams.dphotopigment ~= 0))
        error('Cannot use Asano et al. individual cone model with our weird 4 cone calling mode');
    end
    absorptance = AbsorbanceToAbsorptance(absorbance,staticParams.S,...
        [params.axialDensity(1) ; params.axialDensity(1) ; ...
        params.axialDensity(2) ; params.axialDensity(3)]);
elseif (size(absorbance,1) == 3)
    if (length(params.indDiffParams.dphotopigment) ~= 3)
        error('Density adjustment parameter length not right for cones');
    end
    LDensity = params.axialDensity(1) * (1 + params.indDiffParams.dphotopigment(1)/100);
    MDensity = params.axialDensity(2) * (1 + params.indDiffParams.dphotopigment(2)/100);
    SDensity = params.axialDensity(3) * (1 + params.indDiffParams.dphotopigment(3)/100);
    absorptance = AbsorbanceToAbsorptance(absorbance,staticParams.S,[LDensity ; MDensity ; SDensity]);
    adjIndDiffParams.dphotopigment = [LDensity MDensity SDensity];
elseif (size(absorbance,1) == 1 && params.DORODS)
    if (length(params.indDiffParams.dphotopigment) ~= 1)
        error('Density adjustment parameter length not right for rods');
    end
    RodDensity = params.axialDensity(1) + params.indDiffParams.dphotopigment(1)/100;
    absorptance = AbsorbanceToAbsorptance(absorbance,staticParams.S,RodDensity);
    adjIndDiffParams.dphotopigment = RodDensity;
else
    error('Unexpected number of photopigment lambda max values passed');
end
adjIndDiffParams.absorptance = absorptance;

%% Put together pre-receptor and receptor parts
for i = 1:size(absorptance,1)
    absorptance(i,:) = absorptance(i,:) .* lens .* mac;
end

%% Put it into the right form
if (size(absorptance,1) == 4)
    T_quantalAbsorptions = zeros(3,staticParams.S(3));
    T_quantalAbsorptions(1,:) = staticParams.LserWeight*absorptance(1,:) + ...
        (1-staticParams.LserWeight)*absorptance(2,:);
    T_quantalAbsorptions(2,:) = absorptance(3,:);
    T_quantalAbsorptions(3,:) = absorptance(4,:);
elseif (size(absorptance,1) == 3)
    T_quantalAbsorptions = zeros(3,staticParams.S(3));
    T_quantalAbsorptions(1,:) = absorptance(1,:);
    T_quantalAbsorptions(2,:) = absorptance(2,:);
    T_quantalAbsorptions(3,:) = absorptance(3,:);
elseif (size(absorptance,1) == 1 && params.DORODS)
    T_quantalAbsorptions = zeros(1,staticParams.S(3));
    T_quantalAbsorptions(1,:) = absorptance(1,:);
else
    error('Unexpected number of photopigment lambda max values passed');
end

%% Normalize to max of one for each receptor, and also compute isomerization quantal efficiency.
for i = 1:size(T_quantalAbsorptions,1)
    T_quantalIsomerizations = T_quantalAbsorptions*staticParams.quantalEfficiency(i);
    T_quantalAbsorptionsNormalized(i,:) = T_quantalAbsorptions(i,:)/max(T_quantalAbsorptions(i,:));
end
