function [m,v] = Controller
    % Author: Tomas Pospichal
    
    close all
    clear
    clear classes
    
    %clc
    mfilename('fullpath')
    cd(erase(mfilename('fullpath'),extractAfter(mfilename('fullpath'),'matlab_code'))) % make sure we are in ../matlab_code
    addpath(genpath(pwd)) % add subfolders to path to search for functions
    
    % initial parameters
    startServer = 0;
    img = 'testImg.jpg';
    p = erase(mfilename('fullpath'),extractAfter(mfilename('fullpath'),'matlab_code'));
    p = fullfile(p,'Model','img');
    %img = 'frame_42_delay-0.05s.jpg';
    mlsGrid = [10, 10];
    mlsType = 'Rigid';
    loadCtrlPoints = 1;
    m = Model(p,img,mlsGrid,mlsType,loadCtrlPoints);
    v = View(m);
    
    set(v.fig, 'SizeChangedFcn',{@onFigResize,v,m},...
        'CloseRequestFcn',{@onFigExit,v,m},...
        'WindowButtonMotionFcn',{@onMouseMovement,v,m},...       
        'WindowButtonUpFcn', {@onButtonUp,v,m},...
        'KeyPressFcn', {@keyPress,v},...
        'KeyReleaseFcn',{@keyPress,v});
    % 'WindowButtonDownFcn',{@onButtonDown,v},...
    set(v.ax,'ButtonDownFcn',{@onButtonDown,v,m})
    
    v.fig.Visible = 'on'; % make figure visible (triggers SizeChangedFcn)

%     set(v.slider, 'Callback',{@onSlide,m})    
    ButtonsAssignCallbacks(v.tab1,m)
    %ButtonsAssignCallbacks(v.tab2,m)
    
    set(v.btnTab4{1},'Callback',{@saveCrtlPoints,v,m})
    set(v.btnTab4{2},'Callback',{@saveCrtlPoints,v,m})
    set(v.btnTab4{4},'Callback',{@saveCrtlPoints,v,m})
    set(v.popupStrings(3),'Callback',{@selectCtrlPoints,m})
    set(v.btnTab3{1},'Callback',{@animateMLS,v,m})
    
    
    disp('Done')

    % listener for img
    addlistener(m, 'displayedImg','PostSet', @(o,e) sendImg(m));
    
    
    % menu callbacks
    v.menuBar.ImportCtrlPoints.MenuSelectedFcn = {@MenuSelected,m,v};
    v.menuBar.ExportCtrlPoints.MenuSelectedFcn = {@MenuSelected,m,v};
    v.importImgTypesPrint;
    v.menuBar.ImportImg.MenuSelectedFcn = {@MenuSelected,m,v};
    addlistener(m,'ImgStructChanged',@(o,e) updateTab5(m,v));
    set(v.btnTab5{1},'Callback',@(o,e) updateTab5(m,v))
    set(v.popupStringsImg(1),'Callback',{@selectImg,m,v})
    set(v.popupStringsImg(2),'Callback',@(o,e) updateTab5(m,v))
    
    updateTab5(m,v);
    
    % projector menu callbacks
    v.menuBar.ProjectorConnect.MenuSelectedFcn = {@MenuProjectorConnect,m,v};
    v.menuBar.ProjectorON.MenuSelectedFcn = {@MenuProjectorON,m,v};
    v.menuBar.ProjectorOFF.MenuSelectedFcn = {@MenuProjectorOFF,m,v};
    
    % BBB listener
    addlistener(m.TCP, 'PinPROJ_ON_EXT','PostSet', @(o,e) updateProjectorMenu(m.TCP,v));
    addlistener(m.TCP, 'DISP_CTRLconf','PostSet', @(o,e) updateProjectorMenu(m.TCP,v));  
    addlistener(m.TCP, 'isopen','PostSet', @(o,e) updateProjectorMenu(m.TCP,v));
%   
    if startServer
    output_port = 3000; number_of_retries = 300;
    import java.net.ServerSocket
    import java.io.*

    retry             = 0;
    server_socket  = [];
    output_socket  = [];
    while true
        retry = retry + 1;
        try
