% Used to submit custom function and data to Condor, 
% then returns output back to users.
% INPUT:
% - XStart: double type, start element of the input array.
% - XEnd: double type, end element of the input array.
% - NumberOfPoints: total numbers of the elements of the input array. (including both XStart and XEnd)
% - exeFile: custom compiled function.
% OUTPUT: Output data ,which are returned by Matlab, are represented by
% graph
function DrawGraphMO(XStart, XEnd, NumberOfPoints, cusFunction)

try
    
% validate input data
status = CheckInput(XStart, XEnd, NumberOfPoints, cusFunction);
if(status == 1)
    return;
end

% initilize Matlab variables
status = InitializeVariables(XStart, XEnd, NumberOfPoints);
if(status == 1)
    return;
end

% calculate output values
status = SubmitDataToFunction(cusFunction, NumberOfPoints);
if (status == 1)
    return;
end

% generate log file, create AppLog.xml
status = CreateLogFile(NumberOfPoints, cusFunction);
if(status == 1)
    return;
end

% draw graph with the points are made from elements of XArray and YArray
status = DrawCurrentData(NumberOfPoints);
if(status == 1)
    return;
end


catch err
      % display message when we have exceptions
     msg = getReport(err);
     disp(msg);
     % delete waitbar
     global progressBar;
     if(ishandle(progressBar) == 1)
         delete(progressBar);
     end
end

end

% draw current new points from XArray and YArray
function [status] = DrawCurrentData(NumberOfPoints)
    status = 0;
try
    global progressBar;
    global XToDraw;
    global YToDraw;
    global XArray;
    global YArray;
    % create progress bar
    progressBar = waitbar(0, 'Please wait...', 'CreateCancelBtn', 'setappdata(gcf, ''abort'',1);', 'WindowStyle', 'modal');
        % set bar always on top
            %frames = java.awt.Frame.getFrames();
            %frames(end).setAlwaysOnTop(1);
        % set initial value for param 'abort'
        setappdata(progressBar,'abort',0);
        
    
    % process every 5 points
    global bunchSize;
     
    % start index of array
    count = 1;
    
    % repeat the loop until all jobs completed
    while true
        % get next 5 jobs
        rear = count + bunchSize - 1;
        if(rear > NumberOfPoints)
            rear = NumberOfPoints;
        end
        
        XToDraw = XArray(count: rear);
        YToDraw = YArray(count: rear);
        
        % write new points to log file
        status = WriteLog(XToDraw, YToDraw, rear, NumberOfPoints);
        if(status == 1)
            return;
        end
        % draw new points to graph
        status = PlotXY(XToDraw, YToDraw, rear, NumberOfPoints);
        hold on;
        if(status == 1)
            return;
        end
        
        
        count = rear + 1;
        
        % display progress bar
        waitbar(rear/NumberOfPoints, progressBar, [sprintf('Completed: %.2f', rear*100/NumberOfPoints), '%']);
        % when click on Cancel button, stop here
        if(getappdata(progressBar, 'abort') == 1)
            break;
        end;
        
        
        
        % if all the points are drawn, we are done
        if(count >= NumberOfPoints)
            break;
        end
        
        % delay 1s
        pause(1);
    end
    % remove progress bar
    delete(progressBar);
    
catch err
    disp('ERROR: Cannot draw current data.');
    status = 1;
end
    

end

% log data into file AppLog.xml
function [status] = WriteLog(XToDraw, YToDraw, currentIndex, NumberOfPoints)
    % log completed jobs
    status = LogCompletedJobs(XToDraw, YToDraw);
    if(status == 1)
        return;
    end
    % log unprocessed jobs
    status = LogUnprocessedJobs(currentIndex, NumberOfPoints);
    if(status == 1)
        return;
    end
    % remove extra white spaces in log file AppLog.xml
    status = RemoveSpace('AppLog.xml');
    % exit function if fail
    if(status == 1)
        return;
    end

end

%The extra whitespace is introduced by the Saxon XML processor, 
% which is the library that MATLAB uses for the XML processing.
function [status] = LogCompletedJobs(XToDraw, YToDraw)
    status = 0;
    
