%%%-------------------------------------------------------------------
%%% File    : sherk.erl
%%% Author  : Mats Cronqvist <locmacr@mwlx084>
%%% Description :
%%%
%%% Created : 14 Aug 2006 by Mats Cronqvist <locmacr@mwlx084>
%%%-------------------------------------------------------------------
-module(sherk).

-export([go/0]).  % interactive
-export([ni/0]).  % non-interactive
-export([scan/1,scan/2,scan/3,scan/4,scan/5]).

%% sherk-specific export
-export([to_str/1]).

%% internal exports
-export([log/2]).
-export([loop/1]).

-import(lists,[member/2,flatten/1,usort/1,foldl/3]).
-import(dict,[from_list/1,to_list/1,fetch/2,store/3,append/3]).

-include("log.hrl").

-define(LOOP(X), ?MODULE:loop(X)).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
go() -> spawn_link(fun init/0).

ni() -> init().

scan(File) -> scan(File,'').
scan(File,Patt) -> scan(File,Patt,'').
scan(File,Patt,CBs) -> scan(File,Patt,CBs,0,'').
scan(File,Patt,CBs,Seq) -> scan(File,Patt,CBs,Seq,Seq).
scan(File,Patt,CBs,Min,Max) -> sherk_scan:go(File,Patt,CBs,Min,Max).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
glade_file() ->
  take_first(fun file_exists/1,
             [filename:join([my_path(),priv,glade,"sherk.glade"]),
              filename:join([my_path(),src,"sherk.glade"])]).

file_exists(F) ->
  true = filelib:is_regular(F),
  F.

my_path() ->
  filename:dirname(filename:dirname(code:which(?MODULE))).

take_first(_,[]) -> exit({take_first,nothing});
take_first(Fun,[H|T]) ->
  try Fun(H)
  catch _:_ -> take_first(Fun,T)
  end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init() ->

  %% start the GUI and load the glade file
  gtknode:start(sherk),
  g('GN_glade_init',[glade_file()]),

  %% check the trc source
  check_file(),
  %% set default trc destination
  g('Gtk_file_chooser_set_current_folder',[aq_filechoose,"/tmp"]),
  g('Gtk_file_chooser_set_current_folder',[main_filechoose,"/tmp"]),

  %% init nodes treeview
  NodeList = init_list_store([string]),
  init_tree_view(aq_treeview,{NodeList,init_tree_view_cols([{0,"Nodes"}])}),
  set_selection_mode(aq_treeview,'GTK_SELECTION_MULTIPLE'),

  %% init calls treeview
  CallList = {init_list_store([string,integer,integer]),
              init_tree_view_cols([{0,"MFA"},{1,"calls"},{2,"%"}])},
  CallTree = {init_tree_store([string,integer,integer,integer]),
              init_tree_view_cols([{0,"MFA"},{1,"calls"},
                                   {2,"time"},{3,"cumtime"}])},

  %% init procs treeview
  ProcsTree = init_tree_store([string,integer,integer,integer]),
  ColDescr = [{0,"ID"},{1,"#"},{2,"gc"},{3,"cpu%"}],
  init_tree_view(procs_treeview,{ProcsTree,init_tree_view_cols(ColDescr)}),

  %% init calls combobox
  init_combobox(ProcsTree),

  loop(from_list([{targs         ,[]},
                  {bad_targs     ,[]},
                  {aq_mon        ,undefined},
                  {proxy         ,node()},
                  {orig_ticktime ,net_kernel:get_net_ticktime()},
                  {targ_mon      ,query_targs(node())},
                  {call_list     ,CallList},
                  {call_tree     ,CallTree}])).

