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


%%% ============================================================================
%%% Module information

%%% @doc Utilities for the slicer application.
%%%
%%% @author Istvan Bozo <bozo_i@inf.elte.hu>

-module(refsc_utils).

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

-export([draw_graph/3, draw_graph/4]).

-include("slicer.hrl").

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Drawing utilities
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec draw_graph(Tag :: atom(), Format :: id | text,
                 EdgeList :: [{term(), term(), term()}]) -> ok.
%% @doc The function creates a `pdf' graph from the given edges with
%% type `Tag'. The possible values for type are atoms like:
%% `postdom', `cfg', `cdg'. The file is created in the CWD. For
%% example: if the type is cfg then the created file will be
%% "CWD/graph_cfg.pdf".
draw_graph(Tag, Format, EdgeList) ->
    {ok, CWD} = file:get_cwd(),
    draw_graph(CWD, Tag, Format, EdgeList).

%% @doc The function creates a `pdf' graph from the given edges with
%% type `Tag'. The possible values for type are atoms like: `postdom',
%% `cfg', `cdg'. The file is created in the given directory. For
%% example: if the type is cfg then the created file will be
%% "Path/graph_cfg.pdf".
draw_graph(Path, Tag, Format, EdgeList) ->

    DirName = filename:dirname(Path),

    DotFile = io_lib:format("graph_~s.dot", [atom_to_list(Tag)]),
    DotPath = filename:join([DirName, DotFile]),

    PdfFile = io_lib:format("graph_~s.pdf", [atom_to_list(Tag)]),
    PdfPath = filename:join([DirName, PdfFile]),
    
    try create_dot(DotPath, EdgeList, Format), create_pdf(DotPath, PdfPath)
    catch
        throw:Reason ->
            io:format("The file operation failed with reason: ~p~n", [Reason])
    end,
    ok.

%% @doc Creates a `pdf' file on path `PdfPath' from the `dot' file on
%% path `DotPath'.
create_pdf(DotPath, PdfPath) ->
    Cmd = io_lib:format("dot -Tpdf ~s -o ~s", [DotPath, PdfPath]),
    os:cmd(Cmd).

%% @doc Creates a `dot' file on path `Path' from the given edges.
create_dot(Path, EdgeList, Format) ->
    FileId =
        case file:open(Path, [write]) of
            {ok, FI} -> FI;
            {error, Reason1} ->
                throw(Reason1)
        end,
    io:fwrite(FileId, "digraph{~n",[]),
    case Format of
        text ->
            Nodes =
                lists:usort(lists:append([[N1, N2] || {N1, N2, _}<-EdgeList])),
            [io:fwrite(FileId, node_format(Node), []) || Node <- Nodes];
        id ->
            ok
    end,

    [io:fwrite(FileId, edge_format(Edge), []) || Edge <- EdgeList],
    io:fwrite(FileId, "}~n",[]),
    case file:close(FileId) of
        ok -> ok;
        {error, Reason2} ->
            throw(Reason2)
    end.

node_format({ret, {_, Type, Id}} = Node) ->
    io_lib:format("\"~p\" [label=\"RETURN(~s, ~s)\"];~n",
                  [Node, atom_to_list(Type), integer_to_list(Id)]);
node_format({error, {_, Type, Id}} = Node) ->
    io_lib:format("\"~p\" [label=\"ERROR(~s, ~s)\"];~n",
                  [Node, atom_to_list(Type), integer_to_list(Id)]);
node_format({_, form, Id} = Node) ->
    io_lib:format("\"~p\" [label=\"FORM(~s)\", shape=box];~n",
                  [Node, integer_to_list(Id)]);
node_format({exit_node, {_, _, Id}} = Node) ->
    io_lib:format("\"~p\" [label=\"dummy_exit_node(~p)\"];~n", [Node, Id]);
node_format(Node) ->
    RawText   = ?Syn:flat_text(Node),
    RawTokens = string:tokens(RawText, "\n"),
    Tokens    = [string:strip(RawToken, both, $ )|| RawToken <- RawTokens],
    Text      = string:join(Tokens, " "),
    io_lib:format("\"~p\" [label=\"~s\"];~n", [Node, Text]).

edge_format({SN, EN, []}) ->
    io_lib:format("\"~p\"-> \"~p\";~n", [SN, EN]);
edge_format({SN, EN, Label}) ->
    io_lib:format("\"~p\" -> \"~p\" " ++ edge_label_and_style(Label) ++ ";~n",
                  [SN, EN]).

edge_label_and_style(yes) ->
    "[label=\"yes\", color=blue, fontcolor=blue]";
edge_label_and_style(no) ->
    "[label=\"no\", color=red, fontcolor=red]";
edge_label_and_style(funcall) ->
    "[label=\"funcall\", color=darkgreen, fontcolor=darkgreen]";
edge_label_and_style(error) ->
    "[label=\"error\", color=red, fontcolor=red, style=dashed]";
edge_label_and_style(halt) ->
    "[label=\"halt\", color=red, fontcolor=red, style=dotted]";
edge_label_and_style(pd) ->
    "[label=potpd, style=dashed]";
edge_label_and_style(L) ->
    io_lib:format("[label=\"~s\"]", [atom_to_list(L)]).
