function [d, errmsg] = read(instclass, file_or_struct, varargin)
% [data, errmsg] = read(instclass, file_or_struct, varargin)
% instclass: bb
%
% file_or_struct:  
%     file:  typical usage for a one-time call to look at the data
%             file is the relative path to full raw file name, eg:
%                   '../raw/adcp_pings/9602078r.019'
%     struct: specialized usage for repeated calls (as in "load_enr")
%             uses info from a previous read OF THE SAME FILE 
%             (read the top of this file for more details)
% 
% assume varargin is a collection of name, values pairs
% 'vars', {'vel', 'cor', 'amp', 'ts', 'bt', 'pg', 'nav', 'all'}
% i.e. aquisition of single data types  in a cellarray called 'vars'
%     include any or all in the cellarray to turn them on
% -- or -- if there is only one variable, it can be a string 
% 'vars', 'ts' %(gets soundsped, temperature, roll, pitch, heading, xducerdepth)
% -- or --  use the string 'all' to get all variables possible
% 'vars', 'all'
%% 
% 'yearbase', [yearbase]           % optional: acquisition year  (defaults to first year of file)
% 'proc_yearbase', [yearbase]           % optional: specify date relative to this
%
%  'angle'    [beam angle]           % beam angle from vertical (unnecessary:
%                                    %      only here for compatibility)
%                                  % different ways to specify data extracted:
% 'irange',  [istart istop]        % range of indices to use
% 'ilist', [ index list]           % explicit index list overrides step
% 'step', [step]                   % use only every "step" index
% 'ends', [choose from 0,1,2,4]    % 0 (default - don't do it)
%                                  % 1: get only first profile
%                                  % 2: get first and last profile
%                                  % 3: not implimented
%                                  % 4: get first and last pair of profiles
%                                  % NOTE: use of 'ends' overrides range, step,
%                                  %    and ens_list
% 'back_to_front' [0 or 1]         %   find last good index by reading file
%                                      from back to front (default)
%                                      (otherwise back_to_front = 0 starts
%                                      at the beginning of the file -
%                                      slower but more reliable)
%
%
% 'verbose' [0,1]                  %default is 0; add verbosity
% 'quiet'   [0,1]                  %for consistency with os/read.m (does nothing)
%
%  NOTE on ranges: default is 1:end
%       You may only use one of the following choices.  
%       -  ilist
%       -  irange (step is optional, defaults to 1) - overrides ilist
%       -  ends  - overrides ilist and irange
% 
%  typical one-time usage:
%  rawfile =  '../raw/adcp_pings/nbp2001_091_52596.raw';
%  data = read(bb, rawfile, 'vars', 'amp');
%  data = read(bb, rawfile, 'vars', {'vel', 'ts'});
%  data = read(bb, rawfile, 'yearbase', 2001, 'vars', 'all');
%
%  useful restructuring call:
%  [dd, config] = restruct(bb, data);


%%% ADITIONAL NOTES %%%
%  NOTE a structure called "info" in the outgoing data structure contains
%         - info.fullrawfile        %the whole path to the file
%         - info.rawfilebase        %the base of the file read 
%         - info.ilist              %the index list used to get the data
%                                   % NOTE: must use name, value pair with
%                                   % name 'ilist' to choose the values to
%                                   % be read
%         - info.nbytes             % 
%         - info.config             %
%         - info.last_index         % 
%
%  NOTE file_or_struct - usage with a structure
%          previous call should explicitly output additional info:
%             "d = read(instclass, file_or_strut, 'return_fid', 1, varargin)
%          then call as either
%             "d = read(instclass, d.info , 'return_fid', 1, varargin)
%                                   % does not close raw file upon return
%             "d = read(instclass, d.info , varargin) 
%                                   % closes file upon return
%
%  NOTE ens_num is the raw ensemble value contained in the data.  There could
%       potentially be gaps in ens_num, but it should always increase (with 
%       the single exception of a wrap which occurs at 2^16)
%


% 00/01/31 EF
% 00/02/22 JH (bb from read_os)
% 00/07/05 JH (fixed ipp bug (here and in read_os))  
%  *** NOTE: fix was insufficient - check read_os and fix the fix
% 01/06/02 JH  add instrument class: modify  to match bb/read.m and 
%        nb/read.m since that is the most recent 
% 02/05/23 JH bug fix: now not overwriting config fields:
%                   soundspeed, bin1distance, tr_pressure


%% undocumented features:
%% 'vars' defaults to none, turn on pieces or 'all' exlicitly
%% range defaults to all; choose specific subsets explicitly

% should make it so all cfg fields are initialized - then use fillstruct(x,x,0)
%    to help with debugging (i.e. fail if a new field is introduced)


errmsg = [];

if (nargin < 2)
   help read 
   error('must set instclass and file_or_struct')
end

id_arg = 0;               % (instclass = os ONLY)
                          % Call with 1 instead of zero to get NB if both NB
                          % and BB are being collected.


%set defaults.  all except field "vars" are present in the original
%  cfg structure; fillstruct will update the values listed
%  Field "vars" will be initialized by fillstruct.  set those chosen to 1
cfg.yearbase  = [];  %default: use first year in the file
cfg.proc_yearbase  = [];  %default: cfg.yearbase
cfg.verbose     = 0;
cfg.quiet       = 0;
cfg.step        = 1;
cfg.irange    = []; % default all range specifications to [] (see function
cfg.ilist     = []; % default all range specifications to [] test_range_request
cfg.back_to_front = 1;  %find last_index by reading back to front
cfg.ends      = []; % default all range specifications to []
cfg.beamangle     = NaN; % beam angle
cfg.second_set = 0; % for consistency in calls for different instclasses
cfg.vars =      [];% default variables to []
vars            = {'vel', 'cor', 'amp', 'ts', 'bt', 'pg', 'nav', 'all'};
out.nav         = 0; %initialized "out" as a nonempty structure 


% structure "out" fields from "vars" cellarray initialized as zero
for cii = 1:length(vars)
   out = setfield(out, vars{cii}, 0); 
end


% update cfg.
if (nargin > 2)%   we'll assume the rest is a set of name,value pairs
               %override input structure with name,value pairs
   cfg = fillstruct(cfg, varargin,0 );
end

if isempty(cfg.proc_yearbase)
    cfg.proc_yearbase = cfg.yearbase;
end


%now check whether cellarray 'vars' has been added - use the elements of
% cfg.vars (if it exists) to turn on aquisition of the listed fields
% NOTE: cellarray "vars" does not have "nav", whereas cfg.vars could.
if (isfield(cfg, 'vars'))
   if (~isemptys(cfg.vars))
      if (ischar(cfg.vars))  %if there is only variable chosen, allow a string
         cfg.vars = {cfg.vars};
      end
      
      for cii = 1:length(cfg.vars)
         out = setfield(out, cfg.vars{cii}, 1); 
      end
      
      % out.all (override individual chosen data types and output everything)
      if ismember({'all'}, cfg.vars)
         %they turned it on so get everything
         for cii = 1:length(vars)
            out = setfield(out, vars{cii}, 1);
         end
      end
      out = rmfield(out, 'all');  %now remove: there shouldn't be an out.all
   end
end


id = instrument_ids(instclass, id_arg);
params.id = id;
params.out = out;

%had to wait for this to get params initialized
if (ischar(file_or_struct))
   file = file_or_struct;
   if (~exist(file, 'file'))
      error(sprintf('file %s does not exist\n', file))
   end
elseif (isstruct(file_or_struct))
   finfo = file_or_struct;
   file       = finfo.fullrawfile;
   nbytes     = finfo.nbytes;
   config     = finfo.config;
   last_index = finfo.last_index;
else
   error('second argument was not an existing file or appropriate structure')
end


%open and close in here
params.yearbase = cfg.yearbase; %but it might be empty
params.proc_yearbase = cfg.proc_yearbase; %but it might be empty
if (~exist('config', 'var')) % file_or_struct was a file
   [nbytes, last_index, config] = get_config(instclass, params, cfg, file);
end

if (nbytes==0) % error in read: config has string
   d=[];
   errmsg = config;
   return   %jump out now.
end

if isempty(config.proc_yearbase)
    config.proc_yearbase = config.yearbase;
end

ilist = get_ilist(cfg, last_index);

if (isemptys(ilist))
   d=[];
   errmsg = '"ilist" failed;'
   return
end

%now initialize everything

NP = length(ilist);  %%1 + floor((stop - start) / step);
NC = config.ncells;
NB = config.nbeams_used;


if out.vel, d.vel = zeros(NC, NB, NP); end
if out.cor, d.cor = zeros(NC, NB, NP); end
if out.amp, d.amp = zeros(NC, NB, NP); end
if out.pg,  d.pg  = zeros(NC, NB, NP); end
if out.ts,
   d.heading = zeros(1, NP);
   d.pitch =      d.heading;
   d.roll =       d.heading;
   d.soundspeed = d.heading;
   d.temperature =d.heading;
   d.tr_pressure =d.heading;
end
if out.bt,
   d.bt.vel = zeros(NB, NP);
   d.bt.range = d.bt.vel;
   d.bt.cor =   d.bt.vel;
   d.bt.amp =   d.bt.vel;
   d.bt.pg =    d.bt.vel;
end
if out.nav,
   d.nav.sec_pc_minus_utc = zeros(1, NP);
   d.nav.txy1 = zeros(3, NP);
   d.nav.txy2 = d.nav.txy1;
end

% Always include dday and ens_num.
d.dday = zeros(1, NP);
d.ens_num = zeros(1, NP);
d.num_pings = zeros(1, NP);
d.config = config;
if ~isnan(cfg.beamangle)
   d.config.beamangle = cfg.beamangle;
end

d.depth =  [d.config.bin1distance + (0:(NC-1)) * d.config.cell]';


% now loop through ilist
fid = fopen(file, 'r', 'ieee-le');
for ii = 1:length(ilist)
   if (cfg.verbose) ii, end
   
   fseek(fid, nbytes * (ilist(ii) - 1), 'bof');
   [buf, config] = read_buf(instclass, fid, params, config);
   
   if ischar(buf),
      errmsg = buf;
      d.error = buf;
      break
   end
   d.dday(ii) = buf.dday;
   d.ens_num(ii) = buf.ens_num;
   d.num_pings(ii) = buf.num_pings;
   if out.ts,
      d.heading(ii) = buf.heading;
      d.pitch(ii)   = buf.pitch;
      d.roll(ii)    = buf.roll;
      d.soundspeed(ii)  = buf.soundspeed;
      d.temperature(ii) = buf.temperature;
      d.tr_pressure(ii) = buf.tr_pressure;
   end
   if out.vel, d.vel(:, :, ii) = buf.vel; end
   if out.cor, d.cor(:, :, ii) = buf.cor; end
   if out.amp, d.amp(:, :, ii) = buf.amp; end
   if out.pg,  d.pg(:, :, ii) = buf.pg; end
   if out.bt & d.config.bt_was_on
      d.bt.vel(:, ii) = buf.bt.vel;
      d.bt.range(:, ii) = buf.bt.range;
      d.bt.cor(:, ii) = buf.bt.cor;
      d.bt.amp(:, ii) = buf.bt.amp;
      d.bt.pg(:, ii) = buf.bt.pg;
   end
   if out.nav,
      d.nav.sec_pc_minus_utc(ii) = buf.nav.sec_pc_minus_utc;
      d.nav.txy1(:,ii) = buf.nav.txy1;
      d.nav.txy2(:,ii) = buf.nav.txy2;
   end
end

if out.ts,
   d.heading = 0.01 * d.heading;
   d.pitch = 0.01 * to_signed(d.pitch);
   d.roll = 0.01 * to_signed(d.roll);
   d.temperature = 0.01 * to_signed(d.temperature);
end

if out.vel, d.vel = 0.001 * to_signed(d.vel); end

if out.bt & d.config.bt_was_on
   d.bt.vel = 0.001 * to_signed(d.bt.vel);
   d.bt.range = 0.01 * d.bt.range;  % BAD is 0; leave it.
end

%----------------------------------------
%%%% add things which will make our lives easier later, or perhaps not.


%take out things which are not useful:
d.config = rmfield(d.config, 'offsets');
d.config = rmfield(d.config, 'ids');
d.config = rmfield(d.config, 'vel_multiplier');
d.config = rmfield(d.config, 'ndata');
if out.nav %remove nav structure if if was requested but did not exist
   allnav = [d.nav.sec_pc_minus_utc(:); d.nav.txy1(:); d.nav.txy2(:)];
   if isemptys(find(~isnan(allnav)))
      d = rmfield(d, 'nav');
   end
end


%augment d with some useful information
if (isstr(file_or_struct))
   [fpath, rawfilebase, ext] = fileparts(file);
   d.info.rawfilebase = rawfilebase;
   d.info.fullrawfile = file;
else
   d.info.rawfilebase = finfo.rawfilebase;
   d.info.rawfilebase = finfo.fullrawfile;
end

d.info.ilist = ilist;
d.info.nbytes =     nbytes;
d.info.last_index = last_index;
d.info.config =     config;


if cfg.yearbase ~= cfg.proc_yearbase
    newdday = change_yrbase(newdday, cfg.yearbase, cfg.proc_yearbase);
    d.dday = newdday(:)';
end


fclose(fid);
d.error = [];