loop(LD) ->
  AqMon = fetch(aq_mon,LD),
  TargMon = fetch(targ_mon,LD),
  receive
    %% user bored
    quit                                 -> ok;
    {sherk,{signal,{quit,_}}}            -> ok;
    {sherk,{signal,{main_window,_}}}     -> ok;

    loopdata                             -> ?LOOP(show_ld(LD));
    %% user wants stuff hidden
    {sherk,{signal,{call_window,_}}}     -> hide(call_window),?LOOP(LD);
    {sherk,{signal,{aq_window,_}}}       -> hide_aq_window(),?LOOP(LD);
    {sherk,{signal,{show_aquire,_}}}     -> toggle_aq_window(),?LOOP(LD);

    %% user curious
    {sherk,{signal,{about,_}}}           -> show(about_window),?LOOP(LD);
    {sherk,{signal,{about_window,_}}}    -> hide(about_window),?LOOP(LD);
    {sherk,{signal,{about_ok,_}}}        -> hide(about_window),?LOOP(LD);

    %% user changed directory
    {sherk,{signal,{main_filechoose,_}}} -> check_file(),?LOOP(LD);

    %% user is trying to aquire data
    {sherk,{signal,{aq_filechoose,_}}}   -> aq_check(),?LOOP(LD);
    {sherk,{signal,{aq_treeview,_}}}     -> aq_check(),?LOOP(LD);
    {sherk,{signal,{aq_time_entry,_}}}   -> aq_check(),?LOOP(LD);
    {sherk,{signal,{aq_go_button,_}}}    -> ?LOOP(aq_go(LD));
    {sherk,{signal,{aq_stop_button,_}}}  -> ?LOOP(aq_stop(LD));

    %% let's go
    {sherk,{signal,{go_button,_}}}       -> ?LOOP(procs(LD));

    %% show/change view of call data
    {sherk,{signal,{procs_treeview,'row-activated'}}}->?LOOP(call_procs(LD));
    {sherk,{signal,{call_heavy_radio,toggled}}}      ->?LOOP(call_combo(LD));
    {sherk,{signal,{call_combobox,changed}}}         ->?LOOP(call_combo(LD));

    %% configuring
    {sherk,{signal,{configure,_}}}       -> show(conf_window),?LOOP(LD);
    {sherk,{signal,{conf_window,_}}}     -> hide(conf_window),?LOOP(LD);
    {sherk,{signal,{conf_cancel,_}}}     -> hide(conf_window),?LOOP(LD);
    {sherk,{signal,{conf_ok,_}}}         -> ?LOOP(conf(LD));
    {sherk,{signal,{bad_proxy_ok,_}}}    -> bad_proxy_ok(),?LOOP(LD);
    {sherk,{signal,{bad_proxy_cancel,_}}}-> bad_proxy_cancel(),?LOOP(LD);

    %% a target node went away
    {nodedown, Node}                     -> ?LOOP(downed_target(Node,LD));

    %% aquire proc exited
    {'DOWN',AqMon,_,_,Info}              -> ?LOOP(do_aq_stop(LD,Info));

    %% we got target data from proxy
    {timeout, _, re_query}               -> ?LOOP(re_query_targs(LD));
    {'DOWN',TargMon,_,_,Info}            -> ?LOOP(chk_targs(LD,Info));

    %% user doing some wierd stuff
    X                                    -> ?log([{received,X}]),?LOOP(LD)
  end.

show_ld(LD) ->
  ?log(to_list(LD)),
  LD.

check_file() ->
  try
    Dir = g('Gtk_file_chooser_get_filename',[main_filechoose]),
    sherk_tab:check_file(Dir),
    g('Gtk_widget_set_sensitive',[go_button,true])
  catch
    _:_ -> g('Gtk_widget_set_sensitive',[go_button,false])
  end.


conf(LD) ->
  try
    NLD = proxy(LD),
    hide(conf_window),
    NLD
  catch
    _:_ ->
      g('Gtk_widget_set_sensitive',[conf_window,false]),
      show(bad_proxy_window),
      LD
  end.

proxy(LD) ->
  {Proxy,Cookie,Tick} = 
    case list_to_atom(g('Gtk_entry_get_text',[conf_proxy_entry])) of
      '' ->
        P = node(),
        C = erlang:get_cookie(),
        T = fetch(orig_ticktime,LD),
        {P,C,T};
      P ->
        C = list_to_atom(g('Gtk_entry_get_text',[conf_cookie_entry])),
        erlang:set_cookie(P,C),
        T = rpc:call(P,net_kernel,get_net_ticktime,[]),
        {P,C,T}
  end,
  net_kernel:set_net_ticktime(Tick),
  store(cookie,Cookie,
        store(proxy,Proxy,
              store(targs,[],
                    store(bad_targs,[],LD)))).

