pro saveas_upper_flatfile,loadedData=loadedData,field_names=field_names,filename=filename,statuscode=statuscode,statusmsg=statusmsg,timeflag=timeflag,timerange=timerange

;print,field_names,format='("field_names: ",A)'
;print,filename,format='("filename: ",A)'

tgt_dirname=file_dirname(filename)

fi=file_info(tgt_dirname)
if ~fi.exists then begin
   statusmsg=string(tgt_dirname,format='("saveas_upper_flatfile: Save failed. Directory ",A," does not exist.")')
   statuscode=-1
   return
endif else if ~fi.write then begin
   statusmsg=string(tgt_dirname,format='("saveas_upper_flatfile: Save failed. Directory ",A," is not writeable by you.")')
   statuscode=-1
   return
endif

tgt_basename=file_basename(filename)
extension_pos=strpos(tgt_basename,'.',/reverse_search)
if (extension_pos GT 0) THEN BEGIN
   base_no_extension=strmid(tgt_basename,0,extension_pos)
ENDIF ELSE BEGIN
   base_no_extension=tgt_basename
ENDELSE

stripped_path=tgt_dirname+path_sep()+base_no_extension
; Make filenames for the DAT, DES, ABS, and HED components
dat_filename=stripped_path+'.dat'
des_filename=stripped_path+'.des'
abs_filename=stripped_path+'.abs'
hed_filename=stripped_path+'.hed'

; This file format only supports pathnames up to 44 characters long.

; We'll omit saving the full pathname to save space.  This is a  slight 
; abuse of the format -- the UCLA documentation clearly states that this field 
; in the .hed file denotes a "path", but a 44-character restriction
; on directory+filename is ludicrous, and nearly unusable.

short_filename=base_no_extension+'.dat'

if (strlen(short_filename) GT 44) then begin
   statuscode=-1
   statusmsg=string(short_filename,format='("saveas_upper_flatfile: Save failed. Output filename ",A,"exceeds 44 characters.")')
   return
endif

; Check that all filenames are writable

filearr=[dat_filename,des_filename,abs_filename,hed_filename]
fileinfo=file_info(filearr)
nowrite=where((fileinfo.exists EQ 1) AND (fileinfo.write EQ 0))
already_exists=where(fileinfo.exists EQ 1)

if (nowrite[0] NE -1) then begin
   statusmsg=['saveas_upper_flatfile: Save failed. The following files are not writable',filearr[nowrite]]
   statuscode=-1
   return
endif

if (already_exists[0] NE -1) then begin
   statusmsg=['saveas_upper_flatfile: The following files already exist. Do you wish to overwrite them?',filearr[already_exists]]
   answer=dialog_message(statusmsg,/question,/default_no)
   if (answer NE 'Yes') then begin
      statusmsg='saveas_upper_flatfile: Save cancelled by user.'
      statuscode=-1
      return
   endif
endif


varcount=n_elements(field_names)
dataptrs=ptrarr(varcount)

typecode_names=['undefined', 'byte', 'int', 'long','float', 'double', $
         'complex','string','struct','dcomplex','pointer','objref','uint',$
         'ulong','long64','ulong64'] 

descriptors=replicate({longname:'', shortname:'', units:'', typecode:'', timeflag:0, sort:'0'},varcount)
; check validity, make data pointers
for i=0,varcount-1 do begin

    ; Get the data object from loadedData
    
    groupObj=loadedData->getGroup(field_names[i])

    ; groupObj is guaranteed to be either 0, or a 1-element array of valid
    ; objects

    if obj_valid(groupObj[0])  then begin
       ; look for a trace within this group
       dataObj=groupObj[0]->getObject(field_names[i])
        if ~obj_valid(dataObj) then begin
           statusmsg='saveas_upper_flatfile: No data object found for field name '+field_names[i]
           statuscode=-1
           if (timeflag) then ptr_free,dataptrs
           return
        endif
    endif else begin
       ; no group found by that name, try getting the object directly
       objects=loadedData->getObjects(name=field_names[i])
       if (n_elements(objects) GT 1) then begin
           statusmsg='saveas_upper_flatfile: Multiple data objects found for field name '+field_names[i]
           statuscode=-1
           if (timeflag) then ptr_free,dataptrs
           return
       endif else if ~obj_valid(objects[0]) then begin
           statusmsg='saveas_upper_flatfile: No data object found for field name '+field_names[i]
           statuscode=-1
           if (timeflag) then ptr_free,dataptrs
           return
       endif
       dataObj=objects[0]
    endelse

    ; Process this dataObj
    ; Get data pointer and IsTime flag from dataObj

    dataObj->GetProperty,isTime=isTime,dataPtr=dataPtr,units=units,timeName=timeName

    ; If start/stop times are specified, make a copy of the data array
    ; restricted to the time range.

    if (timeflag EQ 1) then begin
       if (isTime EQ 1) then begin
          ; Case 1: this is a time variable, so test the values directly.
         tr_arr=*dataPtr
         tr_ind=where( (tr_arr GE timerange[0]) AND (tr_arr LE timerange[1]),tr_count)
       endif else begin
          ; Case 2: not a time variable, so get the corresponding array
          ;   of times, test those, then apply those indices to the data
          ;   array.
          time_objs=loadedData->GetObjects(name=timeName)
          time_objs[0]->GetProperty,dataPtr=tr_dataPtr
          tr_arr=*tr_dataPtr
          tr_ind=where( (tr_arr GE timerange[0]) AND (tr_arr LE timerange[1]),tr_count)
       endelse

       if (tr_count EQ 0) then begin
          statusmsg='saveas_upper_flatfile: No samples in specified time range for variable '+field_names[i]
          statusmsg=-1
          if (timeflag) then ptr_free,dataptrs
       endif

       dataPtr=ptr_new((*dataPtr)[tr_ind])  ; This needs to be cleaned up before exit to prevent a serious memory leak
    endif

    if (strlen(units) EQ 0) then units='unknown'

    ; Reject multi-dimensional or unequally sized arrays

    ndims=size(*dataPtr,/n_dimensions)
    nelems=n_elements(*dataPtr)
