function [Cat,SexKeys,Col,OutFiles]=sextractor(Images,DblImages,varargin)
% Execute sextracor on a set of FITS images or SIM images.
% Package: ImUtil.Im
% Description: execute sextracor on a set of FITS images or SIM images.
% Input  : - A list of FITS images (see create_list.m for possible input).
%            Alternatively, this can be a SIM structure array with images.
%            In this case a temporary FITS image will be generated and
%            SExtractor will be exacuated on the temp images.
%          - If provided (i.e., not empty), then exacute SExtractor
%            in a double image mode. This can be a single image or a list
%            of images.
%            Alterantively this parameter can be ignored.
%          * Arbitrary number of pairs or arguments: ...,keyword,value,...
%            where keyword are one of the followings:
%            'InSim'  - A SIM array in which to populate the catalogs.
%                       If empty, then SIM will be created (if needed).
%                       Default is empty.
%            'SexKeys'- Cell array of SExtractor keywords. Default is
%                       {'XWIN_IMAGE','YWIN_IMAGE','ALPHAWIN_J2000','DELTAWIN_J2000',...
%                        'MAG_APER','MAGERR_APER','MU_MAX','FLUX_MAX','FLUX_APER','FLUXERR_APER',...
%                        'FLAGS'}
%                       Alternatively, this can be a full path for a file
%                       name containing the output parameters.
%            'CatType'- Save output catalogs.
%                       Options are: NONE,ASCII,ASCII_HEAD, ASCII_SKYCAT,
%                       ASCII_VOTABLE, FITS_1.0 or FITS_LDAC.
%                       Default is 'ASCII_HEAD'.
%            'DeleteOut'- Delete output file {true|false}. Default is true.
%            'Read2Mat'- Read output into a matlab output.
%            'OutType'- Output type {'cell'|'struct'|'struct_t'|'sim'|'sim_t'|'table'}.
%                       Default is 'sim'.
%            'CatName'- List of file names in which to save the catalogs.
%                       If empty, then file names are: 'Cat_%06d.cat'.
%            'SexPars' - A cell array of additional pairs of parameters to 
%                       pass to SExtractor.
%                       e.g., {'DETECT_THRESH','5'}. Default is {}.
%                       Alternatively this can be a string to pass as is.
%                       Type !sex -dd to see a list of sextractor
%                       parameters.
%            'Verbose' - Verbose {true|false}. Default is false.
%            'SegName' - Segmentation output file name, or cell array of
%                       names.
%            'ProgName'- Program name to run. Default is 'sex-2.5'.
%            'ProgPath'- Program path. Default is '../bin/SExtractor/src'.
%                       Relative paths are relative from the location of
%                       this program.
%            'ParamPath'- SExtractor parameters file path.
%                       Default is '../bin/SExtractor/config/default.param'.
%                       Relative paths are relative from the location of
%                       this program.
%                       If empty use SExtractor default.
%            'ConfigPath'- Configuration file path.
%                       Default is '../bin/SExtractor/config/default.sex'.
%                       Relative paths are relative from the location of
%                       this program.
%                       If empty use SExtractor default.
%            'ConvPath' - Convolution kernel path, or convolution kernel.
%                       Default is '../bin/SExtractor/config/default.conv'.
%                       Relative paths are relative from the location of
%                       this program.
%                       If empty use SExtractor default.
%                       Alternatively, this can be a matched filter matrix
%                       (e.g., generated by construct_matched_filter2d.m).
%            'SortCat' - Column by which to sort the catalog
%                       (e.g., 'DELTA_J2000', or 'YWIN_IMAGE').
%                       If empty then do not sort. Default is empty.
%            --- Additional parameters
%            Any additional key,val, that are recognized by one of the
%            following programs: sims2fits.m
% Output : - Output SExtractor catalogs.
%          - Cell array of catalog column names.
%          - Structure array of catalog column names and indices.
%          - Cell array of SExtractor output file names.
% Tested : Matlab R2014a
%     By : Eran O. Ofek                    Jan 2015
%    URL : http://weizmann.ac.il/home/eofek/matlab/
% Example: [Cat,SexKeys,Col]=ImUtil.Im.sextractor('P*.fits');
%          [Cat,SexKeys,Col]=ImUtil.Im.sextractor('P*.fits',[],'OutType','sim');
%          [Cat,SexKeys,Col]=ImUtil.Im.sextractor('*.fits','OutType','sim_t','SexPars',{'SATUR_LVEL','55000'});
% Reliable: 2
%--------------------------------------------------------------------------
import Util.string.*

