%% Session
% Singleton class that wraps styler and manages a group of Model Slicer
% session

%   Copyright 2013-2020 The MathWorks, Inc.

classdef Session < handle
    properties
        rootModelH = [];
        refModels = [];
        
        % This is a list of 1-3 overlays that are currently displayed on
        % the editor
        visibleOverlays = [];       
        
        % Organize overlay in a map based on slice Index so they can be
        % systematically cleared when closing the model slicer
        overlays = [];
        overlapRules = [];
        atomicChartOverlapRules = [];
        chartRuleMap = [];
        
        % Icon files used to create legends
        overlapIconFile;
        multiOverlapIconFile;
        
        % Notifier for creating model notifications
        notifier = [];
        
        % Fader Style groups
        sliceFader =[];
        editFader = [];
                
    end
    
    properties (GetAccess = public, SetAccess = private)
        % Overlap Highlight color
        overlapColor = [];
    end
    
    methods        
        function this = Session(slConfig)
            this.rootModelH = slConfig.modelH;
            this.overlays = containers.Map('KeyType', 'uint32', ...
                                            'ValueType', 'any');
            this.chartRuleMap = containers.Map('KeyType','double','ValueType','any');
                             
            this.overlapIconFile = this.make_overlap_icon();
            this.multiOverlapIconFile = this.make_multioverlap_icon();
        end

        function overlayAdded(this, overlay)
            if isempty(this.visibleOverlays)
                this.visibleOverlays = overlay;
            else
                if ~ismember(overlay, this.visibleOverlays)
                    this.visibleOverlays(end+1) = overlay;
                end
            end
        end
        
        % This should remove the overlay and destroy all the style groups
        function overlayRemoved(this, overlay)
            this.visibleOverlays(this.visibleOverlays==overlay) = [];
        end
        
        function createSliceFadeStyleGroup(this)
            % Use this call to create the Notifier
            %this.notifier = SliceStyle.Notifier(this);
            
            fadeStyle= createSliceFadeStyle();
            allModels = [this.rootModelH this.refModels];
            if exist('diagram.style.DescendantSelector','class')
                mgr = SliceStyle.Manager.Instance();
                fadeClassName = sprintf('BD_%i',mgr.getNextIndex());
                this.sliceFader = SliceStyle.StyleGroup(fadeStyle, fadeClassName, allModels);
            else
                this.sliceFader = SliceStyle.StyleGroup(fadeStyle, '$BD$', allModels);
            end
        end
        
        function createEditFadeStyleGroup(this)
            fadeStyle= createEditFadeStyle();
            selector = diagram.style.DiagramSelector(this.rootModelH);
            this.editFader = StyleGroup(fadeStyle, selector, this);
        end
        
        % this can wait
        function clearAll(this)            
            % Clear Fade Rules and Fade Style Group
            this.remove_overlap_rules();
            this.sliceFader.clear();            
            this.sliceFader = [];
            
            allOverlays = this.overlays.values();
            for idx=1:numel(allOverlays)
                ov = allOverlays{idx};
                ov.clearAll();  
            end
            
            this.overlays = [];
        end
        
        function addOverlapRules(this, highlightCommonBlocks)
            if numel(this.visibleOverlays)<2
                return;
            end            
            cnt = min(3, numel(this.visibleOverlays));
            allTags = cell(1,cnt);            
            for idx=1:cnt
                overlay = this.visibleOverlays(idx);
                allTags{idx} = overlay.tag;
            end
            if ~exist('highlightCommonBlocks','var')
                highlightCommonBlocks=false;
            end
            add_overlap_rules(this,highlightCommonBlocks, {'Dependencies_','StartingPoints_'},allTags{:});
            add_overlap_rules(this,highlightCommonBlocks, {'StateflowElems_'},allTags{:});
            add_atomicChart_overlap(this);
        end
        
        function add_atomicChart_overlap(this)
            if numel(this.visibleOverlays)<2
                return;
            end            
            cnt = min(3, numel(this.visibleOverlays));
            allTags = cell(1,cnt);            
            for idx=1:cnt
                overlay = this.visibleOverlays(idx);
                allTags{idx} = overlay.tag;
            end
            addSfAtomicChartOverlap(this,allTags,this.visibleOverlays(1:cnt));
        end
        function add_overlap_rules(this,highlightCommonBlocks,type,tag1,tag2,tag3)
            
            nms1 = strcat(type,tag1);
            nms2 = strcat(type,tag2);
            styler = SliceStyle.Manager.Instance().getStyler();
            
            pairs = buildPairs(nms1,nms2);
            cnt = size(pairs,1);
            
            if nargin>5
                nms3 = strcat(type,tag3);
                pairs = [pairs;buildPairs(nms1,nms3)];
                pairs = [pairs;buildPairs(nms2,nms3)];
                cnt = size(pairs,1);
            end
            if ~highlightCommonBlocks
                ovStyle = createOverlapStyle();
                this.overlapColor = [];
            else
                this.overlapColor = findDifferentColor();
                ovStyle = createOverlapStyleForCommonBlockHighlighting(this.overlapColor);
            end
            if ~(highlightCommonBlocks && nargin>5)
                for pidx = 1:cnt
                    select = diagram.style.MultiSelector(pairs(pidx,:),{'Editor'});
                    rule = styler.addRule(ovStyle, select);
                    if isempty(this.overlapRules)
                        this.overlapRules = rule;
                    else
                        this.overlapRules(end+1) = rule;
                    end
                end
            end
            
            if nargin>5
                % Add a special multi-overlap rule
                if ~highlightCommonBlocks
                    mltiOvStyle = createMultiOverlapStyle();
                    this.overlapColor = [];
                else
                    this.overlapColor = findDifferentColor();
                    mltiOvStyle = createOverlapStyleForCommonBlockHighlighting(this.overlapColor);
                end
                select = diagram.style.MultiSelector([nms1(1) nms2(1) nms3(1)] ,{'Editor'});
                rule = styler.addRule(mltiOvStyle, select);
                if isempty(this.overlapRules)
                    this.overlapRules = rule;
                else
                    this.overlapRules(end+1) = rule;
                end
            end
            
            function pairs = buildPairs(nms1,nms2)
                len = length(nms1)*length(nms2);
                pairs = cell(len,2);
                for idx=0:len-1
                    pairs(idx+1,:) = [nms1(floor(idx/2)+1), nms2(mod(idx,2)+1)];
                end
            end
            
            function color = findDifferentColor()
                %Function to find a color not present used in the active
                %slice configurations
                possibleColors = [1 0 0; 0 1 1; 1 0 1; 1 1 0; 0 1 0; 0 0 1];%black and white colors not allowed
                for i=1:length(this.visibleOverlays)
                    possibleColors = possibleColors(~ismember(possibleColors,this.visibleOverlays(i).slCriterion.colorValue,'rows'),:);
                end
                color = possibleColors(1,:);
            end
        end
        
        function addSfAtomicChartOverlap(this,allTags,overlays)
          
            cnt = length(overlays);
            styler = SliceStyle.Manager.Instance().getStyler();
            ovStyle = createOverlapStyle();
            add2WayOverlap(overlays(1),overlays(2),allTags{1},allTags{2});
            
            if cnt == 3
               add2WayOverlap(overlays(2),overlays(3),allTags{2},allTags{3});
               add2WayOverlap(overlays(1),overlays(3),allTags{1},allTags{3});
               add3WayOverlap(overlays(1),overlays(2),overlays(3),allTags{1},allTags{2},allTags{3});
            end
            
                    function add2WayOverlap(ov1,ov2,tag1,tag2)
                        import SliceStyle.*
                        sc1 = ov1.slCriterion;
                        sc2 = ov2.slCriterion;
                        overlapIDs = intersect(sc1.getActiveSubchartIds,sc2.getActiveSubchartIds);
                        for i = 1:length(overlapIDs)
                            id = overlapIDs(i);
                            
                            sc1ChildTag = Overlay.getAtomicChartSelector(id,tag1,0);
                            sc2ChildTag = Overlay.getAtomicChartSelector(id,tag2,0);
                            
                            descSelectorName =  sprintf('%s_%d','subChartOverlap2',i);
                            multiSelect= diagram.style.DescendantSelector({descSelectorName}, {},{sc1ChildTag,sc2ChildTag}, {});
                            rule = styler.addRule(ovStyle,multiSelect);
                            if isempty(this.atomicChartOverlapRules)
                                this.atomicChartOverlapRules = rule;
                            else
                                this.atomicChartOverlapRules(end+1) = rule;
                            end
                            styler.applyClass(id,descSelectorName); 
                            add2Map(id,descSelectorName); 
                        end
                    end

                    function add3WayOverlap(ov1,ov2,ov3,tag1,tag2,tag3)
                        import SliceStyle.*
                        mltiOvStyle = createMultiOverlapStyle();
                        sc1 = ov1.slCriterion;
                        sc2 = ov2.slCriterion;
                        sc3 = ov3.slCriterion;
                        overlapIDs = intersect(intersect(sc1.getActiveSubchartIds,sc2.getActiveSubchartIds),...
                                               sc3.getActiveSubchartIds);
                       for i = 1:length(overlapIDs)
                            id = overlapIDs(i);

                            sc1ChildTag = Overlay.getAtomicChartSelector(id,tag1,0);
                            sc2ChildTag = Overlay.getAtomicChartSelector(id,tag2,0);
                            sc3ChildTag = Overlay.getAtomicChartSelector(id,tag3,0);

                            descSelectorName =  sprintf('%s_%d','subChartOverlap3',i);
                            multiSelect= diagram.style.DescendantSelector({descSelectorName}, {},...
                                                                          {sc1ChildTag,sc2ChildTag,sc3ChildTag}, {});
                            rule = styler.addRule(mltiOvStyle,multiSelect);
                            this.atomicChartOverlapRules(end+1) = rule;
                            styler.applyClass(id,descSelectorName); 
                            add2Map(id,descSelectorName); 
                        end                  
                    end

                    function add2Map(id,selectorName)
                        if ~isKey(this.chartRuleMap,id)
                            this.chartRuleMap(id) = {selectorName};
                        else
                            this.chartRuleMap(id) = [this.chartRuleMap(id) {selectorName}];
                        end
                    end

        end
        
        function remove_overlap_rules(this)
            for idx = 1:numel(this.overlapRules)
                this.overlapRules(idx).remove;
            end
            this.overlapRules = [];
            remove_atomicChart_overlap(this);
        end
        
        function remove_atomicChart_overlap(this)
            
            for idx = 1:numel(this.atomicChartOverlapRules)
                this.atomicChartOverlapRules(idx).remove;
            end
            this.atomicChartOverlapRules = [];
            atomicChartIds = this.chartRuleMap.keys;
            styler = SliceStyle.Manager.Instance().getStyler();
            for i = 1:length(atomicChartIds)
                id = atomicChartIds{i};
                classes =  this.chartRuleMap(id);
                for j = 1:length(classes)
                    styler.removeClass(id,classes{j}); 
                end
            end
        end
        
        %% Disable icon generation until the legend function is incorporated
        % (comment out calls to fillLegendIcon)
        
        function filePath = update_overlay_icon(this, tag, fgColor)
            filePath = fullfile(prefdir,['criter_' tag '.png']);
            %fillLegendIcon(filePath, [0.8 0.8 0.8], fgColor, 2, 0);
        end
        
        function filePath = make_overlap_icon(this)
            filePath = fullfile(prefdir,'overlap.png');
            %fillLegendIcon(filePath, [0.8 0.8 0.8], [0 0 0], 3, 1);
        end
        
        function filePath = make_multioverlap_icon(this)
            filePath = fullfile(prefdir,'multioverlap.png');
            %fillLegendIcon(filePath, [0.8 0.8 0.8], [0 0 0], 4, 1);
        end
        
        function refreshModelHandles(this, modelSlicer)
            allRefModels = Analysis.getReferencedModelBDHandles(modelSlicer);
            this.refModels = allRefModels;
            allModels = [this.rootModelH this.refModels];
            this.sliceFader.handleList = [];
            this.sliceFader.setItems(allModels);
        end
    end   
