%   Copyright 2013-2018 The MathWorks, Inc.

classdef CodeTemplate < handle
    properties (SetAccess = protected)
        CGTFile % Full Path of the CGT File        
    end
    
    properties(Hidden)
        rawSectionsMap % Raw Parsed Sections
        orderedSectionHeaders % ordered array of sections
    end
    
    methods
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        % Constructor
        function obj = CodeTemplate(cgtFileName)
            obj.CGTFile = cgtFileName;
            obj.rawSectionsMap = containers.Map;
            obj.orderedSectionHeaders = {};
            % Actual function that does the parsing of the file
            obj.parseCGTFile();
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        % Utility function for emitting all sections in a loop 
        function emitCGTSections(obj, tokenRepository,isCPPComment)
            % This goes over the array of rawSectionCells to create
            % actual CGTSections
            for i = 1:length(obj.orderedSectionHeaders)
                % Get the Section Name:
                [~] = obj.createAndEmitSection(obj.orderedSectionHeaders{i},tokenRepository,isCPPComment);
            end
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        % Helper function that creates and emits the CGT Section, given a
        % section name
        function stringBuffer = createAndEmitSection(obj, sectionName, tokenRepository,isCPPComment)
            
            if nargin < 4
                isCPPComment = false;
            end
            
            if ~isKey(obj.rawSectionsMap, sectionName)
                error(message('Coder:templates:cgtSectionNotPresent',sectionName, obj.CGTFile));                
            end            
            rawString = obj.rawSectionsMap(sectionName);
            % Extract style, width attributes from raw string
            [style,width] = obj.readSectionHeaderForAttributes(rawString, sectionName,isCPPComment);
            % create the CGT Section with the attributes
            sectionObj = obj.createSectionForRegion(sectionName,style,width);           
            sectionObj.parseSection(rawString,tokenRepository);
            
            if(contains(sectionName, 'Custom'))
               sectionObj.setIsCustomSection(); 
            end
            
            stringBuffer = sectionObj.emitSection();
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        % Check to see if a section is present; before invoking createAndEmit
        function out = isCodeTemplateSection(obj, sectionName)
            out = isKey(obj.rawSectionsMap, sectionName);              
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    end  
    
    methods (Access = private)
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        % The function that parses the CGT file and stores information about the
        % section headers and the raw sections into the properties
        % 'orderedSectionHeaders' and 'rawSectionsMap' respectively.
        function parseCGTFile(obj)
            
            [~,~,ext] = fileparts(obj.CGTFile);
            pre_region_buffer = [];
            if ~isequal(ext,'.cgt')
                error(message('Coder:templates:cgtFileNotFound',obj.CGTFile));
            end
                       
            % Open input file using default encoding to read bytes in using fgetl
            inId = fopen(obj.CGTFile,'r','n');           
            if inId < 0
                error(message('RTW:utility:fileIOError',obj.CGTFile,'open'));
            end
            c = onCleanup(@()fclose(inId));

            % Get encoding that file opened in
            [~,~,~,encoding] = fopen(inId);

            % Load the file into a temporary buffer
            lineCount = 0;
            lineNo = 0;
            customSectionObjCount = 0;
            while true
                str = fgetl(inId);
                
                if ~ischar(str)   % EOF reached
                    break;
                end
                lineCount = lineCount + 1;
                lineNo = lineNo + 1;
                % Skip comment line
                if ~isempty(regexp(str,'^\s*%%','ONCE'))
                    continue;
                end
                
                % Skip empty lines
                if isempty(str)
                    continue;
                end
                
                region = regexp(str, '<[^\s].*?>');
                % Make sure its not a token:
                if region
                    if (region(1) ~=1)
                        region(1) = ~(str(region(1)-1)=='%') || (region(1)-1 == 1);
                    end
                end
                if ~isempty(region) && coder.isEncodingTag(str)
                    new_encoding = coder.getEncoding(str);
                    if ~strcmp(encoding,new_encoding)
                        fPosition = ftell(inId);
                        clear c; % clean up file
                        inId = fopen(obj.CGTFile,'r','n',new_encoding);
                        if inId < 0
                            DAStudio.error('RTW:utility:fileIOError',obj.CGTFile,'open');
                        end
                        c = onCleanup(@()fclose(inId));
                        fseek(inId,fPosition,'bof');
                        encoding = new_encoding;
                    end
                    continue;
                end
                if region
                    if ~isempty(pre_region_buffer)
                        if(~all(isspace(pre_region_buffer)))
                            region_buffer = sprintf('%s\n%s\n%s',['<CustomSection','_', num2str(customSectionObjCount),'>']',pre_region_buffer,...
                                ['<CustomSection','_', num2str(customSectionObjCount),'/>']);
                            pre_region_buffer = [];
                            obj.orderedSectionHeaders{end+1} =['CustomSection','_', num2str(customSectionObjCount)];
                            obj.rawSectionsMap(['CustomSection','_', num2str(customSectionObjCount)])= region_buffer;
                            customSectionObjCount = customSectionObjCount + 1;
                        end
                    end
                                        
                    region_buffer = str;
                    temp = regexp(str, '<[^\s]*', 'match');
                    
                    if(length(temp) > 1)
                        error(message('Coder:templates:cgtmultipleTagOnSameLine', ...
                            str, obj.CGTFile));
                    end
                    
                    regionName = temp{1}(2:end);
                    if regionName(end) =='>'
                        regionName = regionName(1:end-1);
                    end
                    if region_buffer(1) =='%'
                        % we are in a code insertion section that do not
                        % terminate
                        
                        
                        
                    else
                        ispresent = false;
                        endTag =  ['</' regionName '>'];
                        while(~ispresent)
                            str = fgetl(inId);
                            if(~ischar(str))   % EOF is reached
                                break;
                            end
                                
                            % Skip comment line inside region
                            if ~isempty(regexp(str,'^\s*%%','ONCE'))
                                continue;
                            end
                                                      
                            % Check for nested Tags. This test below skips over
                            % any tokens in the line. Also, we do not want to
                            % check beyond the first tag in the line. If it is
                            % not the closing tag of interest, it is a nested
                            % Tag; error out if so.
                            isTag = regexp(str, '(?<![%])<[^\s].*?>','match');
                            if(~isempty(isTag))
                                if(strcmp(isTag{1},endTag))
                                    ispresent = true;                                          
                                else
                                    error(message('Coder:templates:cgtInvalidNestedTag', ...
                                        isTag{1}, obj.CGTFile, endTag));
                                end
                            end
                            
                            region_buffer = sprintf('%s\n%s',region_buffer,str);
                            
                        end
                        if ~ispresent
                            % End Tag was not found
                            error(message('Coder:templates:cgtEndTagMissing', ...
                                endTag, obj.CGTFile));
                        end
                    end                  
                    
                    obj.rawSectionsMap(regionName) = region_buffer;
                    obj.orderedSectionHeaders{end+1} = regionName;
                    
                else
                    % Not inside any section, create Custom Section on the
                    % fly headers
                    if isempty(pre_region_buffer)
                        pre_region_buffer = str;
                    else
                        pre_region_buffer = sprintf('%s\n%s',pre_region_buffer,str);
                    end
                    
                end               
            end
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        % Create a CGTSection object based on the name and the style and width
        % properties. 
        function sectionObj =  createSectionForRegion(~,regionName,style,width)
            
            if ~isempty(strfind(regionName,'CustomSection_'))
                sectionObj = coder.codeTemplate.CustomSection(style, width);
            else
                switch regionName
                    case 'FileBanner'
                        sectionObj = coder.codeTemplate.FileBannerSection(style, width);
                    case 'FunctionBanner'
                        sectionObj = coder.codeTemplate.FunctionBannerSection(style, width);
                    case 'SharedUtilityBanner'
                        sectionObj = coder.codeTemplate.SharedUtilityFunctionBannerSection(style,width);
                    case 'FileTrailer'
                        sectionObj = coder.codeTemplate.FileTrailerSection(style,width);
                    case 'IncludeFilesBanner'
                         sectionObj = coder.codeTemplate.IncludeFilesBannerSection(style,width);
                    case 'TypeDefinitionsBanner'
                         sectionObj = coder.codeTemplate.TypeDefinitionsBannerSection(style,width);
                    case 'NamedConstantsBanner'
                         sectionObj = coder.codeTemplate.NamedConstantsBannerSection(style,width);
                    case 'VariableDeclarationsBanner'
                         sectionObj = coder.codeTemplate.VariableDeclarationsBannerSection(style,width);
                    case 'VariableDefinitionsBanner'
                         sectionObj = coder.codeTemplate.VariableDefinitionsBannerSection(style,width);
                    case 'FunctionDeclarationsBanner'
                         sectionObj = coder.codeTemplate.FunctionDeclarationsBannerSection(style,width);
                    case 'FunctionDefinitionsBanner'
                        sectionObj = coder.codeTemplate.FunctionDefinitionsBannerSection(style,width);
                    case 'CustomSourceCodeBanner'
                        sectionObj = coder.codeTemplate.CustomSourceCodeBannerSection(style,width);
                    case 'CustomHeaderCodeBanner'
                        sectionObj = coder.codeTemplate.CustomHeaderCodeBannerSection(style,width);                        
                    case 'Includes'
                        sectionObj = coder.codeTemplate.IncludeSection(style);
                    case 'Defines'
                        sectionObj = coder.codeTemplate.DefinesSection(style);
                    case 'Enums'
                        sectionObj = coder.codeTemplate.EnumsSection(style);
                    case 'Definitions'
                        sectionObj = coder.codeTemplate.DefinitionsSection(style);
                    case 'Declarations'
                        sectionObj = coder.codeTemplate.DeclarationsSection(style);
                    case 'Functions'
                        sectionObj = coder.codeTemplate.FunctionSection(style);
                    case 'Types'
                        sectionObj = coder.codeTemplate.TypesSection(style);
                    case 'IntrinsicTypes'
                        sectionObj = coder.codeTemplate.IntrinsicTypesSection(style);
                    case 'PrimitiveTypedefs'
                        sectionObj = coder.codeTemplate.PrimitiveTypedefsSection(style);
                    case 'UserTop'
                        sectionObj = coder.codeTemplate.UserTopSection(style);
                    case 'Typedefs'
                        sectionObj = coder.codeTemplate.TypedefsSection(style);
                    case 'CompilerErrors'
                        sectionObj = coder.codeTemplate.CompilerErrorsSection(style);
                    case 'CompilerWarnings'
                        sectionObj = coder.codeTemplate.CompilerWarningsSection(style);
                    case 'Documentation'
                        sectionObj = coder.codeTemplate.DocumentationSection(style);
                    case 'UserBottom'
                        sectionObj = coder.codeTemplate.UserBottomSection(style);
                    otherwise
                        sectionObj = coder.codeTemplate.CodeInsertionSection(style, width);
                end
            end
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        % Read the raw section header and extract the attributes and their values from
        % the first line of the section header.
        function [style, width] = readSectionHeaderForAttributes(obj,rawSection, regionName,isCPPComment)
            str = strrep(rawSection, '=', ' = ');
            lines = regexp(str, '\n', 'split');
            % Attributes must be in the first line only:
            locations = regexp(lines{1}, '\s+','split');
            % Assign Default values to 'style' and 'width' property
            style = 'classic';   % We need two. One for internal use. 
            style1= 'classic';   % One for error checking. 
              
            width = 80;
            
            i = 2;
            attribs = {'style','width'};
            while i <= length(locations)
                % look for pattern 'Attired = value'
                
                if ischar(locations{i})
                    % check if current attribute is a valid attribute for current
                    % region header
                    if isempty(locations{i})
                        break;
                    end
                    
                    if locations{i}(1) == '<' && locations{i}(end) == '>'
                        % An inner tag was found within a larger tag: <tag ... <tag> >
                        error(message('Coder:templates:cgtInvalidTagFormatRegion', regionName, obj.CGTFile));
                        
                    end

                    if locations{i}(end) == '>'
                        break;
                    end
                    attrib_name = '';
                    isValid_attrib = false;
                    for j = 1:length(attribs)
                        if strcmp(locations{i}, attribs{j})
                            isValid_attrib = true;
                            attrib_name = attribs{j};
                            break;
                        end
                    end
                end
                
                if ~isValid_attrib
                    error(message('Coder:templates:cgtInvalidTagAttribNameRegion', locations{i},regionName, obj.CGTFile));
                end
                % error end out if no quotes ' or "
                i = i + 1;
                if i < length(locations) && locations{i} == '='
                    i = i + 1;
                    if locations{i}(end) == '>'
                        locations{i} = locations{i}(1:end-1);
                    end
                    
                    if locations{i}(1) == '='
                        % handle '==' error
                        error(message('Coder:templates:cgtInvalidTagAttribFormatRegion', ...
                            attrib_name, regionName, obj.CGTFile));
                    end
                    
                    attrib_value = obj.getAttribValue(locations{i});
                    if isempty(attrib_value)
                        error(message('Coder:templates:cgtTagAttribMissingQuoteRegion', ...
                            attrib_name, regionName, obj.CGTFile ));
                    end
                    
                    if(strcmp(attrib_name,'style'))
                        style = lower(attrib_value);
                        tokens = regexp(style,'_cpp','split');
                        style1 = tokens{1};
                    else
                        width = attrib_value;
                    end
                    
                    i = i + 1;
                else
                    % Does not obey any known formats; error out
                    error(message('Coder:templates:cgtInvalidTagAttribFormatRegion', ...
                        attrib_name, regionName, obj.CGTFile));
                end
            end
            
            if(isCPPComment)
                style1 = [style1 ,'_cpp'];
            end
            
            % Error checking for 'style' attribute
            try
                style = coder.BannerStyle.(style1);
            catch
                error(message('Coder:templates:incorrectBannerStyle',style));
            end
            
            % Error checking for width attribute
            if(~isnumeric(width))
                if((~all(isstrprop(width, 'digit'))) || (str2double(width) == 0.0))
                    error(message('Coder:templates:incorrectWidthValue',width));
                end
                width = str2double(width);
            end
            % Range check for width property
            width = uint32(width);
            if(width < 60 || width > 120)
                error(message('Coder:templates:incorrectWidthValue',width));
            end            
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function ret = getAttribValue(~,value_str)
            % Function ret = getAttribValue(value_str)
            % Abstract:
            % return a attribute value by parsing a string, like
            % "attribute_value" or 'attribute_value'
            %  return empty string if quote is missing or mismatch
            ret = '';
            if value_str(1) == value_str(end) && length(value_str) > 1 && ...
                    (value_str(1) == '''' || value_str(1) == '"')
                ret = value_str(2:end-1);
            end
            return;
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        
    end
end
% LocalWords:  cgt latin Attrib Instrinsic