bad_proxy_cancel() ->
  g('Gtk_widget_set_sensitive',[conf_window,true]),
  hide(bad_proxy_window).

bad_proxy_ok() ->
  g('Gtk_widget_set_sensitive',[conf_window,true]),
  hide(conf_window),
  hide(bad_proxy_window).

aq_go(LD) ->
  g('Gtk_widget_set_sensitive',[aq_radiobutton_call,false]),
  g('Gtk_widget_set_sensitive',[aq_radiobutton_proc,false]),
  g('Gtk_widget_set_sensitive',[aq_filechoose,false]),
  g('Gtk_widget_set_sensitive',[aq_time_entry,false]),
  g('Gtk_widget_set_sensitive',[aq_treeview,false]),
  g('Gtk_widget_set_sensitive',[aq_go_button,false]),
  g('Gtk_widget_set_sensitive',[aq_stop_button,true]),
  Time = aq_get_time(),
  Procs = all,
  Targs = aq_get_nodes(),
  Flags = aq_get_flags(Targs),
  RTPs = aq_get_rtps(Flags),
  Dest = {file,aq_get_dest(),0,"/tmp"},
  ?log([{time,Time},
        {flags,Flags},
        {rTPs,RTPs},
        {procs,Procs},
        {targs,Targs},
        {dest,Dest}]),
  P = sherk_aquire:go(Time,Flags,RTPs,Procs,Targs,Dest),
  store(aq_mon,erlang:monitor(process,P),LD).

aq_stop(LD) ->
  sherk_aquire:stop(),
  do_aq_stop(LD,aq_stop_wait(fetch(aq_mon,LD))).

aq_stop_wait(Monitor) ->
  receive
    {'DOWN',Monitor,_Type,_Object,Info} -> Info
  end.

do_aq_stop(LD,Reason) ->
  ?log([{aquire_finshed,Reason}]),
  g('Gtk_widget_set_sensitive',[aq_radiobutton_call,true]),
  g('Gtk_widget_set_sensitive',[aq_radiobutton_proc,true]),
  g('Gtk_widget_set_sensitive',[aq_filechoose,true]),
  g('Gtk_widget_set_sensitive',[aq_time_entry,true]),
  g('Gtk_widget_set_sensitive',[aq_treeview,true]),
  g('Gtk_widget_set_sensitive',[aq_stop_button,false]),
  aq_check(),
  store(aq_mon,undefined,LD).

aq_check() ->
  try
    Dir = g('Gtk_file_chooser_get_filename',[aq_filechoose]),
    sherk_aquire:check_dir(Dir),
    [_|_] = get_selected_data(aq_treeview,0),
    _ = list_to_integer(g('Gtk_entry_get_text',[aq_time_entry])),
    g('Gtk_widget_set_sensitive',[aq_go_button,true])
  catch
    _:_ -> g('Gtk_widget_set_sensitive',[aq_go_button,false])
  end.

aq_get_time() ->
  1000*list_to_integer(g('Gtk_entry_get_text',[aq_time_entry])).

aq_get_flags(Nodes) ->
  case g('Gtk_toggle_button_get_active',[aq_radiobutton_proc]) of
    true -> proc_flags(Nodes);
    false -> call_flags(Nodes)
  end.

aq_get_rtps(Flags) ->
  case member(call,Flags) of
    true -> [{'_','_'}];
    false -> []
  end.

aq_get_nodes() ->
  [list_to_atom(N) || N<-get_selected_data(aq_treeview,0)].

aq_get_dest() ->
  g('Gtk_file_chooser_get_filename',[aq_filechoose]).

show_aq_window() ->
  show(aq_window),
  g('Gtk_check_menu_item_set_active',[show_aquire,true]).
hide_aq_window() ->
  hide(aq_window),
  g('Gtk_check_menu_item_set_active',[show_aquire,false]).
