function values = nc_varget_java ( ncfile, varname, varargin )
% NC_VARGET_JAVA:  Java backend for nc_varget.
%
% See the help section for nc_varget.


import ucar.nc2.dods.*     % import opendap reader classes
import ucar.nc2.*          % have to import this (NetcdfFile) 
                           % as well for local reads.
                           % Now that's just brilliant.  



% One of these is set when we figure out what arguments we
% were given.
USE_GET_VAR = 0;   % get the entire variable?
USE_GET_VARA = 0;  % get a contiguous part of the variable?
USE_GET_VARS = 0;  % get a variable with some stride%
USE_GET_VAR1 = 0;  % get just a single value?



% Don't assume we are given coordinates unless we know otherwise.
start = [];
count = [];
stride = [];







switch nargin
case 2
    USE_GET_VAR = 1;


case 3
    error ( '%s:  there cannot be three input arguments.\n', mfilename );

case 4
    %
    % 3rd and 4th must be numeric.  start and count.
    if (ischar(varargin{1}) || ischar(varargin{2}))
        error ( '%s:  if four inputs, the third and fourth must be numeric\n', mfilename );
    else
        USE_GET_VARA = 1;
        start = varargin{1};
        count = varargin{2};
    end

case 5
    %
    % 3rd and 4th must be numeric.  start and count.
    if (~isnumeric(varargin{3}))
        error ( '%s:  if five inputs, the fifth must be numeric\n', mfilename );
    end
    USE_GET_VARS = 1;
    start = varargin{1};
    count = varargin{2};
    stride = varargin{3};

end



%
% Open netCDF file
%

if snc_is_url ( ncfile )
    jncid = DODSNetcdfFile(ncfile);
else
    jncid = NetcdfFile(ncfile);
end


%
% Get the variable object
jvarid = jncid.findVariable(varname);
if isempty ( jvarid )
    strformat = '%s:  findVariable failed on variable ''%s'', file ''%s''.\n';
    error ( strformat, mfilename, varname, ncfile ); 
end



theDataType = jvarid.getDataType();
theDataTypeString = char ( theDataType.toString() ) ;

theDimensions = jvarid.getDimensions();

num_var_dims = theDimensions.size();


%
% Check that the start, count, stride parameters have appropriate lengths.
% Otherwise we get confusing error messages later on.
if ( USE_GET_VAR1 || USE_GET_VARA || USE_GET_VARS )
    if (length(start) ~= num_var_dims)
        close ( jncid );
        fmt = '%s:  length of the start argument (%d) does not equal the number of dimensions (%d) for %s\n';
        error ( fmt, mfilename, length(start), num_var_dims, varname );
    end
end
if ( USE_GET_VARA || USE_GET_VARS )
    if (length(count) ~= num_var_dims)
        close ( jncid );
        fmt = '%s:  length of the count argument (%d) does not equal the number of dimensions (%d) for %s\n';
        error ( fmt, mfilename, length(count), num_var_dims, varname );
    end
end
if ( USE_GET_VARS )
    if (length(stride) ~= num_var_dims)
        close ( jncid );
        fmt = '%s:  length of the stride argument (%d) does not equal the number of dimensions (%d) for %s\n';
        error ( fmt, mfilename, length(stride), num_var_dims, varname );
    end
end






%
% If a singleton, then use GET_VAR1.  This is only because some 
% opendap-enabled mexnc clients have trouble using GET_VAR on 
% singletons.  It is annoying to have to do this, but it works just as
% well.
if num_var_dims == 0

    %
    % Must be a scalar variable.  
    the_var_size = 1;
    USE_GET_VAR1 = 1;
    USE_GET_VAR = 0;
    start = 0;
    count = 1;

%
% otherwise we need to figure out how big the variable is.
else
    for j=1:num_var_dims,
            the_var_size(j) = jvarid.getDimension(j-1).getLength();
    end
end


