function simpleSave( fn, varargin )
%SIMPLESAVE save variables in the simpleSave format
%   simpleSave( filename, vars... )
%
% INPUT:
%   filename -- string -- file path for the file to write, without the
%       suffixes .sss, .ssd
%   vars -- names of variables to save
%
% The variables are saves as fields of the a top-level simpleSave REC
% Data is stored in two files -- .sss and .ssd containing structure and
% binary data. 
%
% The following option flags are supported:
%   '-CTX' -- emit context debug comments in the .ssd file. 
%
% SimpleSave data can contain arrays of arbitrary numeric types,
%   struct arrays, cell arrays and char arrays. Function handles and inline
%   functions are converted to a string representation.
% $Revision: 1.2 $
% By Shai Revzen, Berkeley 2008
    global identity_foo
    identity_foo = 1;
    
    ctx.plan=cell(0,2);
    ctx.msg = '%% CTX(';
    ctx.stack = length(ctx.msg);
    ctx.ctxMsg = false;
    arg={};
    % Collect arguments while parsing any options
    for k=1:length(varargin)
        if varargin{k}(1)=='-'
            switch varargin{k}(2:end);
                case 'CTX';
                    ctx.ctxMsg = true;
                otherwise
                    error('simpleSave:unknownOption',['Unknown option "',varargin{k},'"']);
            end
        else
            arg{end+1} = varargin{k};
        end
    end
	if isempty(arg)
        error('simpleSave:empty','must give list of things to save');
	end	
    data = struct;
	for k=1:length(arg)
        data = setfield( data, arg{k}, evalin('caller',arg{k}) );
	end
	try;
        ctx.f_dcr=fopen([fn '.sss'],'w');
        ctx.f_raw=fopen([fn '.ssd'],'w');
	catch IOE;
        IOE
        fclose(ctx.f_dcr);
        fclose(ctx.f_raw);
        return;
	end
    ctx = simpleSaveStruct( ctx, data );
    try;
        emitData(ctx);
    catch IOE;
        IOE
        warning('simpleSave:corrupt','Error writing -- files may be corrupt');
    end
    fclose(ctx.f_dcr);
    fclose(ctx.f_raw);
return

function ctx = emitData( ctx )
  fprintf(ctx.f_dcr,'DECL_ENDS(1);\n\n');  
  for k=1:size(ctx.plan,1)
      fwrite(ctx.f_raw,ctx.plan{k,1},ctx.plan{k,2});
  end
return

function ctx = planEmit( ctx, dat, fmt )
  ctx.plan{end+1,1} = dat;
  ctx.plan{end,2} = fmt;
return

function ctx = pushCtx( ctx, nm )
  ctx.stack(end+1) = length(ctx.msg);
  ctx.msg = [ctx.msg, nm,','];
return

function ctx = popCtx( ctx )
  ctx.msg = ctx.msg(1:ctx.stack(end));
  ctx.stack = ctx.stack(1:end-1);
return

function ctx = showCtx( ctx, fmt, varargin )
  if ctx.ctxMsg>0
      fprintf(ctx.f_dcr, [ctx.msg(1:end-1),'); ',fmt], varargin{:} );
  end
return

function ctx = emitShape( ctx, sz )
  fprintf(ctx.f_dcr,'SZ(%d',sz(1));
  fprintf(ctx.f_dcr,',%d',sz(2:end));
  fprintf(ctx.f_dcr,')');
return

