classdef layer < handle & matlab.mixin.SetGet & matlab.mixin.Copyable & matlab.mixin.CustomDisplay
    % layer creates a layer object w.r.t the metal layer.
    %
    % l = layer(pobj,1) takes pcbFileRead as input and creates the
    % all the pcb objects on a first metal layer.
    %
    % l = layer(pobj,'Top',Type="Surfaces") takes pcbFileRead as input and
    % creates the surface objects on a 'Top' metal layer.
    %
    % layer properties:
    %
    %   pcBoard       - pcbFileRead object.
    %   LayerNumber   - layer number. This is the 2nd positional argument.
    %      or
    %   LayerName     - layer name. This is the 2nd positional argument.
    %   Type          - Type of the object that could be filtered w.r.t a
    %                   layer. Choice is 'Surface','pins','vias','Traces'.
    %   LayerHeight   - Height from the bottom to the respective layer in 
    %                   inch.
    %   NumSurfaces   - Number of surfaces w.r.t the layer.
    %   NumPins       - Number of pins w.r.t the layer.
    %   NumVias       - Number of vias w.r.t the layer.
    %   NumTraces     - Number of traces w.r.t the layer.
    %   EntityList    - Entity list w.r.t the layer.
    %
    % layer methods:
    %
    %   shapes       - Returns the shape of the entities w.r.t a layer.
    %   layerData    - Returns the object of the entities w.r.t a layer.
    %

    %   Copyright 2023 The MathWorks, Inc.

    properties(SetObservable)
        % pcbFileRead object.
        pcBoard
        % Layer Number
        LayerNumber
        % layer name
        LayerName
        % Type
        Type
    end
    properties(SetAccess = private, GetAccess = public)
        % Layer Height.
        LayerHeight
        % Number of pins w.r.t the layer.
        NumPins
        % Number of Surfaces.
        NumSurfaces
        % Number of Vias.
        NumVias
        % Number of Traces.
        NumTraces
        % Entity list w.r.t the layer.
        EntityList
    end

    properties(Access=private)
        LayerNumFlag
        TypeFlag
    end

    properties(Access=protected)
        privateLayerNumber
    end

    methods
        function obj = layer(pcBoard,Layer,opt)
            arguments
                pcBoard(1,1)  pcbFileRead
                Layer
                opt.Type string {mustBeMember(opt.Type,{'Surface','Pin','Via','Trace','All'})}
            end
            
            obj.pcBoard = pcBoard;
            if isstring(Layer) || ischar(Layer)
                obj.LayerNumFlag = 0;
                obj.LayerName = Layer;
                % if isstring(obj.LayerName)
                %     obj.LayerName =char(obj.LayerName);
                % end
                % Get the corresponding layerNumber for given LayerName
                getLayerNumber(obj,obj.LayerName);
            else
                obj.LayerNumFlag = 1;
                obj.LayerNumber = Layer;
                % Get the corresponding layerName for given LayerNumber
                getLayerName(obj,obj.LayerNumber);
            end

            tf =  fields(opt);
            if ~isempty(tf)
                obj.Type = opt.Type;
            else
                obj.Type = 'All';
            end
        end
    end

    methods % set/get methods
        function set.pcBoard(obj,propval)
            if ~isa(propval,'pcbFileRead')
                error(message('rfpcb:rfpcberrors:Unsupported','This input',...
                    'pcBoard. Supported input is pcbFileRead object'));
            end
            if isPropertyChanged(obj,obj.pcBoard,propval)
                obj.pcBoard =  propval;
            end
        end

        function set.LayerName(obj,propval)
            mustBeNonempty(propval);
            if ~isempty(propval)   
                validateLayerName(obj,propval);
                if isPropertyChanged(obj,obj.LayerName,propval)
                    if isstring(propval)
                        propval = char(propval);
                    end
                    obj.LayerName =  propval;
                    getLayerNumber(obj,obj.LayerName);
                    UpdateLayerProp(obj)
                end
            end
        end

        function set.LayerNumber(obj,propval)
            mustBeNonempty(propval);
            if ~isempty(propval)
                % validate the input Layer number
                validateLayerNumber(obj,propval);
                if isPropertyChanged(obj,obj.LayerNumber,propval)
                    obj.LayerNumber =  propval;
                    getLayerName(obj,obj.LayerNumber);
                    UpdateLayerProp(obj)
                end
            end
        end

        function Lheight = get.LayerHeight(obj)
            Lheight =obj.pcBoard.LayerHeight(1,obj.LayerNumber);
            obj.LayerHeight = Lheight;
        end
        function NumLsurf = get.NumSurfaces(obj)
            m = metalSurface(obj.pcBoard,"Layer",obj.LayerNumber);
            if ~isempty(m.SurfaceInfo)
                NumLsurf = max(m.SurfaceInfo.SurfaceIndex);
            else
                NumLsurf = 0;
            end
            obj.NumSurfaces = NumLsurf;
        end

        function Cpins = get.NumPins(obj)
             p = pins(obj.pcBoard,"Layer",obj.LayerNumber);
            if ~isempty(p.PinInfo)
                Cpins = max(p.PinInfo.PinIndex);
            else
                Cpins = 0;
            end
            obj.NumPins = Cpins;
        end

        function Cvias = get.NumVias(obj)
            v = vias(obj.pcBoard,"Layer",obj.LayerNumber);
            if ~isempty(v.ViaInfo)
                Cvias = max(v.ViaInfo.ViaIndex);
            else
                Cvias = 0;
            end
            obj.NumVias = Cvias;
        end

        function Ctraces = get.NumTraces(obj)
            Ctraces = [];
            obj.NumTraces = Ctraces;
        end

        function set.Type(obj,propval)
            mustBeMember(propval,{'Surface','Pin','Via','Trace','All'});
            if ~isempty(propval)
               obj.Type = propval;
               UpdateEntityList(obj);
            end
        end

        function Elist = get.EntityList(obj)
            % Entity list of layers
            Elist = GetEntityList(obj);
            obj.EntityList = Elist;
        end
    end

     methods (Hidden)
        function UpdateLayerProp(obj)
            if ~isempty(obj.LayerNumber)||~isempty(obj.LayerName)
                prop = get(obj);
            end
        end
     end

    methods
        function layerD = layerData(obj,opt)
            % layerData returns the objects of the entities w.r.t a
            % layer.
            %
            % layerD = layerData(obj) returns the objects of all the
            % entities w.r.t the layer.
            %
            % layerD = layerData(obj,Type="Surfaces") returns the object of
            % surfaces w.r.t the layer.
            %
            %   Input Arguments:
            %
            %       obj         : layer object.
            %       Type        : Type of the entity w.r.t the layer.
            %
            %   Output Arguments:
            %
            %       layerD      : Returns the objects of entities w.r.t the
            %                     layer.

            % Copyright 2023 The MathWorks, Inc.

            arguments
                obj(1,1)  layer
                opt.Type(1,1) {mustBeMember(opt.Type,["Surfaces","Pins","Vias","Traces"])}
            end
            %TF = isfield(opt.Type)
            tf =  fields(opt);
            p = obj.pcBoard;
            lnum = obj.LayerNumber;
            if isempty(tf)
                layerData = struct;
                layerData.Surfaces = metalSurface(p,'layer',lnum);
                layerData.Pins = pins(p,"layer",lnum);
                layerData.Vias = vias(p,"layer",lnum);
                % layerData.Traces = traces(p,"layer",lnum);
                layerData.Traces = [];
            else
                type = opt.Type;
                if strcmp(type,'Surfaces')
                    layerData= metalSurface(p,'layer',lnum);
                elseif strcmp(type,'Pins')
                    layerData =  pins(p,"layer",lnum);
                elseif strcmp(type,'Vias')
                    layerData = vias(p,"layer",lnum);
                elseif strcmp(type,'Traces')
                    %layerData= traces(p,"layer",lnum);
                    layerData = [];
                end
            end
            layerD = layerData;
        end

        function S = shapes(obj,opt)
            %shapes returns all the shapes of the entities w.r.t a layer.
            %
            % S = shapes(obj) returns the shapes of all the entities
            % w.r.t a layer.
            %
            % S = shapes(obj,'Type','pins') returns the shapes of all the pins
            % w.r.t a layer.

            % Copyright 2023 The MathWorks, Inc.

            arguments
                obj(1,1)  layer
                opt.Type(1,1) {mustBeMember(opt.Type,["Surfaces","Pins","Vias","Traces"])}
            end
            %TF = isfield(opt.Type)
            tf =  fields(opt);
            pcb = obj.pcBoard;
            lnum = obj.LayerNumber;

            if isempty(tf)
                layerShape = struct;
                % surfaces
                m = metalSurface(pcb,'layer',lnum);
                layerShape.Surfaces = shapes(m);
                % Pins
                p =  pins(pcb,"layer",lnum);
                layerShape.Pins = shapes(p);
                % Vias
                v =  vias(pcb,"layer",lnum);
                layerShape.Vias = shapes(v);
                % traces
                % t =  traces(pcb,"layer",lnum);
                layerShape.Traces = [];
            else
                type = opt.Type;
                if strcmp(type,'Surfaces')
                    layerData= metalSurface(pcb,'layer',lnum);
                    layerShape = shapes(layerData);
                elseif strcmp(type,'Pins')
                    layerData =  pins(pcb,"layer",lnum);
                    layerShape = shapes(layerData);
                elseif strcmp(type,'Vias')
                    layerData = vias(pcb,"layer",lnum);
                    layerShape = shapes(layerData);
                elseif strcmp(type,'Traces')
                    %layerData= traces(pcb,"layer",lnum);
                    layerShape = [];
                end
            end
            S = layerShape;
        end

        function show(obj,opt)
            %  SHOW  Display the PCB Object w.r.t the metal layer.

            %   Copyright 2023 The MathWorks, Inc.

            arguments
                % obj  {mustBeA(obj,'layer')}
                obj
                opt.Color {mustBeNonNan, mustBeNonempty} ='r'
                opt.LineStyle (1,1) string = "-"
                opt.LineWidth (1,1) {mustBeNumeric,mustBePositive} = 1
            end

            colour = opt.Color;
            LStyle = opt.LineStyle;
            LWidth = opt.LineWidth;

            % filter parent handle input
            %[~, hfig, ~] = em.internal.parseParentArgs([],"ValidParents",{'figure'},'OnlyInput',true);
            hfig = [];
            if isempty(hfig)
                if ~isempty(get(groot,'CurrentFigure'))
                    clf(gcf);
                end
                hfig = gcf;
                clf(hfig);
                ax = axes(hfig);
                axis equal;
            end

            %titlestring = ([class(obj), ' element:', ' layer ', num2str(obj.LayerNumber)]);
            titlestring = (getString(message('rfpcb:eminterop:elementLayer','layer',' 2')));
            title(ax, titlestring);
            xlabel(ax, getString(message('rfpcb:eminterop:xlabelshow')));
            ylabel(ax, getString(message('rfpcb:eminterop:ylabelshow')));

            ann = annotation(hfig,'textbox','Visible','off','Units','pixels','PickableParts','none','HitTest','off','Color','k','LineStyle','-','LineWidth',0.5);
            hfig.WindowButtonMotionFcn = @(src,evt)hover(ax,evt,ann);

            if ~isempty(obj)
                LayerShape = createGeometry(obj);
                for j = 1: length(LayerShape)
                    polyShape = LayerShape(j);
                    for i = 1:numel(polyShape{1})
                        plotShape(polyShape{1}(i),struct('Tooltip',polyShape{1}(i).Name,'ID',-1));
                    end
                end
            end

            function plotShape(shape,data)
                createGeometry(shape)
                geom = shape.getGeometry;
                Br = geom.BoundaryEdges;
                patchinfo.Vertices  = geom.BorderVertices;
                patchinfo.Faces     = geom.polygons{1};
                patchinfo.Parent     = ax;
                p1 = patch(patchinfo,'Tag','InteractiveShape','FaceColor',colour,...
                    'EdgeColor','none','LineStyle',LStyle,'AmbientStrength',0.5,...
                    'FaceLighting','gouraud','DiffuseStrength',0.8, ...
                    'SpecularColorReflectance', 0.6,'SpecularStrength',0.7, ...
                    'FaceOffsetFactor',0.01,'FaceAlpha',0.1,'LineWidth',LWidth);

                if ~isempty(Br)
                    patchinfo.Vertices  = geom.BorderVertices;
                    patchinfo.Faces     = Br{1};
                    p2 = patch(patchinfo,'Tag','InteractiveShape','FaceColor','none',...
                        'EdgeColor',colour,'LineStyle',LStyle,'AmbientStrength',0.5,...
                        'FaceLighting','gouraud','DiffuseStrength',0.8, ...
                        'SpecularColorReflectance', 0.6,'SpecularStrength',0.7, ...
                        'FaceOffsetFactor',0.01,'FaceAlpha',0.1,'LineWidth',LWidth);
                end
                p = {p1,p2};
                assignTagAnddDataToShapePatch(shape,p,data)
                shg
            end

            function assignTagAnddDataToShapePatch(obj,patchCellArray,data)
                % patch  cell array is an ouput from the viewgeom method . the
                % patchCellArray can be a scalar Patch or a cell array or a cell
                % array of cell array of patch objects . To avoid any errors a
                % recursive function is written to index into the cell array
                % recursively and set the tag when we finally endup into a patch object.
                % arguments
                %     obj
                %     patchCellArray (1,:)
                %     data
                % end

                if iscell(patchCellArray)
                    for j = 1: numel(patchCellArray)
                        assignTagAnddDataToShapePatch(obj,patchCellArray{j},data);
                    end
                else
                    for j = 1:numel(patchCellArray)
                        patchObj = patchCellArray(j);
                        patchObj.UserData= data;
                        patchObj.Parent= ax;
                    end

                end
            end

            function hover(ax,evt,ann)
                if isvalid(ax)
                    if(~isempty(ax.UserData) && ax.UserData ~= evt.HitObject)
                        % ax.UserData.FaceColor = 'red';
                        % ax.UserData.EdgeColor = 'red';
                        ax.UserData.FaceAlpha = 0.1;
                        %ax.UserData.Color = 'r';
                        ax.UserData.LineStyle = LStyle;
                        ax.UserData.LineWidth = LWidth;
                    end
                end
                if ~isempty(evt.HitObject.Tag)&& (strcmpi(evt.HitObject.Tag,'InteractiveShape'))
                    ax.UserData = evt.HitObject;
                    %evt.HitObject.FaceColor = 'red';
                    %evt.HitObject.EdgeColor = 'red';
                    %evt.HitObject.LineStyle = '--';
                    evt.HitObject.FaceAlpha = 1;
                    % ax.UserData = evt.HitObject;
                    % evt.HitObject.Color = 'k';
                    % evt.HitObject.LineWidth = 2;
                    %
                    ann.Visible = 'on';
                    ann.Position(1:2) = evt.Point;
                    ann.String = evt.HitObject.UserData.Tooltip;
                elseif isvalid(ann)
                    ann.Visible = 'off';
                end
            end
        end% of show
    end

    methods (Hidden)
        function UpdateEntityList(obj)
            Elist = obj.EntityList;
        end
        
        function LayerPoly = createGeometry(obj)
            type = obj.Type;
            pobj = obj.pcBoard;

            if strcmpi(type,"Surface") || strcmpi(type,"All")
                mobj = metalSurface(pobj,"Layer",obj.LayerNumber);
                MsurfGeom = createGeometry(mobj);
                Lobj{1}= MsurfGeom;
            else
                Lobj{1}= [];
            end

            if strcmpi(type,"Pin") || strcmpi(type,"All")
                pinobj = pins(pobj,"Layer",obj.LayerNumber);
                pinGeom = createGeometry(pinobj);
                Lobj{2}= pinGeom;
            else
                Lobj{2}= [];
            end

            if strcmpi(type,"Via") || strcmpi(type,"All")
                viaobj = vias(pobj,"Layer",obj.LayerNumber);
                viaGeom = createGeometry(viaobj);
                Lobj{3}= viaGeom;
            else
                Lobj{3}= [];
            end

            if strcmpi(type,"Trace") || strcmpi(type,"All")
                traceobj = traces(pobj,"Layer",obj.LayerNumber);
                traceGeom = createGeometry(traceobj);
                Lobj{4}= traceGeom;
            else
                Lobj{4}= [];
            end

            LayerPoly =  {Lobj{1},Lobj{2},Lobj{3},Lobj{4}};
            LayerPoly = LayerPoly(~cellfun('isempty',LayerPoly));
        end

        function validateLayerName(obj,propval)
            mLnum = obj.pcBoard.MetalLayer;
            mLname = stackUp(obj.pcBoard,mLnum).LayerName;
            mustBeMember(propval,mLname);
        end

        function validateLayerNumber(obj,propval)
            mLnum = obj.pcBoard.MetalLayer;
            mustBeMember(propval,mLnum);
        end

        function getLayerNumber(obj,LName)
            stack = stackUp(obj.pcBoard,obj.pcBoard.MetalLayer);
            Lind = strcmpi(stack.LayerName,LName);
            LNum = stack.LayerNumber(Lind);
            obj.LayerNumber = LNum;
        end

        function getLayerName(obj,LNum)
            stack = stackUp(obj.pcBoard,obj.pcBoard.MetalLayer);
            Lind = find(stack.LayerNumber(:,1)==LNum);
            LName = stack.LayerName(Lind);
            LName = LName{1};
            obj.LayerName = LName;
        end

        function EntityList = GetEntityList(obj)

            % List w.r.t layer
            % layer entities contains: Pin, Via,Traces, Surfaces

            List = struct;
            % layername = obj.LayerName;

            % % Get surfaces of layer
            SurfaceCount = obj.NumSurfaces;
            Surf = struct;
            Surf.SurfaceCount = SurfaceCount;
            if SurfaceCount~=0
                SurfaceArea = 7;
                MinSurfaceArea = num2str(min(SurfaceArea)); MaxSurfaceArea = num2str(max(SurfaceArea));unit = 'sqm';
                Surf.MinSurfaceArea = [MinSurfaceArea,' ',unit];
                Surf.MaxSurfaceArea = [MaxSurfaceArea,' ',unit];
            end
            List.Surfaces = Surf;

            % Get the Pins of layer
            pinCount = obj.NumPins;
            Pins= struct;
            Pins.PinCount = pinCount;
            if pinCount~=0
                PinArea = 1;
                MinPinArea = num2str(min(PinArea)); MaxPinArea = num2str(max(PinArea));unit = 'sqm';
                Pins.MinPinArea = [MinPinArea,' ',unit];
                Pins.MaxPinArea = [MaxPinArea,' ',unit];
            end
            List.Pins = Pins;
            %
            % % Get the Vias of layer
            viaCount = obj.NumVias;
            Vias= struct;
            Vias.ViaCount = viaCount;
            if viaCount~=0
                ViaArea = 7;
                MinViaArea = num2str(min(ViaArea)); MaxViaArea = num2str(max(ViaArea));unit = 'sqm';
                Vias.MinViaArea = [MinViaArea,' ',unit];
                Vias.MaxViaArea = [MaxViaArea,' ',unit];
            end
            List.Vias = Vias;
            %
            % % Get traces of layer
            % TraceCount = getlayerTracesCount(layername);
            % Traces = struct;
            % Traces.TraceCount = TraceCount;
            % if TraceCount~=0
            %     TraceArea = 7;
            %     MinTraceArea = num2str(min(TraceArea)); MaxTraceArea = num2str(max(TraceArea));unit = 'sqm';
            %     Traces.MinTraceArea = [MinTraceArea,' ',unit];
            %     Traces.MaxTraceArea = [MaxTraceArea,' ',unit];
            % end
            List.Traces = [];
                     
            if strcmp(obj.Type,'All')
                EntityList = List;
            else
                type = obj.Type;
                if strcmp(type,'Surface')
                    List = rmfield(List,{'Pins','Vias','Traces'});
                elseif strcmp(type,'Pin')
                    List = rmfield(List,{'Surfaces','Vias','Traces'});
                elseif strcmp(type,'Via')
                    List = rmfield(List,{'Pins','Surfaces','Traces'});
                elseif strcmp(type,'Trace')
                    List = rmfield(List,{'Pins','Vias','Surfaces'});
                end
                EntityList = List;
            end

         
        end

        function tf = isPropertyChanged(~,val1,val2)
            % Check if property values are different
            if isempty(val1)        % During constructor call
                tf = true;
            else
                tf = ~isequal(val1,val2);
            end
        end

        % function mustBeA(value,Expobj)
        %     obj = value;
        % 
        %     if isa(obj,Expobj)
        %         value = obj; %#ok<NASGU>
        %     else
        %         eid = 'Value:notValidObject';
        %         msg = 'Incorrect number or types of inputs or outputs for function show';
        %         throwAsCaller(MException(eid,msg));
        %     end
        % end
    end
    methods(Access=protected)
        function group = getPropertyGroups(obj)
            title = '';
            if obj.LayerNumFlag==1
                p = {'pcBoard','LayerNumber','Type','LayerHeight','NumSurfaces','NumPins','NumVias','NumTraces','EntityList'};
            else
                p = {'pcBoard','LayerName','Type','LayerHeight','NumSurfaces','NumPins','NumVias','NumTraces','EntityList'};
            end
            group = objectProps(obj,p,title);
        end
        function group = objectProps(obj, propertyDisplayList,title)
            propList    = properties(obj);
            numProps    = max(size(propertyDisplayList));
            for i = 1:numProps
                if(strcmpi(propertyDisplayList{i},propList(strcmpi(propertyDisplayList{i},propList))))
                    antennaProps.(char(propertyDisplayList{i})) = get(obj,propertyDisplayList{i});
                end
            end
            group = matlab.mixin.util.PropertyGroup(antennaProps,title);
        end

    end
end