% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.

-module(couch_epi_data).

-include("couch_epi.hrl").

%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------

-export([interval/1, data/1]).

%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------

interval(Specs) ->
    extract_minimal_interval(Specs).

data(Specs) ->
    Locators = locate_sources(Specs),
    case lists:foldl(fun collect_data/2, {ok, [], []}, Locators) of
        {ok, Hashes, Data} ->
            {ok, couch_epi_util:hash(Hashes), Data};
        Error ->
            Error
    end.

%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------

collect_data({App, Locator}, {ok, HashAcc, DataAcc}) ->
    case definitions(Locator) of
        {ok, Hash, Data} ->
            {ok, [Hash | HashAcc], [{App, Data} | DataAcc]};
        Error ->
            Error
    end;
collect_data({_App, _Locator}, Error) ->
    Error.

extract_minimal_interval(Specs) ->
    lists:foldl(fun minimal_interval/2, undefined, Specs).

minimal_interval({_App, #couch_epi_spec{options = Options}}, Min) ->
    case lists:keyfind(interval, 1, Options) of
        {interval, Interval} -> min(Interval, Min);
        false -> Min
    end.

locate_sources(Specs) ->
    lists:map(
        fun({ProviderApp, #couch_epi_spec{value = Src}}) ->
            {ok, Locator} = locate(ProviderApp, Src),
            {ProviderApp, Locator}
        end,
        Specs
    ).

locate(App, {priv_file, FileName}) ->
    case priv_path(App, FileName) of
        {ok, FilePath} ->
            ok = check_exists(FilePath),
            {ok, {file, FilePath}};
        Else ->
            Else
    end;
locate(_App, {file, FilePath}) ->
    ok = check_exists(FilePath),
    {ok, {file, FilePath}};
locate(_App, Locator) ->
    {ok, Locator}.

priv_path(AppName, FileName) ->
    case code:priv_dir(AppName) of
        {error, _Error} = Error ->
            Error;
        Dir ->
            {ok, filename:join(Dir, FileName)}
    end.

check_exists(FilePath) ->
    case filelib:is_regular(FilePath) of
        true ->
            ok;
        false ->
            {error, {notfound, FilePath}}
    end.

definitions({file, FilePath}) ->
    case file:consult(FilePath) of
        {ok, Data} ->
            {ok, hash_of_file(FilePath), Data};
        {error, Reason} ->
            {error, {FilePath, Reason}}
    end;
definitions({static_module, Module}) when is_atom(Module) ->
    definitions({static_module, [Module]});
definitions({static_module, Modules}) ->
    Data = lists:append([M:data() || M <- Modules]),
    Hash = couch_epi_functions_gen:hash(Modules),
    {ok, Hash, Data};
definitions({callback_module, Module}) ->
    Data = Module:data(),
    {ok, erlang:phash2(Data), Data}.

hash_of_file(FilePath) ->
    {ok, Data} = file:read_file(FilePath),
    couch_hash:md5_hash(Data).