end


function fillLegendIcon(filePath, bgColor, fgColor, traceWidth, invert)
    iconW = 20;
    iconH = 13;
    centerIdx = ceil(iconH/2);
    traceIdx = [centerIdx-(1:traceWidth) centerIdx+(1:traceWidth)];
    imgData = uint8(zeros(iconH, iconW, 3));

    for idx=1:3
        imgData(:,:,idx) = uint8(246*bgColor(idx));
        if invert
            imgData(centerIdx,:,idx) = uint8(246);
        else
            imgData(centerIdx,:,idx) = uint8(0);
       end
        imgData(traceIdx,:,idx) = uint8(246*fgColor(idx));
    end
    
    imwrite(imgData,filePath);
end


function style = createSliceFadeStyle()
    style = diagram.style.Style;
    style.set('FillColor',   [0.8 0.8 0.8 1.0]);
    style.set('FillStyle',   'Solid');
    style.set('TextColor',   [0.5 0.5 0.5 1.0],'simulink.Block');
    style.set('TextColor',   [0.5 0.5 0.5 1.0],'simulink.Segment');
    style.set('TextColor',   [0 0 0 1],'stateflow.State');
    style.set('TextColor',   [0 0 0 1],'stateflow.Transition');
    style.set('StrokeColor',   [0.6 0.6 0.6 1.0]);
    style.set('Shadow',   []);
    style.set('Opacity',0.5,'stateflow.Transition');
    style.set('Opacity',0.5,'stateflow.State');
