function varargout = seqWriterPlugin( cmd, h, varargin )
% Plugin for seqIo and videoIO to allow writing of seq files.
%
% Do not call directly, use as plugin for seqIo or videoIO instead.
% The following is a list of commands available (swp=seqWriterPlugin):
%  h=swp('open',h,fName,info) % Open a seq file for writing (h ignored).
%  h=swp('close',h)           % Close seq file (output h is -1).
%  swp('addframe',h,I,[ts])   % Writes video frame (and timestamp).
%  swp('addframeb',h,I,[ts])  % Writes video frame with no encoding.
%  info = swp('getinfo',h)    % Return struct with info about video.
%
% The following params must be specified in struct 'info' upon opening:
%  width          - frame width
%  height         - frame height
%  fps            - frames per second
%  quality        - [80] compression quality (0 to 100)
%  codec          - string representing codec, options include:
%   'monoraw'/'imageFormat100'      - black/white uncompressed
%   'raw'/'imageFormat200'          - color (BGR) uncompressed
%   'monojpg'/'imageFormat102'      - black/white jpg compressed
%   'jpg'/'imageFormat201'          - color jpg compressed
%   'monopng'/'imageFormat001'      - black/white png compressed
%   'png'/'imageFormat002'          - color png compressed
%
% USAGE
%  varargout = seqWriterPlugin( cmd, h, varargin )
%
% INPUTS
%  cmd        - string indicating operation to perform
%  h          - unique identifier for open seq file
%  varargin   - additional options (vary according to cmd)
%
% OUTPUTS
%  varargout  - output (varies according to cmd)
%
% EXAMPLE
%
% See also SEQIO, SEQREADERPLUGIN
%
% Piotr's Image&Video Toolbox      Version 2.51
% Copyright 2010 Piotr Dollar.  [pdollar-at-caltech.edu]
% Please email me if you find bugs, or have suggestions or questions!
% Licensed under the Lesser GPL [see external/lgpl.txt]

% persistent variables to keep track of all loaded .seq files
persistent h1 hs cs fids infos tNms;
if(isempty(h1)), h1=int32(now); hs=int32([]); infos={}; tNms={}; end
nIn=nargin-2; in=varargin; o1=[]; cmd=lower(cmd);

% open seq file
if(strcmp(cmd,'open'))
  chk(nIn,2); h=length(hs)+1; hs(h)=h1; varargout={h1}; h1=h1+1;
  [pth name]=fileparts(in{1}); if(isempty(pth)), pth='.'; end
  fName=[pth filesep name]; cs(h)=-1;
  [infos{h},fids(h),tNms{h}]=open(fName,in{2}); return;
end

% Get the handle for this instance
[v,h]=ismember(h,hs); if(~v), error('Invalid load plugin handle'); end
c=cs(h); fid=fids(h); info=infos{h}; tNm=tNms{h};

% close seq file
if(strcmp(cmd,'close'))
  writeHeader(fid,info,c);
  chk(nIn,0); varargout={-1}; fclose(fid); kp=[1:h-1 h+1:length(hs)];
  hs=hs(kp); cs=cs(kp); fids=fids(kp); infos=infos(kp);
  tNms=tNms(kp); if(exist(tNm,'file')), delete(tNm); end; return;
end

% perform appropriate operation
switch( cmd )
  case 'addframe',  chk(nIn,1,2); c=addFrame(fid,info,c,tNm,1,in{:});
  case 'addframeb', chk(nIn,1,2); c=addFrame(fid,info,c,tNm,0,in{:});
  case 'getinfo',   chk(nIn,0); o1=info;
  otherwise,        error(['Unrecognized command: "' cmd '"']);
end
cs(h)=c; varargout={o1};

end

function chk(nIn,nMin,nMax)
if(nargin<3), nMax=nMin; end
if(nIn>0 && nMin==0 && nMax==0), error(['"' cmd '" takes no args.']); end
if(nIn<nMin||nIn>nMax), error(['Incorrect num args for "' cmd '".']); end
end

function getImgFile( fName )
% create local copy of fName which is in a imagesci/private
fName = [fName '.' mexext]; s = filesep;
sName = [fileparts(which('imread.m')) s 'private' s fName];
tName = [fileparts(mfilename('fullpath')) s 'private' s fName];
if(~exist(tName,'file')), copyfile(sName,tName); end
end

