classdef CommunityFeed
    % CommunityFeed - See what's going on at MATLAB Central without leaving MATLAB.
    %   This class depends on the MATLAB-Central-Interface-for-MATLAB found at 
    %   https://www.mathworks.com/matlabcentral/fileexchange/135567-matlab-central-interface-for-matlab
    %   Example: CommunityFeed.thisWeek("app designer", CommunityFeed.Scope.All)

    properties (Constant)
        % Scope is a static property that allows you to select which area
        % of the communty content is included in the results
        Scope = struct('All', 'matlab-answers,file-exchange,blogs,community-highlights,community-contests,cody', ...
            'Answers', 'matlab-answers', ...
            'Blogs', 'blogs', ...
            'Cody', 'cody', ...
            'Contests', 'community-contests', ...
            'ExcludeAnswers', 'file-exchange,blogs,community-highlights,community-contests,cody', ...
            'FileExchange', 'file-exchange', ...
            'Highlights', 'community-highlights')
    end

    methods (Static)

        function last7Days(varargin)
            % Display a feed of content from the last 7 days that matches a
            % query term and scope. The function also takes a size limit
            % for the number of results to return.
            dt = datetime('now', 'Format', 'eee, MMM d, yyyy 00:00:01 a');
            created_after = dt - days(7);
            
            p = inputParser;
            addOptional(p, "queryTerm", "", @isstring);
            addOptional(p, "scope", CommunityFeed.Scope.All, ...
                @(x) any(validatestring(x, struct2cell(CommunityFeed.Scope))));
            addOptional(p, "created_after", created_after);
            addOptional(p, "limit", 12, @isnumeric);
            addOptional(p, "page", 1, @isnumeric);

            parse(p, varargin{:});
      
            results = SearchApi.search( ...
                query = p.Results.queryTerm, ...
                scope = p.Results.scope, ...
                created_after = p.Results.created_after, ...
                sort_order = "updated desc", ...
                page = p.Results.page, ...
                count = p.Results.limit ...
             );

            CommunityFeed.parseData(results, p.Results);
        end

        function last30Days(varargin)
            % Display a feed of content from the last 30 days that matches a
            % query term and scope. The function also takes a size limit
            % for the number of results to return.
            dt = datetime('now', 'Format', 'eee, MMM d, yyyy 00:00:01 a');
            created_after = dt - days(30);
            
            p = inputParser;
            addOptional(p, "queryTerm", "", @isstring);
            addOptional(p, "scope", CommunityFeed.Scope.All, ...
                @(x) any(validatestring(x, struct2cell(CommunityFeed.Scope))));
            addOptional(p, "created_after", created_after);
            addOptional(p, "limit", 12, @isnumeric);
            addOptional(p, "page", 1, @isnumeric);

            parse(p, varargin{:});
      
            results = SearchApi.search( ...
                query = p.Results.queryTerm, ...
                scope = p.Results.scope, ...
                created_after = p.Results.created_after, ...
                sort_order = "updated desc", ...
                page = p.Results.page, ...
                count = p.Results.limit ...
             );

            CommunityFeed.parseData(results, p.Results);
        end
        
        function thisWeek(varargin)
            % Display a feed of content from the the current week from Sunday 
            % that matches a query term and scope. The function also takes a 
            % size limit for the number of results to return.
            dt = datetime('now', 'Format', 'eee, MMM d, yyyy 00:00:01 a');
            day = weekday(dt);
            created_after = dt - days(day-1);
            
            p = inputParser;
            addOptional(p, "queryTerm", "", @isstring);
            addOptional(p, "scope", CommunityFeed.Scope.All, ...
                @(x) any(validatestring(x, struct2cell(CommunityFeed.Scope))));
            addOptional(p, "created_after", created_after);
            addOptional(p, "limit", 12, @isnumeric);
            addOptional(p, "page", 1, @isnumeric);

            parse(p, varargin{:});
      
            results = SearchApi.search( ...
                query = p.Results.queryTerm, ...
                scope = p.Results.scope, ...
                created_after = p.Results.created_after, ...
                sort_order = "updated desc", ...
                page = p.Results.page, ...
                count = p.Results.limit ...
             );

            CommunityFeed.parseData(results, p.Results);
        end

        function thisMonth(varargin)
            % Display a feed of content from the the current month from its  
            % first day that matches a query term and scope. The function 
            % also takes a size limit for the number of results to return.
            dt = datetime('now', 'Format', 'eee, MMM d, yyyy 00:00:01 a');
            created_after = dateshift(dt,'start','month');

            p = inputParser;
            addOptional(p, "queryTerm", "", @isstring);
            addOptional(p, "scope", CommunityFeed.Scope.All, ...
                @(x) any(validatestring(x, struct2cell(CommunityFeed.Scope))));
            addOptional(p, "created_after", created_after);
            addOptional(p, "limit", 12, @isnumeric);
            addOptional(p, "page", 1, @isnumeric);

            parse(p, varargin{:});
      
            results = SearchApi.search( ...
                query = p.Results.queryTerm, ...
                scope = p.Results.scope, ...
                created_after = p.Results.created_after, ...
                sort_order = "updated desc", ...
                page = p.Results.page, ...
                count = p.Results.limit ...
             );

            CommunityFeed.parseData(results, p.Results);

        end

        function today(varargin)
            % Display a feed of content for the current day from midnight  
            % that matches a query term and scope. The function 
            % also takes a size limit for the number of results to return.
            created_after = datetime('today', 'Format', 'eee, MMM d, yyyy 00:00:01 a');

            p = inputParser;
            addOptional(p, "queryTerm", "", @isstring);
            addOptional(p, "scope", CommunityFeed.Scope.All, ...
                @(x) any(validatestring(x, struct2cell(CommunityFeed.Scope))));
            addOptional(p, "created_after", created_after);
            addOptional(p, "limit", 12, @isnumeric);
            addOptional(p, "page", 1, @isnumeric);

            parse(p, varargin{:});

            results = SearchApi.search( ...
                query = p.Results.queryTerm, ...
                scope = p.Results.scope, ...
                created_after = p.Results.created_after, ...
                sort_order = "updated desc", ...
                page = p.Results.page, ...
                count = p.Results.limit ...
             );

            CommunityFeed.parseData(results, p.Results);
        end

        function getNextpage(queryTerm, scope, created_after, page, limit)
            % Requests the next page of results from the API
            obj.queryTerm = queryTerm;
            obj.scope = scope;
            obj.page = page;
            obj.created_after = created_after;
            obj.limit = limit;

            results = SearchApi.search( ...
                query = queryTerm, ...
                scope = scope, ...
                created_after = created_after, ...
                sort_order = "updated desc", ...
                page = page, ...
                count = limit ...
             );

            CommunityFeed.parseData(results, obj);
        end

        function parseData(results, context)
            % Parse and then render the data from the API results and
            % display them in the command window
            itemsCount = length(results.items);
            types = string(zeros(itemsCount,1));
            titles = string(zeros(itemsCount,1));
            dates = string(zeros(itemsCount,1));
            meta = string(zeros(itemsCount,1));
            authors = string(zeros(itemsCount,1));
            
            for n=1:itemsCount
                item = results.items(n);
                switch item.Type
                    case "question"
                        if(item.IsAccepted)
                            meta(n) = "Accepted";
                       else
                            meta(n) = strcat(string(item.AnswerCount), " answers");
                        end
                    case "file"
                        meta(n) = strcat("v",string(item.Version));
                    case "blog"
                        meta(n) = strcat(string(item.Viewcount)," views");
                end
            
                authors(n) = sprintf( ...
                    '<a href="https://www.mathworks.com/matlabcentral/profile/authors/%s">%s</a>', ...
                    string(item.Author.AuthorId), item.Author.AuthorName);
                types(n) = item.Type;

                % Truncate titles
                ellipse = '';
                title = decodeHTMLEntities(item.Title);
                l = strlength(title);
                if l > 75
                    l = 75;
                    ellipse = '...';
                end
                
                titles(n) = sprintf('<a href="%s" title="%s">%s %s</a>', ...
                    item.Url, item.Title, extractBetween(title, 1, l), ellipse);

                dates(n) = datetime(item.UpdatedDate, 'Format', 'eee, MMM d, yyyy h:mm a');
            end
            
            T = table(types, titles, meta, authors, dates, ...
                'VariableNames', {'Item','Title','Details','Author','Date'});

            fprintf('\n    ---------------- Community Feed for "%s" in %s (%d) ----------------\n\n', ...
                context.queryTerm, context.scope, results.total_found);

            disp(T);

            if(results.has_more)
                txt = input("Load more? Y/N: ","s");
                if isempty(txt)
                    txt = 'y';
                end

                if(strcmpi(txt, 'y'))
                    CommunityFeed.getNextpage(context.queryTerm, ...
                        context.scope, ...
                        context.created_after, ...
                        context.page + 1, ...
                        context.limit);
                else
                    return
                end
            end
        end
    end
end
