classdef lanespec
%LANESPEC Create a specification for lanes in drivingScenario/road
%   L = lanespec(numLanes) returns a lane specification object for the
%   given number of lanes. The object defines Width and Marking properties
%   for the lanes. If numLanes is scalar, one travel direction is assumed
%   when forming the default values for the Marking property. For both
%   travel directions, specify numLanes for the left (L) and right (R)
%   sides along the road centers using a 1-by-2 vector [L  R]. The total
%   number of lanes (N) is determined as: N = sum(numLanes). The total
%   number of lane markings (M) is determined as: M = N + 1. Marking for
%   the left and right edges of the road is included in this calculation. A
%   lane is defined to be between the center of the lane markings to its
%   left and right. Lanes and lane markings are defined from left to right
%   along the road centers.
%
%   L = lanespec(numLanes, Name, Value, ...) specifies additional
%   name-value pair arguments described below:
%
%     Width            A scalar or 1-by-N array specifying the width of
%                      each lane in meters. When a scalar value is
%                      specified, it is used for all the lanes. The default
%                      value is 3.6m.
%
%     Marking          A scalar or 1-by-M array of lane marking object(s).
%                      When a scalar value is specified, it is used for all
%                      the lane markings. Use laneMarking(type) function to
%                      construct lane marking objects.
%
%     Type             A scalar or an 1-by-M array of lane type object(s).
%                      Use laneType(type) function to construct a lane type
%                      object.
%
%
%   Example
%   -------
%   % Straight road with three lanes
%   s = drivingScenario;
%   roadCenters = [0 0; 80 0];
%   road(s,roadCenters,'Lanes',lanespec([1 2],'Width',3));
%   plot(s);
%
%   See also laneMarking, laneType, laneSpecConnector,  compositeLaneSpec,
%   road, drivingScenario