;    print,field_names[i],'   ndims: ',ndims,'   nelems: ',nelems
    if (ndims NE 1) then begin
       statusmsg=STRING(field_names[i],format='("saveas_upper_flatfile: multidimensional data not supported for field named ",A)')
       statuscode=-1
       if (timeflag) then ptr_free,dataptrs
       return
    endif
    if (i EQ 0) then begin
       first_elems=nelems
    endif else if (nelems NE first_elems) then begin
       statusmsg=STRING(field_names[i],format='("saveas_upper_flatfile: variables must have identical sample counts, invalid field ",A)')
       statuscode=-1
       if (timeflag) then ptr_free,dataptrs
       return
    endif

    if (isTime) then begin
      descriptors[i].timeflag=1
    endif
 
    descriptors[i].longname=field_names[i]
    descriptors[i].shortname=string(i,format='("c",I-2)') 
    descriptors[i].units=units
    data_value=(*dataPtr)[0]
    type_code=size(data_value,/type)
    case type_code of
       ; 4 byte integer
       3: begin
            descriptors[i].typecode='I4  '
          end
       ; Single precision floating point
       4: begin
            descriptors[i].typecode='R4  '
          end
       ; Double precision floating point
       5: begin
            descriptors[i].typecode='D8  '
          end
       ; All other types are unsupported for this format
       else: begin
               statusmsg='Unsupported datatype ('+typecode_names[type_code]+') for field '+field_names[i]
               statuscode=-1
               if (timeflag) then ptr_free,dataptrs
               return
             end
    endcase
    
    ; Keep copy of data pointer
    dataptrs[i]=dataPtr
endfor

; Write DES (field descriptions) file
openw,lun,des_filename,/get_lun
for i=0,varcount-1 do begin
printf,lun,descriptors[i].longname,descriptors[i].shortname,descriptors[i].units,$
   descriptors[i].typecode,descriptors[i].sort,$
   format='(A-32,A-8,A-16,A-4,A-2)'
endfor
free_lun,lun

; Write empty ABS file
openw,lun,abs_filename,/get_lun
printf,lun,''
free_lun,lun

; Write HED file
owner=getenv('USER')
if (strlen(owner) EQ 0) then owner=getenv('LOGNAME')
if (strlen(owner) EQ 0) then owner='unknown_userid'

monthnames=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
daynames=['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
ts=time_struct(systime(/sec))
mon=monthnames[ts.month-1]
day=daynames[ts.dow]
datestr=day+' '+mon+' '+string(ts.date,format='(I02)')+ $
   ' '+string(ts.hour,ts.min,ts.sec,format='(I02,":",I02,":",I02)') + $
   ' '+string(ts.year,format='(I04)')
columns=varcount
rows=nelems
openw,lun,hed_filename,/get_lun
printf,lun,short_filename,datestr,owner,columns,rows,$
     format='(A-44,A-30,A-20,I3,I5)'
free_lun,lun

; Write binary DAT file

; This is not stated in the documentation, but the file format is
; supposed to be big-endian.

openw,lun,dat_filename,/get_lun,/swap_if_little_endian

for i=0L,nelems-1 do begin
   for j=0L,varcount-1 do begin
     data_value=(*(dataptrs[j]))[i]
     writeu,lun,data_value
   endfor
endfor

; Cleanup and return success
free_lun,lun
statuscode=0
statusmsg=STRING(filename,format='("Data successfully saved to ",A)')
if (timeflag) then ptr_free,dataptrs
return
end