%%% 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.


%%% @doc This module provides an interface for Visual Studio Code. It is intended to be
%%% `start'ed from the command line using `-noshell'. It reads Erlang terms
%%% from the standard input, and passes them to an interpreter function
%%% (implemented by a gen_server). Responses are written to the standard
%%% output in the form required by Visual Studio Code.
%%%
%%% It is possible to run this interface on the same node as the main server,
%%% but distributed operation is also supported, when the input loop has its
%%% own dedicated Erlang node. This enables to have an Erlang shell running on
%%% the server node for development.
%%%
%%% @author Bauer Bence <bauerbence5@gmail.com>

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

-export([run_server/0]).

-include("ui.hrl").
-define(RefStop,1000).

%% @private
run_server() ->
    Srv = spawn_link(fun vscode_server/0),
    receive
        {'EXIT', Srv, Reason} when Reason /= normal ->
            io:format("Server Error: ~p~n", [Reason]),
            ?MODULE:run_server()
    end.

vscode_server() ->
    register(referl_input, self()),
    monitor_node(?REFERL_NODE, true),
    Self = self(),
    referl_ui_evsend:start(Self, msg),
    receive
        {msg, installed} ->
            loop(spawn_link(fun() -> read_loop(Self) end))
    end.

loop(Inp) ->
    receive
        {msg, terminated} ->
            loop(Inp);
        {msg, {gen_event_EXIT,referl_ui_evsend,shutdown}} ->
            loop(Inp);
        {msg, Msg}    -> %global messages
            show_msg(Msg),
            loop(Inp);
        {Inp, Term} ->
            ui_call(Term),
            loop(Inp);
        {'EXIT', Inp, Reason} ->
            io:format("Read loop error: ~p~n", [Reason]),
            Self = self(),
            Inp1 = spawn_link(fun() -> read_loop(Self) end),
            loop(Inp1);
        code_change ->
            exit(code_change);
        {nodedown, _} ->
            spawn(vscode@localhost, init, stop, []),
            init:stop();
        Msg -> %unicast messages
            show_msg(Msg),
            loop(Inp)
    end.

%% @private
read_loop(Pid) ->
    case io:read('') of
        {ok, Term} -> Pid ! {self(), Term};
        eof        ->
            Pid ! {self(), {stop}},
            timer:sleep(?RefStop);
        {error, {_, Mod, Error}} ->
            Pid ! {msg, {error,
                         lists:flatten(
                           io_lib:format("~s", [Mod:format_error(Error)]))}}
    end,
    read_loop(Pid).

ui_call(RNA) when is_tuple(RNA) ->
    case lists:nth(1, tuple_to_list(RNA)) of
        {reqid, _} -> [ReqId | NameArgs] = tuple_to_list(RNA);
        _ ->
            ReqId = ?UI:getid(),
            NameArgs = tuple_to_list(RNA)
    end,
    case ?UI:request(ReqId, list_to_tuple(NameArgs)) of
        ok -> ok;
        deny ->
            show_msg("The request was denied by the job server."++
                     "Please, try again later.");
        error ->
            io:format("Bad server request ~512P~n", [RNA, 6])
    end.

show_msg(X) ->
    io:format("{'Response':~s}~n",[print_json(X)]).

print_json(Tuple) when is_tuple(Tuple) ->
    ["[",
     [[print_json(Elem), ", "] || Elem <- lists:droplast(tuple_to_list(Tuple))] ++ print_json(lists:last(tuple_to_list(Tuple))),
     "]"];

print_json(Float) when is_float(Float)->
    float_to_list(Float);
print_json(List) when is_list(List) ->
    print_string(List, []);
print_json(Atom) when is_atom(Atom) ->
    %atom_to_list(Atom);
    lists:concat(["'",atom_to_list(Atom),"'"]);
print_json(Int) when is_integer(Int) ->
    integer_to_list(Int);
print_json(Fun) when is_function(Fun) ->
    "".

print_string([], Chars) ->
    ["'", escape(lists:reverse(Chars)), "'"];
print_string([Char | Tail], Chars) when 0 =< Char, Char =< 255 ->
    print_string(Tail, [Char | Chars]);
print_string(Rest, Chars) ->
    ["[",
     [[integer_to_list(Ch), ", "] || Ch <- lists:reverse(Chars)],
     [[print_json(D), ", "] || D <- lists:droplast(Rest)] ++ print_json(lists:last(Rest)),
     "]"].

escape([]) ->
    [];
escape([$\' | Tail]) ->
        ["\\\'" | escape(Tail)];
escape([$\" | Tail]) ->
        ["\\\"" | escape(Tail)];
escape([$\\ | Tail]) ->
    ["\\\\" | escape(Tail)];
escape([$\n | Tail]) ->
    ["\\n"  | escape(Tail)];
escape([Chr | Tail]) ->
    [Chr    | escape(Tail)].