end
        
function style = createEditFadeStyle()
    style = diagram.style.Style;
    style.set('FillColor',   [0.8 0.8 0.8 1.0]);
    style.set('FillStyle',   'Solid');
    style.set('TextColor',   [0.5 0.5 0.5 1.0]);
    style.set('StrokeColor',   [0.6 0.6 0.6 1.0]);
    style.set('Shadow',   []);
    style.set('Opacity',1);
end

function style = createOverlapStyle()
    %Defining the styling to be used for overlapping blocks in two
    %configurations
    style = diagram.style.Style;
    style.set('FillColor', [1 1 1 1], 'simulink.Block');
    style.set('FillColor', [1 1 1 1], 'simulink.Segment');
    style.set('FillColor', [1 1 1 1], 'stateflow.State');
    style.set('FillColor', [1 1 1 1], 'stateflow.Transition');
    style.set('FillColor', [1 1 1 1], 'stateflow.Junction');
    style.set('Opacity',1);
    style.set('TextColor', [0 0 0 1]);
    %set strokecolor to be [1, 1, 1, 1] will resulting the text in the
    %block(Like AND) is same as the filled color. So here we do not set the
    %stroke color for block.
    style.set('StrokeColor', [1 1 1 1], 'simulink.Segment');
    style.set('StrokeColor', [1 1 1 1], 'stateflow.State');
    style.set('StrokeColor', [1 1 1 1], 'stateflow.Transition');
    style.set('StrokeColor', [1 1 1 1], 'stateflow.Junction');
    stroke = MG2.Stroke;
    %stroke.Color = [0.3373    0.4824    0.3882 1]; %[0.1 0.2 0.8 1];
    stroke.Color = [0 0 0 1]; %[0.1 0.2 0.8 1];
    stroke.Width = 2.5;
    style.set('StrokeStyle', 'SolidLine');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'simulink.Block');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'simulink.Segment');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'stateflow.State');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'stateflow.Transition');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'stateflow.Junction');
    style.set('StrokeStyle', 'SolidLine', 'simulink.Block');
    style.set('StrokeStyle', 'SolidLine', 'simulink.Segment');
    style.set('StrokeStyle', 'SolidLine', 'stateflow.State');
    style.set('StrokeStyle', 'SolidLine', 'stateflow.Transition');
    style.set('StrokeStyle', 'SolidLine', 'stateflow.Junction');