CurFunDirName = Util.files.which_dir(mfilename);


SIMCatField         = 'Cat';
SIMCatColField      = 'Col';
SIMCatColCellField  = 'ColCell';

if (nargin==1)
    DblImages = [];
else
    if (nargin.*0.5~=floor(nargin.*0.5))
        % assume that DblImage is not provided
        varargin(2:end+1) = varargin;
        varargin{1} = DblImages;
        DblImages = [];
    end
end
    


DefV.InSim           = [];
DefV.SexKeys         = {'XWIN_IMAGE','YWIN_IMAGE','ALPHAWIN_J2000','DELTAWIN_J2000',...
                        'MAG_APER','MAGERR_APER','MU_MAX','FLUX_MAX','FLUX_APER','FLUXERR_APER',...
                        'FLAGS',...
                        'MAG_AUTO','MAGERR_AUTO',...
                        'X2WIN_IMAGE','Y2WIN_IMAGE','XYWIN_IMAGE'};
                        %'MAG_AUTO','MAGERR_AUTO','MU_MAX','FLUX_MAX','FLUX_AUTO','FLUXERR_AUTO','MAG_APER','MAGERR_APER'};
DefV.CatType         = 'ASCII_HEAD'; % NONE,ASCII,ASCII_HEAD, ASCII_SKYCAT, ASCII_VOTABLE, FITS_1.0 or FITS_LDAC
DefV.DeleteOut       = true;  % delete SExtractor output file
DefV.Read2Mat        = true;
DefV.OutType         = 'sim'; %'struct';  % {'cell'|'struct'|'struct_t'|'sim'|'sim_t'|'table'}
DefV.CatName         = [];
%DefV.OutPrefix       = '';
%DefV.OutDir          = '';
DefV.SexPars         = {};
DefV.Verbose         = false;
DefV.SegName         = [];  % segmentation image name
DefV.ProgName        = 'sex-2.5';
DefV.ProgPath        = '~/matlab/bin/SExtractor/src/';
%DefV.ProgPath        = '../../../bin/SExtractor/src/'; % if begin with '.' then path is relative to program location
DefV.ParamPath       = '~/matlab/bin/SExtractor/config/default.param';
%DefV.ParamPath       = '../../../bin/SExtractor/config/default.param';
DefV.ConfigPath      = '~/matlab/bin/SExtractor/config/default.sex';
%DefV.ConfigPath      = '../../../bin/SExtractor/config/default.sex';
DefV.ConvPath        = '~/matlab/bin/SExtractor/config/default.conv';
%DefV.ConvPath        = '../../../bin/SExtractor/config/default.conv';
DefV.CatField        = SIMCatField;
DefV.CatColField     = SIMCatColField;
DefV.CatColCellField = SIMCatColCellField;
DefV.SortCat         = [];
%DefV.RepNaN          = 0;

%InPar = set_varargin_keyval(DefV,'n','use',varargin{:});
InPar = InArg.populate_keyval(DefV,varargin,mfilename);

if (nargin==1)
    DblImages = [];
end


% construct full path to program and config files
ProgFullPath    = sprintf('%s%s',InPar.ProgPath,InPar.ProgName);
ParamFullPath   = InPar.ParamPath;
ConfigFullPath  = InPar.ConfigPath;
%ProgFullPath   = Util.string.construct_fullpath(InPar.ProgName,InPar.ProgPath,CurFunDirName);
%ParamFullPath  = Util.string.construct_fullpath(InPar.ParamPath,'.',CurFunDirName);
%ConfigFullPath = Util.string.construct_fullpath(InPar.ConfigPath,'.',CurFunDirName);

% convolution kernel
ConvFile = [];
if (ischar(InPar.ConvPath))
    ConvFullPath    = InPar.ConvPath;
    %ConvFullPath   = Util.string.construct_fullpath(InPar.ConvPath,'.',CurFunDirName);
