-module(integral).
-export([simpson/4, simpson/3, psimpson/4, psimpson/3, sum/3, psum/3]).
-compile([native]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% exports API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec(simpson/4 :: (From :: float(), To :: float(), N :: integer(), Func :: function()) -> float()).
-spec(psimpson/4 :: (From :: float(), To :: float(), N :: integer(), Func :: function()) -> float()).
-spec(sum/3 :: (I :: integer(), N :: integer(), Func :: function()) -> float()).
-spec(psum/3 :: (I :: integer(), N :: integer(), Func :: function()) -> float()).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% internal API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec(up_to_even/1 :: (N :: integer()) -> integer()).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% implementation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
simpson(From, To, Func) ->
    simpson(From, To, 1000, Func).
simpson(From, To, N, Func) when N > 2 ->
    if 
        From < To -> simpson_no_safe(From, To, up_to_even(N), Func, fun sum/3);
        From > To -> - simpson_no_safe(To, From, up_to_even(N), Func, fun sum/3);
        From == To -> 0.0
    end.
psimpson(From, To, Func) ->
    psimpson(From, To, 1000, Func).
    
psimpson(From, To, N, Func) when N > 2 ->
    if 
        From < To -> simpson_no_safe(From, To, up_to_even(N), Func, fun psum/3);
        From > To -> - simpson_no_safe(To, From, up_to_even(N), Func, fun psum/3);
        From == To -> 0.0
    end.
simpson_no_safe(From, To, N, Func, Sum) ->
    Step = (To - From) / N,
    Result = 2.0 * Sum(1, N div 2 - 1, fun(I) -> Func(From + 2.0 * I * (To - From) / N) end) + 
             4.0 * Sum(1, N div 2, fun(I) -> Func(From - Step + 2.0 * I * (To - From) / N) end) +
             Func(From) + Func(To),
    Result * Step / 3.0.
up_to_even(N) when is_integer(N) ->
    N + (N rem 2).    
sum(I, N, Func) ->
    rec_sum(I, N, Func, 0.0).
rec_sum(N, N, Func, Result) ->
    Func(N) + Result;
rec_sum(I, N, Func, Result) ->
    rec_sum(I + 1, N, Func, Func(I) + Result).
for(N, N, Func) ->
    Func(N);
for(I, N, Func) ->
    Func(I), 
    for(I + 1, N, Func).
psum(I, N, Func) ->
    S = self(),
    Id = make_ref(),
    for(I, N, fun(Arg) -> spawn(fun() -> S ! {calculate_complete, Id, Func(Arg)} end) end),
    sum(I, N, fun(_) -> receive {calculate_complete, Id, Value} -> Value end end).