classdef (Sealed) wordEncoding
%

% Copyright 2018-2022 The MathWorks, Inc.

    properties(Dependent, SetAccess=private)
        NumWords;
        Vocabulary;
    end
    properties(Access=private)
        Wordlist;
    end

    methods
        function enc=wordEncoding(documents,varargin)
            narginchk(1,5);

            enc.Wordlist = textanalytics.internal.wordlist;
            words = documents;
            validateDocumentOrWords(words);
            if isa(words,'tokenizedDocument')
                vocab = words.Vocabulary;
            else
                vocab = unique(words,'stable');
            end

            ip = inputParser;
            assignmentValues = {'first-seen','frequency'};
            addParameter(ip,'Order','first-seen',...
                         @(s) any(validatestring(s,assignmentValues)));
            addParameter(ip,'MaxNumWords',Inf, ...
                         @(x)validateMaxNumWordsValue(x));
            ip.parse(varargin{:});

            maxNumWords = ip.Results.MaxNumWords;
            if isinf(maxNumWords)
                maxNumWords = numel(vocab);
            end
            order = validatestring(ip.Results.Order,assignmentValues);

            switch order
              case 'first-seen'
                enc.Wordlist = addWords(enc.Wordlist,vocab(1:min(maxNumWords,numel(vocab))));
              case 'frequency' % need to find word counts first
                bag = bagOfWords(words);
                tbl = topkwords(bag,Inf);
                enc.Wordlist = addWords(enc.Wordlist,tbl.Word(1:min(maxNumWords,bag.NumWords)));
            end
        end

        function v = word2ind(enc,words,NameValueArgs)
            arguments
                enc wordEncoding
                words
                NameValueArgs.IgnoreCase (1,1) logical = false;
            end

            textanalytics.internal.validateString(words, 2);
            words = string(words);
            if ~isvector(words)
                error(message('textanalytics:wordEncoding:StringVectorExpected'));
            end
            v=findNums(enc.Wordlist,words,NameValueArgs.IgnoreCase);

            % We want to preserve the input vector orientation
            if iscolumn(words)
                v = v';
            end
        end

        function match = ind2word(enc,m)
            narginchk(2,2);
            if ~isa(enc,'wordEncoding')
                error(message('textanalytics:wordEncoding:EncodingExpected'));
            end
            validateattributes(m,{'numeric'},{'ndims',2,'positive'}, 'ind2word', 'M', 2);
            % NaN values are allowed, but real values with non-zero
            % fractional parts are not
            toValidate = m(~isnan(m));
            validateattributes(toValidate,{'numeric'},{'integer'}, 'ind2word', 'M', 2);

            m = cast(m,'double');
            m = gather(m);

            dict = enc.Wordlist;
            match = dict(m);
        end

        function tf = isVocabularyWord(enc, words, NameValueArgs)
            arguments
                enc wordEncoding
                words
                NameValueArgs.IgnoreCase (1,1) logical = false;
            end
            textanalytics.internal.validateString(words, 2);
            dict = enc.Wordlist;
            if NameValueArgs.IgnoreCase
                tf = ismember(dict,words,"ignorecase");
            else
                tf = ismember(dict,words);
            end
        end

        function m = get.NumWords(enc)
            dict = enc.Wordlist;
            m = dict.NumWords;
        end

        function vocab = get.Vocabulary(enc)
            dict = enc.Wordlist;
            vocab = dict.Vocabulary;
        end
    end

    methods (Hidden)
        % wordEncoding can only be scalar, so eq is the same as isequal
        function tf = eq(a,b)
            tf = isequal(a,b);
        end

        function tf = isequal(a,b)
            tf = isa(a,"wordEncoding") && ...
                isa(b,"wordEncoding") && ...
                isequal(a.Wordlist, b.Wordlist);
        end

        function tf = keyMatch(a,b) % for dictionary
            tf = isequal(a,b);
        end

        function h = keyHash(a) % for dictionary, must align with keyMatch semantics
            h = keyHash(a.Wordlist);
        end
    end

    methods(Access=private)
        % no support for these, as with wordEmbedding
        cat(d)
        transpose(d)
        ctranspose(d)
        repmat(d,varargin)
        horzcat(varargin)
        vertcat(varargin)
    end

end

function v=findNums(dict,words,ignoreCase)
    words = words(:);
    if nargin > 2 && ignoreCase
        inds = dict(words,"ignorecase");
    else
        inds = dict(words);
    end
    bad = inds == 0;
    good = ~bad;
    goodvals = inds(good);

    v(good) =  goodvals;
    v(bad) = nan;
end

function validateMaxNumWordsValue(x)
    if isinf(x)
        validateattributes(x,{'numeric'},{'scalar','positive'});
    else
        validateattributes(x,{'numeric'},{'scalar','positive','integer'});
    end
end

function validateDocumentOrWords(x)

    isDocument = isa(x,'tokenizedDocument');
    if ~isDocument && ( ~(textanalytics.internal.stringType(x)) || ~isrow(x) )
        error(message('textanalytics:wordEncoding:InvalidDocumentOrWords'));
    end

    if ~isDocument && (isscalar(string(x)) && contains(x," "))
        warning(message('textanalytics:general:splitWords'));
    end
end