%             if ((number_of_retries > 0) && (retry > number_of_retries))
%                 fprintf(1, 'Too many retries\n');
%                 break;
%             end
            fprintf(1, ['Try %d waiting for client to connect to this ' ...
                        'host on port : %d\n'], retry, output_port);
            % wait for 1 second for client to connect server socket
            server_socket = ServerSocket(output_port);
            server_socket.setSoTimeout(1000);
            output_socket = server_socket.accept;
            fprintf(1, 'Client connected\n');
            client_stream   = output_socket.getInputStream;
            d_input_stream = DataInputStream(client_stream);
                        % get a buffered data input stream from the socket
            
            fprintf(1, 'Connected to server\n');
            % read data from the socket - wait a short time first
            pause(0.5);
            bytes_available = client_stream.available;
            fprintf(1, 'Reading %d bytes\n', bytes_available);
            
            message = zeros(1, bytes_available, 'uint8');
            for i = 1:bytes_available
                message(i) = d_input_stream.readByte;
            end
            
            message = char(message);
            fprintf(message)
            if strcmp(message,'terminator')
                fprintf('next Img')
                m.selectImg('terminator.jpg')
            else
                fprintf('next Img')
                m.selectImg('iRobot.jpg')
            end
            % cleanup
            d_input_stream.close()
            d_output_stream = DataOutputStream(client_stream);
            pause(0.5);
            d_output_stream.write(native2unicode(uint8(message)))
            pause(0.5);
            d_output_stream.close()
            
            break;
            
            % clean up
            server_socket.close;
            output_socket.close;
            break;
            
        catch
            if ~isempty(server_socket)
                server_socket.close
            end
            if ~isempty(output_socket)
                output_socket.close
            end
            % pause before retrying
            pause(1);
        end
    end
    end
end
    

%% init subfunctions
function ButtonsAssignCallbacks(Tab,m)
    for i = 1:length(Tab.Children)
        callbackFcnName = strcat('@',Tab.Tag,'Btn_',Tab.Children(i).Tag);
        set(Tab.Children(i),'Callback',{str2func(callbackFcnName),m})
    end
    for i = [2:3,5:8]
        set(Tab.Children(i),'enable','off')
    end
end
%% window
function onFigResize(~,~,v,m)
    v.ReSizingWindowFcn(m.libraryImgs(m.assigned2Imgs.ToDisplay).imgSize); 
end

function onFigExit(hObject,~,v,model) 
selection = questdlg('Do yo want to close both figure and server?',...
      'Close Request Function',...
      'Yes Both','Only Figure','No','Yes Both'); 
   switch selection 
      case 'Yes Both'
         delete(model)
         delete(v.fig)
         clear v
         disp('Done closing')
       case 'Only Figure'
         delete(v.fig)
         clear v
         disp('Done closing')
      case 'No'   
          disp('Exit cancelled')
   end
end
%% mouse
function onButtonDown(hObject,~,v,m)
% img callback = hit test done by Matlab
if v.mouse.buttonDown == 0%handles.mouse_btn == 0   
    v.mouse.buttonDown = 1; %handles.mouse_btn = 1;
    v.mouse.buttonPulse = 1; % handles.get_point = 1    
end
mouseLogic(v,m)
end
function onButtonUp(hObject,~,v,m)
v.mouse.buttonDown = 0;
v.mouse.holdAndDrag = 0;
% mouseLogic(v,m)
m.moveCtrlPoint(v.mouse.position,'clear');
end
function onMouseMovement(hObject,~,v,m)
% get position inside figure axis
v.mouse.position = round(v.ax.CurrentPoint(1,1:2));

X = v.img.XData;
Y = v.img.YData;

% set to NaN if outside of figure axis
if any(v.mouse.position > [X(2),Y(2)] | v.mouse.position < [X(1),Y(1)])
    v.mouse.position = [NaN,NaN];
end

m.closestCtrlPoint(v.mouse.position);

mouseLogic(v,m)
end

function mouseLogic(v,m)
v.mouse;
if v.mouse.buttonDown == 1 && all(~isnan( v.mouse.position))
    % is a click or hold?
    if v.mouse.buttonPulse == 1
        clickPosition = v.mouse.position;
        % ctrl button pressed within image?
        if v.key.control == 1 
            % add new point
            if isempty(m.selectedCtrlPoints) || m.ctrlPoints(m.selectedCtrlPoints).allowEdit == 1
                m.addCtrlPoint(clickPosition(1),clickPosition(2))
            else
                fprintf('Control points locked for editing, go to MLS settings')
            end
            %fprintf('\nCtrl & (%d,%d)\n',clickPosition(1),clickPosition(2))
        elseif v.key.shift == 1
            % remove closest point within range
            m.deleteClosestPoint()
            %fprintf('\nShift & (%d,%d)\n',clickPosition(1),clickPosition(2))
        else
            %printHelp(clickPosition)
            m.moveCtrlPoint(v.mouse.position,'hold');
        end
    end
        m.moveCtrlPoint(v.mouse.position)

    v.mouse.buttonPulse = 0; 
