function [data,header]=read_grads(file_name,var_name,varargin)
% [var,header]=read_grads(file_name,var_name) is a function to read
% in binary files using their GrADS descriptor header. 
% If var_name is 'all' or non-existent, all variables are read and 
% sent to the base-workspace.
% Please check before-hand the data-type (big/little endian), as well
% as the data precision (single/double...), and adjust if necessary 
% the header at the beginning of read_grads.m.
% Optionally, you can improve the indications from the header file in 
% the sub-function grads_name (hereafter).
% 
% Kristof Sturm, sturm@dkrz.de or sturm@lgge.obs.ujf-grenoble.fr, 29.10.02

global l_quiet

n=strmatch('quiet',{varargin{1:2:end}});
if isempty(n)
  l_quiet=0;
else
  switch lower(varargin{2*(n-1)+2})
   case {1,'yes'}
    l_quiet=1;
   case {0,'no'}
    l_quiet=0;
  end
end

header=struct('FILENAME',file_name,...
	      'VARSIZE',{[]},...
	      'NVAR',0,...
	      'DATANAME','-',...
	      'FID',{[]},...
	      'BINTYPE','-',...
	      'BINPRECISION','float32',...
	      'DSET','-',...
	      'DTYPE',1,...
	      'INDEX','-',...
	      'TITLE','-',...
	      'UNDEF',0,...
	      'OPTIONS',{[]},...
	      'XDEF',{[]},...
	      'YDEF',{[]},...
	      'ZDEF',{[]},...
	      'TDEF',{[]},...
	      'VARS',struct([]),...
	      'FILEHEADER',0,...
	      'THEADER',0,...
	      'XYHEADER',0);

header.XDEF.rev=0;
header.YDEF.rev=0;
header.ZDEF.rev=0;

% [comp,max_size,endian]=computer;
endian='L';
if endian=='L'
  header.BINTYPE='ieee-le'; 
elseif endian=='B'
  header.BINTYPE='ieee-be';
end

data=[];

file_name=deblank(file_name);
if exist(file_name,'file')
  fid_ctl = fopen(file_name,'r');
  % disp(['CTL opened: ',num2str(fid_ctl)])
  % [header.FID]=deal([fid_ctl 0]);
  header(1).FID{1}=fid_ctl;
  % TODO: how is this different from filesep?
  if isunix, sep='/';
  else
    sep='\\';
  end
  if ~isempty(strfind(file_name,sep))
    slash=max(strfind(file_name,sep));
    header(1).DIR=file_name(1:slash);
    file_name=file_name((slash+1):end);
  end
  header(1).FILENAME=file_name;
else
  error(['Inexistent header file ',file_name])
end

% Enabling read_grads to handle netcdf files. 

point=strfind(file_name,'.');
suf=lower(file_name(point(end)+1:end));

if strcmp(suf,'ctl')
% reading the .ctl file : 
while ~feof(fid_ctl)
  line=fgets(fid_ctl);
  if isempty(line(1:end-1)), break, end
  % cutting the line into words.
  line_gaps=isspace(line);
  % removing consecutive blancks.
  while line_gaps(1)==1, line_gaps(1)=[]; line(1)=[]; end
  if line(1)=='*'
    if ~l_quiet
      disp(line)
    end
  else
    no_gaps=0;
    while ~no_gaps
      line_gap2=find(line_gaps);
      if any(diff(line_gap2)==1)
	dup=1+find(diff(line_gap2)==1);
	line_gaps(line_gap2(dup))=[];
	line(line_gap2(dup))=[];
      else
	no_gaps=1;
      end
    end
    entry=line(1:(min(find(line_gaps))-1));
    words={''};
    for n=1:(sum(line_gaps)-1)
      words{n}=line((line_gap2(n)+1):(line_gap2(n+1)-1));
    end
    header=read_header(entry,words);
    % disp(words)
  end
end

% Opening the binary file for later reading:
fid_bin = fopen(header.DATANAME,'r', header.BINTYPE );
% disp(['BIN opened: ',num2str(fid_bin)])
[header(1).FID] = {fid_ctl, fid_bin};

fclose(fid_ctl);
nvar=size(header.VARS,1);
header.NVAR=nvar;

if nargin>1 & isempty(var_name)
  if ~l_quiet
    disp(['Header for ',header.FILENAME,' read.'])
  end
  fclose(header.FID{2});
  return
end

if nargout==0
  assignin('caller','header',header)
  assignin('caller','vars',vars);
end

