%%% This file is part of RefactorErl.
%%%
%%% RefactorErl is free software: you can redistribute it and/or modify
%%% it under the terms of the GNU Lesser General Public License as published
%%% by the Free Software Foundation, either version 3 of the License, or
%%% (at your option) any later version.
%%%
%%% RefactorErl is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%%% GNU Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public License
%%% along with RefactorErl.  If not, see <http://plc.inf.elte.hu/erlang/>.
%%%
%%% The Original Code is RefactorErl.
%%%
%%% The Initial Developer of the Original Code is Eötvös Loránd University.
%%% Portions created  by Eötvös Loránd University and ELTE-Soft Ltd.
%%% are Copyright 2007-2025 Eötvös Loránd University, ELTE-Soft Ltd.
%%% and Ericsson Hungary. All Rights Reserved.

%%% @author Judit Koszegi <kojqaai@inf.elte.hu>

-module(web_helper).
-vsn("$Rev: 17551 $ ").

-export([start_yaws/1]).
-export([get_index_yaws_folder/0]).
-export([convert_list_to_integer/1, string_to_ip/1]).

-export([error_text/2]).


% from the nitro purge
-export([get_errors/1,
    query_request/1, query_request/2, query_request/3, data_request/1,
    syn_request/1, syn_request2/3, generate_node_request/2,
    make_error_message/1]).
-export([init_rqtab/0, insert_to_rqtab/4, find_in_rqtab_by_reqid/1, 
         find_in_rqtab_by_user/1, get_rqtab_elements/0, delete_from_rqtab/1]).
-export([make_ui_request/1, make_ui_request/2]).
-export([filtered_query/1, remote_print/2, get_error_forms_in_database/0]).
-export([get_mods/0, get_funs/0, node_to_text/1]).
-export([execute_system_query/2]).
-export([calculate_result/2, calculate_result/3, calculate_result/4,
    get_result_list/2, get_node_position/1]).

% ---

-include("ui.hrl").

error_text(bound_port, Port)->
    io_lib:format("The given port (~p) has been already bound by another application.", [Port]);
error_text(no_right_associated_to_port, Port) ->
    io_lib:format("you don't have appropriate rights to use the given port (~p).", [Port]).
%%% ============================================================================
%%% Start, configure and stop yaws web server

%% @doc Configure and start yaws web server.
start_yaws(["from_script", YPath, YName, YPort, YListen]) ->
    try
        YawsPathProp = {yaws_path,YPath},
        YawsNameProp = {yaws_name,YName},
        YawsPortProp = {yaws_port, convert_list_to_integer(YPort)},
        YawsListenProp = {yaws_listen, string_to_ip(YListen)},
        YawsIndexFunModuleProp = {yaws_index_fun_module, ?MODULE},
        YawsIndexFunNameProp = {yaws_index_fun_name, get_index_yaws_folder},
        AllProp = [YawsNameProp,YawsPortProp,YawsListenProp,
                   YawsIndexFunModuleProp,YawsIndexFunNameProp],
        case YPath of
            "no_path" -> start_yaws(AllProp);
            _ -> start_yaws([YawsPathProp|AllProp])
        end
    catch
        RefErr ->
            io:format(?Error:error_text(RefErr) ++ "~n"),
            exit("Cannot start yaws.")
    end;

start_yaws(Proplist) ->
    case is_yaws_started() of
        true -> {error, yaws_already_started};
        false -> start_yaws0(Proplist)
    end.