function [info, fid, tNm] = open( fName, info )
% open video for writing, create space for header
t=[fName '.seq']; if(exist(t,'file')), delete(t); end
t=[fName '-seek.mat']; if(exist(t,'file')), delete(t); end
fid=fopen([fName '.seq'],'w','l'); assert(fid~=-1);
fwrite(fid,zeros(1,1024),'uint8');
% initialize info struct (w all fields necessary for writeHeader)
assert(isfield2(info,{'width','height','fps','codec'},1));
switch(info.codec)
  case {'monoraw', 'imageFormat100'}, frmt=100; nCh=1; ext='raw';
  case {'raw', 'imageFormat200'},     frmt=200; nCh=3; ext='raw';
  case {'monojpg', 'imageFormat102'}, frmt=102; nCh=1; ext='jpg';
  case {'jpg', 'imageFormat201'},     frmt=201; nCh=3; ext='jpg';
  case {'monopng', 'imageFormat001'}, frmt=001; nCh=1; ext='png';
  case {'png', 'imageFormat002'},     frmt=002; nCh=3; ext='png';
  otherwise, error('unknown format');
end
if(strcmp(ext,'jpg')), getImgFile( 'wjpg8c' ); end
if(strcmp(ext,'png')), getImgFile( 'png' ); end
info.imageFormat=frmt; info.ext=ext;
if(~isfield2(info,'quality')), info.quality=80; end
info.imageBitDepth=8*nCh; info.imageBitDepthReal=8;
nByte=info.width*info.height*nCh; info.imageSizeBytes=nByte;
info.numFrames=0; info.trueImageSize=nByte+6+512-mod(nByte+6,512);
% generate unique temporary name
[tNm tNm]=fileparts(fName); t=clock; t=mod(t(end),1);
tNm=sprintf('tmp_%s_%09i.%s',tNm,round((t+rand)/2*1e9),ext);
end

function c = addFrame( fid, info, c, tNm, encode, I, ts )
% write frame
nCh=info.imageBitDepth/8; ext=info.ext; c=c+1;
if( encode )
  siz = [info.height info.width nCh];
  assert(size(I,1)==siz(1) && size(I,2)==siz(2) && size(I,3)==siz(3));
end
switch ext
  case 'raw'
    % write an uncompressed image (assume imageBitDepthReal==8)
    if( ~encode ), assert(numel(I)==info.imageSizeBytes); else
      if(nCh==3), t=I(:,:,3); I(:,:,3)=I(:,:,1); I(:,:,1)=t; end
      if(nCh==1), I=I'; else I=permute(I,[3,2,1]); end
    end
    fwrite(fid,I(:),'uint8'); pad=info.trueImageSize-info.imageSizeBytes-6;
  case 'jpg'
    if( encode )
      % write/read to/from temporary .jpg (not that much overhead)
      q=info.quality;
      wjpg8c(I,tNm,struct('quality',q,'comment',{{}},'mode','lossy'));
      fr=fopen(tNm,'r'); assert(fr~=-1); I=fread(fr); fclose(fr);
    end
    assert(I(1)==255 && I(2)==216 && I(end-1)==255 && I(end)==217); % JPG
    fwrite(fid,numel(I)+4,'uint32'); fwrite(fid,I); pad=10;
  case 'png'
    if( encode )
      % write/read to/from temporary .png (not that much overhead)
      p=cell(1,18); p{1}='write'; if(nCh==1), p{5}=0; else p{5}=2; end
      p{2}=I; p{4}=tNm; p{6}=8; p{9}='none'; p{17}=cell(0,2); png(p{:})
      fr=fopen(tNm,'r'); assert(fr~=-1); I=fread(fr); fclose(fr);
    end
    fwrite(fid,numel(I)+4,'uint32'); fwrite(fid,I); pad=10;
  otherwise, assert(false);
end
% write timestamp
if(nargin<7), ts=c/info.fps; end; s=floor(ts); ms=round(mod(ts,1)*1000);
fwrite(fid,s,'int32'); fwrite(fid,ms,'uint16');
% pad with zeros
if(pad>0), fwrite(fid,zeros(1,pad),'uint8'); end
end

function writeHeader( fid, info, c )
fseek(fid,0,'bof'); info.numFrames=c+1;
% first 4 bytes store OxFEED, next 24 store 'Norpix seq  '
fwrite(fid,hex2dec('FEED'),'uint32');
fwrite(fid,['Norpix seq' 0 0],'uint16');
% next 8 bytes for version (3) and header size (1024), then 512 for descr
fwrite(fid,[3 1024],'int32');
if(isfield(info,'descr')), d=info.descr(:); else d=('No Description')'; end
d=[d(1:min(256,end)); zeros(256-length(d),1)]; fwrite(fid,d,'uint16');
% write remaining info
vals=[info.width info.height info.imageBitDepth info.imageBitDepthReal ...
  info.imageSizeBytes info.imageFormat info.numFrames 0 ...
  info.trueImageSize];
fwrite(fid,vals,'uint32');
% store frame rate and pad with 0's
fwrite(fid,info.fps,'float64'); fwrite(fid,zeros(1,432),'uint8');
end