if header.DTYPE          % Gridded data set
  
  % Computing the size of each variable:
  var_size=zeros(nvar,4);  % array of the var. size [X,Y,Z,T]
  var_size(:,1)=header.XDEF.num;
  var_size(:,2)=header.YDEF.num;
  var_size(:,3)=cat(1,header.VARS.levs);
  var_size(:,4)=header.TDEF.num;
  header.VARSIZE={var_size};
  if exist('grads_name','file')
    [vars,header]=grads_name(header,vars);
  end
  
  % reading the bin. file :
  if nargin == 1 | strcmp(lower(var_name),'all')
    read_data(header);
  elseif nargin == 2
    t_limits=[1 header.TDEF.num];
    ivar=strmatch(var_name,{header.VARS.name},'exact');
    if isempty(ivar)
      if ~l_quiet
	disp(['The variable ',var_name,' cannot be found in ',header.DATANAME])
      end
      data=[];
      return
    end
    z_limits=[1 header.VARS(ivar).levs];
    y_limits=[1 header.YDEF.num];
    x_limits=[1 header.XDEF.num];
    data=read_var(header,var_name,t_limits,z_limits,y_limits,x_limits);
    header.XDEF.vec=header.XDEF.vec(x_limits(1):x_limits(2));
    header.XDEF.num=length(header.XDEF.vec);
    header.YDEF.vec=header.YDEF.vec(y_limits(1):y_limits(2));
    header.YDEF.num=length(header.YDEF.vec);
  else
    for n=1:2:nargin-2
      switch lower(varargin{n})
       case {'x'}
	x_limits=varargin{n+1};
       case {'lon'}
	lon_limits=sort(varargin{n+1});
	x_limits=interp1(header.XDEF.vec,1:header.XDEF.num,...
			 lon_limits,'nearest',NaN);
	if any(isnan(x_limits))
	  if ~l_quiet
	    disp(['The X-limits ',num2str(lon_limits),...
		  ' exceed the data coverage ',...
		  num2str(header.XDEF.vec([1 end]))])
	  end
	  data=[];
	  return
	end
       case {'y'}
	y_limits=varargin{n+1};
       case {'lat'}
	lat_limits=sort(varargin{n+1});
	y_limits=interp1(header.YDEF.vec,1:header.YDEF.num,...
			 lat_limits,'nearest',NaN);
	if any(isnan(y_limits))
	  if ~l_quiet
	    disp(['The Y-limits ',num2str(lat_limits),...
		  ' exceed the data coverage ',...
		  num2str(header.YDEF.vec([1 end]))])
	  end
	  data=[];
	  return
	end
	if header.YDEF.rev,
	  y_limits=header.YDEF.num*ones(size(y_limits))-fliplr(y_limits)+1;
	end
       case {'t','time'}
	t_limits=varargin{n+1};
       case {'z','lev'}
	z_limits=varargin{n+1};
	ivar=strmatch(var_name,{header.VARS.name},'exact');
	if isempty(ivar)
	  if ~l_quiet
	    disp(['The variable ',var_name,' cannot be found in ',header.DATANAME])
	  end
	  data=[];
	  return
	end
	if any(z_limits>header.VARS(ivar).levs)
	  if ~l_quiet
	    disp(['The variable ',var_name,' has only ',...
		  header.VARS(ivar).levs,' levels.'])
	  end
	  data=[];
	  return
	end
      end
    end
    if ~exist('t_limits','var'), t_limits=[1 header.TDEF.num]; end
    if ~exist('z_limits','var')
      ivar=strmatch(var_name,{header.VARS.name},'exact');
      if isempty(ivar)
	if ~l_quiet
	  disp(['The variable ',var_name,' cannot be found in ',header.DATANAME])
	end
	data=[];
	return
      end
      z_limits=[1 header.VARS(ivar).levs];
    end
    if ~exist('y_limits','var')
      y_limits=[1 header.YDEF.num];
    end
    if ~exist('x_limits','var')
      x_limits=[1 header.XDEF.num];
    end      
    data=read_var(header,var_name,t_limits,z_limits,y_limits,x_limits);
    header.XDEF.vec=header.XDEF.vec(x_limits(1):x_limits(2));
    header.XDEF.num=length(header.XDEF.vec);
    if header.YDEF.rev,
      y_limits=header.YDEF.num*ones(size(y_limits))-fliplr(y_limits)+1;
    end
    header.YDEF.vec=header.YDEF.vec(y_limits(1):y_limits(2));
    header.YDEF.num=length(header.YDEF.vec);
  end
  
  %    if header.XDEF.rev, header.XDEF.vec=header.XDEF.vec([end:-1:1]); end
  %    if header.YDEF.rev, header.YDEF.vec=header.YDEF.vec([end:-1:1]); end
  %    if header.ZDEF.rev, header.ZDEF.vec=header.ZDEF.vec([end:-1:1]); end
  
else                     % Station data set
  
  data=read_station(header);
  
end

fclose(header.FID{2});