end

end
function printHelp(mousePos)
if any(isnan(mousePos))
    fprintf('Click on image')
else
    fprintf('Mouse position: (%d,%d)',mousePos(1),mousePos(2))
end
% prints how to control the GUI if no p or q points are added
fprintf('\nAdd control points first!\n')
fprintf('-------------------------\n')
fprintf('Ctrl + Mouse Click to add control point\n')
fprintf('Shift + Mouse Click to remove point\n')
fprintf('Mouse click + Drag to add deform destination point\n')
end
%% keypress
function keyPress(hObject,eventdata,v)
% compare button eventdata key with v.key structure names {'control';'shift'}
if any(strcmp(fieldnames(v.key),eventdata.Key))
    % if 'KeyPress' than 1, if 'KeyRelease' than 0
    new_stat = strcmp(eventdata.EventName,'KeyPress');
    if new_stat ~= v.key.(eventdata.Key)
        v.key.(eventdata.Key) = new_stat;
    end
end
end
function onSlide(hObject,~,model)
    % update model (which in turn trigger event that updates view)
    value = get(hObject,'Value');
    model.changeF(value);
    disp('Slider...')
end




%% hTab1 - calibration tab
function tab1Btn_1(hObject,~,model)
    disp('Button 1 pressed')
    % aquire QR corner img
    
end
function tab1Btn_2(hObject,~,model)
    
    disp('Button 2 pressed')
end
function tab1Btn_3(hObject,~,model)
disp('Button 3 pressed')
%         for i = 1:100
%             model.selectImg(model.sourceImg.id+1)
% 
%         pause(1/20)
%     end
    
end
function tab1Btn_4(hObject,~,model)
    disp('Button 4 pressed')
end
function tab1Btn_5(hObject,~,model)
    disp('Button 5 pressed')
end
function tab1Btn_6(hObject,~,model)
    disp('Button 6 pressed')
end
function tab1Btn_7(hObject,~,model)
    disp('Button 7 pressed')
end
function tab1Btn_8(hObject,~,model)
    disp('Button 8 pressed')
end


% %% hTab2
% function tab2Btn_1(hObject,~,model)
%     model.prepDLP
%     disp('Button 2 pressed')
% end
% function tab2Btn_2(hObject,~,model)
%     model.closeSocket
%     disp('Button 2 pressed')
% end
% %% 
function sendImg(model)
    if ~isempty(model.clientSocket)
        model.sendImg

    end
end
%% hTab3
function saveCrtlPoints(hObject,~,v,model)
name = '';
if ~strcmp(hObject.String,'Save')
    name = string(inputdlg('Please enter a name:',hObject.String,[1,50]));
end
model.saveDispExpressions(hObject.String,name);
end
function selectCtrlPoints(hObject,~,model)
model.selectDispCtrlPoint(hObject.Value)
end
function animateMLS(hObject,~,v,model)
    parameters = {v.popupStrings(1).Value,0,v.popupStrings(2).Value,1,0,1,20,10,0};
    model.animateMLS(parameters)
end

%% MENU
function MenuSelected(src,event,m,v)
%x = [('PQ','*.mat','Select One or More Files','MultiSelect','on')];
path = erase(mfilename('fullpath'),extractAfter(mfilename('fullpath'),'matlab_code'));
switch src.Text
    case 'Import control points'
        path = fullfile(path,'Model','SavedCtrlPoints','*.mat');
        [file,path] = uigetfile(path,'Select One or More Files to import Control Points from','MultiSelect','on');
        if file ~= 0
            m.loadCtrlPoints(path,file)
        end
        disp(file);
    case 'Export control points'
        if isempty(m.selectedCtrlPoints)
                helpdlg({'No control points selected','add control points'},'Error: Export control points');
        else
            id = m.selectedCtrlPoints;
            n = [erase(m.ctrlPoints(id).tag,'.mat'),'.mat'];
            path = fullfile(path,'Model','SavedCtrlPoints',n);
            [file,path] = uiputfile(path,'Select PQ export file name and location');
            if file ~= 0
                m.saveCtrlPoints(path,file)
            end
        end
    case 'Import Image'
        path = fullfile(path,'Model','img');
        % import dialog, filter allow only matlab supported img file formats
        [file,path] = uigetfile(v.menuBar.ImgImportIn,'Select Img To Import',path,'MultiSelect','on');
        % uigetfile(path,'Select One or More Files to import Control Points from','MultiSelect','on');
        if ~isequal(file,0)
            m.loadImg(path,file)
        end
    case 'Load image folder'
        path = fullfile(path,'Model','SavedCtrlPoints','*.mat');
        path = uigetdir
        
    otherwise
        disp('Unknown')