toggle_aq_window() ->
  case g('Gtk_check_menu_item_get_active',[show_aquire]) of
    true -> show_aq_window();
    false -> hide_aq_window()
  end.

re_query_targs(LD) ->
  store(targ_mon,query_targs(fetch(proxy,LD)),LD).

query_targs(Proxy) ->
  sherk_aquire:ass_loaded(Proxy,sherk_target),
  erlang:monitor(process,spawn(Proxy,sherk_target,get_nodes,[])).

chk_targs(LD,Atom) when is_atom(Atom) -> ?log({no_proxy,Atom}),LD;
chk_targs(LD,{Pid,Nodes,EpmdStr}) when is_pid(Pid) ->
  try
    erlang:start_timer(5000,self(),re_query),
    OldTargs = fetch(targs,LD),
    BadTargs = fetch(bad_targs,LD),
    [_,Host] = string:tokens(to_str(node(Pid)),"@"),
    Nods = string:tokens(EpmdStr,"\n"),
    CPs = [N || ["name",N|_] <- [string:tokens(Str," ") || Str <- Nods]],
    EpmdTargs = [list_to_atom(CP++"@"++Host) || CP <-CPs],
    Targs = usort(Nodes++EpmdTargs)--[node()],
    foldl(fun new_target/2, LD, (Targs--OldTargs)--BadTargs)
  catch
    _:R -> ?log([{r,R},{pid,Pid},{nodes,Nodes},{epmd,EpmdStr}]),LD
  end.

downed_target(Node,LD) ->
  Ts = fetch(targs,LD),
  BTs = fetch(bad_targs,LD),
  case {member(Node,Ts),member(Node,BTs)} of
    {false,true} -> LD;
    {true,false} -> update_targs(Ts--[Node], append(bad_targs,Node,LD));
    {false,false}-> wierd(down,Node,Ts,BTs),append(bad_targs,Node,LD);
    {true,true}  -> wierd(down,Node,Ts,BTs),update_targs(Ts--[Node], LD)
  end.

new_target(Node,LD) ->
  Ts = fetch(targs,LD),
  BTs = fetch(bad_targs,LD),
  case {member(Node,Ts),member(Node,BTs)} of
    {false,false} -> new_target(Node,Ts,LD);
    {false,true} -> new_target(Node,Ts,store(bad_targs,BTs--[Node],LD));
    {true,false} -> wierd(up,Node,Ts,BTs),LD;
    {true,true} -> wierd(up,Node,Ts,BTs),store(bad_targs,BTs--[Node],LD)
  end.

new_target(Node,Ts,LD) ->
  catch erlang:set_cookie(Node,fetch(cookie,LD)),
  erlang:monitor_node(Node,true),
  update_targs([Node|Ts], LD).

wierd(UpDown,Node,Ts,BTs) ->
  ?log([{node_came,UpDown},{node,Node},{targs,Ts},{bad_targs,BTs}]).

update_targs(Ts,LD) ->
  update_treeview_list(aq_treeview,[[to_str(T)]||T<-Ts]),
  store(targs,Ts,LD).

proc_flags(Nodes) ->
  ['procs','running','garbage_collection',
   timestamp(Nodes),'set_on_spawn'].

call_flags(Nodes) ->
  ['call','return_to','arity'|proc_flags(Nodes)].

timestamp(Nodes) ->
  case [N || N <- Nodes, flag_broken(N,cpu_timestamp)] of
    [] -> 'cpu_timestamp';
    _ -> 'timestamp'
  end.

flag_broken(Node,Flag) ->
  case is_tuple(rpc:call(Node,erlang,trace,[all,true,[Flag]])) of
    true -> true;
    false-> rpc:call(Node,erlang,trace,[all,false,[Flag]]),false
  end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
procs(LD) ->
  g('Gtk_widget_set_sensitive',[go_button,false]),
  File = g('Gtk_file_chooser_get_filename',[main_filechoose]),
  sherk_tab:assert(File),
  Tree = sherk_tree:go(procs),
  update_treeview_tree(procs_treeview,Tree),
  g('Gtk_widget_set_sensitive',[go_button,true]),
  LD.