function ctx = emitName( ctx, nm )
  if isempty(nm)
      fprintf(ctx.f_dcr,'NAME('''')');
  else
      fprintf(ctx.f_dcr,'NAME(''%s'')',nm);
  end
return

function ctx = simpleSaveAny( ctx, dat )
  if ischar(dat)
      ctx = simpleSaveChar(ctx,dat);
  elseif isnumeric(dat)
      ctx = simpleSaveArray(ctx,dat);
  elseif isstruct(dat) 
      ctx = simpleSaveStruct(ctx,dat);
  elseif iscell(dat)
      ctx = simpleSaveCell(ctx,dat);
  elseif islogical(dat)
      ctx = simpleSaveBool(ctx,dat);
  else switch class(dat);
      case 'function_handle';
          ctx = simpleSaveFunction(ctx,dat);
      case 'inline';
          ctx = simpleSaveInline(ctx,dat);          
      otherwise;
          error('simpleSave:unknown',sprintf('Cannot save class "%s"',class(dat)));
      end
  end
return

function nm = newUniqueID
  global identity_foo
  nm = num2str(identity_foo);
  identity_foo = identity_foo + 1;
  return
  
function ctx = simpleSaveFunction( ctx, dat )
  fprintf(ctx.f_dcr,'FUNCTION_HANDLE(''%s'');\n',func2str(dat));
  return;

function ctx = simpleSaveInline( ctx, dat )
  s = cell2struct( struct2cell(dat), fieldnames(dat) );
  args = [s.args ,repmat(',',size(s.args,1),1)].';
  args = strrep(args(1:end-1),' ','');
  expr = strrep(s.expr,'\','\\');
  expr = strrep(expr,'''','\x27');
  fprintf(ctx.f_dcr,'INLINE(''%s'',''%s'');\n',args,expr);
  return;
  
function ctx = simpleSaveChar( ctx, dat ) 
  fprintf(ctx.f_dcr,'CHAR(');
  emitShape(ctx,size(dat));
  ctx = planEmit(ctx,dat,'char');
  fprintf(ctx.f_dcr,');\n');
  return;

function ctx = simpleSaveBool( ctx, dat ) 
  fprintf(ctx.f_dcr,'BOOL(');
  emitShape(ctx,size(dat));
  ctx = planEmit(ctx,dat,'uint8');
  fprintf(ctx.f_dcr,');\n');
  return;
return

function ctx = simpleSaveArray( ctx, dat )
  if any(imag(dat(:)))
      fprintf(ctx.f_dcr,'COMPLEX_%s(',class(dat));
      emitShape(ctx,size(dat));
      ctx = planEmit(ctx,real(dat),class(dat));
      ctx = planEmit(ctx,imag(dat),class(dat));
      fprintf(ctx.f_dcr,');\n');
  else
      fprintf(ctx.f_dcr,'REAL_%s(',class(dat));
      emitShape(ctx,size(dat));
      ctx = planEmit(ctx,dat,class(dat));
      fprintf(ctx.f_dcr,');\n');
  end
return        
          
function ctx = simpleSaveStruct( ctx, dat )
    nm = newUniqueID;
    ctx = pushCtx( ctx, nm );
	fprintf(ctx.f_dcr,'REC(');
	emitShape(ctx,size(dat));
	fn = fieldnames(dat);
	if ~isempty(fn)
        fprintf(ctx.f_dcr,',');
		emitName(ctx,fn{1});
		for k=2:length(fn)
          fprintf(ctx.f_dcr,',');
          emitName(ctx, fn{k});
		end
        fprintf(ctx.f_dcr,'); %% BEGIN_REC_ID(%s)\n',nm);
		dat=dat(:);
        if ~isempty(dat)
            for di=1:length(dat)
                ctx = showCtx(ctx,' AT(%d);\n',di);
                for k=1:length(fn)
                    ctx = showCtx(ctx,' FIELD(''%s'')\n',fn{k});
                    ctx = simpleSaveAny( ctx, getfield(dat(di),fn{k}) );
                end
            end
        end
    else
        fprintf(ctx.f_dcr,'); %%COMMENT(''struct without fields'')\n');
    end
    ctx = showCtx(ctx,'END_REC_ID(%s)\n',nm);
    ctx = popCtx(ctx);   
return

function ctx = simpleSaveCell( ctx, dat )
    nm = newUniqueID;
    ctx = pushCtx( ctx, nm );
	fprintf(ctx.f_dcr,'OBJ(');
	emitShape(ctx,size(dat));
    fprintf(ctx.f_dcr,'); %% BEGIN_OBJ_ID(%s)\n', nm);
	dat=dat(:);
    if ~isempty(dat)
        for di=1:length(dat)
            ctx = showCtx(ctx,' AT(%d);\n',di);
            ctx = simpleSaveAny( ctx, dat{di} );
        end
    end
    ctx = showCtx(ctx,' END_OBJ_ID(%s)\n',nm);
    ctx = popCtx(ctx);   
return
