function paths = helperBLEMeshFloodingSimulation(totalNodes, meshNodes, meshNetworkPlot, simulationTime, srcDstPairs, ...
    ttl, relayNodeIDs, failedNodeIDs, enableVisualization)
%helperBLEMeshFloodingSimulation Run Bluetooth mesh network flooding
%simulation
%   PATHS = helperBLEMeshFloodingSimulation(MESHNODES, MESHNETWORKPLOT,
%   SIMULATIONTIME, SRCDSTPAIRS, TTL, RELAYNODEIDS, FAILEDNODEIDS,
%   ENABLEVISUALIZATION) Runs the simulation and returns the path between
%   specified source and destination pairs.
%
%   PATHS is a cell array containing the paths for given SRCDSTPAIRS.
%
%   TOTALNODES is an integer value.
%
%   MESHNODES is a vector of objects of type helperBLEMeshNetworkNode.
%
%   MESHNETWORKPLOT is an object of type helperBLEMeshVisualizeNetwork.
%
%   SIMULATIONTIME is an integer value.
%
%   SRCDSTPAIRS is an n-by-2 array containing the source and destination
%   pairs.
%
%   TTL indicates time to live (TTL) value used in the message publication
%   between source and destination pairs.
%
%   RELAYNODEIDS, FAILEDNODEIDS are integer vectors.
%
%   ENABLEVISUALIZATION is a boolean flag.
%
%   Note: In the visualization figure, maximum three paths (first three)
%   are considered for display.

%   Copyright 2019 The MathWorks, Inc.

% Configuration
nodeStates = zeros(1, totalNodes);
paths = cell(1, totalNodes);
pathIndex = 1;
% Fetch unique source and destination pairs
uniqueSrcDstNodes = zeros(numel(srcDstPairs)/2, 2);
uniqueIdx = 1;
for pairIdx = 1:(numel(srcDstPairs)/2)
    isUniquePair = true;
    pair = srcDstPairs(pairIdx, :);
    for uniquePairIdx = 1:(numel(uniqueSrcDstNodes)/2)
        if unique(uniqueSrcDstNodes(uniquePairIdx, :) == pair) == 1
            isUniquePair = false;
            break;
        end
    end
    if isUniquePair
        uniqueSrcDstNodes(uniqueIdx, :) = pair;
        uniqueIdx = uniqueIdx + 1;
    end
end
srcDstPairs = uniqueSrcDstNodes(1:uniqueIdx-1, :);

% Assign source and destination pairs to visualization object
if numel(srcDstPairs)/2 > 3
    % Consider first three pairs for visualization
    meshNetworkPlot.SrcDstPairs = srcDstPairs(1:3, :);
else
    meshNetworkPlot.SrcDstPairs = srcDstPairs;
end

% Time step, in milliseconds
timeStep = 1;
timeStepUs = timeStep * 1e3;

for idx = 1:totalNodes
    % Relay feature
    if any(idx == relayNodeIDs)
        meshNodes(idx).NetworkLayer.Relay = true;
    else
        meshNodes(idx).NetworkLayer.Relay = false;
    end
    
    % Node failures
    if any(idx == failedNodeIDs)
        meshNodes(idx).State = 'Off';
    else
        meshNodes(idx).State = 'On';
    end
    
    % Node states
    if strcmp(meshNodes(idx).State, 'On')
        % Relay node
        if meshNodes(idx).NetworkLayer.Relay
            nodeStates(idx) = 2;
        % End node
        else
            nodeStates(idx) = 1;
        end
        
    % Failed node
    else
        nodeStates(idx) = 0;
    end
end

% Update nodes state in the visualization
meshNetworkPlot.NodeState = nodeStates;

if enableVisualization
    % Visualize the network
    meshNetworkPlot.createNetwork();
    % Simulation stop time
    meshNetworkPlot.SimulationTime = simulationTime;
    % Initialize progress bar with zero percentage
    updateProgressBar(meshNetworkPlot, 0);
end