end

function style = createOverlapStyleForCommonBlockHighlighting(color)
    %Defining the styling to be used for overlapping blocks if
    %HighlightCommonBlocks only is selected
    style = diagram.style.Style; 
    %Setting block fill color as white
    style.set('FillColor', [1 1 1 1], 'simulink.Block');
    style.set('FillColor', [1 1 1 1], 'simulink.Segment');
    style.set('FillColor', [1 1 1 1], 'stateflow.State');
    style.set('FillColor', [1 1 1 1], 'stateflow.Transition');
    style.set('FillColor', [1 1 1 1], 'stateflow.Junction');
    style.set('Opacity',1);
    style.set('TextColor', [0 0 0 1]);%Setting text color to black
    %Making the stroke color more bold 
    style.set('StrokeWidth', 1.5, 'simulink.Block');
    style.set('StrokeColor', [0 0 0 1], 'simulink.Segment');
    style.set('StrokeWidth', 1.5, 'simulink.Segment');
    style.set('StrokeColor', [0 0 0 1], 'stateflow.State');
    style.set('StrokeWidth', 1.5, 'stateflow.State');
    style.set('StrokeColor', [0 0 0 1], 'stateflow.Transition');
    style.set('StrokeWidth', 1.5, 'stateflow.Transition');
    style.set('StrokeColor', [0 0 0 1], 'stateflow.Junction');
    style.set('StrokeWidth', 1.5, 'stateflow.Junction');
    stroke = MG2.Stroke;
    %Setting the stroke color to the color provided as parameter
    stroke.Color = [color 1]; %[0.1 0.2 0.8 1];
    stroke.Width = 2;
    style.set('StrokeStyle', 'SolidLine');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'simulink.Block');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'simulink.Segment');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'stateflow.State');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'stateflow.Transition');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'stateflow.Junction');
    stroke.Width = 4.0;
    style.set('StrokeStyle', 'DotLine', 'simulink.Block');
    style.set('StrokeStyle', 'DotLine', 'simulink.Segment');
    style.set('StrokeStyle', 'DotLine', 'stateflow.State');
    style.set('StrokeStyle', 'DotLine', 'stateflow.Transition');
    style.set('StrokeStyle', 'DotLine', 'stateflow.Junction');
