function [properties,F,arguments,fcn]=model(X,method,options,extstruct,w)
%MODEL  Internal function to extracts nonlinear operator models
%
% [properties,F] = model(x)
%
% MODEL returns the constraints needed to model a variable related to an
% extended operator such as min, max, abs, norm, geomean, ...
%
% Examples :
%
% sdpvar x y;
% t = min(x,y);
% [properties,F] = model(t)
% Gives F = [t<x, t<y]

% Author Johan Löfberg
% $Id: model.m,v 1.62 2009-05-05 07:20:46 joloef Exp $


extvar = getvariables(X);
arguments   = cell(1,length(extvar));
properties  = cell(1,length(extvar));

if nargin<2
    method = 'graph';
end

if nargin < 3
    options = [];
end

if nargin < 5
    % Not used
    w = [];
end

if nargin<4
    extstruct = yalmip('extstruct',extvar);
elseif isempty(extstruct)
    extstruct = yalmip('extstruct',extvar);
end

if isempty(extstruct)
    error('This is not a nonlinear operator variable');
end

fcn = extstruct.fcn;
try
    n = yalmip('nvars');
    [F,properties,arguments] = feval(fcn,method,extstruct.var,extstruct.arg{1:end-1});
    newAux = n+1:yalmip('nvars');
    involved = [];
    for i = 1:length(extstruct.arg)-1
        involved = union(involved,getvariables(extstruct.arg{i}));
    end
    if ~isempty(options)
        if ~(strcmp(options.robust.auxreduce,'none'))
            % This info is only needed when we do advanced Robust optimization
            yalmip('setdependence',[getvariables(extstruct.var) newAux],involved);
            yalmip('setdependence',[getvariables(extstruct.var)],newAux);
        end
    end
catch
    error(['Failed when trying to create a model for the "' extstruct.fcn '" operator']);
end

% Make sure all operators have these properties
if ~isempty(properties)
    if ~iscell(properties)
        properties = {properties};
    end
    for i = 1:length(properties)
        properties{i}.name = fcn;
        properties{i} = assertProperty(properties{i},'definiteness','none');
        properties{i} = assertProperty(properties{i},'convexity','none');
        properties{i} = assertProperty(properties{i},'monotonicity','none');
        properties{i} = assertProperty(properties{i},'derivative',[]);
        properties{i} = assertProperty(properties{i},'inverse',[]);
        properties{i} = assertProperty(properties{i},'models',getvariables(extstruct.var));
        properties{i} = assertProperty(properties{i},'convexhull',[]);
        properties{i} = assertProperty(properties{i},'bounds',[]);
        properties{i} = assertProperty(properties{i},'domain',[-inf inf]);
        switch properties{i}.definiteness
            case 'positive'
                properties{i} = assertProperty(properties{i},'range',[0 inf]);
            case 'negative'
                properties{i} = assertProperty(properties{i},'range',[-inf 0]);
            otherwise
                properties{i} = assertProperty(properties{i},'range',[-inf inf]);
        end
        properties{i} = assertProperty(properties{i},'model','unspecified');              
    end
end

% Normalize the callback expression and check for some obsoleted stuff
if ~isempty(properties)
    if isequal(properties{1}.model,'callback')
        F_normalizing = NormalizeCallback(method,extstruct.var,extstruct.arg{:});
        F = F + F_normalizing;
    end
    if length(extstruct.computes)>1
        for i = 1:length(properties)
            properties{i}.models = extstruct.computes;
        end
    end
    for i = 1:length(properties)
        if ~any(strcmpi(properties{i}.convexity,{'convex','concave','none'}))
            disp('More cleaning, strange convextiy returned...Report bug in model.m')
            error('More cleaning, strange convextiy returned...Report bug in model.m')
        end
    end
end

% This is useful in MPT
if ~isempty(F)
    F = tag(F,['Expansion of ' extstruct.fcn]);
end

if ~isempty(properties)
%    properties = properties{1};
end

function properties = assertProperty(properties,checkfor,default);
if ~isfield(properties,checkfor)
    properties = setfield(properties,checkfor,default);
end