classdef metalSurface < handle & matlab.mixin.SetGet & matlab.mixin.Copyable & matlab.mixin.CustomDisplay
% metalSurface creates a metal surfaces w.r.t the InputType in the board.
%
% m = metalSurface(pobj,'Cadnet','Cadnetname') takes pcbFileRead as input 
% and creates the metalSurface object w.r.t. the Cadnetname.
%
% m = metalSurface(pobj,'Layer',1) takes pcbFileRead as an input and 
% creates the metalSurface object for the 1st metal layer.
%
% metalSurface properties:
%
%   pcBoard     - pcbFileRead object.
%   InputType   - Type of the input to get the metal surfaces. The choices
%                 are 'layer' or 'cadnet'. 
%                 The default InputType is 'layer'.
%
%   If InputType = 'Layer'
%   LayerNumber  - Index/Position of the layer numeber in the stack. It 
%                  accepts the scalar value. The default layer number is
%                  first metal layer in the stack.
%   or
%
%   LayerName    - Name of the layer in the stack. It accepts the string.                 
%   SurfaceInfo  - Info of the surfaces in a respective layer. Returns the
%                  table with below fields.
%                  SurfaceIndex : The index of each surfaces in a layer.
%                                 Max(SurfaceIndex) is the count of 
%                                 surfaces in a layer.
%                  Perimeter    : Perimeter of the Surface in meters.
%                  Cadnet       : Name of the cadnet that the surface is
%                                 associated with.
%                  EntityList   : list of entity in the surface.
%
%   If InputType = 'Cadnet'
%   CadnetName   - Name of the cadnet. 
%   LayerName    - Name of the layer in the stack.                
%   SurfaceInfo  - Info of the surfaces of a respective cadnet. Returns the
%                  table with below fields.
%                  SurfaceIndex : The index of each surfaces in a cadnet.
%                                 Max(SurfaceIndex) is the count of 
%                                 surfaces in a cadnet.
%                  Perimeter    : Perimeter of the Surface in inch.
%                  LayerName    : layer name of the respective surface.
%                  LayerNumber  : layer number of the respective surface.
%                  EntityList   : list of entity in a surface.
%
% metalSurface methods:
%   
%   shapes       - Returns the shape of the metal surfaces w.r.t the
%                  InputType
%   surfaceData  - Returns the data of the entities in a surface w.r.t the
%                  InputType
%

