% @doc a word is an ordset of ltrs
%
% This mathematically is a cluster of letters; so in "A + B + AB", this is one
% of the summands.
%
% multiplication is implied in a word
%
% empty word is 1
%
% anything times itself equals itself, so duplication is ignored
%
% multiplication = inclusive union
%
% operations assume all inputs are valid
-module(wfc_word).

-export_type([
    word/0
]).

-export([
    %% constructors
    one/0,
    validate/1,
    from_list/1, to_list/1,
    %% ops
    mul/2, mul/1
]).

-opaque word() :: {w, ordsets:ordset(wfc_ltr:ltr())}.

%%----------------------------
%% constructor
%%----------------------------

-spec one() -> word().

one() ->
    {w, []}.


-spec validate(Word) -> Result
    when Word   :: word(),
         Result :: ok
                 | {error, Reason :: string()}.
% @doc
% check each letter in the word for validity
%
% also check that word shape is valid
% @end

validate(W = {w, Letters}) ->
    case from_list(Letters) of
        {ok, Result} when Result =:= W ->
            ok;
        Error ->
            Error
    end;
validate(X) ->
    {error, wfc_utils:str("wfc_word:validate: malformed word: ~tp", [X])}.



-spec to_list(word()) -> Result
    when Result :: {ok, [wfc_ltr:ltr()]}
                 | {error, string()}.

to_list({w, Ltrs}) -> {ok, Ltrs};
to_list(Bad)       -> {error, wfc_utils:str("wfc_word:to_list: bad letter: ~tp", [Bad])}.



-spec from_list(Ltrs) -> Result
    when Ltrs :: list(wfc_ltr:ltr()),
         Result  :: {ok, word()}
                  | {error, Reason :: string()}.

from_list(Chars) ->
    from_list(ordsets:from_list(Chars), []).


%% validate each letter
from_list([Ltr | Rest], Acc) ->
    case wfc_ltr:validate(Ltr) of
        ok    -> from_list(Rest, [Ltr | Acc]);
        Error -> Error
    end;
% done, all good
from_list([], Acc) ->
    {ok, {w, lists:reverse(Acc)}}.


%%----------------------------
%% ops
%%----------------------------

-spec mul(word(), word()) -> word().
% @doc product of two words
%
% assumes the words are valid

mul({w, X}, {w, Y}) ->
    {w, ordsets:union(X, Y)}.


-spec mul([word()]) -> word().
% @doc multiply a list of words together

mul([Word | Rest]) -> mul(Word, mul(Rest));
mul([])            -> one().