%   Copyright 2017-2019 The MathWorks, Inc.

    properties(SetAccess = private)
        NumLanes
    end
    
    properties
        Width
        Marking
        Type
    end
    
    properties(Constant,Hidden)
        DefaultWidth = 3.6
    end

    methods
        function obj = lanespec(numLanes,varargin)
            %LANES Construct an instance of this class
            fName = mfilename;
            if isscalar(numLanes)
                validateattributes(numLanes,{'numeric'}, {'integer','finite','positive',...
                    '<=',30}, fName, 'NumLanes');
            else
                validateattributes(numLanes,{'numeric'}, {'integer','finite','positive','vector',...
                    'numel',2}, fName, 'NumLanes');
                validateattributes(sum(numLanes),{'numeric'},{'<=',30}, fName, 'NumLanes');
            end
            obj.NumLanes = numLanes;
            % Parse the input
            r = parse(obj,varargin{:});
            % Validate the results
            r = validate(obj,r);
            obj.Width = r.Width;
            obj.Marking = r.Marking;
            obj.Type = r.Type;
        end
  
        function obj = set.Width(obj,value)
            % Set the width of the lane
            value = validateWidth(obj,value);
            obj.Width = value;
        end

        function obj = set.Marking(obj,value)
            % Set the marking of the lane
            value = validateMarking(obj,value);
            obj.Marking = value;
        end

        function obj = set.Type(obj,value)
            % Set the marking of the lane
            value = validateLaneTypes(obj,value);
            obj.Type = value;
        end
    end

    methods(Access = protected)
        function r = parse(~,varargin)
            % Parse the inputs
            p = inputParser;
            % Lane properties
            addParameter(p,'Width',[]);
            addParameter(p,'Marking',[]);
            addParameter(p,'Type',[]);
            % Parse the inputs
            p.parse(varargin{:});
            r = p.Results;
        end

        function r = validate(obj,r)
            % Validate the parsed results
            numLanes = obj.NumLanes;
            totalLanes = sum(numLanes);
            % Road edge markings are included
            numMarkings = totalLanes + 1;
            % Infer the number of travel directions for forming the
            % defaults from the specified number of lanes
            isBothTravelDirections = numel(numLanes) > 1;
            % Lane widths
            if isempty(r.Width)
                r.Width = ones(1,totalLanes).*obj.DefaultWidth;
            else
                val = r.Width;
                r.Width = val;
            end
            % Lane markings
            if isempty(r.Marking)
                % When no marking is provided, use values commonly found in
                % the US roadways. Many other countries also have these
                % values.
                lm = driving.scenario.LaneMarking.empty;
                % Edge markings are solid white. Marking separating traffic
                % in the same direction is dashed white.
                lm(1) = laneMarking('Solid');
                for kndx = 2:numMarkings-1
                    lm(kndx) = laneMarking(driving.scenario.LaneMarking.DefaultTypeSameDirection);
                end
                lm(end+1) = laneMarking('Solid');
                % Marking separating traffic in the opposite directions is
                % double solid yellow.
                if isBothTravelDirections
                    lm(numLanes(1)+1) = laneMarking(driving.scenario.LaneMarking.DefaultTypeOppositeDirections);
                    lm(numLanes(1)+1).Color = driving.scenario.LaneMarking.DefaultColorOppositeDirections;
                else
                    lm(1).Color = driving.scenario.LaneMarking.DefaultColorOppositeDirections;
                end
            else
                lm = r.Marking;
            end
            r.Marking = lm;
            %Lane Types
            if isempty(r.Type)
                % When no lane type is provided, create road with all
                % driving lane types
                lt = driving.scenario.LaneType.empty;
                for kndx = 1:totalLanes
                    lt(kndx) = laneType(LaneTypes.Driving);
                end
            else
                lt = r.Type;
            end
             r.Type = lt;
        end
        
        function value = validateWidth(obj,value)
            % Helper to validate the lane width
            totalLanes = sum(obj.NumLanes);
            % Expand scalar width value
            if isscalar(value)
                value = ones(1,totalLanes).*value;
            end
            validateattributes(value,{'numeric'}, {'real','finite','positive',...
                'vector','numel',totalLanes},mfilename, 'Width');
            % If marking is already set, check to see if the new lane width
            % is more than the marking widths.
            if ~isempty(obj.Marking)
                validateMarkingWidth(obj,obj.Marking);
            end
        end
        
        function validateCompositeMarking(obj,marking)
            % Ensure that marking width does not exceed the lane width
            numLanes = sum(obj.NumLanes);
           laneWidths = obj.Width;
            numMarkings = numLanes+1;
            mWidths = cell(1,numMarkings);
             % If lane width is already set, check to see if the new marking
             % width is more than the lane widths.
             if ~isempty(laneWidths)
                 for kndx = 1:numMarkings
                     if isa(marking(kndx), 'driving.scenario.CompositeMarking')
                         nMarkers = length(marking(kndx).Markings);
                         aggWidth = zeros(1,nMarkers);
                         for mndx = 1: nMarkers
                             if isa(marking(kndx).Markings(mndx),'driving.scenario.SolidMarking')
                                 aggWidth(1,mndx) =  marking(kndx).Markings(mndx).Width;
                             end
                         end
                          mWidths{kndx} = aggWidth;
                     elseif isa(marking(kndx),'driving.scenario.SolidMarking')
                         mWidths{kndx} = marking(kndx).Width;
                     end
                 end
             end
        end

        function value = validateMarking(obj,value)
            % Helper to validate the lane marking
            numMarkings = sum(obj.NumLanes)+1;
            % Expand scalar marking value
            if isscalar(value)
                value = repmat(value,1,numMarkings);
            end
            validateattributes(value,{'driving.scenario.LaneMarking','driving.scenario.LaneBase'},{'numel',numMarkings},mfilename,'Marking');
            % If lane width is already set, check to see if the new marking
            % width is more than the lane widths.
            variableMarkings = false;
            for lmndx = 1:numMarkings
                % Check whether having variable lane markings or not.
                if isa(value(lmndx),'driving.scenario.CompositeMarking')
                    variableMarkings = true;
                    break;
                end
            end
            if ~variableMarkings
                if ~isempty(obj.Width)
                    validateMarkingWidth(obj,value);
                end
            else
                validateCompositeMarking(obj,value);
            end
        end
        
        function value = validateLaneTypes(obj, value)
            % Helper to validate the lane type
            numLanes = sum(obj.NumLanes);
            % Expand scalar lane type value
            if isscalar(value)
                value = repmat(value,1,numLanes);
            end
            if  ~isempty(value)
                validateattributes(value,{'driving.scenario.LaneType'},{'numel',numLanes},mfilename,'Type');
            end
        end

        function validateMarkingWidth(obj,marking)
            % Ensure that marking width does not exceed the lane width
            numLanes = sum(obj.NumLanes);
            numMarkings = numLanes+1;
            mWidths = zeros(1,numMarkings);
            for kndx = 1:length(marking)
                % Using isa for performance reasons instead of polymorphism
                if isa(marking(kndx),'driving.scenario.SolidMarking')
                    mWidths(kndx) = marking(kndx).Width;
                end
            end
        end
    end
end