elseif strcmp(suf,'nc') % Enabling NetCDF in read_grads, based on snctools
  old_dir=pwd;
  if isfield(header,'DIR')
    cd(header.DIR)
  end % if
  
  % Reading the header, conforming to GrADS conventions:
  ncheader=nc_info(file_name);
  
  header.FILENAME=ncheader.Filename;
  header.DATANAME=ncheader.Filename;
  header.NVAR=length(ncheader.DataSet);

  fields={{'lon','longitude','x','i'},'XDEF';
          {'lat','latitude','y','j'},'YDEF';
          {'lev','plev','nv','bnds'},'ZDEF';
          {'time','tps','t','l'},'TDEF'};
  for idim=1:size(fields,1)
    for dim=fields{idim,1}
      dim_id=strmatch(dim{1},{ncheader.DataSet.Name},'exact');
      if ~isempty(dim_id), break, end
    end % for
    if isempty(dim_id)
      disp(['Dim ',fields{idim,2},' is missing.'])
      vec=1;
      num=1;
      type='LINEAR';
    else
      num=ncheader.DataSet(dim_id).Size;
      vec=nc_varget(file_name,dim{1});
      if length(unique(diff(vec)))==1
        type='LINEAR';
      else
        type='LEVELS';
      end
      if all(diff(vec)<0)
        rev=1;
        vec=flipud(reshape(vec,[],1));
      elseif all(diff(vec)>0)
        rev=0;
      else
        error(['Unsuitable vector ',dim{1}])
      end
    end % if
    
    DEF=struct('num',num,'vec',vec,'type',type,'rev',rev);
    
    header=setfield(header,fields{idim,2},DEF);
  end % for idim

  for ivar=1:header.NVAR
    header.VARS(ivar).id=ivar;
    header.VARS(ivar).name=ncheader.DataSet(ivar).Name;
    header.VARS(ivar).size=ones(1,4);
    for idim=1:length(ncheader.DataSet(ivar).Dimension)
      [dim_id,j]=ind2sub(size(cat(1,fields{:,1})),...
                         strmatch(ncheader.DataSet(ivar).Dimension(idim),...
                             cat(1,fields{:,1}),'exact'));
      header.VARS(ivar).size(dim_id)=...
          getfield(eval(['header.',fields{dim_id,2}]),'num');
    end % for
    header.VARS(ivar).levs=header.VARS(ivar).size(3);
  end % for

  header.VARSIZE={cat(1,header.VARS.size)};
  
  if nargin==1 | isempty(var_name) | strcmp(var_name,'all')
    data=[];
    if exist('old_dir','var')
      cd(old_dir)
    end
    return
  end
  
  var_id=strmatch(var_name,{ncheader.DataSet.Name});
  if isempty(var_id)
    error(['Variable ',var_name,' is not available in ',file_name,...
           ': ',{ncheader.DataSet.Name}])
  end % if 
  
  for n=1:2:nargin-2
    switch lower(varargin{n})
     case {'x'}
      x_limits=varargin{n+1};
     case {'lon'}
      lon_limits=sort(varargin{n+1});
      x_limits=interp1(header.XDEF.vec,1:header.XDEF.num,...
                       lon_limits,'nearest',NaN);
      if any(isnan(x_limits))
        if ~l_quiet
          disp(['The X-limits ',num2str(lon_limits),...
                ' exceed the data coverage ',...
                num2str(header.XDEF.vec([1 end])')])
        end
        data=[];
        return
      end
     case {'y'}
      y_limits=varargin{n+1};
     case {'lat'}
      lat_limits=sort(varargin{n+1});
      y_limits=interp1(header.YDEF.vec,1:header.YDEF.num,...
                       lat_limits,'nearest',NaN);
      if any(isnan(y_limits))
        if ~l_quiet
          disp(['The Y-limits ',num2str(lat_limits),...
                ' exceed the data coverage ',...
                num2str(header.YDEF.vec([1 end])')])
        end
        data=[];
        return
      end
      if header.YDEF.rev,
        y_limits=header.YDEF.num*ones(size(y_limits))-fliplr(y_limits)+1;
      end
     case {'t','l','time'}
      t_limits=varargin{n+1};
     case {'z','k','lev'}
      z_limits=varargin{n+1};
      ivar=strmatch(var_name,{header.VARS.name},'exact');
      if isempty(ivar)
        if ~l_quiet
          disp(['The variable ',var_name,' cannot be found in ',header.DATANAME])
        end
        data=[];
        return
      end
      if any(z_limits>header.VARS(ivar).levs)
        if ~l_quiet
          disp(['The variable ',var_name,' has only ',...
                header.VARS(ivar).levs,' levels.'])
        end
        data=[];
        return
      end
    end % switch
  end % for
  
  if ~exist('t_limits','var'), t_limits=[1 header.TDEF.num]; end
  if ~exist('z_limits','var')
    ivar=strmatch(var_name,{header.VARS.name},'exact');
    if isempty(ivar)
      if ~l_quiet
        disp(['The variable ',var_name,' cannot be found in ',header.DATANAME])
      end
      data=[];
      return
    end
    z_limits=[1 header.VARS(ivar).levs];
  end % if
  if ~exist('y_limits','var')
    y_limits=[1 header.YDEF.num];
  end
  if ~exist('x_limits','var')
    x_limits=[1 header.XDEF.num];
  end      
  % data=read_var(header,var_name,t_limits,z_limits,y_limits,x_limits);
  % Retrieve the corresponding variable:
  
  if all(header.VARS(var_id).size([3 4])==[1 1])
    data=nc_varget(file_name,var_name,...
                   [y_limits(1),x_limits(1)]-1,...
                   diff(cat(1,y_limits,x_limits),1,2)'+1);
  elseif header.VARS(var_id).size(3)==1
    data=nc_varget(file_name,var_name,...
                   [t_limits(1),y_limits(1),x_limits(1)]-1,...
                   diff(cat(1,t_limits,y_limits,x_limits),1,2)'+1);
  elseif header.VARS(var_id).size(4)==1
    data=nc_varget(file_name,var_name,...
                   [z_limits(1),y_limits(1),x_limits(1)]-1,...
                   diff(cat(1,z_limits,y_limits,x_limits),1,2)'+1);
  else
    data=nc_varget(file_name,var_name,...
                   [t_limits(1),z_limits(1),y_limits(1),x_limits(1)]-1,...
                   diff(cat(1,t_limits,z_limits,y_limits,x_limits),1,2)'+1);
  end
  
  header.XDEF.vec=header.XDEF.vec(x_limits(1):x_limits(2));
  header.XDEF.num=length(header.XDEF.vec);
  if header.YDEF.rev,
    y_limits=header.YDEF.num*ones(size(y_limits))-fliplr(y_limits)+1;
  end 
  if diff(y_limits)<0, error('Unsuitable Y limits'), end
  header.YDEF.vec=header.YDEF.vec(y_limits(1):y_limits(2));
  header.YDEF.num=length(header.YDEF.vec);
  
  header.ZDEF.vec=z_limits(1):z_limits(2);
  header.ZDEF.num=diff(z_limits)+1;
  
  header.TDEF.vec=t_limits(1):t_limits(2);
  header.TDEF.num=diff(t_limits)+1;
  
  if length(size(data))==2
    data=permute(data,[2 1]);
  elseif length(size(data))==3 & header.ZDEF.num==1
    data=permute(data,[3 2 4 1]);
  elseif length(size(data))==3 & header.TDEF.num==1
    data=permute(data,[3 2 1]);
  elseif length(size(data))==4
    data=permute(data,[4 3 2 1]);
  end % if
  
  if header.YDEF.rev
    data=flipdim(data,2);
  end
  
  if exist('old_dir','var')
    cd(old_dir)
  end
else
  disp(['Suffix .',suf,' is not yet enabled.'])
end % if
return

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
function header=read_header(entry,words)
% sets the header values according to what was read in the .ctl-file.
% cf. GrADS documentation : http://grads.iges.org/grads/gadoc/descriptorfile.html

global l_quiet

header=evalin('caller','header');
fid_ctl=header.FID{1};

grid_fields={'num','type','vec','rev'};

switch upper(entry)
 case 'DSET'                    % Name of the bin. data file.
  param=deal(words{1});
  if param(1)=='^' & isfield(header,'DIR')
    param(1)=[];
    param=[header.DIR,param];
  elseif param(1)=='^'
    param(1)=[];
  end
  if exist(param,'file')
    header.DATANAME=param; 
  else
    error(['Non-existent file ',param])
  end
  %
 case 'DTYPE'                  % Gridded or station data-file (not used later)
  param=lower(words{1});
  switch param
   case 'grib'
    header.DTYPE=1;
   case 'station'
    header.DTYPE=0;
  end
  %
 case 'STNMAP'                  % Name of the map file for station data (set in INDEX)
  if header.DTYPE
    warning([header.FILENAME,' cannot specify STNMAP=',param,' for a gridded data set.'])
  end
  param=deal(words{1});
  if param(1)=='^'
    param(1)=[];
  end
  if exist(param,'file')
    header.INDEX=param; 
  else
    error(['Non-existent file ',param])
  end
  %
 case 'INDEX'                  % Name of the GRIB map file (not used later)
  [header.INDEX]=strcat(words{:});
  %
 case 'TITLE'                  % Data title (not used later)
  line={words{:}};
  space=cell(size(line));
  [space{:}]=deal(char(32));
  line=cat(1,line,space);
  line=reshape(line,1,prod(size(line)));   % Rearranging the title to keep spaces between words.
  header.TITLE=deblank(cat(2,line{:}));
  %
 case 'UNDEF'                  % Missing or undefined data value
  param=words{1};
  if isreal(str2num(param))
    header(1).UNDEF=str2num(param);
  else
    error(['Wrong missing value format : ',param])
  end
  %
 case 'OPTIONS'                % Possible options (not all implemented...)
  for param=words
  param=param{1};
  if ~ischar(param), error(['Wrong option format : ',param]), end
  switch lower(param)
   case 'big_endian'
    header.BINTYPE='ieee-be';
   case 'cray_32bit_ieee'
    header.BINTYPE='cray';
   case 'little_endian'
    header.BINTYPE='ieee-le';
   case 'binprecision'
    % Strange syntax to eliminate digits in BINPRECISION
    param=words{2}(find(isnan(str2double(cellstr(upper(words{2})')))));
    if any(strmatch(param,...
		    {'uchar','schar','int','uint','float','single','double',...
		     'real*','integer*','bit','ubit','char','short','ushort', ...
		     'long','ulong'}))
      header.BINPRECISION=words{2};
    end
   case 'xrev'
    header.XDEF.rev=1;
   case 'yrev'
    header.YDEF.rev=1;
   case 'zrev'
    header.ZDEF.rev=1;
   case 'sequential' 
    % Structure of the binary file: ([4 bit][XYHEADER bit][XDEF.num
    % x YDEF.num x BINPRECISION bit][4 bit]) x NVAR
    header.SEQ=1;
   otherwise
    fprintf('Option %s not yet supported.\n',param);
  end
  end % for param=words
  %
% $$$   Matlab     C/Fortran       Description  
% $$$   'uchar'   'unsigned char'  unsigned character,  8 bits.
% $$$   'schar'   'signed char'    signed character,  8 bits.
% $$$   'int8'    'integer*1'      integer, 8 bits.
% $$$   'int16'   'integer*2'      integer, 16 bits.
% $$$   'int32'   'integer*4'      integer, 32 bits.
% $$$   'int64'   'integer*8'      integer, 64 bits.
% $$$   'uint8'   'integer*1'      unsigned integer, 8 bits.
% $$$   'uint16'  'integer*2'      unsigned integer, 16 bits.
% $$$   'uint32'  'integer*4'      unsigned integer, 32 bits.
% $$$   'uint64'  'integer*8'      unsigned integer, 64 bits.
% $$$   'single'  'real*4'         floating point, 32 bits.
% $$$   'float32' 'real*4'         floating point, 32 bits.
% $$$   'double'  'real*8'         floating point, 64 bits.
% $$$   'float64' 'real*8'         floating point, 64 bits.

 case 'XDEF'                   % Description of the X-axis (longitude usually)
  % Checking the number of x-grid coordinates:
  if isreal(str2num(words{1}))
    words{1}=str2num(words{1});
  elseif str2num(words{1})>1
  else
    error(['Wrong number of x-grid arguments : ', ...
					num2str(words{1})])
  end
  
  switch upper(words{2})
   case 'LINEAR'
    vec(1)=str2num(words{3});
    vec(2)=str2num(words{4});
    vec2=vec(1) + vec(2) * [0:(words{1}-1)];
    words{3}=vec2;				% KS, 24.11.2003: coord vector in all cases. 
   case 'LEVELS'
% $$$                 try % all on the same line
% $$$                     for i=1:words{1}
% $$$                         vec(i)=str2num(words{i+2});
% $$$                     end
% $$$ 		    words{3}=vec;
% $$$                 catch % all levels written column-wise
% $$$                     for i=1:words{1}
% $$$                         % vec(i)=fscanf(fid_ctl,'%i',1);
% $$$                         vec(i)=str2num(fgetl(fid_ctl));
% $$$                     end
% $$$ 		    words{3}=vec;
% $$$                 end
%   vec=str2num([words{3:end}]');
%   vec=str2num(cell2mat({words{3:end}}'))';
    vec=str2num(strvcat(words{3:end}));
    
    while length(vec) < words{1}
      point=ftell(fid_ctl);
      read_line=str2num(fgetl(fid_ctl));
      if isempty(read_line)
	fseek(fid_ctl,point,'bof');
	break
      else
	vec=cat(2,vec,read_line);
      end
    end
    if length(vec)==words{1}
      words{3}=vec;
    else
      error(['Unsuitable number of X-levels : ',num2str(length(vec))])
    end
   otherwise
    error(['Unsuitable x-mapping method : ',words{2}])
  end
  
  %	if header(1).XDEF.rev, words{3}=fliplr(words{3}); end
  [header(1).XDEF]=cell2struct({words{1},words{2},words{3},header(1).XDEF.rev},grid_fields,2);
  
  %
 case 'YDEF'                   % Description of the Y-axis (latitude usually)
   % Checking the number of y-grid coordinates:
   if isreal(str2num(words{1}))
     words{1}=str2num(words{1});
   elseif str2num(words{1})>1
   else
     error(['Wrong number of y-grid arguments : ', ...
	    num2str(words{1})])
   end
   
   switch upper(words{2})
    case 'LINEAR'
     vec(1)=str2num(words{3});
     vec(2)=str2num(words{4});
     vec2=vec(1) + vec(2) * [0:(words{1}-1)];
     words{3}=vec2;
    case 'LEVELS'
% $$$                 try % all on the same line
% $$$                     for i=1:words{1}
% $$$                         vec(i)=str2num(words{i+2});
% $$$                     end
% $$$ 		    words{3}=vec;
% $$$                 catch % all levels written column-wise
% $$$                     for i=1:words{1}
% $$$                         % vec(i)=fscanf(fid_ctl,'%i',1);
% $$$                         vec(i)=str2num(fgetl(fid_ctl));
% $$$                     end
% $$$ 		    words{3}=vec;
% $$$                 end
%    vec=str2num([words{3:end}]');
%    vec=str2num(cell2mat({words{3:end}}'))';
     vec=str2num(strvcat(words{3:end}));
     while length(vec) < words{1}
       point=ftell(fid_ctl);
       read_line=str2num(fgetl(fid_ctl));
       if isempty(read_line)
	 fseek(fid_ctl,point,'bof');
	 break
       else
	 vec=cat(2,vec,read_line);
       end
     end
     if length(vec)==words{1}
       words{3}=vec;
     else
       error(['Unsuitable number of Y-levels : ',num2str(length(vec))])
     end
    otherwise
     error(['Unsuitable y-mapping method : ',words{2}])
   end
   
%	if header.YDEF.rev, words{3}=fliplr(words{3}); end
   [header(1).YDEF]=cell2struct({words{1},words{2},words{3},header(1).YDEF.rev},grid_fields,2);
   %
 case 'ZDEF'                   % Description of the Z-axis
    % Checking the number of z-grid coordinates:
    if isreal(str2num(words{1}))
      words{1}=str2num(words{1});
    elseif str2num(words{1})>1
    else
      error(['Wrong number of z-grid arguments : ', ...
	     num2str(words{1})])
    end
    
    switch upper(words{2})
     case 'LINEAR'
      vec(1)=str2num(words{3});
      vec(2)=str2num(words{4});
      vec2=vec(1) + vec(2) * [0:(words{1}-1)];
      words{3}=vec2;
     case 'LEVELS'
% $$$                 try % all on the same line
% $$$                     for i=1:str2num(words{1})
% $$$                         vec(i)=str2num(words{i+2});
% $$$                     end
% $$$ 		    words{3}=vec;
% $$$                 catch % all levels written column-wise
% $$$                     for i=1:words{1}
% $$$                         % vec(i)=fscanf(fid_ctl,'%i',1);
% $$$                         vec(i)=str2num(fgetl(fid_ctl));
% $$$                     end
% $$$ 		    words{3}=vec;
% $$$                 end
%     vec=str2num([words{3:end}]');
%     vec=str2num(cell2mat({words{3:end}}'))';
      vec=str2num(strvcat(words{3:end}));
      while length(vec) < words{1}
	point=ftell(fid_ctl);
	read_line=str2num(fgetl(fid_ctl));
	if isempty(read_line)
	  fseek(fid_ctl,point,'bof');
	  break
	else
	  vec=cat(2,vec,read_line);
	end
      end
      if length(vec)==words{1}
	words{3}=vec;
      else
	error(['Unsuitable number of Z-levels : ',num2str(length(vec))])
      end
     otherwise
      error(['Unsuitable z-mapping method : ',words{2}])
    end
    
    %	if header.ZDEF.rev, words{3}=fliplr(words{3}); end
    [header(1).ZDEF]=cell2struct({words{1},words{2},words{3},header(1).ZDEF.rev},grid_fields,2);
    %
 case 'TDEF'                   % Description of the T-axis
  % Checking the number of t-grid coordinates:
  if isreal(str2num(words{1}))
    words{1}=str2num(words{1});
  elseif str2num(words{1})>1
  else
    error(['Wrong number of t-scale argument : ', ...
	   num2str(words{1})])
  end
  
  if any(upper(words{2})~='LINEAR')
    error(['Unsuitable t-mapping method : ',words{2}])
  end
  
  param=words{3};
  if (param(3)~=':' | param(6)~='Z' | ...
      ~isreal(str2num(param([1:2 4:5 7:8 12:length(param)]))))
    error(['Unsuitable date format : ',param])
  else
    vec(1)=str2num(param(1:2));          % hour
    vec(2)=str2num(param(4:5));          % min
    vec(3)=str2num(param(7:8));          % day
    switch lower(param(9:11))            % month
     case 'jan'
      vec(4)=1;
     case 'feb'
      vec(4)=2;
     case 'mar'
      vec(4)=3;
     case 'apr'
      vec(4)=4;
     case 'may'
      vec(4)=5;
     case 'jun'
      vec(4)=6;
     case 'jul'
      vec(4)=7;
     case 'aug'
      vec(4)=8;
     case 'sep'
      vec(4)=9;
     case 'oct'
      vec(4)=10;
     case 'nov'
      vec(4)=11;
     case 'dec'
      vec(4)=12;
     otherwise
      error(['Wrong month format : ',param(9:11)])
    end
    
    vec(5)=str2num(param(12:length(param)));       % year
    if vec(5)<10
      vec(5)=vec(5)+2000;
    elseif vec(5)<100
      vec(5)=vec(5)+1900;
    end
    
    % The time-axis is written in the matlab-time format
    % (cf. datestr, datevec...):
    init_time=datenum(vec(5),vec(4),vec(3),vec(1),vec(2),0);
    
    param=words{4};
    if isreal(str2num(param(1:end-2)))
      time_increment=str2num(param(1:end-2));
      param(1:end-2)=[];
    else
      error(['Wrong time increment argument : ',param])
    end
    
    switch lower(param)
     case 'mn'
      time_increment=time_increment*datenum(0,0,0,0,1,0);
     case 'hr'
      time_increment=time_increment*datenum(0,0,0,1,0,0);
     case 'dy'       % NB: datenum(0,0,1,0,0,0) = 1.
      time_increment=time_increment;
     case 'mo'       % standardised month
      time_increment=time_increment*365.25/12;
     case 'yr'       % standardised year
      time_increment=time_increment*365.25;
     otherwise
      error(['Wrong time increment argument : ',param])
    end
    
    % Writing the final time axis:
    clear vec
    vec(1)=init_time;
    vec(2)=time_increment;
    
    [header(1).TDEF]=cell2struct({words{1},words{2},vec,0},grid_fields,2);
  end
  %
 case 'VARS'                   % Number and size of variables
  if isreal(str2num(words{1}))
    n_var = str2num(words{1});
  else
    error(['Wrong variable number argument : ',words{1}])
  end
  
  % Reading the variables : 
  
  var_format={'id','name','levs','units','descr'};
  varl={};
  
  for i=1:n_var
    line_var=fgetl(fid_ctl);
    n_blank=length(line_var);
    line_var=strrep(line_var,'  ',' ');
    while length(line_var) < n_blank
      n_blank=length(line_var);
      line_var=strrep(line_var,'  ',' ');
    end
    while isspace(line_var(1)), line_var(1)=[]; end
    var_loc={i,'',NaN,'',''};
    var_hole=min(find(isspace(line_var)));
    var_loc{2}=line_var(1:(var_hole-1));
    line_var(1:var_hole)=[];
    
    var_hole=min(find(isspace(line_var)));
    var_loc{3}=str2num(line_var(1:(var_hole-1)));
  % cf GrADS doc:  If levs is 0, the variable does not correspond
  % to any vertical level
    var_loc{3}=max([var_loc{3},1]);
    line_var(1:var_hole)=[];
    
    var_hole=min(find(isspace(line_var)));
    var_loc{4}=line_var(1:(var_hole-1));
    line_var(1:var_hole)=[];
    
    var_loc{5}=line_var;
    
    varl=cat(1,varl,var_loc);
  end
  
  vars=cell2struct(varl,var_format,2);
  
  if ~l_quiet
    disp(['Is it the end ? ',fgets(fid_ctl)]);
  else
    endvars=deblank(fgets(fid_ctl));
    if ~strcmpi((endvars),'ENDVARS')
      error(['Wrong end of file : ',endvars])
    end
  end
  
  header(1).VARS=vars;
  assignin('caller','vars',vars);
  %
 case 'FILEHEADER'              %  n-byte header of binary data, not to be read
  [header.FILEHEADER]=str2num(words{1});
  %
 case 'THEADER'                 % n-byte header for each T-block, not to be read
  [header.THEADER]=str2num(words{1});
  %
 case 'XYHEADER'                % n-byte header for each XY-block, not to be read
  [header.XYHEADER]=str2num(words{1});
  %
 otherwise
  error(['Unknown entry : ',entry])
end

return

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
function data=read_data(header,var_name)
% data=read_data(header,var_name) is a function, usually called by
% read_grads.m to use the information contained in the header file
% (.ctl) for reading a binary GrADS file.
% If var_name is 'all', all variables are read and sent to the base-
% workspace.  
% Kristof Sturm, 29.10.02

global l_quiet

vars=header.VARS;
nvar=header.NVAR;
var_size=header.VARSIZE{:};

if nargin == 1 | strcmp(lower(var_name),'all')           % All variables loaded into workspace
  data_list={};
  % initialising the data matrices : 
  var_count=zeros(nvar,1);
  for ivar=1:nvar
    eval([vars(ivar).name,'=NaN*ones(',int2str(header.XDEF.num),',',...
	  int2str(header.YDEF.num),',',...
	  int2str(vars(ivar).levs),',',...
	  int2str(header.TDEF.num),');']);
    fprintf('Size of %s : [%s].\n            %s[X   Y   Z    T]\n',...
	    vars(ivar).name,int2str(size(eval(vars(ivar).name))),...
	    char(32*ones(1,length(vars(ivar).name))));
  end
  
  method = 'loop';  % mainly for debugging... 
    
  switch method
   case 'loop'
    frewind(header.FID{2});
    % Implementing the sequential option:
    if isfield(header,'SEQ') && header.SEQ
      fseek(header.FID{2},4,'bof');
    end

    if header.FILEHEADER
      fseek(header.FID{2},header.FILEHEADER,'bof');
% $$$ if isfield(header,'SEQ') && header.SEQ
% $$$ 	fseek(header.FID{2},8,'cof');
% $$$ end
    end
    
    for l=1:header.TDEF.num
      if header.THEADER
	fseek(header.FID{2},header.THEADER,'cof');
% $$$ 	if isfield(header,'SEQ') && header.SEQ
% $$$ 	  fseek(header.FID{2},8,'cof');
% $$$ 	end
      end
      for ivar=1:nvar
	data=eval(vars(ivar).name);
	count0=var_count(ivar);
	for k=1:vars(ivar).levs
	  if header.XYHEADER
	    fseek(header.FID{2},header.XYHEADER,'cof');
	  end
	  [data(:,:,k,l) count1]=fread(header.FID{2},[header.XDEF.num header.YDEF.num],header.BINPRECISION);
	  count0=count0+count1;
	  if isfield(header,'SEQ') && header.SEQ
	    fseek(header.FID{2},8,'cof');
	  end
	end                                   % end k-loop
	data(data==header.UNDEF)=NaN;   % replacing the
                                        % missing/undefined values
                                        % by NaN (not-a-number)
					
%	data(data==header.UNDEF)=0;   % replacing the
                                      % missing/undefined values by
                                      % NaN (not-a-number)
%	data=sparse(data);			      
	eval([vars(ivar).name,'=data;']);
	var_count(ivar)=var_count(ivar)+count0;
	% bytes(ivar)=ftell(header.FID{2});
	clear data
      end                                     % end ivar-loop
    end                                       % end l-loop
    
    % Send the relevent variables into the base workspace:
    %	assignin('base','var_count',var_count);
    %       assignin('base','bytes',bytes);
    for ivar=1:nvar
      data=eval(vars(ivar).name);
      if header.XDEF.rev, data=flipdim(data,1); end
      if header.YDEF.rev, data=flipdim(data,2); end
      if header.ZDEF.rev, data=flipdim(data,3); end
      assignin('base',vars(ivar).name,data);
    end
    %                                             % end 'loop'
   otherwise
    for ivar=1:nvar
      data=read_var(header,vars(ivar).name);
      assignin('base',vars(ivar).name,data);
      data_list=cat(1,data_list,{data});
    end
    data=[];
    assignin('caller','data_list',data_list);
  end
else                                          % Individual var. reading
  switch var_name
   case {vars.name}
    fprintf('Variable %s extracted from %s. \n',var_name, ...
	    header.DATANAME);
    data=read_var(header,var_name);
   otherwise
    error(['Variable ',var_name,' not available in ', ...
	   header.DATANAME]);
  end
end

return

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
function data=read_var(header,ivar,t_limits,z_limits,y_limits,x_limits)
% data=read_varseq(header,ivar) reads in 'ivar' from
% 'header.DATANAME' and return it as a 4-D matlab matrix. 
% 'ivar' can be the variable''s short name or its number id.

global l_quiet

vars=header.VARS;
nvar=header.NVAR;
var_size=header.VARSIZE{:};

% different behaviour of the f* functions according to the chosen data precision :
switch lower(header.BINPRECISION)
 case {'uchar','schar','int8','uint8'}
  byte_length=1;
 case {'int16','uint16'}
  byte_length=2;  
 case {'float32','single','int32','uint32'}
  byte_length=4;
 case {'float64','double','int64','uint64'}
  byte_length=8;
 otherwise
  byte_length=1;
end

if isempty(ivar)
  if ~l_quiet
    disp(['Header for ',header.FILENAME,' read.'])
  end
  data=[];
  return
end

if ischar(ivar)
  var_name=ivar;
  ivar=strmatch(ivar,{vars.name},'exact');
  if isempty(ivar)
    if ~l_quiet
      disp(['The variable ',var_name,' cannot be found in ',header.DATANAME])
    end
    data=[];
    return
  end
  clear var_name
end

if ivar==1
  byte_ini=0;
else
  byte_ini = sum(prod(var_size(1:(ivar-1),1:3),2),1) ;
end

if isfield(header,'SEQ') && header.SEQ
  byte_ini=byte_ini+(sum(var_size(1:ivar-1,3))*8+4)/byte_length;
end

if header.FILEHEADER
  byte_ini = byte_ini + header.FILEHEADER/byte_length;
% $$$   if isfield(header,'SEQ') && header.SEQ
% $$$     byte_ini=byte_ini+8/byte_length;
% $$$   end
end

if header.THEADER
  byte_ini = byte_ini + header.THEADER/byte_length;
% $$$   if isfield(header,'SEQ') && header.SEQ
% $$$     byte_ini=byte_ini+8/byte_length;
% $$$   end
end

if header.XYHEADER
  byte_ini=byte_ini+(sum(var_size(1:ivar-1,3))+1)*header.XYHEADER/byte_length;
% $$$   if isfield(header,'SEQ') && header.SEQ
% $$$     byte_ini=byte_ini+(sum(var_size(1:ivar-1,3))+1)*8/byte_length;
% $$$   end
end

block_length = sum(prod(var_size(:,1:3),2),1);

if isfield(header,'SEQ') && header.SEQ
 block_length=block_length+sum(var_size(:,3))*8/byte_length;
end

if header.XYHEADER
  block_length=block_length+sum(var_size(:,3))*header.XYHEADER/byte_length;
end

if header.THEADER
  block_length=block_length+header.THEADER/byte_length;
end

slice_length = header.XDEF.num*header.YDEF.num+header.XYHEADER/byte_length;

if isfield(header,'SEQ') && header.SEQ
  slice_length = slice_length + 8/byte_length ;
end

var_length = prod([header.XDEF.num header.YDEF.num diff(z_limits)+1]);

if header.XYHEADER
  var_length = var_length + (diff(z_limits)+1)*header.XYHEADER/byte_length;
end

if isfield(header,'SEQ') && header.SEQ
  var_length=var_length+(diff(z_limits)+1)*8/byte_length;
end

% disp(byte_ini)
% Selecting the starting time slice :
byte_ini=byte_ini + (t_limits(1)-1)*block_length + (z_limits(1)-1)*slice_length ;

err_stat = fseek(header.FID{2},byte_ini*byte_length,'bof');
if err_stat == -1, ferror(header.FID{2}), end

% data=NaN(diff(x_limits)+1,diff(y_limits)+1,diff(z_limits)+1,diff(t_limits)+1);
data=NaN*ones(diff(x_limits)+1,diff(y_limits)+1,diff(z_limits)+1,diff(t_limits)+1);

%   size(data)
count_byte = 0;
count_byte2 = 0;
for l=1:(t_limits(2)-t_limits(1)+1)
  file_ind = ftell(header.FID{2});
  for k=1:(z_limits(2)-z_limits(1)+1)
    if all([x_limits y_limits]==[1 header.XDEF.num 1 header.YDEF.num])
    try
      [data(:,:,k,l),n]=fread(header.FID{2},[header.XDEF.num header.YDEF.num],header.BINPRECISION);
      count_byte=count_byte+n;
    % TODO replace by log
    catch
      warning(['Data read-in interrupted at l=',int2str(l),' and k=',int2str(k)])
      break
    end
    else
    % Advance to the j-th row:
    fseek(header.FID{2},(y_limits(1)-1)*header.XDEF.num*byte_length,'cof');
    for j=1:(diff(y_limits)+1)
      fseek(header.FID{2},(x_limits(1)-1)*byte_length,'cof');
      [data(:,j,k,l),n]=fread(header.FID{2},diff(x_limits)+1,header.BINPRECISION);
      fseek(header.FID{2},(header.XDEF.num-x_limits(2))*byte_length,'cof');
    end
    fseek(header.FID{2},(header.YDEF.num-y_limits(2))*header.XDEF.num*byte_length,'cof');
    end
    if isfield(header,'SEQ') && header.SEQ
      fseek(header.FID{2},8,'cof');
    end
    if header.XYHEADER
      fseek(header.FID{2},header.XYHEADER,'cof');
    end
  end
  count_byte2=count_byte2+ftell(header.FID{2})-file_ind;
  if l < header.TDEF.num
    err_stat=fseek(header.FID{2},(block_length-var_length)*byte_length,'cof');
    if err_stat == -1, ferror(header.FID{2}), end
  end
end

% disp([byte_ini block_length slice_length var_length file_ind/byte_length ftell(header.FID{2})/byte_length])

data(find(data==header.UNDEF))=NaN;   % replacing the missing/undefined values by NaN (not-a-number)

if header.XDEF.rev, data=flipdim(data,1); end
if header.YDEF.rev, data=flipdim(data,2); end
if header.ZDEF.rev, data=flipdim(data,3); end

%if count_byte ~= count_byte2/byte_length
%  warning(['Number of elements read : ',num2str(count_byte),...
%	   ' .NEQ. number of bytes advanced in ',header.DATANAME,': ',num2str(count_byte2/byte_length)])
%end

%fprintf('%i bytes were read in %s .\n size(%s) = [%s], i.e. %i elements.\n',...
%        count_byte2,header.DATANAME,vars(ivar).name,int2str(size(data)),count_byte);
%	count_byte2,header.DATANAME,vars(ivar).name,int2str(size(data)),prod(size(data)));

return

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
function data=read_station(header)
% This function uses the information read from the GrADS .ctl file
% to read STATION data into the matlab workspace. 
% No reference to the .map file is currently made.
% cf. http://grads.iges.org/grads/gradoc/aboutstationdata.html
% Kristof Sturm, 30.X.02

global l_quiet

fid_bin = header.FID{2};
fseek(fid_bin,0,'eof');
eof=ftell(fid_bin);
frewind(fid_bin);
file_end=0;
istat=1;

data_format={'id','name','lat','lon','var'};
data_tot={};
% Reading the header :

while 1
  
  data_loc={0,'',0,0,[]};
  
  boh=ftell(fid_bin);
  
  stid=fscanf(fid_bin,'%8c',1);
  lat=fread(fid_bin,1,'float32');
  lon=fread(fid_bin,1,'float32');
  tim=fread(fid_bin,1,'float32');
  nlev=fread(fid_bin,1,'int32');
  nflag=fread(fid_bin,1,'int32');
  
  eoh=ftell(fid_bin);
  length_h=eoh-boh;
  
  var=NaN(header.NVAR,header.TDEF.num);
  
  % Reading all variables, skipping the headers :
  for t=1:header.TDEF.num
    
    var(:,t)=fread(fid_bin,header.NVAR,'float32');
    
    if ftell(fid_bin) < (eof-2*length_h)
      file_err=fseek(fid_bin,2*length_h,'cof');
      if file_err==-1, ferror(fid_bin), end
    else
      fprintf('EOF reached for %s.\n\n',header.DATANAME);
      file_end=1;
      break
    end
    var(find(var==header.UNDEF))=NaN;
  end
  
  if file_end, break, end
  
  % Rewinding for reading the first of the next headers :
  file_err=fseek(fid_bin,-length_h,'cof');
  if file_err==-1, ferror(fid_bin), end
  
  % saving all relevant informations into the local cell :
  [data_loc]={istat,deblank(lower(stid)),lat,lon,var};
  data_tot=cat(1,data_tot,data_loc);
  istat=istat+1;
end

data=cell2struct(data_tot,data_format,2);

return
  