%   Copyright 2023 The MathWorks, Inc.

    properties(SetObservable)
        %pcbFileRead object.
        pcBoard
        %Type of the input to return the metalSurface.
        LayerNumber
        %Name of the layer.
        LayerName
        %Name of the cadnet.
        CadnetName
        %Type of the input to get the surfaces.
        InputType
    end

    properties(SetAccess = private, GetAccess = public)
        % Info Table of the surfaces w.r.t the InputType.
        SurfaceInfo
    end

    properties(Access=private)
        LayerNumFlag
    end

    methods
         function obj = metalSurface(pcBoard,InputType,value)
            parserObj = inputParser;

            % add parameters
            addRequired(parserObj,'pcBoard');
            addRequired(parserObj,'InputType');
           
            if strcmpi(InputType,'Layer')
                if isstring(value) || ischar(value)
                   addOptional(parserObj,'LayerName',value);
                else
                   addOptional(parserObj,'LayerNumber',value);
                end
            elseif strcmpi(InputType,'Cadnet')
                addOptional(parserObj,'CadnetName',value);
            end

            % parse the input
            parse(parserObj,pcBoard,InputType);
            obj.pcBoard        = pcBoard;
          
            if strcmpi(InputType,'Layer')
                if isstring(value) || ischar(value)
                   obj.LayerName     = parserObj.Results.LayerName;
                   getLayerNumber(obj,value); 
                else
                   obj.LayerNumber     = parserObj.Results.LayerNumber; 
                   getLayerName(obj,value);
                   obj.LayerNumFlag = 1;
                end
            elseif strcmpi(InputType,'Cadnet')
                obj.CadnetName      = parserObj.Results.CadnetName;
            end

            obj.InputType = InputType;

        end

        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.InputType(obj,propval)
            if ~isempty(propval)
                validatestring(propval,{'Layer','Cadnet'}, ...
                    class(obj),'InputType');
                if isPropertyChanged(obj,obj.InputType,propval)
                    if isstring(propval)
                        propval = char(propval);
                    end
                    obj.InputType =  propval;
                    createDisplayTable(obj);
                end
            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);
                    UpdateMetalSurfaceProp(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);
                    UpdateMetalSurfaceProp(obj)
                end
            end
        end

        function set.CadnetName(obj,propval)
            mustBeNonempty(propval);
            if ~isempty(propval)
                if ~((ischar(propval) || isStringScalar(propval)) && strlength(propval) > 0)
                    error(message('rfpcb:rfpcberrors:PCBNameNotString','CadnetName'));
                end
                validateCadnetName(obj,propval)
                if isPropertyChanged(obj,obj.CadnetName,propval)
                    if isstring(propval)
                        propval = char(propval);
                    end
                    obj.CadnetName =  propval;
                    UpdateMetalSurfaceProp(obj)
                end
            end
        end
    end


 methods (Hidden)
     function UpdateMetalSurfaceProp(obj)
            if ~isempty(obj.InputType)
                prop = get(obj);
                createDisplayTable(obj);
            end
     end

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

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

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

     function LName = 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 createDisplayTable(obj)
         % Get the count of total number of surfaces on each layer
         % and the layer ID that is used internally to access the database

         % Note: Validate the Surf_LayerId and SurfCntLayer in unit test
         if ~isempty(obj.InputType)
             if strcmpi(obj.InputType,'Layer')
                 Sid   = int32(obj.LayerNumber);
                 Surf_LayerId = getLayerSurfaceId(Sid)';
             else
                 Surf_LayerId = getCadnetSurfaceIndexList(obj.CadnetName);
             end

             if ~isempty(Surf_LayerId)
                 % Get the surface properties
                 SurfProps_Layer = cell2mat(arrayfun(@(x) getSurfaceProperties(x),int32(Surf_LayerId),'UniformOutput',false));

                 i = 1:length(SurfProps_Layer);
                 if ~isempty(obj.LayerNumber)
                     % Get the list of cadnets of the layer
                     SurfaceCadnet = arrayfun(@(x) SurfProps_Layer(x).cadnetName,int32(i),'UniformOutput',false);
                 else
                     % List of LayerNumber
                     SurfaceLayerName = arrayfun(@(x) SurfProps_Layer(x).layerName,int32(i),'UniformOutput',false);
                     SurfaceLayerNum = cell2mat(arrayfun(@(x) SurfProps_Layer(x).layerNumber,int32(i),'UniformOutput',false));
                 end


                 %SurfaceLNum = arrayfun(@(x) SurfProps_Layer(x).layerNumber,int32(i),'UniformOutput',false);

                 % Determine if each artwork is positive/negative. If positive,
                 % retrun 'True' else 'false'
                 % SurfacePositive = arrayfun(@(x) SurfProps_Layer(x).positive,int32(i),'UniformOutput',false);
                 % SurfaceArtWork  = arrayfun(@(x) CheckSurfArtWork(obj,SurfacePositive(x)),int32(i),'UniformOutput',false);

                 % Get the Entity list in each surface
                 List = GetEntityList(obj,Surf_LayerId);

                 % Determine the perimeter of each surface
                 Perimeter     = zeros(1,length(Surf_LayerId));
                 Perimeter(1,:) = 2;

                 Sind = 1:length(Surf_LayerId);
                 if strcmpi(obj.InputType,'Layer')
                     varNames = {'SurfaceIndex','Perimeter','Cadnet','EntityList'} ;
                     obj.SurfaceInfo = table(Sind',Perimeter',SurfaceCadnet',List');
                 else
                     varNames = {'SurfaceIndex','Perimeter','LayerName','LayerNumber','EntityList'} ;
                     obj.SurfaceInfo = table(Sind',Perimeter',SurfaceLayerName',SurfaceLayerNum',List');
                 end
                 obj.SurfaceInfo.Properties.VariableNames = varNames;
             else
                 if strcmpi(obj.InputType,'Layer')
                     varNames = {'SurfaceIndex','Perimeter','Cadnet','EntityList'} ;
                     obj.SurfaceInfo = cell2table(cell(0,4),'VariableNames',varNames);
                 else
                     varNames = {'SurfaceIndex','Perimeter','LayerName','LayerNumber','EntityList'} ;
                     obj.SurfaceInfo = cell2table(cell(0,5),'VariableNames',varNames);
                 end
                 %obj.SurfaceInfo.Properties.VariableNames = varNames;
             end
         end
     end

     function EntityList = GetEntityList(~,Surface_ID)

         % This list w.r.t surface contains one island polygon and multiple
         % hole polygon 
         % Types of Hole polygons this code contains:
         % (PolyHole (H records), Pin (OP), Via (OV),
         % AntipadCircle (OH), AntipadRectangle (OR), overlap Hole)
         % Note: (All the records such as H, OP, OV, OH, OR are placed 
         % under cutouts ): 

         for surfIdx = 1 : numel(Surface_ID)
             List{surfIdx} = struct;

             % Get the Hole Polygon in the surface
             PolyIsland = 1;
             if PolyIsland~=0
                 IslandPolygon= struct;
                 IslandPolygon.NumIslandPoly = PolyIsland;
                 PolyIslandArea = 12;
                 IslandPolygon.Area = PolyIslandArea;
                 List{surfIdx}.IslandPolygon = IslandPolygon;
             end

             % Get the Pin Hole (OP records) in the surface
             pinCount = getSurfacePinCount(int32(Surface_ID(surfIdx)));
             Pins= struct;
             Pins.NumPins = 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{surfIdx}.Pins = Pins;

             % Get the Via Hole(OV records) in the surface
             viaCount = getSurfaceViaCount(int32(Surface_ID(surfIdx)));
             Vias= struct;
             Vias.NumVias = 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{surfIdx}.Vias = Vias;

             % Get the Round hole (OH records) in the surface
             AntipadCircleCount = getSurfaceRoundHoleCount(int32(Surface_ID(surfIdx)));
             % Get the Rectangular Hole (OR records) in the surface
             rectHoleCount = getSurfaceRectHoleCount(int32(Surface_ID(surfIdx)));
             % Get the overlap Hole
             overlapCount = getSurfaceOverlapCount(int32(Surface_ID(surfIdx)));
             % Get the PolyHole (H records) in the surface
             PolyHoleCount = getSurfaceHolePolygonCount(int32(Surface_ID(surfIdx)));
             % Return the cutouts in the surface
             Cutouts= struct;
             Cutouts.NumCutouts = PolyHoleCount+AntipadCircleCount+rectHoleCount+overlapCount;
             PholeCnt = Cutouts.NumCutouts;
             if PholeCnt~=0
                 PolyHoleArea = 19;
                 MinPolyHoleArea = num2str(min(PolyHoleArea)); MaxPolyHoleArea = num2str(max(PolyHoleArea));unit = 'sqm';
                 Cutouts.MinArea = [MinPolyHoleArea,' ',unit];
                 Cutouts.MaxArea = [MaxPolyHoleArea,' ',unit];
             end            
             List{surfIdx}.Cutouts = Cutouts;

             % Get the traces(OT records) in the surface
             TraceCount = getSurfaceTraceCount(int32(Surface_ID(surfIdx)));
             Traces = struct;
             Traces.NumTraces = 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{surfIdx}.Traces = Traces;
         end
         EntityList = List;
     end

     function Area = createGeometry(obj,varargin)
         if  strcmpi(obj.InputType,'Layer')
             layerID = int32(obj.LayerNumber);
             % surfCount = zeros(numel(layerID),1);
             % for Lind = 1:numel(layerID)
             %     surfCount(Lind,1) = getNumOfSurfsInLayer(layerID(Lind,1));
             % end
             Surf_LayerId = getLayerSurfaceId(layerID);
             Surf_LayerId =Surf_LayerId';
         else
             Surf_LayerId = getCadnetSurfaceIndexList(obj.CadnetName)';
         end
         Surf_LayerId =Surf_LayerId';

         surfIdx = 1:numel(Surf_LayerId);
         Area = arrayfun(@(x) drawSurface(obj,Surf_LayerId(x),x),surfIdx);
     end
 end

 methods
     function S = shapes(obj)
         %shapes returns all the shapes of the surfaces w.r.t the input 
         % type.         
         % S = shapes(obj) returns the shapes of the metal layer w.r.t the
         % input type.
         %

         % Copyright 2023 The MathWorks, Inc.
        
         poly = createGeometry(obj);
         npoly = numel(poly);
         %S = struct;
         S = cell(npoly,1);
         for i = 1:npoly   
             S{i} = poly(i);
         end
     end
     function SurfaceD = surfaceData(obj,SurfInd,opt)
            % surfaceData returns the data of the objects in a surface 
            % w.r.t the surface index.
            %
            % surfD = surfaceData(obj,SurfInd) returns the data of the
            % objects w.r.t the SurfInd.
            %
            %   Input Arguments:
            %
            %       obj         : metalSurface object.
            %       SurfInd     : Surface index of the surfaces w.r.t the
            %                     input type. It accepts scalar or vector
            %                     values.
            %       Type        : Type of the object w.r.t the surface. 
            %  
            %   Output Arguments:
            %  
            %       SurfaceD : Returns the data of the list in surface.

            % Copyright 2023 The MathWorks, Inc.

         arguments
             obj(1,1)  metalSurface
             SurfInd {mustBeNumeric,...
                 mustBeNonNan,mustBeFinite,mustBeInteger,...
                 mustBePositive,mustBeValidSurface(obj,SurfInd)}=obj.SurfaceInfo.SurfaceIndex;
             opt.Type(1,1) {mustBeMember(opt.Type,["IslandPolygon","Pin","Via","Cutout","Antipad","Trace"])}
         end
         %TF = isfield(opt.Type)
         tf =  fields(opt);
         if ~isempty(SurfInd)
             Nsurf = length(SurfInd);
             for i = 1:Nsurf
                 SIslandPoly = surfaceIslandPolygon(obj,SurfInd(i));
                 SPin  =  surfacePin(obj,SurfInd(i));
                 SVia  =  surfaceVia(obj,SurfInd(i));
                 SHole =  surfaceCutout(obj,SurfInd(i));
                 SApad =  surfaceAntipad(obj,SurfInd(i));
                 Strace =  surfaceTrace(obj,SurfInd(i));
                 SurfData{i} = {SIslandPoly,SPin,SVia,SHole,SApad,Strace}';
             end
             if size(SurfData,2)==1
                 SurfaceD = SurfData{1};
             else
                 SurfaceD = SurfData';
             end
             if ~isempty(tf)
                 type = opt.Type;
                 if size(SurfData,2)==1
                     i = 1;
                 else
                     i = 1:size(SurfaceD,1);
                 end
                 if strcmpi(type,'IslandPolygon')
                     surfIpoly = arrayfun(@(x) surfData(obj,SurfData{x},1) ,i);
                     SurfaceD = surfIpoly;
                 elseif strcmpi(type,'Pin')
                     surfpin = arrayfun(@(x) surfData(obj,SurfData{x},2) ,i);
                     SurfaceD = surfpin;
                 elseif strcmpi(type,'Via')
                     surfvia = arrayfun(@(x) surfData(obj,SurfData{x},3) ,i);
                     SurfaceD = surfvia;
                 elseif strcmpi(type,'Cutout')
                     surfcutOut = arrayfun(@(x) surfData(obj,SurfData{x},4) ,i);
                     SurfaceD = surfcutOut;
                 elseif strcmpi(type,'Antipad')
                     surfApad = arrayfun(@(x) surfData(obj,SurfData{x},5) ,i);
                     SurfaceD = surfApad;
                 elseif strcmpi(type,'Trace')
                     surfT = arrayfun(@(x) surfData(obj,SurfData{x},6) ,i);
                     SurfaceD = surfT;
                 end
             end
         else
            SurfaceD = []; 
         end
     end
 end

    methods(Hidden)
        function Sdata = surfData(~,SurfData,n)
            Sdata = SurfData{n};
        end

        function Surfpoly = drawSurface(obj,SurfID,surfIdx)
            surfID = SurfID;   % Surface ID
            surfInd = surfIdx ; % Surface Index

            % Get all the polygon list (island poly followed by hole polys) in each surface
            PolyList = arrayfun(@(x) fieldnames(obj.SurfaceInfo.("EntityList"){x}),surfInd,'UniformOutput',false);
            PolyList = string(PolyList{1});
            if ~isempty(obj.LayerNumber)
                Cadnetname = obj.SurfaceInfo.Cadnet(surfInd);
            else
                Cadnetname = obj.CadnetName;
            end
          
            NumSurfHole = ~ismember(PolyList,{'Pins','Traces'});
            % Update PolyList by removing the Pin (Pin is not the cutout in the surface)
            PolyList = PolyList(NumSurfHole);
            % Number of surface polygon(Island and hole) w.r.t each surface
            NumSurfPoly = size(PolyList,1);
            % Size of surface polygon(Island and hole)
            SurfPoly = cell(1,NumSurfPoly);
            orig_state1 = warning;
            orig_state2 = warning;
            warning('off','MATLAB:polyshape:boundary3Points');
            warning('off','MATLAB:polyshape:repairedBySimplify');
            % Create the Island and hole polygon
            for i = 1:NumSurfPoly
                if strcmpi(PolyList(i),"IslandPolygon")
                    SurfIslandPoly = surfaceIslandPolygon(obj,surfInd);
                    %SurfPoly{i} = SurfIslandPoly.shapes;
                    IslandPolyShape = shapes(SurfIslandPoly);
                    SurfPoly{i} = IslandPolyShape;
                end

                if strcmp(PolyList(i),'Vias')
                    SurfVia = surfaceVia(obj,surfInd);
                    %SurfViaPoly = SurfVia.Shape;
                    SurfViaPoly = shapes(SurfVia);
                    if ~isempty(SurfViaPoly)
                        SurfPoly{i} = SurfViaPoly;
                    else
                        SurfPoly{i} = [];
                    end
                end

                if strcmpi(PolyList(i),"Cutouts")
                    SurfHole = surfaceCutout(obj,surfInd);
                    %SurfHolePoly = SurfHole.Shape;
                    SurfHolePoly = shapes(SurfHole);
                     if ~isempty(SurfHolePoly)
                        SurfPoly{i} = SurfHolePoly;
                    else
                        SurfPoly{i} = [];
                     end
                end
            end
            ISurfpoly = SurfPoly{1};

            % Remove te empty cell from the SurfPoly
            m = ~cellfun(@isempty,SurfPoly);
            SurfPoly = SurfPoly(m);

                if numel(SurfPoly)==1
                    Surfpoly = ISurfpoly;
                elseif numel(SurfPoly)>1
                    HSurfpoly = SurfPoly{2};
                    if numel(SurfPoly)>2
                        for j = 3:numel(SurfPoly)
                            HSurfpoly = HSurfpoly+SurfPoly{j};
                        end
                    end
                     Surfpoly = ISurfpoly- HSurfpoly;
                    % if any(inpolygon(HSurfpoly.Vertices(:,1),HSurfpoly.Vertices(:,2),ISurfpoly.Vertices(:,1),ISurfpoly.Vertices(:,2)))
                    %     Surfpoly = ISurfpoly- HSurfpoly;
                    % else
                    %     Surfpoly = ISurfpoly;
                    %     printSurfInd = SurfID
                    % end
                end

            if ~isempty(obj.LayerNumber)
                CadName = Cadnetname{1};
                CadName = strrep(CadName, '_', '\_');
                ToolTip = ['Surface',newline CadName];
                Surfpoly.Name = ToolTip;
            end
           
            warning(orig_state1);
            warning(orig_state2);
        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

    end
    methods(Access=protected)
        function group = getPropertyGroups(obj)
            title = '';
            if strcmpi(obj.InputType,'Layer')
                if obj.LayerNumFlag==1
                    p = {'pcBoard','InputType','LayerNumber','SurfaceInfo'};
                else
                    p = {'pcBoard','InputType','LayerName','SurfaceInfo'};
                end
            else
                p = {'pcBoard','InputType','CadnetName','SurfaceInfo'};
            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

function mustBeValidSurface(SurfObj,value)
SurfaceInd = value;
SurfaceIndExp = SurfObj.SurfaceInfo.SurfaceIndex;
if any(SurfaceInd==SurfaceIndExp)|| isempty(SurfaceInd)
    value = value;  %#ok<ASGSL>
else
    eid = 'Value:notValid';
    msg = getString(message('rfpcb:rfpcberrors:SurfaceIndex',num2str(SurfaceIndExp')));
    throwAsCaller(MException(eid,msg));
end
end