start_yaws0(Proplist) ->
    case (proplists:get_value(yaws_path,Proplist)) of
        undefined -> ok;
        YawsDir ->
            case code:add_path(YawsDir) of
                true -> ok;
                {error,_} ->
                    throw(?RefError(file_notdir,[YawsDir]))
            end
    end,
    case code:ensure_loaded(yaws) of
        {error,_} -> throw(?RefErr0r(yaws_not_loaded));
            _ -> ok
    end,
    Port = proplists:get_value(yaws_port,Proplist,8001),
    validate_port_number(Port),
    Listen = proplists:get_value(yaws_listen,Proplist,{0,0,0,0}),
    validate_ip_tuple(Listen),
    Name0 = proplists:get_value(yaws_name,Proplist,"refactorErl"),
    Name = case is_atom(Name0) of
               true -> atom_to_list(Name0);
               false -> Name0
           end,
    validate_name(Name),
    YawsIndexFunModule = proplists:get_value(yaws_index_fun_module,
                                             Proplist,web_helper),
    YawsIndexFunName = proplists:get_value(yaws_index_fun_name,Proplist,
                                           get_index_yaws_folder),
    YawsAppMod = proplists:get_value(yaws_app_mod,Proplist,no_appmod),
    %Nitrogen = proplists:get_value(nitrogen_prop,Proplist,no_nitrogen),
    YawsPostSize = proplists:get_value(yaws_partial_post_size,Proplist,1024),

    GconfList = [{id, Name}, {servername, Name}, {logdir, ?MISC:data_dir()}],
    %GconfList = case Nitrogen of
    %                no_nitrogen -> [{id, Name},
    %                                {servername, Name},
    %                                {logdir, ?MISC:data_dir()}];
    %                with_nitrogen ->[
    %                                 {ebin_dir,
    %            [?NITRO_CORE:get_nitrogen_site_dir()++"/ebin"]},
    %                                 {include_dir,
    %            [?NITRO_CORE:get_nitrogen_site_dir()++"/include"]},
    %                                 {id, Name},
    %                                 {servername,Name},
    %                                 {logdir, ?MISC:data_dir()}]
    %            end,

    SconfList =
    case YawsAppMod of
        no_appmod ->[{docroot, apply(YawsIndexFunModule,YawsIndexFunName,[])},
                     {port, Port},
                     {listen, Listen}];
        _ ->        [{docroot, apply(YawsIndexFunModule,YawsIndexFunName,[])},
                     {port, Port},
                     {listen, Listen},
                     {appmods, YawsAppMod},
                    {partial_post_size,YawsPostSize}]
    end,
    DocRoot = apply(YawsIndexFunModule,YawsIndexFunName,[]),
    case proplists:get_value(only_config, Proplist, false) of
        false ->
            yaws:start_embedded(DocRoot,SconfList,GconfList);
        true ->
            yaws_api:embedded_start_conf(DocRoot, SconfList, GconfList, Name)
    end.


validate_port_number(Port) ->
    case is_number(Port) of
        true -> is_unbound_port(Port);
        false ->throw(?RefError(port_format_error,[io_lib:format("~p",[Port])]))
    end.

validate_ip_tuple(Listen) ->
    Str = io_lib:format("~p",[Listen]),
    case Listen of
        {A,B,C,D} ->
            case is_number(A) andalso is_number(B) andalso
                is_number(C) andalso is_number(D) of
                true -> ok;
                false -> throw(?RefError(ip_format_error,[Str]))
            end;
        _ -> throw(?RefError(ip_format_error,[Str]))
    end.

validate_name(Name) ->
    case is_string(Name) of
        true -> ok;
        false ->throw(?RefError(name_format_error,[io_lib:format("~p",[Name])]))
    end.

is_unbound_port(Port)->
    % business logic and also some fragments were copied from yaws_ctl.erl
    case gen_tcp:connect({127,0,0,1}, Port,
                         [{active, false},
                          {reuseaddr, true},
                          binary,
                          {packet, 2}], 2000) of
        {ok, Socket} ->
            gen_tcp:close(Socket),
            throw(?LocalError(bound_port, Port));
        {error,eacces} ->
            throw(?LocalError(no_right_associated_to_port, Port));
        {error, _} ->
            true
    end.

is_char(Ch) ->
    if Ch < 0 -> false;
       Ch > 255 -> false;
       true -> true
    end.

is_string(Str) ->
    case is_list(Str) of
        false -> false;
        true -> lists:all(fun is_char/1, Str)
    end.

get_index_yaws_folder() ->
    Path0 = filename:split(filename:dirname(code:which(web_helper))),
    Path =
        filename:join([
                       filename:join(
                         lists:sublist(Path0,length(Path0)-1)),
                      "web"]),
    Path.

convert_list_to_integer(String) ->
    try
        list_to_integer(String)
    catch _A:_B ->
            throw(?RefError(list_to_integer_error,[String]))
    end.

take_and_drop(L) ->
    {convert_list_to_integer(lists:takewhile(fun(X) -> not(X == $.) end,L)),
     (lists:dropwhile(fun(X) -> not(X == $.) end,L))}.


string_to_ip(YPort) ->
    L1 = YPort,
    {Number1,L2} = take_and_drop(L1),
    case L2 of
        [] ->  throw(?RefError(ip_format_error,[YPort]));
        _ ->
            {Number2,L3} = take_and_drop(tl(L2)),
            case L3 of
                [] -> throw(?RefError(ip_format_error,[YPort]));
                _ ->  {Number3,L4} = take_and_drop(tl(L3)),
                      case L4 of
                          [] -> throw(?RefError(ip_format_error,[YPort]));
                          _ ->  {Number4,_L5} = take_and_drop(tl(L4)),
                                {Number1,Number2,Number3,Number4}
                      end
            end
    end.