end

function style = createMultiOverlapStyle()
    %Defining the styling to be used for overlapping blocks in three
    %configurations
    style = diagram.style.Style;
    style.set('FillColor', [1 1 1 1], 'simulink.Block');
    style.set('FillColor', [1 1 1 1], 'simulink.Segment');
    style.set('FillColor', [1 1 1 1], 'stateflow.State');
    style.set('FillColor', [1 1 1 1], 'stateflow.Transition');
    style.set('FillColor', [1 1 1 1], 'stateflow.Junction');
    style.set('Opacity',1);
    style.set('TextColor', [0 0 0 1]);
    %set strokecolor to be [1, 1, 1, 1] will resulting the text in the
    %block (Like AND) is same as the filled color. So here we do not set
    %the stroke color for block.
    style.set('StrokeWidth', 1.5, 'simulink.Block');
    style.set('StrokeColor', [1 1 1 1], 'simulink.Segment');
    style.set('StrokeWidth', 1.5, 'simulink.Segment');
    style.set('StrokeColor', [1 1 1 1], 'stateflow.State');
    style.set('StrokeWidth', 1.5, 'stateflow.State');
    style.set('StrokeColor', [1 1 1 1], 'stateflow.Transition');
    style.set('StrokeWidth', 1.5, 'stateflow.Transition');
    style.set('StrokeColor', [1 1 1 1], 'stateflow.Junction');
    style.set('StrokeWidth', 1.5, 'stateflow.Junction');
    
    stroke = MG2.Stroke;
    %stroke.Color = [0.3373    0.4824    0.3882 1]; %[0.1 0.2 0.8 1];
    stroke.Color = [0 0 0 1]; %[0.1 0.2 0.8 1];
    stroke.Width = 4.0;
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'simulink.Block');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'simulink.Segment');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'stateflow.State');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'stateflow.Transition');
    style.set('Trace', MG2.TraceEffect(stroke, 'Outer'), 'stateflow.Junction');
    style.set('StrokeStyle', 'DotLine', 'simulink.Block');
    style.set('StrokeStyle', 'DotLine', 'simulink.Segment');
    style.set('StrokeStyle', 'DotLine', 'stateflow.State');
    style.set('StrokeStyle', 'DotLine', 'stateflow.Transition');
    style.set('StrokeStyle', 'DotLine', 'stateflow.Junction');
end