try
    % write the completed jobs to AppLog.xml
    xDoc = xmlread('AppLog.xml');
    % get the 'LogInfo' node
    xRoot = xDoc.getDocumentElement;
    % get the 'CompletedJobs' node
    completedJobNode = xRoot.getChildNodes.item(1);
    % append child to completeJob node
    for i=1: length(XToDraw)
        % create Job node
        jobNode = xDoc.createElement('CompletedJob');
            % create X node
            xNode = xDoc.createElement('X');
            % add value to X node
            xNode.appendChild(xDoc.createTextNode(num2str(XToDraw(i))));
            jobNode.appendChild(xNode);
            % create Y node
            yNode = xDoc.createElement('Y');
            % add value to Y node
            yNode.appendChild(xDoc.createTextNode(num2str(YToDraw(i))));
            jobNode.appendChild(yNode);
        % append Job node to CompletedJob node
        completedJobNode.appendChild(jobNode);      
    end
    
     % write data to file AppLog.xml
     xmlwrite('AppLog.xml',xDoc);
    
catch err
    status = 1;
    disp('ERROR: Cannot write completed jobs to AppLog.xml');
    % display message when we have exceptions
     msg = getReport(err);
     disp(msg);
end
end    

function [status] = LogUnprocessedJobs(currentIndex, NumberOfPoints)
    status = 0;
    global XArray;
    global YArray;
try
    % write the unprocessed jobs to AppLog.xml
    xDoc = xmlread('AppLog.xml');
    % get the node 'LogInfo'
    xRoot = xDoc.getDocumentElement;
    % get the node 'UnprocessedJobs'
    unprocessedJobNode = xRoot.getChildNodes.item(5);
    % remove node UnprocessedJobs and all its children
    xRoot.removeChild(unprocessedJobNode);
    % create new node UnprocessedJobs
    unprocessedJobNode = xDoc.createElement('UnprocessedJobs');
    % append child to node UnprocessedJobs
    for i = currentIndex + 1: NumberOfPoints
            % create UnprocessedJob node
            jobNode = xDoc.createElement('UnprocessedJob');
            % create X node
            xNode = xDoc.createElement('X');
            % add value to X node
            xNode.appendChild(xDoc.createTextNode(num2str(XArray(i))));
            jobNode.appendChild(xNode);
             % create Y node
            yNode = xDoc.createElement('Y');
            % add value to Y node
            yNode.appendChild(xDoc.createTextNode(num2str(YArray(i))));
            jobNode.appendChild(yNode);
        % append Job node to UnprocessedJobs node
        unprocessedJobNode.appendChild(jobNode);
       
    end
    % append new node UnprocessedJobs to document
    xRoot.appendChild(unprocessedJobNode);
    % write data to file AppLog.xml
    xmlwrite('AppLog.xml', xDoc);
    
catch err
    status = 1;
    disp('ERROR: Cannot write unprocessed jobs to AppLog.xml');
    % display message when we have exceptions
     msg = getReport(err);
     disp(msg);
end
end


% draw current new points to screen
function [status] = PlotXY(XToDraw, YToDraw, currentIndex, NumberOfPoints)

status = 0;

try
    strComplete = sprintf('Percentage of Completion: %.2f%', currentIndex*100/NumberOfPoints);
        
    plot(XToDraw, YToDraw, '.-r','LineStyle','none', 'MarkerFaceColor','r');
    title(strComplete);
    xlabel('X value');
    ylabel('Y value');

catch err
    disp('ERROR: Cannot draw new points.');
    status = 1;
end
end

% remove spaces in the log file
function [status] = RemoveSpace(filePath)
    status = 0;
    try
        % Read file.
        fId = fopen(filePath, 'r');
        fileContents = fread(fId, '*char')';
        fclose(fId);

        % Write new file.
        fId = fopen(filePath, 'w');
        % Remove extra lines.
        fwrite(fId, regexprep(fileContents, '\n\s*\n', '\n'));
        fclose(fId);
    catch err
        msg = sprintf('ERROR: Error when remove white spaces in the file %s', filePath);
        disp(msg);
        status = 1;
    end