%
% If the user had set non-positive numbers in "count", then
% we replace them with what we need to get the
% rest of the variable.
if USE_GET_VARA || USE_GET_VARS
    neg_inds = find(count<0);
    count(neg_inds) = the_var_size(neg_inds) - start(neg_inds);
end



%
% Is the requested size negative?
ind = find(count<0);
if ~isempty(ind)
    close ( jncid );
    hyperslab_start = sprintf ( '[%d]', start );
    hyperslab_request = sprintf ( '[%d]', count );
    hyperslab_extent = sprintf ( '[%d]', the_var_size );
    strformat = '%s:  requested ''%s'' data hyperslab extent %s + %s is negative, restate to fit existing data bounds %s.\n';
    error ( strformat, mfilename, varname, hyperslab_start, hyperslab_request, hyperslab_extent );
end


if isempty(stride)
    stride = ones(size(count));
end


%
% Make sure that all three of start, count, and stride have
% the same length
if ( length(start) ~= length(count) )
    close ( jncid );
    error ( '%s:  length of start argument must be the same as the count argument.\n', mfilename );
end
if ( length(start) ~= length(stride) )
    close ( jncid );
    error ( '%s:  length of start argument must be the same as the stride argument.\n', mfilename );
end

%
% Does the hyperslab exceed the dataset size?
if USE_GET_VARA || USE_GET_VARS
    last_datum_location_retrieved = start + (count-1).*stride;
    ind = find (last_datum_location_retrieved >= the_var_size );
    if ~isempty(ind)
        close ( jncid );
        hyperslab_start = sprintf ( '[%d]', start );
        hyperslab_request = sprintf ( '[%d]', count );
        hyperslab_stride = sprintf ( '[%d]', stride );
        hyperslab_extent = sprintf ( '[%d]', the_var_size );
        format = '%s:  requested ''%s'' data hyperslab start %s, count %s, stride %s exceeds existing data bounds %s.\n';
        error ( format, mfilename, varname, hyperslab_start, hyperslab_request, hyperslab_stride, hyperslab_extent );
    end
end




%
% Get slab, unless the count is zero.
%
% There is a bug in Mexcdf.  If the count of the
% first dimension is zero, it core dumps.  Something to figure
% out later.
if prod(count) == 0
    close ( jncid );
    error ( '%s:  retrieval request was for 0 elements.\n', mfilename );  
end




%
% Finally!  Read the freakin' data.
try
    if USE_GET_VAR1

        % Read a scalar
        values = nc_var1_get_java ( jvarid, theDataTypeString );

    elseif USE_GET_VAR
    
        % Read everything.
        values = nc_var_get_java ( jvarid, theDataTypeString );

    elseif USE_GET_VARA
    
        % Read a contiguous subset
        values = nc_vara_get_java ( jvarid, theDataTypeString, start, count );

    elseif USE_GET_VARS
    
        % Read a contiguous subset
        values = nc_vars_get_java ( jvarid, theDataTypeString, start, count, stride );

    else
    
        fmt = '%s:  could not read "%s", file "%s".  Last error "%s"\n';
        error ( fmt, mfilename, varname, ncfile, lasterr );
    
    end
    
catch
    close ( jncid );
    rethrow ( lasterror );
end
    
    

%
% If it's a 1D vector, make it a column vector.
% Apparently, there's no need to otherwise permute the data.
if length(the_var_size) == 1
    values = values(:);
end                                                                                   


%
% Handle the fill value, if any.  Change those values into NaN.
fillvalue_att = jvarid.findAttribute ( '_FillValue' );
if ~isempty(fillvalue_att)
    switch ( char ( theDataType.toString() ) )
    case 'char'
        %
        % For now, do nothing.  Does a fill value even make sense with char data?
        % If it does, please tell me so.
        %

    case { 'double', 'float', 'long', 'short', 'byte' }
        fill_value = fillvalue_att.getNumericValue().doubleValue();
        fill_inds = find(values==fill_value);
        values(fill_inds) = NaN;

    end