is_yaws_started()->
    lists:any(fun({yaws,_,_})->
                true;
                (_)->false
            end, application:which_applications()).




%%% below are functions formerly in the removed nitrogen files


-define(RQ_TAB, 
    filename:join([?MISC:data_dir(),
                   "running_queries_table"++ 
                   ?MISC:graph_based_postfix()])).  

%%% get_errors
get_errors(Forms) when is_list(Forms)->
    ErrorFiles=query_request(Forms,[{form, back}]),
    lists:foldl(fun({File, Form}, Acc)->
        {Index, First}=case syn_request2(File, form, Form) of
            1 -> {1,true};
            Int -> {Int-1,false}
        end,
        {StartPos,Length} = case First of
            true->
                [Token]=query_request(Form, [{flex, last}]),
                case query_request(?Token,pos,Token) of
                    {EPos,_} ->
                    %%  Len= ?Form:form_length(Form),
                    %%  Len=(data_request(Form))#form.length,
                        Len = form_length_request(Form),
                        {EPos-Len,Len};
                    not_found -> {not_found, 0}
                end;
            false->
                PrevForm=query_request(File, ?File:form(Index)),
                [Token]=query_request(PrevForm, [{flex, last}]),
                case query_request(?Token,pos,Token) of
                      {SPos, _} ->
                   %%     Len=(data_request(Form))#form.length
                          Len = form_length_request(Form)
                             +query_request(?Token,len,
                                            query_request(?Token,data,Token)),
                        {SPos+1,Len-1}; %% 2 ????
                    not_found -> {not_found, 0}
                end
            end,
        Tag=(data_request(Form))#form.tag,
        case {ErrorMessage=make_error_message(Tag),StartPos} of
            {"", _} -> Acc;
            {_, not_found} -> Acc;
            _ -> Acc++[{query_request(?File,path,File), 
                        StartPos, 
                        Length,
                        ErrorMessage}]
        end
    end, [], lists:zip(ErrorFiles, Forms));
                
get_errors([])->[];

get_errors(_)->[].

%%% ============================================================================
%%% UI router helper functions

query_request(Arg1) ->
    case make_ui_request({graph_query, ?Query, exec, [Arg1]}) of
        {error, {deny,_}} -> throw(request_denied);
        {error, E} -> throw(E);
        {ok,R} -> R
    end.
query_request(Arg1,Arg2) ->
    case make_ui_request({graph_query, ?Query,exec, [Arg1,Arg2]}) of
        {error, {deny,_}} -> throw(request_denied);
        {error, E} -> throw(E);
        {ok,R} -> R
    end.
query_request(Mod,Fun,Arg) ->
    case make_ui_request({graph_query, Mod, Fun, [Arg]}) of
        {error, {deny,_}} -> throw(request_denied);
        {error, E} -> throw(E);
        {ok,R} -> R
    end.
data_request(Arg) ->
    case make_ui_request({graph_data, Arg}) of
        {error, {deny,_}} -> throw(request_denied);
        {error, E} -> throw(E);
        {ok,R} -> R
    end.
syn_request(Arg) ->
    case make_ui_request({syn_leaves, Arg}) of
        {error, {deny,_}} -> throw(request_denied);
        {error, E} -> throw(E);
        {ok,R} -> R
    end.
syn_request2(Arg1, Arg2, Arg3) ->
    case make_ui_request({syn_index, Arg1, Arg2, Arg3}) of
        {error, {deny,_}} -> throw(request_denied);
        {error, E} -> throw(E);
        {ok,R} -> R
    end.
    
generate_node_request(Arg1, Arg2) ->
    case make_ui_request({html_generate_node, Arg1, Arg2}) of
        {error, {deny,_}} -> throw(request_denied);
        {error, E} -> throw(E);
        {ok,R} -> R
    end.
    
form_length_request(Arg) ->
    case make_ui_request({form_length, Arg}) of
        {error, {deny,_}} -> throw(request_denied);
        {error, E} -> throw(E);
        {ok,R} -> R
    end.

%%% 

