classdef surfaceTrace < handle
% surfaceTrace creates a surfaceTrace object that returns the data of the 
% surface traces.
%
% S = surfaceTrace(sobj,SurfInd) creates a trace object of a metalSurface 
% w.r.t the surface index.
%
% surfaceTrace input arguments:
%   sobj            - metalSurface object.
%   SurfInd         - Index of the surface in a metalSurface. It accepts 
%                     the scalar value.
%
% surfaceTrace properties:
%   NumTraces         - Number of traces in a surface.
%   TracePoint        - X and Y Points of trace in meter.
%   TraceWidth        - Width of the trace in meter.
%   
% surfaceTrace methods:  
%   shapes  - Returns the shape of the trace in a surface

% Copyright 2023 The MathWorks, Inc.

    properties(SetAccess = private, GetAccess = public)
        % Number of traces in a surface.
        NumTraces
        % X and Y Points of trace in meter.
        TracePoints
        % Width of the trace in meter.
        TraceWidth
    end

    properties(Access=private)
        PrivateSurfaceInd
        PrivateLayerNumber
        PrivateCadnetName
        PrivateInputType
    end

    methods
        function obj = surfaceTrace(Sobj,SurfInd)
            arguments
                Sobj(1,1)  metalSurface
                SurfInd(1,1) {mustBeNumeric,...
                    mustBeNonNan,mustBeFinite,mustBeInteger,...
                    mustBePositive,mustBeValidNumber(Sobj,SurfInd)};
            end
            obj.PrivateInputType = Sobj.InputType;
            if strcmpi( obj.PrivateInputType,'Layer')
                obj.PrivateLayerNumber = Sobj.LayerNumber;
            elseif strcmpi( obj.PrivateInputType,'Cadnet')
                obj.PrivateCadnetName = Sobj.CadnetName;
            end
            obj.PrivateSurfaceInd = SurfInd;
        end
    end
    methods %-------Set/Get-------- %
        function traceCount = get.NumTraces(obj)
          
            if ~isempty(obj.PrivateLayerNumber)
                Lid = obj.PrivateLayerNumber;
                Sind = obj.PrivateSurfaceInd;
                Surf_Id = getLayerSurfaceId(int32(Lid));
            else
                Surf_Id = getCadnetSurfaceIndexList(obj.PrivateCadnetName)';
                Sind = obj.PrivateSurfaceInd;
            end
            surfIdx = Surf_Id(Sind);
            traceCount = getSurfaceTraceCount(int32(surfIdx));
            obj.NumTraces =  traceCount;
        end

        function tracept = get.TracePoints(obj)
            Sind = obj.PrivateSurfaceInd;
            [tp,~] = getSurfaceTracePoint(obj,Sind);
            tracept = tp';
            obj.TracePoints = tp;
        end

        function traceW = get.TraceWidth(obj)
            Sind = obj.PrivateSurfaceInd;
            [~,tw] = getSurfaceTracePoint(obj,Sind);
            traceW = cell2mat(tw');
            obj.TraceWidth = traceW;
        end
    end

    methods (Hidden)
        
        function [SurfTracept,TraceWidth] = getSurfaceTracePoint(obj,surfInd)
            % This function is used to create the trace points

            % Number of traces in a surface
            traceCnt = obj.NumTraces;
           % Get the surface index w.r.t the layer or cadnet
            if ~isempty(obj.PrivateLayerNumber)
                Lid = obj.PrivateLayerNumber;
                Surf_LayerId = getLayerSurfaceId(int32(Lid))';
            else
                Surf_LayerId = getCadnetSurfaceIndexList(obj.PrivateCadnetName)';
            end
            surfIdx = Surf_LayerId(surfInd);

            if ~(traceCnt == 0)
                tind = 1: traceCnt;
                [TraceVertices,Twidth] = arrayfun(@(x) CreateTraceVertices(obj,surfIdx,x),tind,'UniformOutput',false);
                SurfTracept =  TraceVertices;
                TraceWidth = Twidth;              
            else
                SurfTracept = [];
                TraceWidth = [];
            end
        end

        function Tracepoly = createTracePolygon(obj,surfInd)
             % This function is used to create the trace polygon
           
             traceCnt = obj.NumTraces;
             if ~(traceCnt == 0)
                 tind = 1: traceCnt;
                 TrPoly = arrayfun(@(x) CreateTracePolygon(obj,surfInd,x),tind,'UniformOutput',false);
                 Tracepoly= TrPoly{1};
                 for i = 2: size(TrPoly,2)
                     Tracepoly = Tracepoly+TrPoly{i};
                 end
             else
                 Tracepoly = [];
             end
        end
    end

    methods(Hidden)
        function Tpoly = CreateTracePolygon(obj,~,tind)
            % Number of traces in a surface

            tracept = obj.TracePoints;
            traceW = obj.TraceWidth;
            Tp = removeMiddlePoints(obj,tracept{tind});

            if length(unique(tracept{tind}(:,1)))>1 || length(unique(tracept{tind}(:,2)))>1 || length(tracept{tind}(:,1))==2
            %if length(Tp(:,1))==2
                lineX = Tp(:,1);
                lineY = Tp(:,2);
                lineXs = [lineX(1),lineX(end)]';
                lineYs = [lineY(1),lineY(end)]';
                thickness = traceW(tind);
                tracepoly = convertLineToPolygon(obj,lineXs,lineYs,thickness);
                Tpoly = tracepoly;
            else
                t = tracePoint("TracePoints",Tp,'Width',traceW(tind));
                Tpoly = t;
            end

        end

        function [TraceVertices,Twidth] = CreateTraceVertices(~,surfIdx,tind)
            traceData = getSurfaceTraceData(int32(surfIdx), int32(tind));
            TracIn = [traceData.xInside,traceData.yInside];
            TracEd = [traceData.xEdge,traceData.yEdge];
            TracOut = [traceData.xOutside,traceData.yOutside];
            TraceVertices = [TracIn;TracEd;TracOut];
            Twidth = traceData.width;
        end

        function  polyObj = convertLineToPolygon(~,lineXs,lineYs, thickness)
            angle = atan2((lineYs(2)-lineYs(1)),(lineXs(2)-lineXs(1)));
            polyXVals = [];
            polyXVals(1) = lineXs(1) + 0.5*thickness*cos(angle+pi/2);
            polyXVals(2) = lineXs(1) + 0.5*thickness*cos(angle-pi/2);
            polyXVals(3) = lineXs(2) + 0.5*thickness*cos(angle-pi/2);
            polyXVals(4) = lineXs(2) + 0.5*thickness*cos(angle+pi/2);
            polyYVals(1) = lineYs(1) + 0.5*thickness*sin(angle+pi/2);
            polyYVals(2) = lineYs(1) + 0.5*thickness*sin(angle-pi/2);
            polyYVals(3) = lineYs(2) + 0.5*thickness*sin(angle-pi/2);
            polyYVals(4) = lineYs(2) + 0.5*thickness*sin(angle+pi/2);
            
            zValues = zeros(4,1);
            polyVertices = [polyXVals(:), polyYVals(:), zValues(:)];
            polyObj = antenna.Polygon("Vertices", polyVertices);
        end
        function endPoints = removeMiddlePoints(obj,points)
            % Initialize the output variable
            endPoints = [];

            % Loop through each point
            for i = 1:length(points)
                % Get the current point
                currentPoint = points(i,:);

                % Check if the current point is collinear with the previous and next points
                if i > 1 && i < length(points)
                    previousPoint = points(i-1,:);
                    nextPoint = points(i+1,:);

                    % Calculate the cross product of the vectors formed by the points
                    crossProduct = (currentPoint(1)-previousPoint(1))*(nextPoint(2)-previousPoint(2)) - (currentPoint(2)-previousPoint(2))*(nextPoint(1)-previousPoint(1));

                    % If the cross product is zero, the points are collinear
                    if crossProduct == 0
                        % If the current point is collinear, skip adding it to the output
                        continue;
                    end
                end

                % Add the current point to the output
                endPoints = [endPoints; currentPoint];
            end
        end
    end

    methods
        function S = shapes(obj)
            %shapes returns the shape of the traces in a surfaces
            %
            % S = shapes(obj) returns the shapes of traces in a surfaces
            %

            % Copyright 2023 The MathWorks, Inc.

            surfInd = obj.PrivateSurfaceInd;
            Tracepoly = createTracePolygon(obj,surfInd);

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