=encoding utf8

=head1 NAME

text/lorem - Generate lorem ipsum style placeholder text.

=head1 SYNOPSIS

  from text/lorem import lorem, lorem_markdown;

  say( lorem( { paragraphs: 3 } ) );
  say( lorem( {
      paragraphs: 5,
      format: "html",
      headings: true,
      lists: true,
  } ) );
  say( lorem_markdown( 4, true, true ) );

=head1 DESCRIPTION

This pure-Zuzu module generates placeholder prose in plain text, HTML,
or Markdown. The paragraph count controls the number of content blocks.
When list generation is enabled, a bullet list containing four to eight
items counts as one paragraph. Random headings may be inserted before
content blocks and do not count towards the paragraph total.

=head1 EXPORTED FUNCTIONS

=over

=item * C<< lorem(Dict options?) >>

Generate text using C<options>. Supported keys are C<paragraphs>,
C<format>, C<headings>, and C<lists>. The C<format> value may be
C<text>, C<html>, or C<markdown>.

=item * C<< lorem_text(Number paragraphs := 3, Boolean headings := false,
Boolean lists := false) >>

=item * C<< lorem_html(Number paragraphs := 3, Boolean headings := false,
Boolean lists := false) >>

=item * C<< lorem_markdown(Number paragraphs := 3, Boolean headings := false,
Boolean lists := false) >>

=back

=head1 COPYRIGHT AND LICENCE

B<< text/lorem >> is copyright Toby Inkster.

It is free software; you may redistribute it and/or modify it under
the terms of either the Artistic License 1.0 or the GNU General Public
License version 2.

=cut

from std/math import Math;
from std/string import join, substr;

const _WORDS := [
	"lorem", "ipsum", "dolor", "sit", "amet", "consectetur",
	"adipiscing", "elit", "sed", "do", "eiusmod", "tempor",
	"incididunt", "ut", "labore", "et", "dolore", "magna",
	"aliqua", "enim", "ad", "minim", "veniam", "quis",
	"nostrud", "exercitation", "ullamco", "laboris", "nisi",
	"aliquip", "ex", "ea", "commodo", "consequat", "duis",
	"aute", "irure", "in", "reprehenderit", "voluptate",
	"velit", "esse", "cillum", "eu", "fugiat", "nulla",
	"pariatur", "excepteur", "sint", "occaecat", "cupidatat",
	"non", "proident", "sunt", "culpa", "officia", "deserunt",
	"mollit", "anim", "id", "est", "laborum",
];

const _TOPICS := [
	"integer", "facilisis", "viverra", "finibus", "aliquam",
	"vehicula", "mauris", "rhoncus", "porta", "lectus",
	"phasellus", "lacinia", "praesent", "tincidunt", "ornare",
	"gravida", "sollicitudin", "fringilla", "dictum", "maximus",
];

function _rand_int ( Number max ) {
	return floor( Math.rand(max) );
}

function _rand_between ( Number min, Number max ) {
	return min + _rand_int( max - min + 1 );
}

function _chance ( Number probability ) {
	return Math.rand(1) < probability;
}

function _word () {
	return _WORDS[ _rand_int( _WORDS.length() ) ];
}

function _topic_word () {
	return _TOPICS[ _rand_int( _TOPICS.length() ) ];
}

function _capitalize ( String text ) {
	return uc( substr( text, 0, 1 ) ) _ substr( text, 1 );
}

function _sentence ( Boolean seed ) {
	let words := [];
	if ( seed ) {
		words := [ "lorem", "ipsum", "dolor", "sit", "amet" ];
	}

	let want := _rand_between( 8, 18 );
	while ( words.length() < want ) {
		words.push( _word() );
	}

	words[0] := _capitalize( words[0] );
	return join( " ", words ) _ ".";
}

function _paragraph ( Boolean seed ) {
	let sentences := [];
	let want := _rand_between( 3, 6 );
	let i := 0;

	while ( i < want ) {
		sentences.push( _sentence( seed and i == 0 ) );
		i++;
	}

	return join( " ", sentences );
}

function _heading () {
	let words := [];
	let want := _rand_between( 2, 4 );
	let i := 0;

	while ( i < want ) {
		words.push( _capitalize( _topic_word() ) );
		i++;
	}

	return join( " ", words );
}

