function nc_varput( ncfile, varname, data, varargin )
% NC_VARPUT:  Write data into a NetCDF file.
%
% USAGE:  nc_varput(file, var, data, start, count, stride );
%
% PARAMETERS:
% Input:
%     ncfile:
%         Name of NetCDF file.
%     variable:
%         Variable whose data we want
%     data:
%         This matrix has to fit into the netcdf variable, of course.
%     start, count:
%         Zero-based indices that describe how much of the data is
%         requested.  If neither start nor count are supplied, then
%         all of the data is to be written.  
%     stride: 
%         Zero-based indices that describe the contiguity of the
%         data to be written.  If a stride argument is 2, then every
%         other datum in the file is written to.  If stride is not 
%         supplied, it defaults to 1 along each dimension.
% Output:
%     None.  Throws an exception in case of an error condition.
%     
%   
% Examples:
%    Suppose you have a netcdf variable called 'x' of size 6x4.  If you 
%    have an array of data called 'mydata' that is 6x4, then you can 
%    write to the entire variable with 
% 
%        >> nc_varput ( 'foo.nc', 'x', mydata );
%
%    If you wish to only write to the first 2 rows and three columns,
%    you could do the following
%
%        >> subdata = mydata(1:2,1:3);
%        >> nc_varput ( 'foo.nc', 'x', subdata, [0 0], [2 3] );
%
%
%


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% $Id: nc_varput.m 1976 2006-11-01 19:55:24Z johnevans007 $
% $LastChangedDate: 2006-11-01 14:55:24 -0500 (Wed, 01 Nov 2006) $
% $LastChangedRevision: 1976 $
% $LastChangedBy: johnevans007 $
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



%
% Must have at least 3 input arguments, no more than 6
% Three inputs means to write an entire dataset (put_var_xxx or put_var1_xxx).
% Five inputs means to write part of a dataset with default stride (put_vara_xxx).
% Six inputs means strided writes (put_vars_xxx).
if nargin < 3 | nargin == 4 | nargin > 6
    fmt = '%s:  number of input arguments cannot possibly be correct.\n';
    msg = sprintf ( fmt, mfilename );
    error ( msg );
end



%
% Error checking on the inputs.
if ~ischar(ncfile)
	error ( '%s:  the filename must be character.\n', mfilename );
end
if ~ischar(varname)
	error ( '%s:  the variable name must be character.\n', mfilename );
end

if nargin == 5
	if ~isnumeric ( varargin{1} )
		error ( '%s:  the ''start'' argument must be numeric.\n', mfilename );
	end
	if ~isnumeric ( varargin{2} )
		error ( '%s:  the ''count'' argument must be numeric.\n', mfilename );
	end
elseif nargin == 6
	if ~isnumeric ( varargin{1} )
		error ( '%s:  the ''start'' argument must be numeric.\n', mfilename );
	end
	if ~isnumeric ( varargin{2} )
		error ( '%s:  the ''count'' argument must be numeric.\n', mfilename );
	end
	if ~isnumeric ( varargin{3} )
		error ( '%s:  the ''stride'' argument must be numeric.\n', mfilename );
	end
end






%
% Unpack the optional parameters
if nargin == 5
    start = varargin{1};
    count = varargin{2};
elseif nargin == 6
    start = varargin{1};
    count = varargin{2};
    stride = varargin{3};
end



%
% open the file.  
[ncid, status] = mexnc('open', ncfile, nc_write_mode);
if (status ~= 0)

    ncerr_msg = mexnc ( 'strerror', status );
    error ( '%s:  ''open'' failed on %s (%s).\n', mfilename, ncfile, ncerr_msg  );

end




%
% check to see if the variable already exists.  
[varid, status] = mexnc('INQ_VARID', ncid, varname );
if ( status ~= 0 )
    mexnc ( 'close', ncid );
    ncerr_msg = mexnc ( 'strerror', status );
    error ( '%s:  INQ_VARID failed on %s (%s).\n' , mfilename, varname, ncerr_msg );    
end
if ( varid < 0 )
    mexnc ( 'close', ncid );
    fmt = '%s:  Could not find variable %s in file %s.\n';
    msg = sprintf ( fmt, mfilename, varname, ncfile );    
    error ( msg );
end



%
% Get variable information.
[dud,var_type,nvdims,var_dim,dud, status]=mexnc('INQ_VAR',ncid,varid);
if status < 0 
    mexnc ( 'close', ncid );
    ncerr_msg = mexnc ( 'strerror', status );
    fmt = '%s:  ''inq_var'' operation failed on %s, variable (%s).\n';
    msg = sprintf ( fmt, mfilename, ncfile, variable, ncerr_msg );
    error ( msg );