make_error_message(Tag)->
    try
    case Tag of
        {1,Mess} -> Mess;
        {no_include_file, Name} -> 
            io_lib:format("Include file not found: ~p", [Name]);
        {no_include_lib, Name} ->
            io_lib:format("Include lib not found: ~p", [Name]);
        {include_lib, application_not_specified, Name} ->
            io_lib:format("Application not specified -include lib: ~p", [Name]);
        {unknown_env_in_include, [Name]}->
            io_lib:format("Unknown env in include: ~p", [Name]);
        {include_error, IncName, Reason} ->
            io_lib:format("Include error in ~p. Reason: ~p",[IncName,Reason]);
        _ -> ""
    end
    catch
        _Type : _Error ->
            ""
    end.


%%% ============================================================================
%%% Low- level operations on 'running_queries_table' dets table
init_rqtab()->
    dets:open_file(?RQ_TAB,[]),
    dets:delete_all_objects(?RQ_TAB),
    dets:close(?RQ_TAB).

insert_to_rqtab(ReqID,User, QueryId, QStr) ->
    dets:open_file(?RQ_TAB,[]),
    dets:insert(?RQ_TAB,{ReqID,User,QueryId, QStr}),
    dets:close(?RQ_TAB).

find_in_rqtab_by_reqid(ReqID) ->
    dets:open_file(?RQ_TAB,[]),
    Result=dets:match_object(?RQ_TAB,{ReqID,'_','_','_'}),
    dets:close(?RQ_TAB),
    Result.

find_in_rqtab_by_user(User) ->
    dets:open_file(?RQ_TAB,[]),
    Result=dets:match_object(?RQ_TAB,{'_',User,'_','_'}),
    dets:close(?RQ_TAB),
    Result.

get_rqtab_elements()->
    dets:open_file(?RQ_TAB,[]),
    Result=dets:match_object(?RQ_TAB,{'_','_','_','_'}),
    dets:close(?RQ_TAB),
    Result.
    
delete_from_rqtab(RowPattern) ->
    dets:open_file(?RQ_TAB,[]),
    dets:match_delete(?RQ_TAB,RowPattern),
    dets:close(?RQ_TAB).


%%% ============================================================================
%%% UI requests

make_ui_request(UIArgs)->
    make_ui_request(UIArgs, []).

make_ui_request(UIArgs, Args)->
    %make request to UI router
    ReqID = ?UI:getid(),
    User = proplists:get_value(user, Args, nobody),
    QStr = proplists:get_value(querystr, Args, ""),
    NeedToAdministrate = 
        case User of
            nobody -> 
                false;
            _ when QStr /= "" -> 
                insert_to_rqtab(ReqID, User, no_id, QStr),
                true;
            _ -> 
                false
        end,
    case ?UI:request(ReqID,UIArgs) of
        deny -> {error, {deny, "The request was denied by the job server."++
                                "Please, try again later."}};
        ok -> ui_loop(ReqID, Args, NeedToAdministrate)
    end.

ui_loop(ReqID, Args, NeedToAdministrate)->
    receive
        {ReqID, reply, R} ->
            NeedToAdministrate andalso 
                delete_from_rqtab({ReqID, '_','_','_'}),
            R;        
        {ReqID, query_id, QueryId}->
            NeedToAdministrate andalso
                begin
                    Result= find_in_rqtab_by_reqid(ReqID),
                    case Result of
                        [{ReqID, User, _, QStr}]->
                            insert_to_rqtab(ReqID, User,
                                                          QueryId, QStr);
                        _ -> ok
                    end
                end,
            ui_loop(ReqID, Args, NeedToAdministrate);       
        {ReqID, progress, {add, _File, _, _Max}} ->
            ui_loop(ReqID, Args, NeedToAdministrate);
        {ReqID, progress, {drop, _File, _, _Max}} ->
            ui_loop(ReqID, Args, NeedToAdministrate);
        {ReqID, progress, {add, File, Percent, FormCount, FormMax, KBps}} ->
            remote_print({File, Percent, FormCount, FormMax,KBps},
                                       Args),
            ui_loop(ReqID, Args, NeedToAdministrate);
        {ReqID, progress, {drop, File, _Percent, FormCount, FormMax, KBps}} ->
            remote_print({File, FormCount/FormMax, FormCount,
                                        FormMax, KBps},Args),
            ui_loop(ReqID, Args, NeedToAdministrate)
    end.



%%% remote print

remote_print({File, Percent, FormCount, FormMax, KBps}, Args)->
    KBpsTxt=case KBps of
        +0.0 -> "";
        -0.0 -> "";
        0 -> "";
        _ -> ?MISC:format("~5.2f kB/s", [(0.0 + KBps)])
    end,
    Data=io_lib:format("File: ~s (~p / ~p) ~s", 
        [File, FormCount, FormMax, KBpsTxt]),
    remote_print({lists:flatten(Data), Percent*100}, Args);

