classdef (Sealed) ElectromagneticBCAssignment < handle & matlab.mixin.CustomDisplay
    %ElectromagneticBCAssignment - Defines a boundary condition (BC) for an electromagnetic model
    %  The ElectromagneticBCAssignment object stores the specified boundary
    %  condition and appends the object to the BoundaryConditions property
    %  of ElectromagneticModel.
    %
    % Instances of this class can only be created by calling the
    % electromagneticBC method of the ElectromagneticModel class.
    %
    % See also pde.ElectromagneticModel/electromagneticBC
    
    % Copyright 2020-2022 The MathWorks, Inc.
    
    properties (SetAccess = private)
        % RegionType - Type of geometric region to which the BC is assigned. 
        RegionType;
    end
    
    properties
        % RegionID - IDs of the region to which BC is assigned.
        RegionID;
    
        % Voltage - Voltage on the boundary.
        Voltage;

        % SurfaceCurrentDensity - Current density on the boundary.
        SurfaceCurrentDensity;

        % ElectricField - Electric field on the boundary.
        ElectricField;

        % MagneticField - Magnetic field on the boundary.
        MagneticField;
        
        % MagneticPotential - Magnetic potential on the boundary.
        MagneticPotential;

        % FarField - Type of far field boundary condition
        FarField;
        
        % Vectorized - Indicates whether a user-written function returns
        %              boundary condition matrices at multiple locations.
        Vectorized;

        % Thickness - The width of the absorbing layer
        Thickness;

        % Exponent - Exponent in the rate of energy decay in absorbing
        %            layer
        Exponent;

        % Scaling - Coefficient in the rate of energy decay in absorbing
        %           layer
        Scaling;
    end
    
    methods
        function obj=ElectromagneticBCAssignment(embcr,bcInput)
            arguments
                embcr pde.ElectromagneticBCAssignmentRecords
                bcInput struct
            end
            
            obj.RecordOwner = embcr;
            validBoundary = embcr.ParentModel.validBoundary;
            obj.RegionType = validBoundary;
            obj.RegionID = bcInput.(validBoundary);
            obj.Type = 'dirichlet';
            bcInput = rmfield(bcInput,validBoundary);
            inputFields = fields(bcInput);
            for f = 1:numel(inputFields)
                obj.(inputFields{f}) = bcInput.(inputFields{f});
            end

            obj.PropertiesToValidate = setdiff(properties(obj),{'RegionType','RegionID','FarField','Vectorized','Thickness','Exponent','Scaling'});



            if ~isempty(obj.SurfaceCurrentDensity)
                obj.Type = 'neumann';
            end

            % these errors must come after all setters have been called
            if ~isempty(obj.FarField) && isempty(obj.Thickness)
                error(message('pde:ElectromagneticModel:missingAbsorbingThickness'));
            end
            if ~isempty(obj.Thickness) && isempty(obj.FarField)
                error(message('pde:ElectromagneticModel:ThicknessNoFarField'));
            end
        end
        
        function set.RegionID(self, rids)
            self.ValidateRegionID(rids);
            self.RegionID = rids;
        end
        
        function set.Voltage(self, coef)
            self.CoefPrecheck(coef,'Voltage');
            self.Voltage = coef;
        end
        %
        function set.SurfaceCurrentDensity(self, coef)
            self.CoefPrecheck(coef,'SurfaceCurrentDensity');
            self.SurfaceCurrentDensity = coef;
        end
       
        function set.ElectricField(self,coef)
            self.CoefPrecheck(coef,'ElectricField');
            self.ElectricField = coef;
        end
        %
        function set.MagneticField(self,coef)
            self.CoefPrecheck(coef,'MagneticField');
            self.MagneticField = coef;
        end
        %
        function set.MagneticPotential(self, coef)
            self.CoefPrecheck(coef,'MagneticPotential');
            self.MagneticPotential = coef;
        end
        %
        function set.Vectorized(self, vec)
            mustBeTextScalar(vec);
            mustBeMember(vec ,{'on','off'});
            self.Vectorized = vec;
        end
        %
        function set.FarField(self,type)
            mustBeTextScalar(type);
            mustBeMember(type,{'absorbing'});
            self.FarField = type;
        end
        %
        function set.Thickness(self,coef)
            self.CoefAbsorbingPrecheck(coef,'Thickness');
            self.Thickness = coef;
        end
        %
        function set.Exponent(self,coef)
            self.CoefAbsorbingPrecheck(coef,'Exponent');
            self.Exponent = coef;
        end
        %
        function set.Scaling(self,coef)
            self.CoefAbsorbingPrecheck(coef,'Scaling');
            self.Scaling = coef;
        end
    end % methods
    
    
    properties(Access = {?pde.internal.pde3DBCImpl})
        % Type - Type of boundary condition.
        Type;
    end
    
    methods(Hidden, Access = {?pde.ElectromagneticBCAssignmentRecords})
        function ok=CoefAbsorbingPrecheck(self,coef,coefName)
            % function to validate inputs specifically for the absorbing
            % region
            if isempty(coef)
                return;
            end
            if isempty(self.RecordOwner)
                return; % skip validation if loading from file
            end

            validateattributes(coef(:),{'double'},{'nonnan','nonempty','nonsparse','finite','real'},coefName);

            if ~isempty(self.FarField)
                if self.RecordOwner.ParentModel.IsTwoD
                    dim = 2;
                else
                    dim = 3;
                end

                if ~isempty(self.ElectricField) && ~isequal(self.ElectricField,zeros(dim,1))
                    error(message('pde:ElectromagneticModel:multipleBCsDefined'))
                end

                if ~isempty(self.MagneticField) && ~isequal(self.MagneticField,zeros(dim,1))
                    error(message('pde:ElectromagneticModel:multipleBCsDefined'))
                end
                
                % set PEC/PMC BCs for far field BCs to guarantee solvability
                % of system of equations (actual BCs don't matter here)
                self.ElectricField = zeros(dim,1);
                self.MagneticField = zeros(dim,1);
            end
            

            % validate > 0
            if coef <= 0
                error(message('pde:ElectromagneticModel:absorbingParamPositive',coefName));
            end
            ok = true;
        end

        function ok=CoefPrecheck(self,coef,coefName)
            if isempty(coef)
                return;
            end
            if isempty(self.RecordOwner)
                return; % Skips validation of rgnid while loading model from file.
            end
            
            % validate function handle or double input
            validateattributes(coef,{'double','function_handle'},{},'',coefName);
            if isa(coef, 'function_handle') && ~(nargin(coef) == 2)
                error(message('pde:thermalBCSpecification:invalidFunctionHandle'));
            end
            if isa(coef,'function_handle') % if function handle, valid
                ok = true;
                return
            end

            dim = allowedSize(self,coefName);
            
            if dim == 1
                shapeAttribute = 'scalar';
            else
                shapeAttribute = {'vector','numel',dim};
            end
            
            % validate const boundary condition based on type and dim
            if contains(string(coefName),"ElectricField") || contains(string(coefName),"MagneticField")
                validateattributes(coef(:),{'double'},[{'nonnan','nonempty','nonsparse','finite'} shapeAttribute(:)'],coefName);
            else
                validateattributes(coef(:),{'double'},[{'nonnan','nonempty','nonsparse','real','finite'} shapeAttribute(:)'],coefName);
            end
            ok = true;
            
        end
        
        function dim = allowedSize(self,coefName)
            dim = 1;
            if isequal(coefName,"MagneticPotential") && ~self.RecordOwner.ParentModel.IsTwoD
                dim = 3;
            end
            if isequal(coefName,"ElectricField") || isequal(coefName,"MagneticField")
                if self.RecordOwner.ParentModel.IsTwoD
                    dim = 2;
                else
                    dim = 3;
                end
            end
        end

        function validateTypeSizeComplexity(self, loc, state, complexAllowed)
            for prop = self.PropertiesToValidate.'
                if isempty(self.(prop{1}))
                    continue
                end
                coeffName = prop{1};
                coeffSize = allowedSize(self,coeffName);
                [complexityInput,sizeInput] = self.RecordOwner.ParentModel.validateFcnHdl(self.(prop{1}), loc, state);

                if complexityInput && ~complexAllowed
                    regionInfo = pde.ElectromagneticModel.getRegionText(self.RegionType,self.RegionID);
                    error(message('pde:ElectromagneticModel:invalidComplex',coeffName,regionInfo));
                end

                if (sizeInput ~= coeffSize)
                    error(message('pde:ElectromagneticModel:incorrectSizeCoefFunction', coeffName, coeffSize));
                end
            end
        end
    end
    
    methods(Hidden=true, Access = {?pde.ElectromagneticModel})
        function tf=numericCoefficients(self,varargin)
            tf = ~(isa(self.MagneticPotential, 'function_handle')  || ...
                isa(self.Voltage, 'function_handle') ...
                );
        end
    end
    
    methods(Access = private)
        function ok=ValidateRegionID(self,rgnid)
            if strcmpi(rgnid,'default') % not user specified ID.
                return;
            end
            if isempty(self.RecordOwner)
                return; % Skips validation of rgnid while loading model from file.
            end
            maxBoundaryID = self.RecordOwner.ParentModel.maxBoundaryID;
            validateattributes(rgnid,{'double'},{'nonsparse','integer','finite','positive','vector','<=',maxBoundaryID},'RegionID');
            ok = true;
        end
    end
        

    methods(Hidden=true)
        function delete(self)
            if ~isempty(self.RecordOwner) && isvalid(self.RecordOwner)
                self.RecordOwner.delistBoundaryConditions(self);
            end
        end
    end
    
    methods(Access = protected)

        function propgrp = getPropertyGroups(self)
            % customize the visible properties based on analysis type.
            propList = properties(self);
            commonProps = {'RegionID', 'RegionType','Vectorized'};
            farfieldGroup = [];
            if contains(self.RecordOwner.ParentModel.AnalysisType,'electrostatic','IgnoreCase',true)
                analysisSpecificProps = {'Voltage'};
                propList = [commonProps,analysisSpecificProps];
            elseif contains(self.RecordOwner.ParentModel.AnalysisType,'magnetostatic','IgnoreCase',true)
                analysisSpecificProps = {'MagneticPotential'};
                propList = [commonProps,analysisSpecificProps];
            elseif contains(self.RecordOwner.ParentModel.AnalysisType,'conduction','IgnoreCase',true)
                analysisSpecificProps = {'Voltage','SurfaceCurrentDensity'};
                propList = [commonProps,analysisSpecificProps];
            elseif contains(self.RecordOwner.ParentModel.AnalysisType,'harmonic','IgnoreCase',true) 
                farfieldList = {'FarField','Thickness','Scaling','Exponent'};
                analysisSpecificProps = {'ElectricField','MagneticField'};
                propList = [commonProps,analysisSpecificProps];
                farfieldGroup = matlab.mixin.util.PropertyGroup(farfieldList,"Far Field");
            end
            mainGroup = matlab.mixin.util.PropertyGroup(propList);
            propgrp = [mainGroup,farfieldGroup];
        end

    end

    properties (Hidden = true, Access={?pde.internal.pde3DBCImpl,?pde.ElectromagneticBCAssignmentRecords})
        h = 1;
        r;
        q;
        g;
    end
    
    properties (Hidden = true, Access='private')
        RecordOwner;
        PropertiesToValidate;
    end
end