end



%
% Determine the size of the netcdf variable.
if nvdims == 0
    nc_count = 1;
else

    %
    % Case of variable with well-defined dimensions.  Much easier.
    for n=1:nvdims,
        dimid=var_dim(n);
        [dim_size,status]=mexnc('INQ_DIMLEN',ncid,dimid);
        if status < 0 
            mexnc ( 'close', ncid );
            ncerr_msg = mexnc ( 'strerror', status );
            format = '%s:  ''INQ_DIM'' operation failed on dimid %d, variable (%s).\n';
            error ( format, mfilename, ncfile, variable, ncerr_msg );
        end
        nc_count(n)=dim_size;
    end
end



%
% Make sure that the rank of the input data is correct.
if (nvdims == 0)

    %
    % singleton case.
    if numel(data) ~= 1
        mexnc ( 'close', ncid );
        efmt = '%s:  matrix is too large (%d elements) for this variable (1 element)';
        msg = sprintf ( efmt, mfilename, numel(data) );
        error ( msg );
    end

elseif (nvdims == 1)

    %
    % This is a special case.  Matlab reports 1D vectors as having at least rank 2.
    if ndims(data) > 2
        mexnc ( 'close', ncid );
        efmt = '%s:  matrix is too large (%d elements) for this variable (1 element)';
        msg = sprintf ( efmt, mfilename, ndims(data) );
        error ( msg );
    end

    %
    % At least one of the input data sizes must be 1
    sz = size(data);
    if ~any(find(sz==1))
        mexnc ( 'close', ncid );
        efmt = '%s:  input data should be a row vector, not %d x %d';
        msg = sprintf ( efmt, mfilename, sz(1), sz(2) );
        error ( msg );
    end

else

    %
    % 2D and higher case.  These cases are easier.
    if ( ndims(data) ~= length(nc_count) )
        mexnc ( 'close', ncid );
        efmt = '%s:  rank of input data (%d) does not match rank of netcdf variable (%d).\n';
        msg = sprintf ( efmt, mfilename, ndims(data), length(nc_count) );
        error ( msg );
    end
end









%
% Singletons are a special case.  We need to set the start and count carefully.
if nvdims == 0

    %
    % Must be a singleton variable with no defined dimensions.  
    switch nargin

    case 3
        %
        % This is the case of "nc_varput ( file, var, data );"
        start = 0;
        count = 1;

    case 5
        %
        % This is the case of "nc_varput ( file, var, data, start, count );"
        % So the user gave us "start" and "count".  Did they do so correctly?
        if ( start ~= 0 ) | (count ~= 1 )
            mexnc ( 'close', ncid );
            fmt = '%s:  in case of singleton variable,  ''start'' must be [0] and ''count'' must be [1].  Either do that or do not supply them.\n';
            msg = sprintf ( fmt, mfilename );
            error ( msg );
        end

    case 6
        fmt = '%s:  strides make no sense for a singleton variable.\n';
        msg = sprintf ( fmt, mfilename );
        error ( msg );
    end


end







%
% Check that the start, count, and stride arguments have the same length.
if ( nargin > 3 )
    if ( length(start) ~= length(count) )
        mexnc ( 'close', ncid );
        error ( '%s:  start and count arguments must have the same length.\n', mfilename );
    end
end
if ( nargin == 6 )
    if ( length(start) ~= length(stride) )
        mexnc ( 'close', ncid );
        error ( '%s:  start and stride arguments must have the same length.\n', mfilename );
    end
end


% If only 3 inputs and if not a singleton variable, then START is [0,..] and COUNT is 
% the size of the data.  
if ( nargin ==3 ) & ( nvdims > 0 )
    start = zeros(1,nvdims);
    count = size(data);
end


%
% check that the length of the start argument matches the rank of the variable.
if length(start) ~= length(nc_count)
    mexnc ( 'close', ncid );
    fmt = '%s:  length of start argument (%d) does not make sense with a variable rank of %d.\n';
    msg = sprintf ( fmt, mfilename, length(start), length(nc_count) );
    error ( msg );
end



%
% Figure out which write routine we will use.  If the target variable is a singleton, then we must use
% VARPUT1.  If a stride was given, we must use VARPUTG.  Otherwise just use VARPUT.
if nvdims == 0
    write_op = 'put_var1';
elseif nargin == 3
    write_op = 'put_var';