remote_print(Data, Args)->
    RemotePrinterFun=proplists:get_value(remote_printer_fun, Args, 
                                         fun(_)->ok end),
    spawn(fun()->RemotePrinterFun(Data) end),
    ok.

%% @doc Replaces group of whitespace in the given query with 1 blank char.
filtered_query([]) -> [];
filtered_query(Query=[C|_]) when is_integer(C) ->
    {ok, Mp}=re:compile("(\s|\t|\n)+",[]),
    re:replace(Query,Mp," ",[{return, list}, global]).


%%% get error forms in database
 
get_error_forms_in_database() ->
    try
        case query_request(?Query:seq([file],?File:error_forms())) of
            [] -> [];
            ErrorForms -> get_errors(ErrorForms)
        end
    catch
        request_denied -> []
    end.


get_mods() ->
    try
        [atom_to_list(?Mod:name(M)) || 
            M<-query_request(?Mod:all())]
    catch
        request_denied -> []
    end.

get_funs() ->
    try
        [function_text(F) || F<-query_request(
            ?Query:seq([?Mod:all(),?Mod:locals_all()]))]
    catch
        request_denied -> []
    end.

function_text(FuncNode) ->
    atom_to_list(query_request(?Mod,name,
                 hd(query_request(FuncNode,?Fun:module()))))
    ++":"++atom_to_list(query_request(
        ?Fun,name,FuncNode))
    ++"/"++integer_to_list(query_request(
        ?Fun,arity,FuncNode)).

execute_system_query(Query,{query_display_opt,Option,needed_pattern,Pattern})->
    R=case calculate_result(Query,Option) of
        {result,Res} -> Res;
        _ -> [{result,""}]
    end,
    [Result] = ?MISC:pgetu([result],R),
    get_result_list(Result,Pattern).




calculate_result(Q,DisplayOpt)->
    calculate_result(Q,DisplayOpt, []).

calculate_result(Q,DisplayOpt, StartOpt) when is_list(StartOpt)->
    calculate_result(Q,DisplayOpt, StartOpt, []).
calculate_result(Q,DisplayOpt, StartOpt, Options) 
  when is_list(StartOpt) andalso is_list(Options)->
    SendBackQueryId=proplists:get_value(save_query_id, Options, false),
    User=proplists:get_value(user, Options, nobody),
    Req = {transform,semantic_query,
           [{ask_missing,false},
            {send_back_query_id, SendBackQueryId},
            {querystr,Q},
            {display_opt,DisplayOpt},
            {start_opt,StartOpt}]},
    case make_ui_request(Req,[{user,User},{querystr,Q}]) of
        {ok, Result} -> Result;
        M -> M
    end.


get_result_list(QueryResult,NeededPattern)->
    ResultList=string:tokens(QueryResult," \t\n"),
    case NeededPattern of
        none -> {result,ResultList};
        _ -> {result, lists:filter(fun(E)->
                                       case string:chr(E,NeededPattern) of
                                          0 -> false;
                                          _ -> true 
                                       end 
                                   end,ResultList)}
    end.

%% @doc Return start and end position of given node
get_node_position(Node) ->
    try
        Tokens=syn_request(Node),
        {Start,_}=query_request(?Token,pos,hd(Tokens)),
        {_,End}=query_request(?Token,pos,lists:last(Tokens)),
        {Start-1,End}
    catch
        request_denied -> 0
    end.

%% @doc Converts then given node's contents to plain text
node_to_text(Node) ->
    try 
        data_request(Node),
        case Node of
            {_,clause,_} -> 
                FuncNode=query_request(Node,
                    ?Query:seq([?Clause:form(),?Form:func()])),
                if
                    FuncNode==[] -> "";
                    true -> function_text(hd(FuncNode))
                end;
            {_,func,_} -> 
                function_text(Node);
            {_,module,_} -> 
                atom_to_list(query_request(?Mod,name,Node));
            _ ->  only_text(Node)
        end
    catch
        request_denied -> "Database was in use.";
        _:_ -> "Node does not exist anymore."
    end.

%% @doc Returns text without whitespaces
only_text(Node) ->
    try
        [[T#token.text] || Token <- syn_request(Node), 
        #lex{data=T=#token{}} <- [data_request(Token)]]
    catch
        request_denied -> ""
    end.