elseif (isnumeric(InPar.ConvPath))
    if (isempty(InPar.ConvPath))
        % do nothing
    else
        % write matched filter to file
        ConvFile = tempname;
        SizeMF = size(InPar.ConvPath);
        Str    = Util.string.str_duplicate('%11.7f ',SizeMF(2),'\n');
        FIDconv    = fopen(ConvFile,'w');
        fprintf(FIDconv,'CONV NORM\n');
        fprintf(FIDconv,Str,InPar.ConvPath.');
        fclose(FIDconv);
        ConvFullPath = ConvFile;
    end
else
    error('Unknown ConvPath option');
end


        
    
    
% write FITS files to disk if needed
if (SIM.issim(Images) || isstruct(Images) || isnumeric(Images))
    % write temp FITS images
    if (InPar.Verbose)
        fprintf('Write temporary FITS images\n')
    end        
    if (isnumeric(Images))
        Tmp = Images;
        clear Images;
        Images.Im = Tmp;
    end
    %ImList = sims2fits(Images,varargin{:},'TmpName',true);
    %ImList = sims2fits(Images,'TmpName',true);
    ImList = FITS.sim2fits(Images);
    
    TmpImages = true;
else
    % create list of FITS images
    [~,ImList] = Util.files.create_list(Images,NaN);
    TmpImages = false;
end

if (SIM.issim(DblImages) || isstruct(DblImages))
    % write temp FITS images
    if (InPar.Verbose)
        fprintf('Write temporary FITS images for Dbl image mode\n')
    end
    if (isnumeric(DblImages))
        Tmp = DblImages;
        clear DblImages;
        DblImages.Im = Tmp;
    end
    DblImList = sims2fits(DblImages,varargin{:},'TmpName',true);
    Ndblim    = numel(DblImList);
    TmpDblImages = true;
else
    % create list of FITS images
    [~,DblImList] = Util.files.create_list(DblImages,NaN);
    Ndblim    = numel(DblImList);
    TmpDblImages = false;
end


% Extract extra parameters to pass to SExtractor
ExtraParStr = Util.string.construct_keyval_string(InPar.SexPars);

% if (ischar(InPar.SexPars)),
%     % use string as is
%     ExtraParStr = InPar.SexPars;
% else
%     % build string from pairs of parameters
%     Narg = length(InPar.SexPars);
%     ExtraParStr = ' ';
%     for Ip=1:2:Narg-1,
%         ExtraParStr = sprintf('%s -%s %s',ExtraParStr,InPar.SexPars{Ip},InPar.SexPars{Ip+1});
%     end
% end

% prepare output parameters file
if (isempty(InPar.SexKeys))
    ParamFile      = ParamFullPath;
    IsTmpParamFile = false;
    FID = fopen(ParamFile,'r');
    InPar.SexKeys  = textscan(FID,'%s','commentstyle','#');
    fclose(FID);
else
    if (ischar(InPar.SexKeys))
        % output parameters
        ParamFile      = InPar.SexKeys;
        IsTmpParamFile = false;
        FID = fopen(ParamFile,'r');
        InPar.SexKeys  = textscan(FID,'%s','commentstyle','#');
        fclose(FID);
    else
        ParamFile      = Util.files.create_list(InPar.SexKeys,[],'n');   % convert to file
        IsTmpParamFile = true;
    end
end
Nparam  = numel(InPar.SexKeys);
Col     = cell2struct(num2cell(1:1:Nparam),InPar.SexKeys,2);
SexKeys = InPar.SexKeys;

% prepare config file
if (isempty(InPar.ConfigPath))
    ConfigStr = '';
else
    ConfigStr = sprintf('-c %s',ConfigFullPath);
end

% prepare convolution kernel
if (isempty(InPar.ConvPath))
    ConvStr = '';
else
    ConvStr = sprintf('-FILTER_NAME %s',ConvFullPath);
end

switch lower(InPar.OutType)
    case {'sim','sim_t'}
        %Cat = images2sim(Images); %,varargin{:});
        Cat = Images;
        %Cat = FITS.read2sim(Images); %,varargin{:});
    otherwise
        % do nothing
end


%--- Run over all images ---
OutFiles = [];
Nim = numel(ImList);
% initilaization
switch lower(InPar.OutType)
    case {'cell','table'}
        Cat = cell(Nim,1);
    case {'struct','struct_t'}
        if (isempty(InPar.InSim))
            Cat = struct(InPar.CatField,cell(Nim,1), InPar.CatColCellField,cell(Nim,1), InPar.CatColField,cell(Nim,1));
        else
            Cat = InPar.InSim;
        end
    case {'sim','sim_t'}
        % Cat is already defined as SIM.
%         if (isempty(InPar.InSim)),
%             Cat = simdef(Nim,1);
%         else
%             Cat = InPar.InSim;
%         end
    otherwise
        error('Unknown OutType option');
end
OutFiles = cell(Nim,1);