% Random application data at source nodes
for idx = 1:(numel(srcDstPairs)/2)
    srcNode = srcDstPairs(idx, 1);
    dstNode = srcDstPairs(idx, 2);
    
    % Configure source and destination addresses
    sourceAddress = dec2hex(srcNode, 4);
    destinationAddress = dec2hex(dstNode, 4);
    % Configure application data at source node (random data)
    lowerTransportPDU = randi([0 255], 1, randi([1 10]))';
    % Configure ctl (access message)
    ctl = 0;
    
    % Push lower transport PDU into network layer input queue
    meshNodes(srcNode).NetworkLayer.pushUpperLayerPDU(sourceAddress, ...
        destinationAddress, ttl(idx), lowerTransportPDU, ctl);
end

% Network messages originated at each node in the mesh network
meshNetworkChannel = cell(totalNodes, 3);
for idx = 1:totalNodes
    meshNetworkChannel{idx, 1} = meshNodes(idx);
end

% Buffer to store all the message exchanges between mesh nodes within the
% network
maxTuples = totalNodes*totalNodes;
totalTuples = cell(maxTuples, 3);
tupleIndex = 1;

% Run simulation
for stepTimer = 1:timeStep:simulationTime
    % Buffer to store message transmissions as tuples (srcNode, dstNode)
    messageTuples = zeros(totalNodes*2, 3);
    
    % Index for message transmissions
    messageTransmissionsIdx = 1;
    
    pathPairIDs = cell(totalNodes, 3);
    
    pathPairIndex = 1;
    
    destinationFlag = false;
    
    % Update each node state
    for nodeIdx = 1:totalNodes
        curNode = meshNodes(nodeIdx);
        % Get indexes of the current node and indexes of messages within
        % the vicinity of 'curNode'
        [curIdx, vicinityNodeIdxs] = helperBLEMeshChannelMessage(...
            curNode, meshNetworkChannel, meshNetworkPlot.Positions, ...
            meshNetworkPlot.VicinityRange);
        
        % Pass empty message to 'curNode'
        if isempty(vicinityNodeIdxs)
            % Pass message from vicinity node to 'curNode'
            meshNetworkChannel{curIdx, 3} = curNode.run(timeStepUs, []);
        else
            % Update mesh message of the current node (when messages are
            % transmitted from more than one vicinity node, consider
            % message from one of the node)
            meshNetworkChannel{curIdx, 3} = curNode.run(timeStepUs, ...
                meshNetworkChannel{vicinityNodeIdxs(1), 2});
            octets = curNode.NetworkLayer.pMessageIdentifier';
            % Calculate unique integer from the message identifier at the
            % network layer
            msgID = 0;
            for idx = 1 : 5
                msgID = msgID + (octets(end-(idx-1))*(10^(idx-1)));
            end
            messageTuples(messageTransmissionsIdx, :) = [...
                meshNetworkChannel{vicinityNodeIdxs(1), 1}.Identifier ...
                curNode.Identifier msgID];
            
            % Update index
            messageTransmissionsIdx = messageTransmissionsIdx + 1;
            
            % Message processed at 'curNode'
            if curNode.NetworkLayer.pMessageDestinedToNode || ...
                    curNode.NetworkLayer.pMessageRelayedFromNode
                % Update the buffer with recent message exchanges between
                % nodes within the mesh network
                totalTuples(tupleIndex, :) = {...
                    messageTuples(messageTransmissionsIdx-1, 1) ...
                    messageTuples(messageTransmissionsIdx-1, 2) ...
                    msgID};
                
                if tupleIndex > maxTuples
                    tupleIndex = 1;
                else
                    tupleIndex = tupleIndex + 1;
                end
            end
            
            % Set flag if destination reached
            if curNode.NetworkLayer.pMessageDestinedToNode
                destinationFlag = true;
                
                % Store the source-destination pair IDs
                pathPairIDs{pathPairIndex, 1} = hex2dec(curNode.NetworkLayer.pDecodedSRC);
                pathPairIDs{pathPairIndex, 2} = curNode.Identifier;
                pathPairIDs{pathPairIndex, 3} = msgID;
                pathPairIndex = pathPairIndex + 1;
            end
        end
    end
    
    % Visualize transactions
    if messageTransmissionsIdx > 1 && enableVisualization
        % Visualize message transmissions
        messageTransmissions(meshNetworkPlot, messageTuples(1:messageTransmissionsIdx-1, :));
        pause(0.25);
    end
    
    % Update the network messages
    meshNetworkChannel(:, 2) = meshNetworkChannel(:, 3);
    meshNetworkChannel(:, 3) = cell(totalNodes, 1);
    
    % Highlight destination
    if destinationFlag
        % Update destination IDs
        pathPairIDs = pathPairIDs(1:pathPairIndex-1, :);
    
        % Blink destination node in the visualization
        for pathIdx = 1:pathPairIndex-1
            % Fetch source-destination pair
            srcNode = pathPairIDs{pathIdx, 1};
            dstNode = pathPairIDs{pathIdx, 2};
            pathID = pathPairIDs{pathIdx, 3};
            maxIndex = min(3, numel(srcDstPairs)/2);
            
            if enableVisualization && any(srcNode == srcDstPairs(1:maxIndex, 1)) && ...
                    any(dstNode == srcDstPairs(1:maxIndex, 2))
                meshNetworkPlot.highlightDstNode(dstNode);
            end
            
            % Filter tuples by source-destination pair
            tupleSet = zeros(totalNodes*2, 2);
            tupleSetIndex = 1;
            for tupleIdx = 1:maxTuples
                if ~isempty(totalTuples{tupleIdx, 1}) && all(totalTuples{tupleIdx, 3} == pathID)
                    tupleSet(tupleSetIndex, :) = [totalTuples{tupleIdx, 1} totalTuples{tupleIdx, 2}];
                    tupleSetIndex = tupleSetIndex + 1;
                end
            end
            
            % Path derivation from source node to destination node
            path = helperBLEMeshPath(srcNode, dstNode, meshNodes, tupleSet(1:tupleSetIndex-1, :));
            
            % Store path
            paths(pathIndex) = {path};
            pathIndex = pathIndex + 1;
            if enableVisualization && all(path ~= -1) && any(srcNode == srcDstPairs(1:maxIndex, 1)) && ...
                    any(dstNode == srcDstPairs(1:maxIndex, 2))
                meshNetworkPlot.showPath(path);
            end
        end
    end
    
    % Update progress bar
    if enableVisualization && (rem((stepTimer/simulationTime)*100, 1) == 0)
        updateProgressBar(meshNetworkPlot, stepTimer);
    end
