%% @author Yariv Sadan <yarivsblog@gmail.com> [http://yarivsblog.com]
%% @copyright Yariv Sadan 2006-2007
%% @modify by Lili 2013
%%
%% @doc  meta: Simple Metaprogramming for Erlang
%%
%%  meta is an Erlang library
%%  that simplifies the creation and manipulation of Erlang modules in
%%  runtime.
%%
%%  You don't need to know meta in order to use ErlyWeb; meta
%%  is included in ErlyWeb because ErlyWeb uses it internally.
%%
%%  meta uses Erlang's capabilities for hot code swapping and
%%  abstract syntax tree transformations to do its magic. meta is inspired by
%%  the rdbms_codegen.erl module in the RDBMS application written by
%%  Ulf Wiger. RDBMS is part of Jungerl ([http://jungerl.sf.net]).
%%
%%  Here's a quick example illustrating how to use meta:
%%  ```
%%  test_meta() ->
%%    M1 = meta:new(foo),
%%    {ok, M2} = meta:add_func(M1, "bar() -> 1 + 1."),
%%    meta:compile(M2),
%%    foo:bar(),   % returns 2``
%%    meta:has_func(M2, bar, 0). % returns true
%%  '''
%%
%%  New functions can be expressed either as strings of Erlang code
%%  or as abstract forms. For more information, read the Abstract Format
%%  section in the ERTS User's guide
%%   ([http://erlang.org/doc/doc-5.5/erts-5.5/doc/html/absform.html#4]).
%%
%%  Using the abstract format, the 3rd line of the above example
%%   would be written as
%%   ```
%%     {ok,M2} = meta:add_func(M1, {function,1,bar,0,
%%                              [{clause,1,[],[],
%%                               [{op,1,'+',{integer,1,1},{integer,1,1}}]}]).
%%   '''
%%
%%   <p>The abstact format may look more verbose in this example, but
%%   it's also easier to manipulate in code.</p>
%%

%% Copyright (c) Yariv Sadan 2006-2007
%%
%% Permission is hereby granted, free of charge, to any person
%% obtaining a copy of this software and associated documentation
%% files (the "Software"), to deal in the Software without restriction,
%% including without limitation the rights to use, copy, modify, merge,
%% publish, distribute, sublicense, and/or sell copies of the Software,
%% and to permit persons to whom the Software is furnished to do
%% so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be included
%% in all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


-module(meta).
-author("Yariv Sadan (yarivsblog@gmail.com, http://yarivsblog.com").
-export([new/1,
   new_from_forms/1,
   new_from_src/1,
   new_from_module/1,
   new_from_module/2,
   new_from_module/3,
   new_from_file/1,
   new_from_file/2,
   new_from_file/3,
   get_module/1,
   get_modulename/1,
   set_module/2,
   get_forms/1,
   set_forms/2,
   get_exports/1,
   set_exports/2,
   get_export_all/1,
   set_export_all/2,
   remove_export/3,
   get_attribute/2,
   add_inc/2,
   add_func/2,
   add_func/3,
   add_funcs/3,
   remove_func/3,
   is_has_func/4,
   is_has_func/3,
   is_export/3,
   get_all_func/1,
   get_func/3,
   %%get_func_src/3,
   replace_func/2,
   compile/1,
   compile/2,
   rename/2,
   curry/2,
   curry/4,
   curry/5,
   curry_add/3,
   curry_add/4,
   curry_add/5,
   curry_add/6,
   curry_replace/3,
   curry_replace/4,
   embed_params/2,
   embed_params/4,
   embed_params/5,
   embed_all/2,
   extend/2,
   extend/3,
   extend/4,
   asb_to_src/1,
   beam_to_src/1,
   to_src/1,
   to_src/2,
   to_forms/1
]).

-export([get_records_name/1,
   get_record/2]).

-define(L(Obj), io:format("LOG ~s ~w ~p\n", [?FILE, ?LINE, Obj])).
-define(S(Obj), io:format("LOG ~s ~w ~s\n", [?FILE, ?LINE, Obj])).
-include("meta.hrl").
-include_lib("kernel/include/file.hrl").

%%  for a module.
%%hThe abstract form for the function, as described
-type func_form() :: term().

%% The record type holding the abstract representation for a module.

-type meta_mod() :: #meta_mod{}.

%% @doc Create a new meta_mod for a module with the given name.
-spec new(Module :: atom()) -> meta_mod().
new(ModuleName) when is_atom(ModuleName) ->
   #meta_mod{module = ModuleName}.


%% @doc build a meta_mod from forms.
-spec new_from_forms([erl_parse:abstract_form()]) ->
   {ok, meta_mod()} | {error, {invalid_module, term()}}.
new_from_forms([{attribute, _, file, {FileName, _FileNum}},
   {attribute, _, module, ModuleName} | Forms]) ->
   Mod =
      lists:foldr(fun(Form, MateMode) ->
         insert_form(Form, MateMode)
                  end,
         #meta_mod{module = ModuleName,
            file = FileName},
         Forms),
   {ok, Mod};
new_from_forms(Mod) ->
   {error, {invalid_module, Mod}}.

insert_form({attribute, _, export, ExportList}, #meta_mod{exports = Exports} = Mod) ->
   Mod#meta_mod{exports = ExportList ++ Exports};

insert_form({attribute, _, compile, export_all}, #meta_mod{export_all = false} = Mod) ->
   Mod#meta_mod{export_all = true};

insert_form({attribute, _Line, record, _} = RecordForm, #meta_mod{records = Records} = Mod) ->
   Mod#meta_mod{records = [RecordForm | Records]};

insert_form({attribute, _Line, _Other, _} = AttributeForm, #meta_mod{attributes = Attributes} = Mod) ->
   Mod#meta_mod{attributes = [AttributeForm | Attributes]};

insert_form({eof, _} = Eof, Mod) ->
   Mod#meta_mod{eof = Eof};

insert_form(From, #meta_mod{forms = Forms} = Mod) ->
   Mod#meta_mod{forms = [From | Forms]}.


new_from_src(Src) ->
   Forms = dynamic_compile:get_forms_from_src(Src),
   new_from_forms(Forms).


%% @equiv new_from_module(ModuleName, [])
new_from_module(ModuleName) ->
   new_from_module(ModuleName, []).

%% @equiv new_from_module(ModuleName, IncludePaths, [])
new_from_module(ModuleName, IncludePaths) ->
   new_from_module(ModuleName, IncludePaths, []).

%% @doc Create a meta_mod tuple for an existing module. If ModuleName is a
%% string, it is interpreted as a file name (this is the same as calling
%% @{link meta:new_from_file}). If ModuleName is an atom, meta attempts to
%% find its abstract represtation either from its source file or from
%% its .beam file directly (if it has been compiled with debug_info).
%% If the abstract representation can't be found, this function returns
%% an error.
%%
%% The IncludePaths parameter is used when 'ModuleName' is a file name.
%%
-spec new_from_module(ModuleName :: atom() | string(), IncludePaths :: [string()],
   Macros :: [{atom(), term()}]) ->
   {ok, meta_mod()} | {error, {invalid_module, atom()}}.

new_from_module(ModuleName, IncludePaths, Macros) when is_list(ModuleName) ->
   new_from_file(ModuleName, IncludePaths, Macros);
new_from_module(ModuleName, IncludePaths, Macros) when is_atom(ModuleName) ->
   [_Exports, _Imports, _Attributes,
      {compile, [_Options, _Version, _Time, {source, Path}]}] =
      ModuleName:module_info(),
   case new_from_file(Path, IncludePaths, Macros) of
      {ok, _Mod} = Res ->
         Res;
      _ ->
         case code:which(ModuleName) of
            Path1 when is_list(Path1) ->
               case get_forms(ModuleName, Path1) of
                  {ok, Forms} ->
                     new_from_forms(Forms);
                  _Other ->
                     {error, {invalid_module, ModuleName}}
               end;
            _Err ->
               {error, {invalid_module, ModuleName}}
         end
   end.

%% @equiv new_from_file(SrcFilePath, [])
new_from_file(SrcFilePath) ->
   new_from_file(SrcFilePath, []).

% @equiv new_from_file(SrcFilePath, IncludePaths, [])
new_from_file(SrcFilePath, IncludePaths) ->
   new_from_file(SrcFilePath, IncludePaths, []).

%% @doc Create a meta_mod for a module from its source file.
%%
-spec new_from_file(SrcFilePath :: string(),
   IncludePaths :: [string()],
   Macros :: [{atom(), term()}]) ->
   {ok, meta_mod()} | {error, {invalid_module, term()}}.
new_from_file(SrcFilePath, IncludePaths, Macros) ->
   case epp:parse_file(SrcFilePath, [filename:dirname(SrcFilePath) |
      IncludePaths], Macros) of
      {ok, Forms} ->
         new_from_forms(Forms);
      _err ->
         {error, {invalid_module, SrcFilePath}}
   end.

-compile({inline, {get_module, 1}}).
%% @doc Return the module name for the meta_mod.
-spec get_module(meta_mod()) -> atom().
get_module(MetaMod) -> MetaMod#meta_mod.module.

get_modulename(MetaMod) ->
   atom_to_list(get_module(MetaMod)).

%% @doc Set the meta_mod's module name.
-spec set_module(MetaMod :: meta_mod(), NewName :: atom()) -> NewMod :: meta_mod().
set_module(MetaMod, NewName) ->
   MetaMod#meta_mod{module = NewName}.

%% @doc Return the list of function forms in the meta_mod.
-spec get_forms(MetaMod :: meta_mod()) -> [term()].
get_forms(MetaMod) ->
   MetaMod#meta_mod.forms.

set_forms(MetaMod, Forms) ->
   MetaMod#meta_mod{forms = Forms}.

%% @doc Return the list of exports in the meta_mod.
%%
-spec get_exports(MetaMod :: meta_mod()) -> [{FuncName :: atom(), Arity :: integer()}].
get_exports(MetaMod) ->
   case MetaMod#meta_mod.export_all of
      false ->
         MetaMod#meta_mod.exports;
      true ->
         lists:foldl(
            fun({function, _L, Name, Arity, _Clauses}, Exports) ->
               [{Name, Arity} | Exports];
               (_Form, Exports) ->
                  Exports
            end, [], MetaMod#meta_mod.forms)
   end.


%% @doc Set the meta_mod's export list to the new list.
%%
-spec set_exports(MetaMod :: meta_mod(),
   Exports :: [{FuncName :: atom(), Arity :: integer()}]) -> NewMod :: meta_mod().
set_exports(MetaMod, Exports) ->
   MetaMod#meta_mod{exports = Exports}.

%% @doc Get the export_all value for the module.
%%
%% @spec get_export_all(MetaMod::meta_mod) -> true | false
get_export_all(MetaMod) ->
   MetaMod#meta_mod.export_all.

%% @doc Set the export_all value for the module.
%%
-spec set_export_all(MetaMod :: meta_mod(), Val :: true | false) -> meta_mod().
set_export_all(MetaMod, Val)
   when Val =:= true;
   Val =:= false ->
   MetaMod#meta_mod{export_all = Val}.

%% @doc Remove the export from the list of exports in the meta_mod.
%%
%% @spec remove_export(MetaMod::meta_mod(), FuncName::atom(),
%%   Arity::integer()) -> NewMod::meta_mod()
remove_export(MetaMod, FuncName, Arity) ->
   MetaMod#meta_mod{exports =
   lists:delete({FuncName, Arity},
      MetaMod#meta_mod.exports)}.

%% @doc Get the value a the module's attribute.
-spec get_attribute(meta_mod(), AttName :: atom()) -> [term()].
get_attribute(MetaMod, AttName) ->
   lists:foldl(
      fun({attribute, _, Name, Val}, AccIn) when Name =:= AttName ->
         [Val | AccIn];
         ({attribute, _, _, _}, AccIn) ->
            AccIn
      end,
      [],
      MetaMod#meta_mod.attributes).


%% Get the abstract representation, if available, for the module.
%%
%% Strategy:
%% 1) Try to get the abstract code from the module if it's compiled
%%    with debug_info.
%% 2) Look for the source file in the beam file's directory.
%% 3) If the file's directory ends with 'ebin', then search in
%%    [beamdir]/../src
get_forms(Module, Path) ->
   case beam_lib:chunks(Path, [abstract_code]) of
      {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} ->
         {ok, Forms};
      _Err ->
         case filename:find_src(Module, [{"ebin", "src"}]) of
            {error, _} = Err ->
               get_forms_from_binary(Module, Err);
            {SrcPath, _} ->
               Filename = SrcPath ++ ".erl",
               epp:parse_file(Filename, [filename:dirname(Filename)], [])
         end
   end.

get_dirs_in_dir(Dir) ->
   case file:list_dir(Dir) of
      {error, _} ->
         undefined;
      {ok, Listing} ->
         lists:foldl(
            fun(Name, Acc) ->
               Path = Dir ++ "/" ++ Name,
               case file:read_file_info(Path) of
                  {ok, #file_info{type = directory}} -> [Path | Acc];
                  _ -> Acc
               end
            end, [], Listing)
   end.

%% @doc Try to infer module source files from the beam code path.
get_forms_from_binary(Module, OrigErr) ->
   Ret =
      case code:where_is_file(atom_to_list(Module) ++ ".beam") of
         non_existing ->
            OrigErr;
         Filename ->
            %% We could automatically obtain a list of all dirs under this dir, but we just do
            %% a hack for now.
            Basedir = filename:dirname(Filename),
            Lastdir = filename:basename(Basedir),
            if Lastdir == "ebin" ->
               Rootdir = filename:dirname(Basedir),
               DirList = [Rootdir ++ "/src"],
               get_forms_from_file_list(Module, Rootdir,
                  DirList ++ get_dirs_in_dir(Rootdir ++ "/src"));
               true ->
                  DirList = [Basedir],
                  get_forms_from_file_list(Module, Basedir, DirList)
            end
      end,
   if Ret == [] -> OrigErr;
      true -> Ret
   end.
get_forms_from_file_list(_Module, _Basedir, []) ->
   [];
get_forms_from_file_list(Module, Basedir, [H | T]) ->
   Filename = H ++ "/" ++ atom_to_list(Module) ++ ".erl",
   case file:read_file_info(Filename) of
      {ok, #file_info{type = regular}} ->
         epp:parse_file(Filename, [filename:dirname(Filename)], []);
      _ ->
         get_forms_from_file_list(Module, Basedir, T)
   end.

add_inc(MetaMod, Inc) ->
   {ok, MetaMod#meta_mod{includes = [Inc | MetaMod#meta_mod.includes]}}.

%% @doc Add a new function to the meta_mod and return the resulting meta_mod.
%% This function calls add_func(MetaMod, Form, true).
-spec add_func(meta_mod(), func_form() | string()) ->
   {ok, NewMod :: meta_mod()} | {error, parse_error}.
add_func(MetaMod, Form) ->
   add_func(MetaMod, Form, true).

%% @doc Add a new function to the meta_mod and return the new MetaMod
%% record. Export is a boolean variable indicating if the function should
%% be added to the module's exports.

-spec add_func(MetaMod :: meta_mod(),
   Func :: func_form() | string(),
   Export :: boolean()) ->
   {ok, NewMod :: meta_mod()} | {error, parse_error}.
add_func(MetaMod, Func, Export) when is_list(Func) ->
   case parse_func_string(Func) of
      {ok, Form} ->
         add_func(MetaMod, Form, Export);
      Err ->
         Err
   end;
add_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form, true) ->
   Foo =
      {ok, MetaMod#meta_mod{
         exports = [{FuncName, Arity} | MetaMod#meta_mod.exports],
         forms = [Form | MetaMod#meta_mod.forms]
      }},
   Foo;
add_func(MetaMod, {function, _Line, _FuncName, _Arity, _Clauses} = Form, false) ->
   {ok, MetaMod#meta_mod{forms = [Form | MetaMod#meta_mod.forms]}};

add_func(_, _, _) ->
   {error, parse_error}.

add_funcs(MetaMod, Funs, Export) ->
   Fun =
      fun(Function, Meta) ->
         case add_func(Meta, Function, Export) of
            {error, _, R} ->
               exit({function, R, Function});
            {ok, New} ->
               New
         end
      end,
   {ok, lists:foldl(Fun, MetaMod, Funs)}.


parse_func_string(Func) ->
   case erl_scan:string(Func) of
      {ok, Toks, _} ->
         case erl_parse:parse_form(Toks) of
            {ok, _Form} = Res ->
               Res;
            _Err ->
               {error, parse_error, _Err}
         end;
      _Err ->
         {error, parse_error, _Err}
   end.

%% @doc Try to remove the function from the meta_mod.
%% If the function exists, the new meta_mod is returned. Otherwise,
%% original meta_mod is returned.
%%
-spec remove_func(MetaMod :: meta_mod(), FuncName :: string(), Arity :: integer())
      -> NewMod :: meta_mod().
remove_func(MetaMod, FuncName, Arity) ->
   MetaMod#meta_mod{forms =
   lists:filter(
      fun({function, _Line, FuncName1, Arity1, _Clauses})
         when FuncName1 =:= FuncName, Arity =:= Arity1 ->
         false;
         (_) ->
            true
      end, MetaMod#meta_mod.forms),
      exports =
      lists:filter(
         fun({FuncName1, Arity1})
            when FuncName1 =:= FuncName,
            Arity1 =:= Arity ->
            false;
            (_) ->
               true
         end, MetaMod#meta_mod.exports)
   }.

is_export(#meta_mod{exports = Exports}, FuncName, Arity) ->
   lists:member({FuncName, Arity}, Exports).


%% @doc Check whether the meta_mod has a function with the given name
%%   and arity.
-spec is_has_func(MetaMod :: meta_mod(), FuncName :: atom(), Arity :: integer()) -> boolean().
is_has_func(MetaMod, FuncName, Arity) ->
   lists:any(fun({function, _Line, FuncName1, Arity1, _Clauses})
      when FuncName1 == FuncName, Arity1 == Arity ->
      true;
      (_) ->
         false
             end, MetaMod#meta_mod.forms).

is_has_func(MetaMod, FuncName, Arity, true) ->
   is_has_func(MetaMod, FuncName, Arity) andalso is_export(MetaMod, FuncName, Arity);
is_has_func(MetaMod, FuncName, Arity, false) ->
   is_has_func(MetaMod, FuncName, Arity).


get_all_func(#meta_mod{forms = Forms}) ->
   Forms.


%% @doc Get the form for the function with the specified arity in the
%%   meta_mod.
%%
%% @spec get_func(MetaMod::meta_mod() | atom(),
%%   FuncName::atom(), Arity::integer()) ->
%%     {ok, func_form()} | {error, Err}
get_func(Module, FuncName, Arity) when is_atom(Module) ->
   case meta:new_from_module(Module) of
      {ok, C1} ->
         get_func(C1, FuncName, Arity);
      Err ->
         Err
   end;
get_func(MetaMod, FuncName, Arity) when is_record(MetaMod, meta_mod) ->
   get_func2(MetaMod#meta_mod.forms, FuncName, Arity).

get_func2([], FuncName, Arity) ->
   {error, {function_not_found, {FuncName, Arity}}};
get_func2([{function, _Line, FuncName, Arity, _Clauses} = Form | _Rest],
   FuncName, Arity) ->
   {ok, Form};
get_func2([_Form | Rest], FuncName, Arity) ->
   get_func2(Rest, FuncName, Arity).

%%get_func_src(MetaMod, FuncName, Arity) -> when is_atom(Module) ->
%%Asb = get_func(Meta, FuncName, Arity),
%%asb_to_src([Asb]).

%% @doc
%% Replace an existing function with the new one. If the function doesn't exist
%% the new function is added to the meta_mod.
%% This is tantamount to calling meta:remove_func followed by meta:add_func.
%%



-spec replace_func(MetaMod :: meta_mod(), Function :: string() | func_form()) ->
   {ok, NewMod :: meta_mod()} | {error, Error :: term()}.
replace_func(MetaMod, Function) when is_list(Function) ->
   case parse_func_string(Function) of
      {ok, Form} ->
         replace_func(MetaMod, Form);
      Err ->
         io:format("-------> ~p~n", [Err]),
         Err
   end;
replace_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form) ->
   Mod1 = remove_func(MetaMod, FuncName, Arity),
   add_func(Mod1, Form);
replace_func(_MetaMod, _) ->
   {error, parse_error}.


%% @doc transform metaMod to Forms.
%%
to_forms(#meta_mod{module = Module,
   file = File,
   includes = Includes,
   records = Records,
   attributes = Attributes,
   forms = Froms,
   eof = Eof} = MetaMod) ->
   FileName =
      case File of
         undefined -> atom_to_list(get_module(MetaMod));
         Val -> Val
      end,
   ExtRecords = extrecords(Includes),
   FormsFile = {attribute, 1, file, {FileName, 1}},
   FormModule = {attribute, 2, module, Module},
   FormExports = {attribute, 3, export, get_exports(MetaMod)},

   FormsBody1 =
      lists:foldr(fun(Form, AccIn) -> [Form | AccIn] end,
         [Eof],
         Froms),
   FormsBody2 =
      lists:foldr(fun(Form, AccIn) -> [Form | AccIn] end,
         FormsBody1,
         Attributes),

   FormsBody3 =
      lists:foldr(fun(Form, AccIn) -> [Form | AccIn] end,
         FormsBody2,
         Records ++ ExtRecords),

   [FormsFile, FormModule, FormExports | FormsBody3].

extrecords(Includes) ->
   Fun =
      fun(Include, Records) ->
         {ok, EppForms} = epp:parse_file(Include, []),
         filter_record(EppForms) ++ Records
      end,
   lists:foldl(Fun, [], Includes).

filter_record(EppForms) ->
   Fun = fun(#attribute{name = record}) -> true; (_) -> false end,
   lists:filter(Fun, EppForms).

%% @doc Compile the module represented by the meta_mod and load the
%% resulting BEAM into the emulator. This function calls
%% compile(MetaMod, [report_errors, report_warnings]).
%%
-spec compile(MetaMod :: meta_mod()) -> ok | {error, term()}.
compile({ok, MetaMod}) ->
   compile(MetaMod);
compile(MetaMod) ->
   compile(MetaMod, [report_errors, report_warnings,
      return_errors]).

%% @doc Compile the module represented by the meta_mod and load the
%% resulting BEAM into the emulator. 'Options' is a list of options as
%% described in the 'compile' module in the Erlang documentation.
%%
%% If the 'outdir' option is provided,
%% the .beam file is written to the destination directory.
%%
-spec compile(MetaMod :: meta_mod(), Options :: [term()]) -> ok | {error, term()}.
compile(#meta_mod{} = MetaMod, Options) ->
   case catch compile:forms(to_forms(MetaMod), Options) of
      {ok, Module, Bin} ->
         Res =
            case lists:keysearch(outdir, 1, Options) of
               {value, {outdir, OutDir}} ->
                  file:write_file(OutDir ++ ['/' | atom_to_list(MetaMod#meta_mod.module)] ++ ".beam",
                     Bin);
               false -> ok
            end,
         %io:format("Module ~p Res ~p ~n", [Module, Res]),
         case Res of
            ok ->
               %io:format("Module2 ~p ~n", [Module]),
               catch code:purge(Module),
               %io:format("Module3 ~p  ~n", [Module]),
               %%case code:load_binary(Module, atom_to_list(Module) ++ ".erl", Bin) of %% Fix Xref bug
               case catch code:load_binary(Module, atom_to_list(Module), Bin) of
                  {module, _Module} ->
                     ok;
                  Err ->
                     Err
               end;
            Err ->
               Err
         end;
      Err ->
         Err
   end;
compile(NotMetaMod, Options) ->
   io:format("compile ~p ~p~n", [NotMetaMod, Options]).

%% @doc Change the name of the function represented by the form.
%%
%% @spec rename(Form::func_form(), NewName::atom()) ->
%%   {ok, NewForm::func_form()} | {error, Err}
rename({function, Line, _Name, Arity, Clauses}, NewName) ->
   {function, Line, NewName, Arity, Clauses}.


%% @doc Get the curried form for the function and parameter(s). Currying
%% involves replacing one or more of the function's leading parameters
%% with predefined values.
%%
%% @spec curry(Form::func_form(), Param::term() | [term()]) ->
%%   {ok, NewForm::func_form()} | {error, Err}
curry(Form, Param) when not is_list(Param) ->
   curry(Form, [Param]);
curry({function, _Line, _Name, Arity, _Clauses}, Params)
   when length(Params) > Arity ->
   {error, too_many_params};
curry({function, Line, Name, Arity, Clauses}, NewParams) ->
   NewClauses =
      lists:foldl(
         fun(Clause, Clauses1) ->
            [curry_clause(Clause, NewParams) | Clauses1]
         end, [], Clauses),
   {ok, {function, Line, Name, Arity - length(NewParams), NewClauses}}.

curry_clause({clause, L1, ExistingParams, Guards, _Exprs} = Clause,
   NewParams) ->
   {FirstParams, LastParams} =
      lists:split(length(NewParams), ExistingParams),
   %%     Matches =
   %%  lists:foldl(
   %%    fun({Var, NewVal}, Acc) ->
   %%            [{match, 1, Var, erl_parse:abstract(NewVal)} | Acc]
   %%    end, [], lists:zip(FirstParams, NewParams)),
   %%    {clause, L1, LastParams, Guards, Matches ++ Exprs}.

   Vals =
      lists:foldl(
         fun({{var, _, Name}, NewVal}, Acc) ->
            [{Name, erl_parse:abstract(NewVal)} | Acc];
            (_, Acc) ->
               Acc
         end, [], lists:zip(FirstParams, NewParams)),

   NewExprs = replace_vars(Clause, Vals),

   {clause, L1, LastParams, Guards, NewExprs}.

replace_vars(Clause, Vals) ->
   Tree =
      erl_syntax_lib:map(
         fun({var, _L2, Name} = Expr) ->
            case proplists:lookup(Name, Vals) of
               none ->
                  Expr;
               {_, Val} ->
                  Val
            end;
            (Expr) ->
               Expr
         end, Clause),
   {clause, _, _, _, NewExprs} = erl_syntax:revert(Tree),
   NewExprs.


%% @doc Curry the function from the module with the given param(s)
%%
%% @spec curry(ModName::atom(), Name::atom(), Arity::integer(),
%%   Params::term() | [term()]) ->
%%    {ok, NewForm} | {error, Err}
curry(ModName, Name, Arity, Params) when is_atom(ModName) ->
   case new_from_module(ModName) of
      {ok, MetaMod} ->
         curry(MetaMod, Name, Arity, Params);
      Err ->
         Err
   end;

%% @doc Curry the function from the meta_mod with
%%  the given param(s)
%%
%% @spec curry(MetaMod::meta_mod(), Name::atom(), arity::integer(),
%%   Params::term() | [terms()]) ->
%%    {ok, NewForm} | {error, Err}
curry(MetaMod, Name, Arity, Params) ->
   case get_func(MetaMod, Name, Arity) of
      {ok, Form} ->
         curry(Form, Params);
      Err ->
         Err
   end.


%% @doc Curry the function from the module or meta_mod
%%  with the param(s), and return its renamed form.
%%
%% @spec curry(Module::atom() | meta_mod(), Name::atom(), Arity::integer(),
%%   Params::term() | [terms()], NewName::atom()) ->
%%    {ok, NewForm} | {error, Err}
curry(Module, Name, Arity, Params, NewName) ->
   case curry(Module, Name, Arity, Params) of
      {ok, NewForm} ->
         {ok, rename(NewForm, NewName)};
      Err ->
         Err
   end.


%% @doc Add the curried form of the function in the meta_mod
%%  with its curried form.
%%
%% @spec curry_add(MetaMod::meta_mod(), Form::func_form(),
%%   Params::term() | [term()]) ->
%%    {ok, NewMetaMod::meta_mod()} | {error, Err}
curry_add(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) ->
   curry_add(MetaMod, Name, Arity, Params).

%% @doc Add the curried form of the function
%%   in the meta_mod with its curried form.
%%
%% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(),
%%   Params::term() | [term()]) ->
%%    {ok, NewMetaMod::meta_mod()} | {error, Err}
curry_add(MetaMod, Name, Arity, Params) ->
   curry_change(MetaMod, Name, Arity, Params, false).

%% @doc Curry the function form from the meta_mod, then add it
%%   to the other meta_mod with a new name.
%%
%% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(),
%%   Params::[term()], NewName::atom()) -> {ok, NewMod::meta_mod()} |
%%     {error, Err}
curry_add(MetaMod, Name, Arity, Params, NewName) ->
   curry_add(MetaMod, MetaMod, Name, Arity, Params, NewName).

%% @doc Curry the function in the module, rename the curried form, and
%%   add it to the meta_mod.
%%
%% @spec curry_add(MetaMod::meta_mod(), Module::atom() | meta_mod(),
%%   Name::atom(), Arity::integer(), Params::term() | [term()],
%%   NewName::atom()) ->
%%     {ok, NewMod::meta_mod()} | {error, Error}
curry_add(MetaMod, Module, Name, Arity, Params, NewName) ->
   case curry(Module, Name, Arity, Params, NewName) of
      {ok, Form} ->
         add_func(MetaMod, Form);
      Err ->
         Err
   end.


curry_change(MetaMod, Name, Arity, Params, Remove) ->
   case get_func(MetaMod, Name, Arity) of
      {ok, OldForm} ->
         case curry(OldForm, Params) of
            {ok, NewForm} ->
               MetaMod1 =
                  case Remove of
                     true ->
                        remove_func(MetaMod, Name, Arity);
                     false ->
                        MetaMod
                  end,
               add_func(MetaMod1, NewForm);
            Err ->
               Err
         end;
      Err ->
         Err
   end.

%% @doc Replace the function in the meta_mod with
%%   its curried form.
%%
%% @spec curry_replace(MetaMod::meta_mod(), Form::func_form(),
%%   Params::term() | [term()]) ->
%%    {ok, NewMetaMod::meta_mod()} | {error, Err}
curry_replace(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) ->
   curry_replace(MetaMod, Name, Arity, Params).


%% @doc Replace the function in the meta_mod with
%%   its curried form.
%%
%% @spec curry_replace(MetaMod::meta_mod(), Name::atom(),
%%   Arity::integer(), Params::term() | list()) ->
%%    {ok, NewMetaMod::meta_mod()} | {error, Err}
curry_replace(MetaMod, Name, Arity, Params) ->
   curry_change(MetaMod, Name, Arity, Params, true).


%% @doc This function takes a function form and list of name/value pairs,
%% and replaces all the function's parameters that whose names match an
%% element from the list with the predefined value.
%%
%% @spec embed_params(Func::func_form(),
%%   Vals::[{Name::atom(), Value::term()}]) -> NewForm::func_form()
embed_params({function, L, Name, Arity, Clauses}, Vals) ->
   NewClauses =
      lists:map(
         fun({clause, L1, Params, Guards, _Exprs} = Clause) ->
            {EmbeddedVals, OtherParams} =
               lists:foldr(
                  fun({var, _, VarName} = Param, {Embedded, Rest}) ->
                     case proplists:lookup(VarName, Vals) of
                        none ->
                           {Embedded, [Param | Rest]};
                        {_, Val} ->
                           {[{VarName, erl_parse:abstract(Val)} |
                              Embedded], Rest}
                     end;
                     (Param, {Embedded, Rest}) ->
                        {Embedded, [Param | Rest]}
                  end, {[], []}, Params),
            NewExprs = replace_vars(Clause, EmbeddedVals),
            {clause, L1, OtherParams, Guards, NewExprs}


         %%                  {Params1, Matches1, _RemainingVals} =
         %%                      lists:foldl(
         %%                        fun({var, _L2, ParamName} = Param,
         %%                            {Params2, Matches2, Vals1}) ->
         %%                                case lists:keysearch(ParamName, 1, Vals1) of
         %%                                    {value, {_Name, Val} = Elem} ->
         %%                                        Match = {match, L1, Param,
         %%                                                 erl_parse:abstract(Val)},
         %%                                        {Params2, [Match | Matches2],
         %%                                         lists:delete(Elem, Vals1)};
         %%                                    false ->
         %%                                        {[Param | Params2], Matches2, Vals1}
         %%                                end;
         %%                           (Param, {Params2, Matches2, Vals1}) ->
         %%                                {[Param | Params2], Matches2, Vals1}
         %%                        end, {[], [], Vals}, Params),
         %%                  [{clause, L1, lists:reverse(Params1), Guards,
         %%                                lists:reverse(Matches1) ++ Exprs} | Clauses1]
         end, Clauses),
   NewArity =
      case NewClauses of
         [{clause, _L2, Params, _Guards, _Exprs} | _] ->
            length(Params);
         _ ->
            Arity
      end,
   {function, L, Name, NewArity, NewClauses}.

%% @doc Apply {@link embed_params/2} to a function from the meta_mod and
%%   add the resulting function to the meta_mod, and return the resulting
%%   meta_mod.
%%
%% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(),
%%   Values::proplist()) -> {ok, NewMetaMod::meta_mod()} | {error, Err}
embed_params(MetaMod, Name, Arity, Values) ->
   embed_params(MetaMod, Name, Arity, Values, Name).

%% @doc Apply embed_params/2 to the function from the meta_mod and
%%   add the resulting function to the meta_mod after renaming the function.
%%
%% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(),
%%   Values::proplist(), NewName::atom()) ->
%%    {ok, NewMetaMod::meta_mod()} | {error, Err}
embed_params(MetaMod, Name, Arity, Values, NewName) ->
   case get_func(MetaMod, Name, Arity) of
      {ok, Form} ->
         NewForm = embed_params(Form, Values),
         add_func(MetaMod, rename(NewForm, NewName));
      Err ->
         Err
   end.


%% @doc Apply the embed_params function with the list of {Name, Value}
%% pairs to all forms in the meta_mod. Exports
%% for functions whose arities change due to the embedding are preserved.
%%
%% @spec embed_all(MetaMod::meta_mod(), Vals::[{Name::atom(),
%%   Value::term()}]) -> NewMetaMod::meta_mod()
embed_all(MetaMod, Vals) ->
   Forms = get_forms(MetaMod),
   Exports = get_exports(MetaMod),
   {NewForms, Exports3, NewExports} =
      lists:foldl(
         fun({function, _L, Name, Arity, _Clauses} = Form,
            {Forms1, Exports1, NewExports1}) ->
            {function, _, _, NewArity, _} = NewForm =
               embed_params(Form, Vals),
            Exports2 = lists:delete({Name, Arity}, Exports1),
            NewExports2 =
               case length(Exports2) == length(Exports1) of
                  true ->
                     NewExports1;
                  false ->
                     [{Name, NewArity} | NewExports1]
               end,
            {[NewForm | Forms1], Exports2, NewExports2};
            (Form, {Forms1, Exports1, NewExports1}) ->
               {[Form | Forms1], Exports1, NewExports1}
         end, {[], Exports, []}, Forms),
   #meta_mod{module = get_module(MetaMod),
      exports = Exports3 ++ NewExports,
      forms = lists:reverse(NewForms),
      export_all = get_export_all(MetaMod)}.

%% @doc extend/2
%% Add all the parent module's functions that are missing from the child
%% module to the child module. The new functions in the child module are
%% shallow: they have the name and arity as their corresponding functions in
%% the parent meta_mod, but instead of implementing their logic they call
%% the parent module's functions.
%%
%% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod()) ->
%%      NewChildMod::meta_mod()
extend(Parent, Child) ->
   extend(Parent, Child, 0).

%% @doc Similar to extend/2, with the addition of the 'ArityDiff' parameter,
%% which indicates the difference
%% in arities meta should use when figuring out which functions to
%% generate based on the modules' exports. This is sometimes
%% useful when calling extend() followed by embed_all().
%%
%% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod(),
%%    ArityDiff::integer()) ->
%%      NewChildMod::meta_mod()
extend(Parent, Child, ArityDiff) ->
   extend(Parent, Child, ArityDiff, []).

extend(Parent, Child, ArityDiff, Options) ->
   {{ParentName, ParentExports, ParentMod}, ChildMod} =
      get_extend_data(Parent, Child),
   ChildExports = get_exports(ChildMod),
   ChildExports1 = [{ExportName, ExportArity + ArityDiff} ||
      {ExportName, ExportArity} <-
         ChildExports],
   ExportsDiff = ParentExports -- ChildExports1,
   NewChild =
      lists:foldl(
         fun({FuncName, Arity}, ChildMod1) ->
            Func =
               case lists:member(copy, Options) of
                  true ->
                     {ok, ParentFunc} =
                        meta:get_func(ParentMod, FuncName, Arity),
                     ParentFunc;
                  _ ->
                     Params = get_params(
                        ParentMod, FuncName, Arity),
                     Clause1 =
                        {clause, 1, Params, [],
                           [{call, 1,
                              {remote, 1, {atom, 1, ParentName},
                                 {atom, 1, FuncName}},
                              Params}]},
                     {function, 1, FuncName, Arity, [Clause1]}
               end,
            {ok, ChildMod2} = add_func(ChildMod1, Func),
            ChildMod2
         end, ChildMod, ExportsDiff),
   NewChild.

get_extend_data(Parent, Child) when is_atom(Parent) ->
   [{exports, Exports} | _] = Parent:module_info(),
   Exports1 = Exports -- [{module_info, 0}],
   Exports2 = Exports1 -- [{module_info, 1}],
   ParentMod = case meta:new_from_module(Parent) of
                  {ok, M} -> M;
                  {error, _} -> undefined
               end,
   get_extend_data({Parent, Exports2, ParentMod}, Child);
get_extend_data(Parent, Child) when is_record(Parent, meta_mod) ->
   get_extend_data({get_module(Parent),
      get_exports(Parent),
      Parent}, Child);
get_extend_data(Parent, Child) when is_list(Parent) ->
   case new_from_file(Parent) of
      {ok, M1} ->
         get_extend_data(M1, Child);
      Err ->
         Err
   end;
get_extend_data({_, _, _} = ParentData, Child) when is_atom(Child);
   is_list(Child) ->
   case new_from_module(Child) of
      {ok, MetaMod} ->
         {ParentData, MetaMod};
      Err ->
         Err
   end;
get_extend_data(ParentData, Child) when is_record(Child, meta_mod) ->
   {ParentData, Child}.

get_params(_, _, 0) -> [];
get_params(undefined, _FuncName, Arity) ->
   [{var, 1, list_to_atom("P" ++ integer_to_list(Num))}
      || Num <- lists:seq(1, Arity)];
get_params(ParentMod, FuncName, Arity) ->
   {ok, {function, _L, _Name, _Arity,
      [{clause, _, Params, _Guards, _Exprs} | _]}} =
      get_func(ParentMod, FuncName, Arity),
   Params.


%% @doc Return the pretty-printed source code for the module.
%%
%% @spec to_src(MetaMod::meta_mod()) -> string()
to_src(MetaMod) when is_record(MetaMod, meta_mod) ->
   ExportsForm =
      {attribute, 1, export, get_exports(MetaMod)},
   AllForms = [{attribute, 1, module, get_module(MetaMod)}, ExportsForm |
      get_forms(MetaMod)],
   erl_prettypr:format(erl_syntax:form_list(AllForms)).

%% @doc Asb from to str. Asb return by get_func.
asb_to_src(Asb) ->
   erl_prettypr:format(erl_syntax:form_list([Asb])).

beam_to_src(Beam) ->
   {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Beam, [abstract_code]),
   io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).


%% @doc Write the pretty printed source code for the module
%%   to the file with the given file name.
%%
%% @spec to_src(MetaMod::meta_mod(), FileName::string()) ->
%%   ok | {error, Error}
to_src(MetaMod, FileName) ->
   Src =
      io_lib:format("%% Warning: Do not edit this file. It was automatically\n"
      "%% generated by 'meta' on ~p.\n\n", [calendar:local_time()])
      ++
      to_src(MetaMod),
   file:write_file(FileName, list_to_binary(Src)).


%% ===================================================================================
%% form parse
%% ===================================================================================

%% @doc get all records name.
-spec get_records_name(meta_mod()) -> [atom()].
get_records_name(#meta_mod{records = Records}) ->
   lists:map(
      fun({attribute, _Line, record, {RecordName, _Fields}}) ->
         RecordName end,
      Records).

-spec get_record(meta_mod(), atom()) -> {atom(), list()}.
get_record(#meta_mod{records = Records}, RecordName) ->
   [{_, _, _, Record}] =
      lists:filter(
         fun({_Att, _L, record, {Name, _F}}) ->
            if Name =:= RecordName -> true;
               true -> false
            end
         end,
         Records),
   Record.
