%% Copyright (c) 2008-2022 Robert Virding
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.

-module(lfe_edlin_expand).

%% A default LFE expand function for edlin, expanding modules and
%% functions. It knows about LFE symbol syntax but as yet only works
%% for (mod:fun ...) and not (: mod fun ...)

-export([expand/1, format_matches/1]).

-import(lists, [reverse/1, nthtail/2, prefix/2]).

%% expand(CurrentBefore) ->
%%     {yes, Expansion, Matches} | {no, Expansion, Matches}
%%  Try to expand the word before as either a module name or a
%%  function name. CurrentBefore is reversed and over_word/3 reverses
%%  the characters it finds. In certain cases possible expansions are
%%  printed.

expand(Bef0) ->
    {Bef1,S1,_} = over_symbol(Bef0, [], 0),
    case Bef1 of
        [$:|Bef2] ->                            %After a ':'
            {Bef3,S2,_} = over_symbol(Bef2, [], 0),
            need_lparen(Bef3, fun () -> expand_function_name(S2, S1, " ") end);
        Bef2 ->
            need_lparen(Bef2, fun () -> expand_module_name(S1, ":") end)
    end.

need_lparen(Bef, Do) ->
    case over_white(Bef, [], 0) of
        {[$(|_],_,_} -> Do();
        {_,_,_} -> {no,[],[]}
    end.

%% expand(Bef0) ->
%%     {Bef1,Word,_} = edlin:over_word(Bef0, [], 0),
%%     case over_white(Bef1, [], 0) of
%%         {[$:|Bef2],_White,_Nwh} ->
%%             {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
%%             {_,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
%%             expand_function_name(Mod, Word);
%%         {_,_,_} ->
%%             expand_module_name(Word)
%%     end.

expand_module_name("", _) ->
    {no, [], []};
expand_module_name(Prefix, CompleteChar) ->
    ModPaths = [ {list_to_atom(M),P} || {M,P,_} <- code:all_available() ],
    match(Prefix, ModPaths, CompleteChar).

expand_function_name(ModStr, FuncPrefix, CompleteChar) ->
    case to_symbol(ModStr) of
        {ok, Mod} ->
            Exports =
                case erlang:module_loaded(Mod) of
                    true ->
                        Mod:module_info(exports);
                    false ->
                        case beam_lib:chunks(code:which(Mod), [exports]) of
                            {ok, {Mod, [{exports,E}]}} ->
                                E;
                            _ ->
                                {no, [], []}
                        end
                end,
            case Exports of
                {no, [], []} ->
                    {no, [], []};
                Exports ->
                    match(FuncPrefix, Exports, CompleteChar)
            end;
        error ->
            {no, [], []}
    end.

%% If it's a quoted symbol, atom_to_list/1 will do the wrong thing.
to_symbol(Str) ->
    case lfe_scan:string(Str) of
        {ok,[{symbol,_,A}],_} -> {ok,A};
        _ -> error
    end.

match(Prefix, Alts, Extra0) ->
    Len = length(Prefix),
    Matches = lists:sort([{S, A} || {H, A} <- Alts,
                                    begin
                                        S = hd(lfe_io:fwrite1("~w", [H])),
                                        prefix(Prefix, S)
                                    end]),
    case longest_common_head([N || {N, _} <- Matches]) of
        {partial, []} ->
            {no, [], Matches};                  %format_matches(Matches)};
        {partial, Str} ->
            case nthtail(Len, Str) of
                [] -> {yes,[],Matches};         %format_matches(Matches)};
                Remain -> {yes,Remain,[]}
            end;
        {complete, Str} ->
            Extra = case {Extra0,Matches} of
                        {" ",[{Str,0}]} -> ")";
                        {_,_} -> Extra0
                    end,
            {yes, nthtail(Len, Str) ++ Extra, []};
        no ->
            {no,[],[]}
    end.

%% Return the list of names L in multiple columns.
format_matches(L) ->
    S = format_col(lists:sort(L), []),
    ["\n" | S].

format_col([], _) -> [];
format_col(L, Acc) -> format_col(L, field_width(L), 0, Acc).

format_col(X, Width, Len, Acc) when Width + Len > 79 ->
    format_col(X, Width, 0, ["\n" | Acc]);
format_col([A|T], Width, Len, Acc0) ->
    H = case A of
            %% If it's a tuple {string(), integer()}, we assume it's an
            %% arity, and meant to be printed.
            {H0, I} when is_integer(I) ->
                H0 ++ "/" ++ integer_to_list(I);
            {H1, _} -> H1;
            H2 -> H2
        end,
    Acc = [io_lib:format("~-*s", [Width,H]) | Acc0],
    format_col(T, Width, Len+Width, Acc);
format_col([], _, _, Acc) ->
    lists:reverse(Acc, "\n").

field_width(L) -> field_width(L, 0).

field_width([{H,_}|T], W) ->
    case length(H) of
        L when L > W -> field_width(T, L);
        _ -> field_width(T, W)
    end;
field_width([H|T], W) ->
    case length(H) of
        L when L > W -> field_width(T, L);
        _ -> field_width(T, W)
    end;
field_width([], W) when W < 40 ->
    W + 4;
field_width([], _) ->
    40.

longest_common_head([]) ->
    no;
longest_common_head(LL) ->
    longest_common_head(LL, []).

longest_common_head([[]|_], L) ->
    {partial, reverse(L)};
longest_common_head(LL, L) ->
    case same_head(LL) of
        true ->
            [[H|_]|_] = LL,
            LL1 = all_tails(LL),
            case all_nil(LL1) of
                true ->
                    {complete, reverse([H|L])};
                false ->
                    longest_common_head(LL1, [H|L])
            end;
        false ->
            {partial, reverse(L)}
    end.

same_head([[H|_]|T1]) -> same_head(H, T1).

same_head(H, [[H|_]|T]) -> same_head(H, T);
same_head(_, [])        -> true;
same_head(_, _)         -> false.

all_tails(LL) -> all_tails(LL, []).

all_tails([[_|T]|T1], L) -> all_tails(T1, [T|L]);
all_tails([], L)         -> L.

all_nil([]) -> true;
all_nil([[] | Rest]) -> all_nil(Rest);
all_nil(_) -> false.

%% over_symbol(Chars, InitialStack, InitialCount) ->
%%      {RemainingChars,CharStack,Count}
%% over_non_symbol(Chars, InitialStack, InitialCount) ->
%%      {RemainingChars,CharStack,Count}
%%  Step over symbol/non-symbol characters pushing the stepped over
%%  ones on the stack.

over_symbol(Cs, Stack, N) ->
    L = length([1 || $| <- Cs]),
    case L rem 2 of
        0 -> over_symbol1(Cs, Stack, N);
        1 -> until_quote(Cs, Stack, N)
    end.

until_quote([$||Cs], Stack, N) ->
    {Cs, [$||Stack], N+1};
until_quote([C|Cs], Stack, N) ->
    until_quote(Cs, [C|Stack], N+1).

over_symbol1([$||Cs], Stack, N) ->
    until_quote(Cs, [$||Stack], N+1);
over_symbol1(Cs, Stack, N) ->
    over_symbol2(Cs, Stack, N).

over_symbol2([C|Cs], Stack, N) ->
    case symbol_char(C) of
        true -> over_symbol2(Cs, [C|Stack], N+1);
        false -> {[C|Cs],Stack,N}
    end;
over_symbol2([], Stack, N) when is_integer(N) ->
    {[],Stack,N}.

%% over_non_symbol([C|Cs], Stack, N) ->
%%     case symbol_char(C) of
%%         true -> {[C|Cs],Stack,N};
%%         false -> over_non_symbol(Cs, [C|Stack], N+1)
%%     end;
%% over_non_symbol([], Stack, N) ->
%%     {[],Stack,N}.

symbol_char($:) -> false;                       %We want to separate on this
symbol_char(C) -> lfe_scan:symbol_char(C).

%% over_white(Chars, InitialStack, InitialCount) ->
%%    {RemainingChars,CharStack,Count}.

over_white([$\s|Cs], Stack, N) ->
    over_white(Cs, [$\s|Stack], N+1);
over_white([$\t|Cs], Stack, N) ->
    over_white(Cs, [$\t|Stack], N+1);
over_white(Cs, Stack, N) when is_list(Cs) ->
    {Cs,Stack,N}.
