
:- lib(en_list/2).
:- lib(min_max/3).

n_breaks_defaults( Defs ) :-
	Defs = [centre(false),fixed_width(false)].

/** n_breaks( +Vector, +N, -Breaks, -Opts ).

For a vector of values, create N break points.

The number of Breaks is always odd when Centre is true. This interprets odd N as the number of 
break points, even if N it is taken to be the number of intervals.

==
?- n_breaks( [1,3,4,4,5,5,6,8], 4, Bs, [] ).
Bs = [1.0, 2.75, 4.5, 6.25, 8.0].

?- n_breaks( [0.21,3,4,4,5,5,6,8], 4, Bs, [centre(1)] ).
Bs = [0.21, 0.4075, 0.605, 0.8025, 1.0, 2.75, 4.5, 6.25, 8.0].

?- n_breaks( [0.21,3,4,4,5,5,6,8], 4, Bs, [centre(1),fixed_width(true)] ).
Bs = [-6.0, -4.25, -2.5, -0.75, 1.0, 2.75, 4.5, 6.25, 8.0].
==

Opts 
    
  * centre(Centre=false)
    when an arithmetic value is given, the breaks are symmetrically split left and right of Centre

  * fixed_width(Sym=false)
    if true and Centre arithmetic, the shorter of the left or right is extended to keep
    the breaks of fixed width

@author  nicos angelopoulos
@version 0.1 2015/5/27

@tbd add some polymorphism for Vector

*/

n_breaks( Vals, N, Breaks, ArgS ) :-
    en_list( ArgS, Args ),
    n_breaks_defaults( Defs ),
    append( Defs, Args, Opts ),
	memberchk( centre(Centre), Opts ),
	NumN is N,
	n_breaks_centre( Centre, Vals, NumN, Breaks, Opts ).

n_breaks_centre( Centre, Vals, N, Breaks, Opts ) :-
	number(Centre),
	!,
	memberchk( fixed_width(Fixed), Opts ),
	n_breaks_fixed_centre( Fixed, Centre, Vals, N, Breaks ).

n_breaks_centre( _Centre, Vals, N, Breaks, _Opts ) :-
	min_max( Vals, Min, Max ),
	n_breaks_range( Min, Max, N, Breaks ).

n_breaks_fixed_centre( true, Centre, Vals, N, Breaks ) :-
	min_max( Vals, Min, Max ),
	RelMax is max( abs(Max-Centre), abs(Centre - abs(Min)) ),
	ConcMax is Centre + RelMax,
	ConcMin is Centre - RelMax,
	Half is floor( N ),
	n_breaks_range( ConcMin, Centre, Half, Left ),
	n_breaks_range( Centre, ConcMax, Half, [_|Right] ),
	append( Left, Right, Breaks ).
n_breaks_fixed_centre( false, Centre, Vals, N, Breaks ) :-
	min_max( Vals, Min, Max ),
	Half is floor( N ),
	n_breaks_range( Min, Centre, Half, Left ),
	n_breaks_range( Centre, Max, Half, [_|Right] ),
	append( Left, Right, Breaks ).

n_breaks_range( Min, Max, N, Breaks ) :-
	Ivl is abs(Max-Min) / N,
	findall( Break, (between(0,N,I),Break is Min + (Ivl * I)), Breaks ).