end








%
% Handle the missing value, if any.  Change those values into NaN.
missing_value_att = jvarid.findAttribute ( 'missing_value' );
if ~isempty(missing_value_att)
    switch ( char ( theDataType.toString() ) )
    case 'char'

        %
        % For now, do nothing.  Does a fill value even make sense with 
        % char data?  Matlab doesn't allow for NaNs in character arrays.

    case { 'double', 'float', 'long', 'short', 'byte' }
        missing_value = missing_value_att.getNumericValue().doubleValue();
        mv_inds = find(values==missing_value);
        values(mv_inds) = NaN;

    end
end









%
% Handle the scale factor and add_offsets. 
scale_factor_att = jvarid.findAttribute ( 'scale_factor' );
add_offset_att = jvarid.findAttribute ( 'add_offset' );
if ~isempty(scale_factor_att)
    switch ( char ( theDataType.toString() ) )
    case 'char'

        % For now, do nothing.  Does scaling make sense for char data?

    case { 'double', 'float', 'long', 'short', 'byte' }
        scale_factor = scale_factor_att.getNumericValue().doubleValue();
        if ~isempty(add_offset_att)
            add_offset = add_offset_att.getNumericValue().doubleValue();
        else
            add_offset = 0.0;
        end

        values = values * scale_factor + add_offset;

    end
end





%
% remove any singleton dimensions.
values = squeeze ( values );



close ( jncid );


return









function values = nc_var1_get_java ( jvarid, theDataTypeString )
% NC_VAR1_GET_JAVA:  reads a scalar.

switch ( theDataTypeString )

    case 'char'
        values = jvarid.read();
        values = char ( values.toString() );

    case { 'double', 'float', 'int', 'short', 'byte' }
        values = jvarid.readScalarDouble();

    otherwise
        fmt = '%s:  unhandled datatype ''%s''';
        error ( fmt, mfilename, theDataTypeString );
    
end
    
    
return






function values = nc_var_get_java ( jvarid, theDataTypeString )
% NC_VAR_GET_JAVA:  reads the entire variable

values = jvarid.read();
switch ( theDataTypeString )
    case 'char'
        values = copyToNDJavaArray(values);
    case { 'double', 'float', 'int', 'short', 'byte' }
        values = copyToNDJavaArray(values);
        values = double(values);
    otherwise
        fmt = '%s:  unhandled datatype ''%s''';
        error ( fmt, mfilename, theDataTypeString );
    
end
return







    
    
function values = nc_vara_get_java ( jvarid, theDataTypeString, start, count )
% NC_VARA_GET_JAVA:  reads a contiguous subset

values = jvarid.read(start, count);
switch ( theDataTypeString )
    case 'char'
        values = copyToNDJavaArray(values);
    case { 'double', 'float', 'int', 'short', 'byte' }
        values = copyToNDJavaArray(values);
        values = double ( values );
    otherwise
        fmt = '%s:  unhandled datatype ''%s''';
        error ( fmt, mfilename, theDataTypeString );
    
end
return
    







function values = nc_vars_get_java ( jvarid, theDataTypeString, start, count, stride )
% NC_VARS_GET_JAVA:  reads a strided subset

% Have to use the method with the section selector.
% "1:2,10,:,1:100:10"
extent = start + count.*stride-1;
section_selector = '';
for j = 1:length(start)
    section_selector = sprintf ( '%s,%d:%d:%d', section_selector, start(j), extent(j), stride(j) );
end

%
% Get rid of the first comma.
section_selector(1) = [];

values = jvarid.read(section_selector);
switch ( theDataTypeString )
    case 'char'
        values = copyToNDJavaArray(values);
    case { 'double', 'float', 'int', 'short', 'byte' }
        values = copyToNDJavaArray(values);
        values = double ( values );
    otherwise
        fmt = '%s:  unhandled datatype ''%s''';
        error ( fmt, mfilename, theDataTypeString );
    
end
    
    
return