elseif nargin == 5
    write_op = 'put_vara';
elseif nargin == 6
    write_op = 'put_vars';
else
    error ( '%s:  unhandled write op.  How did we come to this??\n', mfilename );
end


%
% We are now in a position to do a check on the input var size vs. 
% the known size in the netcdf file.  If we don't do this, it would 
% be possible to send a larger then expected chunk of data to the 
% netcdf file, have parts of it get lopped off in order to fit, and 
% never be the wiser.
switch ( write_op )

case 'put_var1'

    %
    % Just check that the length of the input data was 1.
    if numel(data) ~= 1
        mexnc ( 'close', ncid );
        fmt = '%s:  write_op was ''put_var1'', but the length of the data was %d.\n';
        msg = sprintf ( fmt, mfilename, numel(data) );
        error ( msg );
    end

case 'put_var'

    %
    % Since 'put_var' writes all the data, check that the
    % extents match up exactly.  
    if ( numel(data) ~= prod(nc_count) )
        mexnc ( 'close', ncid );
        fmt = '%s:  total number of input datums was %d, but the netcdf variable size is %d elements.\n';
        msg = sprintf ( fmt, mfilename, numel(data), prod(nc_count ) );
        error ( msg );
    end

case { 'put_vara', 'put_vars' }

    %
    % Just check that the chunk of data the user gave us is the same
    % size as the given count.  This works for put_vars as well.
    if ( numel(data) ~= prod(count) )
        mexnc ( 'close', ncid );
        fmt = '%s:  total number of input datums was %d, but the count parameter indicated %d elements.\n';
        msg = sprintf ( fmt, mfilename, numel(data), prod(count ) );
        error ( msg );
    end

otherwise 
    mexnc ( 'close', ncid );
    fmt = '%s:  unhandled write_op %s.\n';
    msg = sprintf ( fmt, write_op )
    error ( msg );


end


%
% Scale the data if we have the appropriate attributes.
have_scale_factor = 0;
have_add_offset = 0;
[dud, dud, status] = mexnc('INQ_ATT', ncid, varid, 'scale_factor' );
if ( status == 0 )
    have_scale_factor = 1;
end
[dud, dud, status] = mexnc('INQ_ATT', ncid, varid, 'add_offset' );
if ( status == 0 )
    have_add_offset = 1;
end
if have_scale_factor

    [scale_factor, status] = mexnc ( 'get_att_double', ncid, varid, 'scale_factor' );
    if ( status ~= 0 )
        mexnc('close',ncid);
        error ( '%s:  ATT_GET_DOUBLE failed on scale_factor.\n', mfilename );
    end
    if have_add_offset
        [add_offset, status] = mexnc ( 'get_att_double', ncid, varid, 'add_offset' );
        if ( status ~= 0 )
            mexnc('close',ncid);
            error ( '%s:  ATT_GET_DOUBLE failed on add_offset.\n', mfilename );
        end
    else
        add_offset = 0.0;
    end

    data = (double(data) - add_offset) / scale_factor;

end


%
% Handle the fill value, if any.  Change those values into NaN.
[dud, dud, status] = mexnc('INQ_ATT', ncid, varid, '_FillValue' );
if ( status == 0 )

    switch ( class(data) )
    case 'double'
        [fill_value, status] = mexnc ( 'get_att_double', ncid, varid, '_FillValue' );
    case 'single'
        [fill_value, status] = mexnc ( 'get_att_float', ncid, varid, '_FillValue' );
    case 'int32'
        [fill_value, status] = mexnc ( 'get_att_int', ncid, varid, '_FillValue' );
    case 'int16'
        [fill_value, status] = mexnc ( 'get_att_short', ncid, varid, '_FillValue' );
    case 'int8'
        [fill_value, status] = mexnc ( 'get_att_schar', ncid, varid, '_FillValue' );
    case 'uint8'
        [fill_value, status] = mexnc ( 'get_att_uchar', ncid, varid, '_FillValue' );
    case 'char'
        [fill_value, status] = mexnc ( 'get_att_text', ncid, varid, '_FillValue' );
    otherwise
        mexnc('close',ncid);
        error ( '%s:  unhandled datatype %s\n', mfilename, nc_datatype_string(var_type) );
    end

    if ( status ~= 0 )
        mexnc('close',ncid);
        ncerrmsg = mexnc ( 'strerror', status );
        error ( '%s:  GET_ATT_XXX failed on _FillValue, var %s, file %s (%s).\n', mfilename, varname, ncfile, ncerrmsg );
    end


    ind = find(isnan(data));
    data(ind) = fill_value;