function _list_items () {
	let items := [];
	let want := _rand_between( 4, 8 );
	let i := 0;

	while ( i < want ) {
		items.push( _sentence(false) );
		i++;
	}

	return items;
}

function _text_list ( Array items ) {
	let lines := [];
	for ( let item in items ) {
		lines.push( "- " _ item );
	}
	return join( "\n", lines );
}

function _html_list ( Array items ) {
	let lines := [ "<ul>" ];
	for ( let item in items ) {
		lines.push( "<li>" _ item _ "</li>" );
	}
	lines.push( "</ul>" );
	return join( "\n", lines );
}

function _render_heading ( String text, String format ) {
	if ( format eq "html" ) {
		return "<h2>" _ text _ "</h2>";
	}
	if ( format eq "markdown" ) {
		return "## " _ text;
	}
	return text;
}

function _render_paragraph ( String text, String format ) {
	if ( format eq "html" ) {
		return "<p>" _ text _ "</p>";
	}
	return text;
}

function _render_list ( Array items, String format ) {
	if ( format eq "html" ) {
		return _html_list(items);
	}
	return _text_list(items);
}

function _option ( options, String key, fallback ) {
	if ( typeof options == "Dict" and options.exists(key) ) {
		return options.get(key);
	}
	return fallback;
}

function _normalize_count ( count ) {
	if ( typeof count ne "Number" ) {
		die "text/lorem: paragraphs expects Number";
	}

	let paragraphs := int(count);
	if ( paragraphs != count or paragraphs < 0 ) {
		die "text/lorem: paragraphs must be a non-negative integer";
	}

	return paragraphs;
}

function _normalize_flag ( value, String name ) {
	if ( typeof value ne "Boolean" ) {
		die "text/lorem: " _ name _ " expects Boolean";
	}
	return value;
}

function _normalize_format ( raw_format ) {
	if ( typeof raw_format ne "String" ) {
		die "text/lorem: format expects String";
	}

	let format := lc(raw_format);
	if ( format ne "text" and format ne "html" and format ne "markdown" ) {
		die "text/lorem: format must be text, html, or markdown";
	}

	return format;
}

function _generate ( Number paragraphs, String format, Boolean headings,
Boolean lists ) {
	let blocks := [];
	let first_list := lists and paragraphs > 0 ? _rand_int(paragraphs) : -1;
	let first_heading := headings and paragraphs > 0
		? _rand_int(paragraphs)
		: -1;
	let i := 0;

	while ( i < paragraphs ) {
		let make_heading := headings
			and ( i == first_heading or _chance(0.25) );
		let make_list := lists and ( i == first_list or _chance(0.25) );

		if ( make_heading ) {
			blocks.push( _render_heading( _heading(), format ) );
		}

		if ( make_list ) {
			blocks.push( _render_list( _list_items(), format ) );
		}
		else {
			blocks.push( _render_paragraph( _paragraph( i == 0 ), format ) );
		}

		i++;
	}

	if ( format eq "html" ) {
		return join( "\n", blocks );
	}
	return join( "\n\n", blocks );
}

function lorem ( options? ) {
	let paragraphs := _normalize_count( _option( options, "paragraphs", 3 ) );
	let format := _normalize_format( _option( options, "format", "text" ) );
	let headings := _normalize_flag( _option( options, "headings", false ),
		"headings" );
	let lists := _normalize_flag( _option( options, "lists", false ), "lists" );

	return _generate( paragraphs, format, headings, lists );
}

function lorem_text ( Number paragraphs := 3, Boolean headings := false,
Boolean lists := false ) {
	return _generate(
		_normalize_count(paragraphs),
		"text",
		_normalize_flag( headings, "headings" ),
		_normalize_flag( lists, "lists" ),
	);
}

function lorem_html ( Number paragraphs := 3, Boolean headings := false,
Boolean lists := false ) {
	return _generate(
		_normalize_count(paragraphs),
		"html",
		_normalize_flag( headings, "headings" ),
		_normalize_flag( lists, "lists" ),
	);
}

function lorem_markdown ( Number paragraphs := 3, Boolean headings := false,
Boolean lists := false ) {
	return _generate(
		_normalize_count(paragraphs),
		"markdown",
		_normalize_flag( headings, "headings" ),
		_normalize_flag( lists, "lists" ),
	);
}
