% file: "barnes.erl"

-module(barnes2).
-export([?MODULE/0]).

?MODULE() ->
    Stars = create_scenario(1000, 1.0),
    R = hd(loop(10,1000.0,Stars,0)),
    Str = lists:flatten(io:lib_format("~s", [R])),
    {R,Str =:= {1.00000,-1.92269e+4,-1.92269e+4,2.86459e-2,2.86459e-2}}.

create_scenario(N, M) ->
  create_scenario0(0, 0, trunc(math:sqrt(N)), M).

create_scenario0(_X, _SN, _SN, _M) -> 
  [];
create_scenario0(SN, Y, SN, M) -> 
  create_scenario0(0, Y+1, SN, M);
create_scenario0(X, Y, SN, M) ->
  XPos0 = (((20000.0 * 2) / SN) * X) - 20000.0,
  YPos0 = (((20000.0 * 2) / SN) * Y) - 20000.0,
  Calibrate = ((20000.0 * 2) / SN) / 2,
  XPos = XPos0 + Calibrate,
  YPos = YPos0 + Calibrate,
  [{M, XPos, YPos, 0.0, 0.0} | create_scenario0(X+1, Y, SN, M)].

relpos_to_quadrant(DX, DY) when DX >= 0 ->
  if
    DY >= 0 -> 0;
    true -> 3
  end;
relpos_to_quadrant(_, DY) -> 
  if
    DY >= 0 -> 1;
    true -> 2 
  end.

quadrant_to_dx(0, D) -> D;
quadrant_to_dx(1, D) -> -D;
quadrant_to_dx(2, D) -> -D;
quadrant_to_dx(3, D) -> D.

quadrant_to_dy(Q,D) ->
  if
    Q < 2 -> D;
    true -> -D
  end.

create_tree(Stars) ->
  create_tree0(Stars, empty).

create_tree0([],Tree) -> 
  Tree;
create_tree0([{M,X,Y,_,_} | Stars], Tree) ->
  create_tree0(Stars, insert_tree_element(Tree, M, X, Y, 0.0, 0.0, 20000.0)).

insert_tree_element(empty, M, X, Y, _OX, _OY, _D) ->
  {body,M,X,Y};
insert_tree_element({branch,M0,SubTree}, M, X, Y, OX, OY, D) ->
  Q = relpos_to_quadrant(X-OX,Y-OY),
  D2 = D / 2,
  DX = quadrant_to_dx(Q,D2),
  DY = quadrant_to_dy(Q,D2),
  {branch,M0+M,setelement(Q+1,SubTree,
                          insert_tree_element(element(Q+1,SubTree),
                                              M, X, Y, OX+DX, OY+DY,D2))};
insert_tree_element({body,M0,X0,Y0},M,X,Y,OX,OY,D) ->
  resolve_body_conflict(M,X,Y,M0,X0,Y0,OX,OY,D).

resolve_body_conflict(M0, X0, Y0, M1, X1, Y1, OX, OY, D) ->
  T = {empty,empty,empty,empty},
  Q0 = relpos_to_quadrant(X0-OX,Y0-OY),
  Q1 = relpos_to_quadrant(X1-OX,Y1-OY),
  D2 = D / 2,
  if
    Q0 == Q1 -> DX = quadrant_to_dx(Q0,D2),
                DY = quadrant_to_dy(Q1,D2),
                {branch,M0+M1,setelement(Q0+1,T,
                                         resolve_body_conflict(M0,X0,Y0,
                                                               M1,X1,Y1,
                                                               OX+DX,OY+DY,
                                                               D2))} ;
    true -> {branch,M0+M1, setelement(Q1+1,
                                      setelement(Q0+1,T,{body,M0,X0,Y0}),
                                      {body,M1,X1,Y1})}
  end.

compute_acceleration(empty, _, _, _, _, _,L) -> 
  {{0.0, 0.0},L+1};
compute_acceleration({body,BM,BX,BY}, _D, _OX, _OY, X, Y,L) ->
  DX = BX - X,
  DY = BY - Y,
  R2 = (DX * DX) + (DY * DY),
  Divisor = R2 * math:sqrt(R2),
  if
    Divisor < 0.000001 -> % was: Divisor < ?EPSILON -> 
      {{0.0, 0.0},L+1};
    true -> 
      Expr = BM / Divisor,
      {{DX * Expr, DY * Expr},L+1}
  end;
compute_acceleration({branch,M,SubTree}, D, OX, OY, X, Y,L) ->
  DX = OX - X,
  DY = OY - Y,
  R2 = (DX * DX) + (DY * DY),
  DD = D*D,
  R2_THETA2 = 0.09 * R2, % TRY 2.0 *R2, !!!  was: R2_THETA2 = ?THETA2*R2,
  if
    % Ok to approximate?
    DD < R2_THETA2 -> 
      Divisor = R2 * math:sqrt(R2),
      if
        Divisor < 0.000001 ->
          {{0.0,0.0},L};
        true -> 
          Expr = M / Divisor,
          {{DX*Expr, DY*Expr},L+1}
        end;
    % Not ok to approximate...
    true -> 
      D2 = D / 2,
      {{AX0, AY0},L1} = compute_acceleration(element(1,SubTree),
                                        D2, OX + quadrant_to_dx(0,D2),
                                        OY + quadrant_to_dy(0,D2),X,Y,L),
      {{AX1, AY1},L2} = compute_acceleration(element(2,SubTree),
                                        D2, OX + quadrant_to_dx(1,D2),
                                        OY + quadrant_to_dy(1,D2),X,Y,L1),
      {{AX2, AY2},L3} = compute_acceleration(element(3,SubTree),
                                        D2,OX + quadrant_to_dx(2,D2),
                                        OY + quadrant_to_dy(2,D2),X,Y,L2),
      {{AX3, AY3},L4} = compute_acceleration(element(4,SubTree),
                                        D2, OX + quadrant_to_dx(3,D2),
                                        OY + quadrant_to_dy(3,D2),X,Y,L3),
      {{AX0+AX1+AX2+AX3, AY0+AY1+AY2+AY3},L4+1}
  end.

compute_star_accelerations(_Tree,[],L) -> 
  {[],L};
compute_star_accelerations(Tree,[{_,X, Y,_,_}|Stars],L) ->
  {A,AL} = compute_acceleration(Tree, 20000.0, 0.0, 0.0, X, Y,L),
  {B,BL} = compute_star_accelerations(Tree, Stars,AL),
  {[A | B],BL}.

compute_next_state([],_,_) -> 
  [];
compute_next_state([{M,X,Y,VX,VY}|Stars],[{AX,AY}|Accs],Time) ->
  VX0 = VX + (AX * Time),
  VY0 = VY + (AY * Time),
  [{M,X+(VX*Time),Y+(VY*Time),VX0,VY0} | compute_next_state(Stars,Accs,Time)].

advance_time(Time,Stars,L) ->
  Tree = create_tree(Stars),
  {Acc,NL} = compute_star_accelerations(Tree, Stars,L),
  {compute_next_state(Stars, Acc, Time),NL}.

loop(0,_Time,Stars,_L) -> 
  Stars;
loop(N,Time,Stars,L) -> 
  {NS,NL} = advance_time(Time,Stars,L),
  loop(N-1,Time,NS,NL).
