classdef TwoDimensionalMOT < Simulator.MOTCaptureProcess & matlab.mixin.Copyable

    properties (Access = private)
        MagneticGradienDefault                       = 0.40; % T/m
        ExitDivergenceDefault                        = 15e-3;
        DistanceBetweenPushBeamAnd3DMOTCenterDefault = 0;
        PushBeamDistanceDefault                      = 0.32;
    end
    
    properties (Access = public)
        TotalPower;
        LandegFactor;
        MagneticSubLevel;
        MagneticGradient;
        CaptureVelocity;
        ExitDivergence;
        DistanceBetweenPushBeamAnd3DMOTCenter;
        PushBeamDistance;
        TimeSpentInInteractionRegion;
        ParticleDynamicalQuantities;
        InitialParameters;
        BootstrapSampleLength;
        BootstrapSampleNumber;
        Results;
    end
    
    methods
        function this  = TwoDimensionalMOT(varargin)
            this@Simulator.MOTCaptureProcess('SimulationMode', '2D', varargin{:});
            p = inputParser;
            p.KeepUnmatched = true;
            addParameter(p, 'TotalPower',             0.8,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'LandegFactor',             1,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'MagneticSubLevel',         1,...
                @(x) assert(isnumeric(x) && isscalar(x)));  
            addParameter(p, 'MagneticGradient',      0.38,...
                @(x) assert(isnumeric(x) && isscalar(x)));  
            
            p.parse(varargin{:});
            
            this.TotalPower          = p.Results.TotalPower;
            this.LandegFactor        = p.Results.LandegFactor;
            this.MagneticSubLevel    = p.Results.MagneticSubLevel;  
            this.MagneticGradient    = p.Results.MagneticGradient; 
            this.ExitDivergence      = this.ExitDivergenceDefault;
            this.DistanceBetweenPushBeamAnd3DMOTCenter = this.DistanceBetweenPushBeamAnd3DMOTCenterDefault;
            this.PushBeamDistance    = this.PushBeamDistanceDefault;
            this.InitialParameters   = struct;
            this.InitialParameters.TotalPower      = this.TotalPower;
            this.InitialParameters.LandegFactor    = this.LandegFactor;
            this.InitialParameters.MagneticSubLevel= this.MagneticSubLevel;
            this.InitialParameters.MagneticGradient= this.MagneticGradient;
            this.BootstrapSampleLength = 0.5 * this.NumberOfAtoms;
            this.BootstrapSampleNumber = 1000;
        end
        
        function restoreDefaults(this)
            this.TotalPower                            = this.InitialParameters.TotalPower;
            this.LandegFactor                          = this.InitialParameters.LandegFactor;
            this.MagneticSubLevel                      = this.InitialParameters.MagneticSubLevel;
            this.MagneticGradient                      = this.InitialParameters.MagneticGradient;
            this.ExitDivergence                        = this.ExitDivergenceDefault;
            this.DistanceBetweenPushBeamAnd3DMOTCenter = this.DistanceBetweenPushBeamAnd3DMOTCenterDefault;
            this.PushBeamDistance                      = this.PushBeamDistanceDefault;
        end
    end % - lifecycle    

    methods
        function set.TotalPower(this,val)
            this.TotalPower = val;
        end
        function ret = get.TotalPower(this)
            ret = this.TotalPower;
        end
        function set.LandegFactor(this,val)
            this.LandegFactor = val;
        end
        function ret = get.LandegFactor(this)
            ret = this.LandegFactor;
        end
        function set.MagneticSubLevel(this,val)
            this.MagneticSubLevel = val;
        end
        function ret = get.MagneticSubLevel(this)
            ret = this.MagneticSubLevel;
        end
        function set.MagneticGradient(this,val)
            this.MagneticGradient = val;
        end
        function ret = get.MagneticGradient(this)
            ret = this.MagneticGradient;
        end
        function set.CaptureVelocity(this,val)
            this.CaptureVelocity = val;
        end
        function ret = get.CaptureVelocity(this)
            ret = this.CaptureVelocity;
        end
        function set.ExitDivergence(this,val)
            this.ExitDivergence = val;
        end
        function ret = get.ExitDivergence(this)
            ret = this.ExitDivergence;
        end
        function set.DistanceBetweenPushBeamAnd3DMOTCenter(this,val)
            this.DistanceBetweenPushBeamAnd3DMOTCenter = val;
        end
        function ret = get.DistanceBetweenPushBeamAnd3DMOTCenter(this)
            ret = this.DistanceBetweenPushBeamAnd3DMOTCenter;
        end
        function set.PushBeamDistance(this,val)
            this.PushBeamDistance = val;
        end
        function ret = get.PushBeamDistance(this)
            ret = this.PushBeamDistance;
        end
        function set.TimeSpentInInteractionRegion(this,val)
            this.TimeSpentInInteractionRegion = val;
        end
        function ret = get.TimeSpentInInteractionRegion(this)
            ret = this.TimeSpentInInteractionRegion;
        end
        function set.ParticleDynamicalQuantities(this,val)
            this.ParticleDynamicalQuantities = val;
        end
        function ret = get.ParticleDynamicalQuantities(this)
            ret = this.ParticleDynamicalQuantities;
        end
        function set.InitialParameters(this,val)
            this.InitialParameters = val;
        end
        function ret = get.InitialParameters(this)
            ret = this.InitialParameters;
        end
        function set.BootstrapSampleLength(this,val)
            this.BootstrapSampleLength = val;
        end
        function ret = get.BootstrapSampleLength(this)
            ret = this.BootstrapSampleLength;
        end
        function set.BootstrapSampleNumber(this,val)
            this.BootstrapSampleNumber = val;
        end
        function ret = get.BootstrapSampleNumber(this)
            ret = this.BootstrapSampleNumber;
        end
        function set.Results(this, val)
            this.Results = val;
        end
        function ret = get.Results(this)
            ret = this.Results;
        end
    end % - setters and getters
    
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %- Methods
    
    methods(Access = protected)
        function cp = copyElement(this)
            % Shallow copy object
            cp = copyElement@matlab.mixin.Copyable(this);
            
            % Forces the setter to redefine the function handles to the new copied object
            
            pl = properties(this);
            for k = 1:length(pl)
                sc = superclasses(this.(pl{k}));
                if any(contains(sc,{'matlab.mixin.Copyable'}))
                    cp.(pl{k}) = this.(pl{k}).copy();
                end
            end
        end
    end
    
    methods (Static)
        
        % Creates an Instance of Class, ensures singleton behaviour (that there
        % can only be one Instance of this class
        function singleObj = getInstance(varargin)
            % Creates an Instance of Class, ensures singleton behaviour
            persistent localObj;
            if isempty(localObj) || ~isvalid(localObj)
                localObj =  Simulator.TwoDimensionalMOT(varargin{:});
            end
            singleObj = localObj;
        end
    end
    
end
