-module(egg_types).

-export([t_to_gen/1, t_to_pattern_gen/1]).
-export([is_subtype/2]).

-include_lib("referl_qc/include/prop_based_testing.hrl").

-include_lib("../include/types.hrl").

is_subtype(?union(Types), T2) ->
    lists:any(fun(T) -> is_subtype(T, T2) end,
              lists:filter(fun(T) ->  T =/= ?none end, Types));
is_subtype(T1, T2) -> erl_types:t_is_subtype(T1, T2) orelse erl_types:t_is_subtype(T2, T1).



%%=============================================================================
%%
%% EQC generator: type description -> Erlang term
%%
%%=============================================================================

t_to_gen(L) when is_list(L) ->
  lists:map(fun t_to_gen/1, L);
t_to_gen(?any) ->
  42;
%% t_to_gen(?none, _RecDict) ->
%%   "none()";
%% t_to_gen(?unit, _RecDict) ->
%%   "no_return()";
t_to_gen(?atom(?any)) ->
    [choose($a, $z) | list(choose($a, $z))];
t_to_gen(?atom(Set)) ->
    elements(Set);

%% t_to_gen(?bitstr(8, 0), _RecDict) ->
%%   "binary()";
%% t_to_gen(?bitstr(0, 0), _RecDict) ->
%%   "<<>>";
%% t_to_gen(?bitstr(0, B), _RecDict) ->
%%   io_lib:format("<<_:~w>>", [B]);
%% t_to_gen(?bitstr(U, 0), _RecDict) ->
%%   io_lib:format("<<_:_*~w>>", [U]);
%% t_to_gen(?bitstr(U, B), _RecDict) ->
%%   io_lib:format("<<_:~w,_:_*~w>>", [B, U]);

%% t_to_gen(?function(?any, ?any), _RecDict) ->
%%   "fun()";
%% t_to_gen(?function(?any, Range), RecDict) ->
%%   "fun((...) -> " ++ t_to_gen(Range, RecDict) ++ ")";
%% t_to_gen(?function(?product(ArgList), Range), RecDict) ->
%%   "fun((" ++ comma_sequence(ArgList, RecDict) ++ ") -> "
%%     ++ t_to_gen(Range, RecDict) ++ ")";