for Iim=1:1:Nim
    % for each image
    if (InPar.Verbose)
        fprintf('Call SExtractor for image number %d out of %d (image: %s)\n',Iim,Nim,ImList{Iim});
    end

    % prepare segmentation image ouput
    if (isempty(InPar.SegName))
        SegStr = '';
    else
        if (ischar(InPar.SegName))
            SegStr = sprintf('-CHECKIMAGE_TYPE SEGMENTATION -CHECKIMAGE_NAME %s',InPar.SegName);
        else
            SegStr = sprintf('-CHECKIMAGE_TYPE SEGMENTATION -CHECKIMAGE_NAME %s',InPar.SegName{Iim});
        end
    end

    
    % prep images for dbl image mode
    if (isempty(DblImages))
        % single image
        ImageName = ImList{Iim};
    else
        % double image mode
        ImageName = sprintf('%s,%s',DblImList{min(Iim,Ndblim)},ImList{Iim});
    end
    
    % prep output image name
    if (isempty(InPar.CatName))
        CatFile = tempname;
    else
        if (ischar(InPar.CatName))
            CatFile = InPar.CatName;
        else
            CatFile = InPar.CatName{Iim};
        end
    end

    
    % run sextractor
    RunStr = sprintf('%s %s %s -PARAMETERS_NAME %s -CATALOG_NAME %s -CATALOG_TYPE %s %s %s %s',ProgFullPath,ImageName,ConfigStr,ParamFile,CatFile,upper(InPar.CatType),ExtraParStr,ConvStr,SegStr);
    %RunStr
    
    % run SExtractor
    system(RunStr);
    
    % read output
    if (InPar.Read2Mat)
        
        switch lower(InPar.CatType)
            case 'ascii_head'
                FID = fopen(CatFile,'r');
                ReadSex = textscan(FID,'%n','commentstyle','#');
                fclose(FID);
                CatSex  = reshape(ReadSex{1},[Nparam, length(ReadSex{1})./Nparam]).';
            case 'fits_1.0'
                CatSex = cell2mat(fitsread(CatFile,'BinTable'));
            case 'fits_ldac'
                CatSex = cell2mat(read_fits_ldac(CatFile));
            otherwise
                error('Catalog type option not available using this setup');
        end
    end
    
    % sort output catalog
    if (~isempty(InPar.SortCat))
        CatSex = sortrows(CatSex,Col.(InPar.SortCat));
    end
    
        
    
    
    
    % save output
    % DefV.OutType       = 'cell';  % {'cell'|'struct'|'sim'|'table'}
    switch lower(InPar.OutType)
        case 'cell'
            Cat{Iim} = CatSex;
        case 'struct'
            Cat(Iim).(InPar.CatField)        = CatSex;
            Cat(Iim).(InPar.CatColCellField) = InPar.SexKeys;
            Cat(Iim).(InPar.CatColField)     = Col;
        case 'struct_t'
            Cat(Iim).(InPar.CatField) = array2table(CatSex);
            Cat(Iim).(InPar.CatField).Properties.VariableNames = InPar.SexKeys;
            Cat(Iim).(InPar.CatColCellField) = InPar.SexKeys;
            Cat(Iim).(InPar.CatColField)     = Col;
        case 'sim'
            Cat(Iim).(SIMCatField)        = CatSex;
            Cat(Iim).(SIMCatColCellField) = InPar.SexKeys;
            Cat(Iim).(SIMCatColField)     = Col;
        case 'sim_t'
            Cat(Iim).(SIMCatField) = array2table(CatSex);
            Cat(Iim).(SIMCatField).Properties.VariableNames = InPar.SexKeys;
            Cat(Iim).(SIMCatColCellField) = InPar.SexKeys;
            Cat(Iim).(SIMCatColField)     = Col;
        case 'table'
            Cat{Iim} = array2table(CatSex);
        otherwise
            error('Unknown OutType option');
   end
     
    % delete output file name
    if (InPar.DeleteOut)
        delete(CatFile);
    else
        OutFiles{Iim} = CatFile;
    end

    
    
    
end


if (TmpImages)
    % delete temp images
    if (InPar.Verbose)
        fprintf('Delete temporary FITS images\n');
    end
    Util.files.delete_cell(ImList);
end
if (TmpDblImages)
    % delete temp Dbl images
    if (InPar.Verbose)
        fprintf('Delete temporary FITS images for dbl image mode\n');
    end
    Util.files.delete_cell(TmpDblImages);
end    
    

% delete temporary ParamFile
if (IsTmpParamFile)
    delete(ParamFile);
end
if (~isempty(ConvFile))
    delete(ConvFile);
end

switch lower(InPar.OutType)
    case 'sim'
        Cat = SIM.struct2sim(Cat);
end
