function varargout = build_mex(varargin)

% function build_mex(varargin)
%
% Build mex file using the same syntax as MATLABs built-in mex() function.
%
% The only difference is that this command automatically uses the minGW
% toolchain for compiling and linking, borrowing some code from the gnumex
% project.
%
% build_mex handles two additional (And helpful) options:  First, it will pass
% "-l" switches straight to the linker, so if we want to link
% against library named libfftw3.a, we need only include a "-lfftw3" switch
% along with the inputs.
%
% Further, if we simply leave out libraries all together, mr-mex will just
% link against all of the libraries in ./LIBS, and include all of the
% header files found in that directory.
%
% The other thing build_mex does is to wrap the stdlib "malloc",
% "calloc", "realloc", and "free" calls at link time with thier matlab
% counterparts (mxMalloc, mxFree, etc...).  It is necessary to
% do this when using external libraries.  IT is done by default.  It can 
% be disabled by including "-nowrap" as an input argument; the linker 
% script generates the appropriate arguments to pass to GCC.
%
% EXAMPLE:
%
% >> build_mex -v -nowrap -lfftw3 faster_specgram_.c
%
% verbosely generates faster_specgram_.dll using the mingw gnu tools,
% wrapping all memory allocation calls with the matlab versions, and
% linking to the mingw library libfftw3.a.

%--
% declare and initialize some variables
%--

wrapflag = 1;

explinkflag = 0;

libs = cell(0);

libpath = [this_path, 'LIBS'];

inclpath = libpath;

mexopts_tpl = [this_path, filesep, 'Magic', filesep, 'mexopts.in'];

mexopts_file = [this_path, filesep, 'Magic', filesep, 'mexopts.bat'];

%--
% Check for MinGW presence
%--

if (isempty(mingw_root))
	error('Unable to find MinGW.');
end
	
%--
% generate standard mex call, but using our own mexopts.bat
%--

% NOTE: this string will grow.

str = ['mex -f "', mexopts_file, '"'];

%--
% grab "special" args here, pass the rest to regular mex
%--

for ix = 1:length(varargin);
	
	%--
	% handle symbol wrapping
	%---

	% NOTE: typically used to replace allocation (malloc) routines
	
	if strcmp(varargin{ix}, '-nowrap')
	
		wrapflag = 0;
		
	%--
	% handle library linking
	%--
	
	elseif strncmp(varargin{ix}, '-l', 2)
		
		explinkflag = 1;
		
		%--
		% try to get local library
		%-- 
		
		libfile = ['lib', varargin{ix}(3:end), '.a'];
		
		fp = find_file(libfile,libpath);
		
		if iscell(fp)
			fp = fp{1};
		end
		
		%--
		% append libraries to link to list
		%--
		
		% NOTE: try to pass actual library file to linker
		
		if ~isempty(fp)

			libs{end + 1} = fix_path(strtrim(fp), '"');
			
		else
			
			disp('WARNING: Could not find specified library, using default search path.');
			
			libs{end + 1} = varargin{ix};
			
		end
				
	%--
	% handle mex arguments
	%--
	
	% NOTE: these pass right through
	
	else
	
		% NOTE: handle file location in special way
		
		if (exist(varargin{ix},'file'))
			varargin{ix} = fix_path(varargin{ix},'"');
		end
		
		str = [str ' ' varargin{ix}];
		
	end
	
end

%--
% if no explicit link, then just link against everything we've got.
%--

if (~explinkflag)
	libs = fix_path(find_file('.a', libpath, '-a', '-p'), '"');
end

%--
% point gcc to our include directories
%--

d = dir(inclpath);

for ix = 1:length(d)
	
	if ((d(ix).name(1) ~= '.') && d(ix).isdir)
		str = [str, ' ', '-I"', inclpath, filesep, d(ix).name, '"']; 
	end
	
end

%--
% make mexopts.bat file from template
%--

generate_mexopts(mexopts_tpl, mexopts_file, wrapflag, libs);

%--
% run mex with our newly-tweaked options file, and the passed-in args.
%--

if (~nargout)
	eval(str);
else
	varargout{1} = str;
end


%-----------------------------------------
% GENERATE_MEXOPTS
%-----------------------------------------

function generate_mexopts(in, out, wrapflag, libs)

% generate_mexopts - generate mexoptions file that uses mingw
% ---------------------------------------------------------
%
% generate_mexopts(out,linker,wrap,libs)
%
% Input:
% ------
%  in - options template file
%  out - options file
%  wrapflag - wrap indicator
%  libs - libraries to link

%--
% get pointers to our special files
%--

lines = file_readlines(in);

%--
% set up tokens and associated values
%--

envars = { ...
	'$LINK_LIB$',	cell_cat(libs, ' '); ...
	'$LINKFLAGS$',	wrapstr(wrapflag); ...
	'$PERLPATH$',	fix_path(perlpath, '"'); ...
	'$MATLAB$',		fix_path(matlabroot); ...
	'$MRMEXPATH$',	[this_path, 'Magic']; ...
	'$MINGWPATH$',  [mingw_root, filesep, 'bin']; ...
	'$LINKER$',		[perlpath ' ' fix_path(linker, '"')] ...
};

%--
% perform token replacement
%--

[lix, vx] = cell_find(lines, envars(:, 1), @strfind);

for ix = 1:length(lix)
	lines(lix(ix)) = strrep(lines(lix(ix)), envars(vx(ix), 1), envars(vx(ix), 2));
end

%--
% output file
%--

file_writelines(out, lines, @printf_escape);
	
%--------------------------------------
% PERLPATH
%--------------------------------------

function p = perlpath

p = fix_path(fullfile(matlabroot, 'sys\perl\win32\bin\perl'), '"');


%--------------------------------------
% CELL_CAT
%--------------------------------------

function s = cell_cat(c, j)

s = '';

for ix = 1:length(c)
	s = [s, c{ix}, j];
end


%--------------------------------------
% WRAPSTR
%--------------------------------------

function s = wrapstr(flag)

if flag
	s = '-Wl,--wrap,malloc,--wrap,calloc,--wrap,free,--wrap,realloc';
else
	s = '';
end


%--------------------------------------
% THIS_PATH
%--------------------------------------

function s = this_path()

s = fix_path(fileparts(mfilename('fullpath')));


%--------------------------------------
% LINKER
%--------------------------------------

function s = linker()

s = [this_path, filesep, 'Magic', filesep, 'linkmex.pl'];


%--------------------------------------
% FIX_PATH
%--------------------------------------

function p = fix_path(p, q)


if (nargin < 2)
	q = 0;
end

if (iscell(p))
	
	for ix = 1:length(p)
		p{ix} = fix_path(p{ix}, q);
	end
	
	return;
	
end

if (q)
	
	if (p(1) ~= '"')
		p = ['"', p, '"'];
	end	
	
	return
	
end

if (p(1) == '"')
	p = p(2:end-1);
end

if (p(end) ~= filesep)
	p(end + 1) = filesep;
end


%--------------------------------
% CELL_FIND
%--------------------------------

function [ixlist, jxlist] = cell_find(a, b, compare_handle)

ixlist = []; jxlist = [];

for ix = 1:length(a)
	
	for jx = 1:length(b)
		
		if any(compare_handle(a{ix}, b{jx}))
			
			ixlist(end + 1) = ix; jxlist(end + 1) = jx;
			
		end
		
	end
	
end
