function [model,parms] = loreta_similarity(data,class_labels,init_model,parms)
% [model,parms] = loreta_similarity(data,class_labels,init_model,parms)
%
% Input:   
% -- data             d X N matrix (d = instance dimension, N = number of instances)
% -- class_labels     N X 1 vector
% -- init_model       structure with init_model.A, init_model.B being d X k matrices where k is the model rank. The model is W = A*B'.
%   parameters:
%   -- parms.step_size   (default 1)
%   -- parms.rseed       random seed (default 1)
%   -- parms.num_steps   user defined
%   -- parms.inner_loop  size of inner loop for counting losses and reporting progress (default 1000)
%   -- parms.verbose     report once every parms.inner_loop steps (default 0)
%   -- parms.use_psd     use PSD version of Loreta instead of general
%   low-rank. Uses only init_model.A. The model is W=A*A'
%
% Output:
% -- model            structure with model.A, model.B being d X k matrices
% with k the model rank. The model is W = A*B', unless parms.use_psd = 1 in which case the model is W=A*A'.
%                     model.loss_steps is an indicator vector containing all loss events = all model update events
% -- parms


    [d,N] = size(data);

    if ~isfield(parms,'step_size')
        parms.step_size = 1;
        fprintf('step size value set to 1 \n')
    end

    [dA,kA] = size(init_model.A);
    [dB,kB] = size(init_model.B);

    if dA~=d 
        error('data dim is %d while model.A dim is %d',d,dA);
    elseif dB~=d
        error('data dim is %d while model.B dim is %d',d,dB);
    end

    if kA~=kB
        error('model.A and model.B are of incompatible sizes %d and %d',kA,kB);
    end



    if ~isfield(parms,'rseed')
        parms.rseed = 1;
    end

    rand('twister',parms.rseed);
    if ~isfield(parms,'inner_loop')
        parms.inner_loop = 1000;
    end

    if ~isfield(parms,'verbose')
        parms.verbose = 0;
    end

    if ~isfield(parms,'use_psd')
        parms.use_psd = 0;
        fprintf('Using Loreta-1 low-rank model \n');
    else
        if parms.use_psd == 0
            fprintf('Using Loreta-1 low-rank model \n');
        else
            fprintf('Using Loreta PSD model \n');
        end
    end

    

    if ~isfield(parms,'num_steps')       
        fprintf('no number of steps provided, please set parms.num_steps \n');
    end

   

   % rearrange data
   %-----------------------
   [unused_values, inds] = sort(class_labels); %#ok
   class_labels = class_labels(inds);
   data = data(:,inds);
   classes = sort(unique(class_labels));
   num_classes = length(classes);
  
   % Translate class labels to serial numbers 1,2,...
   new_class_labels = zeros(size(class_labels));
   for i=1:length(classes)
     new_class_labels(class_labels==classes(i)) = i;
   end
   class_labels = new_class_labels;
   class_sizes = zeros(num_classes,1);
   class_start = zeros(num_classes,1);
   for k=1:num_classes
     class_sizes(k) = sum(class_labels==k);
     class_start(k) = find(class_labels==k, 1, 'first');
   end    
   %------------------------


    A = init_model.A;
    if parms.use_psd == 0
        B = init_model.B;
    end    
    clear init_model;
    
    % initialize pseudo inverses - assumes A and B are of rank k
    Apinv = (A'*A)\A';
    if parms.use_psd == 0
        Bpinv = (B'*B)\B';
    end
    %---------

    num_steps = parms.num_steps;
    step_size = parms.step_size;
    inner_loop_size = parms.inner_loop;
    outer_loss = zeros(1,floor(num_steps/inner_loop_size));
    inner_loss = zeros(1,inner_loop_size);
    inner_l = 0;
    ctime = clock;



    for i=1:num_steps

        %sample a query/positive/negative trio
        q_ind =floor(rand*N)+1;
        q_label = class_labels(q_ind);
        p_ind = class_start(q_label)+floor(rand*class_sizes(q_label));
        n_ind = floor(rand*N)+1;        
        n_label = class_labels(n_ind);
        while n_label==q_label
            n_ind = floor(rand*N)+1;
            n_label = class_labels(n_ind);
        end
        %---------------

        x1 = data(:,q_ind); 
        x2 = data(:,p_ind)-data(:,n_ind); 
        
        if parms.use_psd == 0
            pred = (x1'*A)*(B'*x2);    
            if pred<1
                inner_loss(i-inner_l*inner_loop_size) = 1;
                [A,B,Apinv,Bpinv] = online_step_loreta1(A,B,Apinv,Bpinv,x1,x2,1,step_size);            
            end
        else            
            pred = (x1'*A)*(A'*x2);    
            if pred<1
                inner_loss(i-inner_l*inner_loop_size) = 1;
                [A,Apinv] = online_step_loreta_psd(A,Apinv,x1,x2,1,step_size);            
            end
        end           

        if mod(i,inner_loop_size) == 0              
            inner_l = inner_l+1;
            outer_loss(inner_l) = mean(inner_loss);
            inner_loss = zeros(1,inner_loop_size);
            if parms.verbose == 1       
                c = clock;
                fprintf('iteration %d out of %d, time elapsed %g sec \n',i,num_steps,etime(c,ctime));
            end
        end
    end
   
           

    %set output
    model.A = A;
    if parms.use_psd == 0
        model.B = B;
    end
    model.loss_steps = outer_loss;



    
    