end


end

%% Projector menu
function updateProjectorMenu(TCP,v)
if TCP.isopen
    s = 'Status: Connected';
    if TCP.PinPROJ_ON_EXT && TCP.DISP_CTRLconf
        s = strcat(s,' - Light On');
    else
        s = strcat(s,' - Light Off');
    end    
    v.menuBar.ProjectorConnect.Text = 'Disconnect';
    v.menuBar.ProjectorON.Enable = 'on';
    v.menuBar.ProjectorOFF.Enable = 'on';
    v.menuBar.ProjectorSend.Enable = 'on';
    v.menuBar.ProjectorSynch.Enable = 'on';
else
    s = 'Status: Disconected - simulation mode';
    v.menuBar.ProjectorConnect.Text = 'Connect';
    v.menuBar.ProjectorON.Enable = 'off';
    v.menuBar.ProjectorOFF.Enable = 'off';
    v.menuBar.ProjectorSend.Enable = 'off';
    v.menuBar.ProjectorSynch.Enable = 'off';
end
set(v.menuBar.ProjectorStatus,'Text',s)
end
function MenuProjectorConnect(src,event,m,v)
if m.TCP.isopen
    m.TCP.close;
else
    m.TCP.open;
end
end
function MenuProjectorON(src,event,m,v)
if m.TCP.isopen
    m.TCP.TurnONProjector;
end
end

function MenuProjectorOFF(src,event,m,v)
if m.TCP.isopen
    m.TCP.TurnOFFProjector;
end
end

%% Tab 5
% callback GUI filter chkeckbox -> create filter

% callback GUI select img -> Model
% - decipher filter, send library id - Model changes ID + imread if
%   neccesary



% listener Model -> GUI show (filtered)
function updateTab5(m,v)

selectedID = m.assigned2Imgs.ToDisplay;

% if filter on
if v.btnTab5{1}.Value
    % get string to search for in library
    filterString = v.popupStringsImg(2).String(v.popupStringsImg(2).Value);
    % find the indexes containig string
    filteredID = find(cell2mat(cellfun(@(x)  any(strcmp(x,filterString)),{m.libraryImgs(:).usedIn},'UniformOutput',false)));
else
    % else all indexes
    filteredID = 1:length(m.libraryImgs);
end   

% update strings 
filteredNames = {m.libraryImgs(filteredID).Name};

    
    % if displayed image not in filtered
    if ~any(ismember(selectedID,filteredID))
        filteredID = [selectedID,filteredID];
        filteredNames = [{sprintf('%s*',m.libraryImgs(selectedID).Name)},filteredNames(:)'];
    end
    % transform id
    selectedID = find(filteredID == selectedID);

filterableKeys = unique(vertcat(m.libraryImgs(:).usedIn));

% save
v.btnTab5{2}.Key = filteredID;

v.popupStringsImg(2).String = filterableKeys;
v.popupStringsImg(1).String = filteredNames;
v.popupStringsImg(1).Value = selectedID;
end

function selectImg(src,event,m,v)
% pass key to dictrionary -> library index
IDKeys = 1:length(m.libraryImgs);
if ~isempty(v.btnTab5{2})
    IDKeys = v.btnTab5{2}.Key;
end

m.displayImg(IDKeys(src.Value));
end

function checkBoxFilter(src,~,v,m)
%obj.popupStringsImg(1).String
v.btnTab5{2}.customP = find(cell2mat(cellfun(@(x)  any(strcmp(x,'task2')),{m.libraryImgs(:).usedIn},'UniformOutput',false)));
%v.btnTab5{2}.customP = find({m.libraryImgs(:).usedIn},'task0');
v.updatePopupImg;
end