call_procs(LD) ->
  Model = g('Gtk_tree_view_get_model',[procs_treeview]),
  [Path] = g('GN_tree_view_get_selected',[procs_treeview]),
  set_active_combo(Model, Path),
  update_call(LD,Model,Path).

call_combo(LD) ->
  Model = g('Gtk_combo_box_get_model',[call_combobox]),
  g('Gtk_combo_box_get_active_iter',[call_combobox,iter]),
  Path = g('Gtk_tree_model_get_string_from_iter',[Model,iter]),
  set_active_treeview(Model, Path),
  update_call(LD,Model,Path).

update_call(LD,Model,Path) ->
  show(call_window),
  [PidStr] = get_data(Model,0,[Path]),
  case g('Gtk_toggle_button_get_active',[call_heavy_radio]) of
    true ->
      init_tree_view(call_treeview,fetch(call_list,LD)),
      List = sherk_list:go({call,PidStr}),
      update_treeview_list(call_treeview,List);
    false->
      init_tree_view(call_treeview,fetch(call_tree,LD)),
      Tree = sherk_tree:go({callgraph,PidStr}),
      update_treeview_tree(call_treeview,Tree)
  end,
  LD.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_tree_view(TreeView,{Model,Cols}) ->
  g('Gtk_tree_view_collapse_all',[TreeView]),
  remove_cols(TreeView),
  g('Gtk_tree_view_set_model',[TreeView,Model]),
  [g('Gtk_tree_view_append_column',[TreeView,Col]) || Col <- Cols].

remove_cols(TreeVw) ->
  case g('Gtk_tree_view_get_column',[TreeVw,0]) of
    'NULL' -> ok;
    Col ->
      g('Gtk_tree_view_remove_column',[TreeVw,Col]),
      remove_cols(TreeVw)
  end.

init_tree_view_cols(DataColTitles) ->
  [init_tree_view_col(DC,T) || {DC,T} <- DataColTitles].

init_tree_view_col(DataCol,Title) ->
  Col = g('Gtk_tree_view_column_new',[]),
  Renderer = g('Gtk_cell_renderer_text_new',[]),
  g('Gtk_tree_view_column_pack_start',[Col,Renderer,false]),
  g('Gtk_tree_view_column_set_title', [Col,Title]),
  g('Gtk_tree_view_column_set_resizable',[Col,true]),
  g('Gtk_tree_view_column_add_attribute',[Col,Renderer,"text",DataCol]),
  Col.

