classdef MOTCaptureProcess < handle & matlab.mixin.Copyable

    properties (Access = public)
        SimulationMode; % MOT type
        TimeStep;
        SimulationTime;
        NumberOfAtoms;
        ErrorEstimationMethod;
        
        %Flags
        SpontaneousEmission;
        SidebandBeam;
        PushBeam;
        Gravity;
        BackgroundCollision;
        
        DebugMode;
        DoSave;
        SaveDirectory;
    end
    
    properties
        Beams = {};                %Contains beam objects
    end % - public
    
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %- Methods
    
    methods
        %% Class Destructor (Clears object)
        function this  = MOTCaptureProcess(varargin)
            
            p = inputParser;
            p.KeepUnmatched = true;
            addParameter(p, 'SimulationMode', '2D',... 
                @(x) any(strcmpi(x,{'2D','3D', 'Full'})));
            addParameter(p, 'ErrorEstimationMethod', 'jackknife',... 
                @(x) any(strcmpi(x,{'jackknife','bootstrap'})));
            addParameter(p, 'NumberOfAtoms',    5000,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'TimeStep',       10e-06,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'SimulationTime',       3e-03,...
                @(x) assert(isnumeric(x) && isscalar(x) && (x > 0)));  
            addParameter(p, 'SpontaneousEmission',  false,...
                @islogical);
            addParameter(p, 'SidebandBeam',         false,...
                @islogical);
            addParameter(p, 'PushBeam',             false,...
                @islogical);
            addParameter(p, 'Gravity',              false,...
                @islogical);
            addParameter(p, 'BackgroundCollision',  false,...
                @islogical);
            addParameter(p, 'DebugMode',            false,...
                @islogical);
            addParameter(p, 'SaveData',             false,...
                @islogical);
            addParameter(p, 'SaveDirectory',          pwd,...
                @ischar);
            
            p.parse(varargin{:});
            
            this.SimulationMode       = p.Results.SimulationMode;
            this.ErrorEstimationMethod= p.Results.ErrorEstimationMethod;
            
            this.NumberOfAtoms        = p.Results.NumberOfAtoms;
            this.TimeStep             = p.Results.TimeStep;
            this.SimulationTime       = p.Results.SimulationTime;  
            
            this.SpontaneousEmission  = p.Results.SpontaneousEmission;
            this.SidebandBeam         = p.Results.SidebandBeam;
            this.PushBeam             = p.Results.PushBeam;
            this.Gravity              = p.Results.Gravity;
            this.BackgroundCollision  = p.Results.BackgroundCollision;
            
            this.DebugMode            = p.Results.DebugMode;
            this.DoSave               = p.Results.SaveData;
            this.SaveDirectory        = p.Results.SaveDirectory;
            
            switch this.SimulationMode
                case "2D" 
                    this.Beams{1}   = Simulator.Beams('Blue');
                    this.Beams{2}   = Simulator.Beams('BlueSideband');
                    this.Beams{3}   = Simulator.Beams('Push');
                case "3D"
                    this.Beams{1} = Simulator.Beams('Red');
                % Development In progress
                case "Full"
                % Development In progress
            end
        end
    end % - lifecycle    

    methods
        function set.TimeStep(this, val)
            assert(val > 1e-06, 'Not time efficient to compute for time steps smaller than 1 microsecond!');
            this.TimeStep = val;
        end
        function ret = get.TimeStep(this)
            ret = this.TimeStep;
        end
        function set.SimulationTime(this, val)
%             assert(val <= 5e-03, 'Not time efficient to compute for time spans longer than 5 milliseconds!');
            this.SimulationTime = val;
        end
        function ret = get.SimulationTime(this)
            ret = this.SimulationTime;
        end
        function set.NumberOfAtoms(this, val)
            assert(val <= 50000, '!!Not time efficient to compute for atom numbers larger than 50,000!!');
            this.NumberOfAtoms = val;
        end
        function ret = get.NumberOfAtoms(this)
            ret = this.NumberOfAtoms;
        end
        
        function set.DebugMode(this, val)
            this.DebugMode = val;
        end
        function ret = get.DebugMode(this)
            ret = this.DebugMode;
        end
        function set.DoSave(this, val)
            this.DoSave = val;
        end
        function ret = get.DoSave(this)
            ret = this.DoSave;
        end
        function set.SaveDirectory(this, val)
            this.SaveDirectory = val;
        end
        function ret = get.SaveDirectory(this)
            ret = this.SaveDirectory;
        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.MOTCaptureProcess(varargin{:});
            end
            singleObj = localObj;
        end
    end
    
end