end




% create application log file
function [status] = CreateLogFile(NumberOfPoints, cusFunction)
status = 0;
try
     docNode = com.mathworks.xml.XMLUtils.createDocument('LogInfo');
     docRootNode = docNode.getDocumentElement;
     % create attribute TotalPoints = 1000
     docRootNode.setAttribute('TotalJobs', num2str(NumberOfPoints));
     % create attribute Function= 'a.out'
     docRootNode.setAttribute('Function', cusFunction);
     
     thisElement = docNode.createElement('CompletedJobs');
     docRootNode.appendChild(thisElement); 
     
     thisElement = docNode.createElement('FailedJobs');
     docRootNode.appendChild(thisElement);
     
     thisElement = docNode.createElement('UnprocessedJobs');
     docRootNode.appendChild(thisElement);
     
     % write data to file AppLog.xml
     xmlwrite('AppLog.xml',docNode);
     
catch err
        status = 1;
        disp('ERROR: Cannot create log file!');
end
end


function [status] = SubmitDataToFunction(cusFunction, NumberOfPoints)
status = 0;
try
    % get the function handle from its name
    funcHandle = str2func(cusFunction);
    % save output values to YArray
    global XArray;
    global YArray;
    YArray = funcHandle(XArray);
    
catch err
    disp('ERROR: Cannot sumbmit input values to custom function.');
    status = 1;
end
    

end

function [status] = InitializeVariables(XStart, XEnd, NumberOfPoints)
status = 0;
try
     % create array of points X
     global XArray;   
     XArray = linspace(XStart, XEnd, NumberOfPoints);
     % swap elements in XArray so we can see the special points first
     XArray = SwapElements(XArray);
     % list of completed output results
     global YArray;
     YArray = zeros(1, NumberOfPoints);
     % progress bar
     global progressBar;
     % arrays to draw
     global XToDraw;
     XToDraw = [];
     global YToDraw;
     YToDraw = [];
     % bunch of points that are processed at one time
     global bunchSize;
     bunchSize = 5;
catch err
     disp('ERROR: Cannot initialize Matlab variables.');
     status = 1;
end

end

% Swap the elements of an array, so we can get the special elements first. 
% For example: 
%  input array: [1 2 3 4 5 6 7 8]
%  output array: [1 4 8 2 6 3 5 7]
% INPUT:
% - inList: input array
% OUTPUT:
% - outList: an array. This is the new array that contains all the elements but in a new order.
function [outList] = SwapElements(inList)
    n = length(inList);
    checkedList = zeros(1,n);
    count = 2;
    outList = [inList(1) inList(n)];
    checkedList(1) = 1;
    checkedList(n) = 1;
    
    while(sum(checkedList) < n)
       part = int32(n/count);
       if(part == 0)
           break;
       end
       range = int32(n/count);
       while(part <= n)
           if(checkedList(part) == 0)
               outList = [outList inList(part)];
               checkedList(part) = 1;
           end
           part = range + part;
       end
       count = count * 2;
                
    end

end

% Validate the input data of function DrawGraph
% OUTPUT:
%   0: valid
%   1: invalid
function [status] = CheckInput(XStart, XEnd, NumberOfPoints, cusFunction)
    status = 0;
    try
        if(XStart > XEnd)

            disp('Start point must be less than or equal end point.');
            status = 1;
            return;

        end
         % check the lower bound and upper bound for NumberOfPoints
        if(NumberOfPoints <=0 || NumberOfPoints > 50000)

            disp('Number of points must be a positive integer from 1 to 50,000');
            status = 1;
            return;

        end


        if(mod(NumberOfPoints,1) ~=0)

            disp('Number of points must be a positive integer.');
            status = 1;
            return;
        end

        if(exist([cusFunction, '.m']) == 0)
            disp('ERROR: Can not find Matlab function.');
            status = 1;
            return;

        end
    catch err
        disp('ERROR: Invalid input data.');
        status = 1;
    end
end