set_active_treeview(Model, Path) ->
  g('Gtk_tree_model_get_iter_from_string',[Model,iter,Path]),
  Sel = g('Gtk_tree_view_get_selection',[procs_treeview]),
  g('Gtk_tree_selection_unselect_all',[Sel]),
  g('Gtk_tree_selection_select_iter',[Sel,iter]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_list_store(Cols)->
  g('Gtk_list_store_newv',[length(Cols),Cols]).

update_treeview_list(View,List) ->
  Model = g('Gtk_tree_view_get_model',[View]),
  hide(View),
  g('Gtk_list_store_clear', [Model]),
  g('Gtk_widget_freeze_child_notify',[View]),
  list_insert(Model, List),
  g('Gtk_widget_thaw_child_notify',[View]),
  show(View).

list_insert(Store,Rows) ->
  Row_f = fun() -> {'Gtk_list_store_append',[Store,iter]} end,
  Col_f = fun(Row) -> foldl(fun col_f/2, {0,Store,[]}, Row) end,
  g(flatten([[Row_f()|element(3,Col_f(Row))] || Row<-Rows])).

col_f(Val,{N,Store,O}) ->
  {N+1,Store,
   [{'GN_value_set',[val,Val]},
    {'Gtk_list_store_set_value',[Store,iter,N,val]}|O]}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_combobox(Model) ->
  g('Gtk_combo_box_set_model',[call_combobox,Model]),
  Rend = g('Gtk_cell_renderer_text_new',[]),
  g('Gtk_cell_layout_pack_start',[call_combobox,Rend,false]),
  g('Gtk_cell_layout_add_attribute',[call_combobox,Rend,"text",0]).

set_active_combo(Model, Path) ->
  g('Gtk_tree_model_get_iter_from_string',[Model,iter,Path]),
  g('Gtk_combo_box_set_active_iter',[call_combobox,iter]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_tree_store(Cols)->
  g('Gtk_tree_store_newv',[length(Cols),Cols]).

update_treeview_tree(View,Tree) ->
  Model = g('Gtk_tree_view_get_model',[View]),
  hide(View),
  g('Gtk_tree_store_clear',[Model]),
  g('Gtk_widget_freeze_child_notify',[View]),
  tree_insert(Model, Tree),
  g('Gtk_widget_thaw_child_notify',[View]),
  show(View).

tree_insert(Store,Tree) ->
  g(flatten(tree_insert(Store,[0],Tree))).

tree_insert(_Store,_Path,[]) -> [];
tree_insert(Store,Path,[{_,Row,SubTree}|T]) ->
  [update_iter(Path, Store),
   tree_insert_row(Store,0,Row),
   tree_insert(Store,[0|Path],SubTree),
   tree_insert(Store,[hd(Path)+1|tl(Path)],T)].

tree_insert_row(_Store,_N,[]) -> [];
tree_insert_row(Store,N,[Col|Cols]) ->
  [tree_insert_val(Store,N,Col),
   tree_insert_row(Store,N+1,Cols)].

tree_insert_val(Store,N,Val) ->
  [{'GN_value_set',[val,Val]},
   {'Gtk_tree_store_set_value', [Store,iter,N,val]}].

update_iter([I], Store) ->
  {'Gtk_tree_store_insert',[Store,iter,'NULL',I]};
update_iter([I|D], Store) ->
  [{'Gtk_tree_model_get_iter_from_string',[Store,daddy,pth(D)]},
   {'Gtk_tree_store_insert',[Store,iter,daddy,I]}].

pth(P) -> flatten(pth(P,[])).
pth([I],O) -> [to_str(I)|O];
pth([H|T],O) -> pth(T,[$:,to_str(H)|O]).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_selection_mode(View,Mode) ->
  Sel = g('Gtk_tree_view_get_selection',[View]),
  g('Gtk_tree_selection_set_mode',[Sel,Mode]).

get_selected_data(View,Col) ->
  Model = g('Gtk_tree_view_get_model',[View]),
  get_data(Model,Col,g('GN_tree_view_get_selected',[View])).

get_data(_Model,_Col,[]) -> [];
get_data(Model,Col,[Path|Paths]) ->
  g('Gtk_tree_model_get_iter_from_string',[Model,daddy,Path]),
  g('GN_value_unset',[val]),
  g('Gtk_tree_model_get_value',[Model,daddy,Col,val]),
  [g('GN_value_get',[val])|get_data(Model,Col,Paths)].

show(W) -> g('Gtk_widget_show',[W]).
hide(W) -> g('Gtk_widget_hide',[W]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

log(ProcInfo,Term) when not is_list(Term) -> log(ProcInfo,[Term]);
log(ProcInfo,List) ->
  error_logger:info_report([{in,CF}||{current_function,CF}<-ProcInfo]++List).

to_str(X) when is_pid(X) -> pid_to_list(X);
to_str(X) when is_atom(X) -> atom_to_list(X);
to_str(X) when is_integer(X) -> integer_to_list(X);
to_str(X) when is_float(X) ->  float_to_list(X);
to_str(X) when is_list(X) ->
  case is_string(X) of
    true -> X;
    false -> flatten(io_lib:fwrite("~p",[X]))
  end;
to_str(X) -> flatten(io_lib:fwrite("~p",[X])).

is_string(X) when not is_list(X) -> false;
is_string([]) -> true;
is_string([H|T]) when is_integer(H), H >= $ , H =< $~ -> is_string(T);
is_string(_) -> false.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
g(C,As) -> g([{C,As}]).

g(CAs) -> gtknode:cmd(sherk,CAs).