end

    

%
% write the data
pdata = permute(data, fliplr( 1:ndims(data) ));
switch ( write_op )

    case 'put_var1'
        switch ( class(data) )
        case 'double'
            status = mexnc ( 'put_var1_double', ncid, varid, start, pdata  );
        case 'single'
            status = mexnc ( 'put_var1_float', ncid, varid, start, pdata );
        case 'int32'
            status = mexnc ( 'put_var1_int', ncid, varid, start, pdata );
        case 'int16'
            status = mexnc ( 'put_var1_short', ncid, varid, start, pdata );
        case 'int8'
            status = mexnc ( 'put_var1_schar', ncid, varid, start, pdata );
        case 'uint8'
            status = mexnc ( 'put_var1_uchar', ncid, varid, start, pdata );
        case 'char'
            status = mexnc ( 'put_var1_text', ncid, varid, start, pdata );
        otherwise
            mexnc('close',ncid);
            error ( '%s:  unhandled data class %s\n', mfilename, class(pdata) );
        end

    case 'put_var'
        switch ( class(data) )
        case 'double'
            status = mexnc ( 'put_var_double', ncid, varid,  pdata  );
        case 'single'
            status = mexnc ( 'put_var_float', ncid, varid,  pdata );
        case 'int32'
            status = mexnc ( 'put_var_int', ncid, varid,  pdata );
        case 'int16'
            status = mexnc ( 'put_var_short', ncid, varid,  pdata );
        case 'int8'
            status = mexnc ( 'put_var_schar', ncid, varid,  pdata );
        case 'uint8'
            status = mexnc ( 'put_var_uchar', ncid, varid,  pdata );
        case 'char'
            status = mexnc ( 'put_var_text', ncid, varid,  pdata );
        otherwise
            mexnc('close',ncid);
            error ( '%s:  unhandled data class %s\n', mfilename, class(pdata) );
        end
    
    case 'put_vara'
        switch ( class(data) )
        case 'double'
            status = mexnc ( 'put_vara_double', ncid, varid, start, count,  pdata  );
        case 'single'
            status = mexnc ( 'put_vara_float', ncid, varid,  start, count, pdata );
        case 'int32'
            status = mexnc ( 'put_vara_int', ncid, varid, start, count,  pdata );
        case 'int16'
            status = mexnc ( 'put_vara_short', ncid, varid, start, count,  pdata );
        case 'int8'
            status = mexnc ( 'put_vara_schar', ncid, varid,  start, count, pdata );
        case 'uint8'
            status = mexnc ( 'put_vara_uchar', ncid, varid, start, count,  pdata );
        case 'char'
            status = mexnc ( 'put_vara_text', ncid, varid, start, count,  pdata );
        otherwise
            mexnc('close',ncid);
            error ( '%s:  unhandled data class %s\n', mfilename, class(pdata) );
        end

    case 'put_vars'
        switch ( class(data) )
        case 'double'
            status = mexnc ( 'put_vars_double', ncid, varid, start, count, stride,  pdata  );
        case 'single'
            status = mexnc ( 'put_vars_float', ncid, varid,  start, count, stride, pdata );
        case 'int32'
            status = mexnc ( 'put_vars_int', ncid, varid, start, count, stride,  pdata );
        case 'int16'
            status = mexnc ( 'put_vars_short', ncid, varid, start, count, stride,  pdata );
        case 'int8'
            status = mexnc ( 'put_vars_schar', ncid, varid, start, count, stride, pdata );
        case 'uint8'
            status = mexnc ( 'put_vars_uchar', ncid, varid, start, count, stride,  pdata );
        case 'char'
            status = mexnc ( 'put_vars_text', ncid, varid, start, count, stride,  pdata );
        otherwise
            mexnc('close',ncid);
            error ( '%s:  unhandled data class %s\n', mfilename, class(pdata) );
        end

    otherwise 
        mexnc ( 'close', ncid );
        error ( '%s:  unknown write operation''%s''.\n', mfilename, write_op );


end

if ( status < 0 )
    mexnc ( 'close', ncid );
    ncerr = mexnc ( 'strerror', status );
    error ( '%s:  write operation ''%s'' failed on %s with error ''%s'', file %s.\n', mfilename, write_op, variable, ncerr, ncfile );
end

status = mexnc ( 'close', ncid );
if ( status < 0 )
    error ( '%s:  ''%s''.\n', mfilename, mexnc('STRERROR',status));
end


return