%% t_to_gen(?identifier(Set), _RecDict) ->
%%   if Set =:= ?any -> "identifier()";
%%      true -> sequence([io_lib:format("~w()", [T])
%% 		       || T <- set_to_list(Set)], [], " | ")
%%   end;
%% t_to_gen(?opaque(Set), _RecDict) ->
%%   sequence([case is_opaque_builtin(Mod, Name) of
%% 	      true  -> io_lib:format("~w()", [Name]);
%% 	      false -> io_lib:format("~w:~w()", [Mod, Name])
%% 	    end
%% 	    || #opaque{mod = Mod, name = Name} <- set_to_list(Set)], [], " | ");
%% t_to_gen(?matchstate(Pres, Slots), RecDict) ->
%%   io_lib:format("ms(~s,~s)", [t_to_gen(Pres, RecDict),
%% 			      t_to_gen(Slots,RecDict)]);

t_to_gen(?nil) ->
    [];
%% t_to_gen(?nonempty_list(Contents, Termination), RecDict) ->
%%   ContentString = t_to_gen(Contents, RecDict),
%%   case Termination of
%%     ?nil ->
%%       case Contents of
%% 	?char -> "nonempty_string()";
%% 	_ -> "["++ContentString++",...]"
%%       end;
%%     ?any ->
%%       %% Just a safety check.
%%       case Contents =:= ?any of
%% 	true -> ok;
%% 	false ->
%% 	  erlang:error({illegal_list, ?nonempty_list(Contents, Termination)})
%%       end,
%%       "nonempty_maybe_improper_list()";
%%     _ ->
%%       case t_is_subtype(t_nil(), Termination) of
%% 	true ->
%% 	  "nonempty_maybe_improper_list("++ContentString++","
%% 	    ++t_to_gen(Termination, RecDict)++")";
%% 	false ->
%% 	  "nonempty_improper_list("++ContentString++","
%% 	    ++t_to_gen(Termination, RecDict)++")"
%%       end
%%   end;

t_to_gen(?list(Contents, ?nil, ?unknown_qual)) ->
    list(t_to_gen(Contents));
%% t_to_gen(?list(Contents, Termination, ?unknown_qual), RecDict) ->
%%   ContentString = t_to_gen(Contents, RecDict),
%%   case Termination of
%%     ?nil ->
%%       case Contents of
%% 	?char -> "string()";
%% 	_ -> "["++ContentString++"]"
%%       end;
%%     ?any ->
%%       %% Just a safety check.
%%       %% case Contents =:= ?any of
%%       %%   true -> ok;
%%       %%   false ->
%%       %%     L = ?list(Contents, Termination, ?unknown_qual),
%%       %%     erlang:error({illegal_list, L})
%%       %% end,
%%       "maybe_improper_list()";
%%     _ ->
%%       case t_is_subtype(t_nil(), Termination) of
%% 	true ->
%% 	  "maybe_improper_list("++ContentString++","
%% 	    ++t_to_gen(Termination, RecDict)++")";
%% 	false ->
%% 	  "improper_list("++ContentString++","
%% 	    ++t_to_gen(Termination, RecDict)++")"
%%       end
%%   end;

t_to_gen(?int_set(Set)) ->
    elements(Set);
%% t_to_gen(?byte) -> "byte()";
%% t_to_gen(?char) -> "char()";
%% t_to_gen(?integer_pos) -> "pos_integer()";
%% t_to_gen(?integer_non_neg) -> "non_neg_integer()";
%% t_to_gen(?integer_neg) -> "neg_integer()";
t_to_gen(?int_range(From, To)) ->
    choose(From, To);
t_to_gen(?integer(?any)) ->
    int();
t_to_gen(?float) ->
    real();
t_to_gen(?number(?any, ?unknown_qual)) ->
    int();

t_to_gen(?product(_List)) ->
  throw("product unimplemented");

t_to_gen(?tuple(?any, ?any, ?any)) ->
  {};

t_to_gen(?tuple(Elements, _Arity, ?any)) ->
  ?LET(L, lists:map(fun t_to_gen/1, Elements), list_to_tuple(L));

t_to_gen(?tuple(Elements, Arity, _Tag)) ->
  t_to_gen(?tuple(Elements, Arity, ?any));

t_to_gen(?tuple_set(_) = S) ->
  oneof([t_to_gen(T) || T <- erl_types:t_tuple_subtypes(S), T =/= ?none]);

t_to_gen(?union(Types)) ->
  oneof([t_to_gen(T) || T <- Types, T =/= ?none]);

t_to_gen(T) ->
  io:format("t_to_gen:~p~n", [T]),
  throw(unimplemented).



%%=============================================================================
%%
%% EQC generator: type description -> Erlang pattern
%%
%%=============================================================================

varname() ->
    ?LET({C, Cs},
         {choose($A, $Z), list(oneof([choose($a, $z), choose($A, $Z)]))},
         [C|Cs]).

val_or_var(Gen) ->
    oneof([Gen, {variable, varname()}]).

t_to_pattern_gen(L) when is_list(L) ->
    lists:map(fun t_to_pattern_gen/1, L);

t_to_pattern_gen(?any) ->
  {variable, varname()};

t_to_pattern_gen(?list(Contents, ?nil, ?unknown_qual)) ->
    list(t_to_pattern_gen(Contents));

t_to_pattern_gen(?tuple(Elements, _Arity, ?any)) ->
    ?LET(L, lists:map(fun t_to_pattern_gen/1, Elements), list_to_tuple(L));

t_to_pattern_gen(?tuple(Elements, Arity, _Tag)) ->
    t_to_pattern_gen(?tuple(Elements, Arity, ?any));

t_to_pattern_gen(?tuple_set(_) = S) ->
    oneof([t_to_pattern_gen(T) || T <- erl_types:t_tuple_subtypes(S), T =/= ?none]);

t_to_pattern_gen(?union(Types)) ->
    oneof([{variable, varname()} | [t_to_pattern_gen(T) || T <- Types, T =/= ?none]]);

t_to_pattern_gen(X) ->
    val_or_var(t_to_gen(X)).