end

% Cell array of paths (each element represent path between source to
% destination nodes)
paths = paths(1:pathIndex-1);

% Update cell array of paths in the order of srcDstPairs specified
pathInOrder = cell(1, numel(srcDstPairs)/2);
for pathIdx = 1:numel(paths)
    srcDstForPath = [paths{pathIdx}(1) paths{pathIdx}(end)];
    for pairIdx = 1:numel(srcDstPairs)/2
        if unique(srcDstPairs(pairIdx, :) == srcDstForPath) == 1
            pathInOrder{pairIdx} = paths{pathIdx};
        end
    end
end

for idx = 1:numel(pathInOrder)
    if isempty(pathInOrder{idx})
        pathInOrder{idx} = -1;
    end
end
paths = pathInOrder;

% Collect statistics of each node
nodeStatistics = cell(1, totalNodes);
for idx = 1:totalNodes
    if strcmp(meshNodes(idx).State, 'On')
        if meshNodes(idx).NetworkLayer.Relay
            type = 'Relay';
        else
            type = 'End';
        end
        stats = {'Node ID', meshNodes(idx).Identifier, ...
            'Node Type', type, ...
            'Tx', meshNodes(idx).NetworkLayer.MsgsTransmitted, ...
            'Rx', meshNodes(idx).NetworkLayer.MsgsReceived, ...
            'AppRx', meshNodes(idx).NetworkLayer.MsgsForAppRx, ...
            'Relayed', meshNodes(idx).NetworkLayer.MsgsRelayed, ...
            'Dropped', meshNodes(idx).NetworkLayer.MsgsDropped};
        nodeStatistics{idx} = stats;
    end
end

if enableVisualization
    % Update statistics in visualization object
    meshNetworkPlot.updateNodeStatistics(nodeStatistics);
end
end
