
:- lib( options ).
:- lib( true/2 ).

io_sections_defaults( [separator(`//`),process(=),process_options(false),
                       terminating_separator(true),
				   separator_id(false)] ).

/** io_sections( +File, -Sections, +Opts ).

	Read a file to a list of Sections. 
	In vanilla operation, each section is a list of the codes read-in. 
	Each section is delimited by a marker line. 

Opts 
  * process(Pgoal= (=))
    Goal to process the Sections before storing.

  * process_opts(Popts=false)
    else pass Sopts to processor Pgoal (as last arg)

  * separator_call(SepCall)
    if given it is used to separate sections

  * separator_id(Sid=false)
    if true SepCall is called with an extra argument which is 
    used to create SectionId-Section pairlists of sections 

  * separator(Sep=`\\`)
    section separating line, used if SepCall is not present 
    (back compatibility, this is now define as sep_call(==(Line))
    
  * terminating_separator(Tmn=true)
    section separating line

==
 ?- cd( '/usr/local/users/nicos/work/2015/15.10.05-lmtk3_substrates' ).
 ?- io_sections( 'uniprot_sprot.dat', Sects, process(length) ).

==
@author nicos angelopoulos
@version  0.1 2015/10/05
@version  0.2 2016/02/04
@tbd generalise delimiting to a call

*/
io_sections( File, Sections, Args ) :-
	options_append( io_sections, Args, Opts ),
	( memberchk(separator_call(Call),Opts) ->
		true
		;
		options( separator(Sep), Opts ),
		Call = ==(Sep)
	),
	options( separator_id(Sid), Opts ),
	options( [separator(Sep),process(Proc)], Opts ),
	options( [process_options(Popts)], Opts ),
	open( File, read, Stream ),
	read_line_to_codes( Stream, Line ),
	options( terminating_separator(Tmn), Opts ),
	io_sections_stream( Line, Stream, Call, Popts, Proc, Tmn, Sid, null, [], Sections ),
	close( Stream ).

io_sections_stream( end_of_file, _Stream, _Call, Popts, Proc, Tmn, Sid, Id, Acc, Sections ) :-
	!,
	io_sections_end_acc( Tmn, Sid, Id, Popts, Proc, Acc, Sections ).
io_sections_stream( Line, Stream, Call, Popts, Proc, Tmn, Sid, Id, Acc, Sections ) :-
	% Line == Sep,
	io_section_sep_call( Sid, Call, Line, NxtId ),
	!,
	io_accumulator_section( Acc, Sid, Id, Popts, Proc, Sections, Tections ),
	read_line_to_codes( Stream, New ),
	io_sections_stream( New, Stream, Call, Popts, Proc, Tmn, Sid, NxtId, [], Tections ).
io_sections_stream( Line, Stream, Call, Popts, Proc, Tmn, Sid, Id, Acc, Sections ) :-
	read_line_to_codes( Stream, New ),
	io_sections_stream( New, Stream, Call, Popts, Proc, Tmn, Sid, Id, [Line|Acc], Sections ).

io_section_sep_call( true, Call, Line, Id ) :-
	call( Call, Line, Id ).
io_section_sep_call( false, Call, Line, _Id ) :-
	call( Call, Line ).

% the first clause is to catch empty ones, particularly the first one when we have Sid=true
% maybe a lookahead would be better
io_accumulator_section( [], _Sid, _Id, _Popts, _Proc, Sections, Sections ) :- !.
io_accumulator_section( Acc, Sid, Id, Popts, Proc, Sections, Tections ) :-
	reverse( Acc, Provisional ),
	optioned_call( Popts, Proc, Provisional, Section ),
	io_section_id( Sid, Id, Section, Iection ),
	Sections = [Iection|Tections].

io_section_id( true, Id, Section, Id-Section ).
io_section_id( false, _Id, Section, Section ).

optioned_call( false, Proc, Provisional, Section ) :-
	!,
	call( Proc, Provisional, Section ).
optioned_call( Popts, Proc, Provisional, Section ) :-
	call( Proc, Provisional, Section, Popts ).

io_sections_end_acc( [] ) :- !.
io_sections_end_acc( Acc ) :- 
	% fixme: use pack(pack_errors)
	throw( non_empty_end_accumulator(Acc) ).

io_sections_end_acc( true, _Sid, _Id, _Popts, _Proc, Acc, [] ) :-
	io_sections_end_acc( Acc ).
io_sections_end_acc( false, Sid, Id, Popts, Proc, Acc, Sections ) :-
	io_accumulator_section( Acc, Sid, Id, Popts, Proc, Sections, [] ).
