#!/usr/bin/perl
#
# Description: Compile script for the JavaScript toolkit version of verovio.
#
# Changes from bash script version:
#   Allow for excluding specific fonts.
#      Example:      ./buildToolkit -x "Gootville,Bravura"
#      Long Example: ./buildToolkit --exclude "Gootville,Bravura"
#   Allow exclusion of specific importers (only works with PAE and Humdrum for now).
#      Example:      ./buildToolkit -P
#      Long Example: ./buildToolkit --no-pae
#   Made VEROVIO_ROOT selectable from the command-line in case it or this directory needs to move.
#      Example:      ./buildToolkit -r ../..
#      Long Example: ./buildToolkit --root ../..
#   Automated generation of source code filelist.
# Note:
#   VERSION_NAME not significantly used so removed.
#   For smallest toolkit footprint use these options:
#     ./buildToolkit -x "Gootville,Bravura" -DHPX
#   (no Gootville or Bravura fonts; no importers)
#

use strict;
use Getopt::Long;

sub print_help {
print <<"EOT";

Options:
-c      Chatty mode: display compiler progress
-l      Light version with no increased memory allocation
-r DIR  Verovio root directory
-v N    Version number (e.g., 1.0.0); no number by default
-w      Webassembly version
-x      Font exclusion list (case-sensitive)
-D      Disable DARMS importer
-H      Disable Humdrum importer
-P      Disable PAE importer
-X      Disable MusicXML importer
-M      Clean existing makefile

EOT
}

chomp (my $EMCC = `which em++`);
chomp (my $EMCMAKE = `which emcmake`);
chomp (my $EMMAKE = `which emmake`);
die "ERROR: emscripten compiler (emcc) not found.\n" unless $EMCC;
die "ERROR: emscripten cmake (emcmake) not found.\n" unless $EMCMAKE;
die "ERROR: emscripten make (emmake) not found.\n" unless $EMMAKE;

# Parse command-line options
my ($lightQ, $version, $chattyQ, $helpQ, $exclusion, $wasmQ, $makeQ);
my ($nopae, $nohumdrum, $nomusicxml, $nodarms);
my ($FLAGS_NAME, $VERSION, $CHATTY);

my $cpp = 17;             # c++ version to compile (11, 14, 17)
my $VEROVIO_ROOT  = ".."; 

Getopt::Long::Configure("bundling");
GetOptions (
   'C|cpp=s'       => \$cpp,
   'c|chatty'      => \$chattyQ,
   'h|?|help'      => \$helpQ,
   'l|light'       => \$lightQ,
   'r|root=s'      => \$VEROVIO_ROOT,
   'v|version=s'   => \$VERSION,
   'x|exclusion=s' => \$exclusion,
   'w|wasm'        => \$wasmQ,
   'D|no-darms'    => \$nodarms,
   'H|no-humdrum'  => \$nohumdrum,
   'P|no-pae'      => \$nopae,
   'X|no-musicxml' => \$nomusicxml,
   'M|make' => \$makeQ
);

# Default compiling uses ASM and large file support.
# Memory is increased (TOTAL_STACK) for processing large files (tested up to 7MB)
# Empirically, the memory amount required is roughly 5 times the file size.
# This can be disabled in the light version, which uses the default memory settings.
my $FLAGS = "-O3";
$FLAGS .= " -DNDEBUG";
$FLAGS .= " --memory-init-file 0";
$FLAGS .= " -std=c++$cpp";

my $LFLAGS =  " -s ASM_JS=1";
$LFLAGS .= " -s INITIAL_MEMORY=256MB";
$LFLAGS .= " -s TOTAL_STACK=128MB";
$LFLAGS .= " -s WASM=0";

# Process command-line options:

if ($wasmQ) {
	print "Creating WASM toolkit version\n";
	# We can try to find the cause of integer overflow runtime errors with
	# $FLAGS = " -g4 -O0 --emit-symbol-map";
    # For now, trapping them (followign option does not work with Safari)
	#$FLAGS .= " -mnontrapping-fptoint";
	$FLAGS .= " -s STRICT=1"; 
	# Linker flags
	$LFLAGS = " -s WASM=1";
	$LFLAGS .= " -s INITIAL_MEMORY=512MB";
	$LFLAGS .= " -s TOTAL_STACK=256MB";
    $LFLAGS .= " -s SINGLE_FILE=1";
	$FLAGS_NAME = "-wasm";
}

if ($lightQ) {
	print "Creating low-memory (light) toolkit version\n";
	$LFLAGS = " -s ASM_JS=1 -s WASM=0";
	$FLAGS_NAME = "-light";
}

if ($chattyQ) {
	$CHATTY = "-v";
}

if (!$nohumdrum) {
    $FLAGS_NAME .= "-hum";
}

if ($helpQ) {
	print_help();
	exit 2;
}

my $FILENAME = "verovio-toolkit$FLAGS_NAME.js";

my $DATA_DIR  = "data";
my $BUILD_DIR = "build";
$BUILD_DIR .= "/$VERSION" if $VERSION;
my $NPM = "npm";
$NPM .= "-dev" if not($VERSION);

print "Compiled files will be written to $BUILD_DIR\n";

`mkdir -p $BUILD_DIR`;
`mkdir -p $DATA_DIR`;

syncSvgResources($exclusion);

# Generate the git commit file
print "Creating commit version header file...\n";
`$VEROVIO_ROOT/tools/get_git_commit.sh`;

my $defines = "-DUSE_EMSCRIPTEN";

my $cmake = "-DBUILD_AS_WASM=ON";
$cmake .= " -DNO_PAE_SUPPORT=ON" if ($nopae);
$cmake .= " -DNO_DARMS_SUPPORT=ON" if ($nodarms);
$cmake .= " -DNO_HUMDRUM_SUPPORT=ON" if ($nohumdrum);
$cmake .= " -DNO_MUSICXML_SUPPORT=ON" if ($nomusicxml);

my $embed   = "--embed-file $DATA_DIR/";
my $output  = "-o $BUILD_DIR/verovio.js";

my $exports = "-s EXPORTED_FUNCTIONS=\"[";
$exports .= "'_vrvToolkit_constructor',";
$exports .= "'_vrvToolkit_destructor',";
$exports .= "'_vrvToolkit_edit',";
$exports .= "'_vrvToolkit_editInfo',";
$exports .= "'_vrvToolkit_getAvailableOptions',";
$exports .= "'_vrvToolkit_getElementAttr',";
$exports .= "'_vrvToolkit_getElementsAtTime',";
$exports .= "'_vrvToolkit_getExpansionIdsForElement',";
$exports .= "'_vrvToolkit_getHumdrum',";
$exports .= "'_vrvToolkit_convertHumdrumToHumdrum',";
$exports .= "'_vrvToolkit_convertMEIToHumdrum',";
$exports .= "'_vrvToolkit_getLog',";
$exports .= "'_vrvToolkit_getMEI',";
$exports .= "'_vrvToolkit_getMIDIValuesForElement',";
$exports .= "'_vrvToolkit_getNotatedIdForElement',";
$exports .= "'_vrvToolkit_getOptions',";
$exports .= "'_vrvToolkit_getPageCount',";
$exports .= "'_vrvToolkit_getPageWithElement',";
$exports .= "'_vrvToolkit_getTimeForElement',";
$exports .= "'_vrvToolkit_getTimesForElement',";
$exports .= "'_vrvToolkit_getVersion',";
$exports .= "'_vrvToolkit_loadData',";
$exports .= "'_vrvToolkit_loadZipDataBase64',";
$exports .= "'_vrvToolkit_loadZipDataBuffer',";
$exports .= "'_vrvToolkit_redoLayout',";
$exports .= "'_vrvToolkit_redoPagePitchPosLayout',";
$exports .= "'_vrvToolkit_renderData',";
$exports .= "'_vrvToolkit_renderToMIDI',";
$exports .= "'_vrvToolkit_renderToPAE',";
$exports .= "'_vrvToolkit_renderToSVG',";
$exports .= "'_vrvToolkit_renderToTimemap',";
$exports .= "'_vrvToolkit_setOptions',";
$exports .= "'_malloc',";
$exports .= "'_free'";
$exports .= "]\"";

my $extra_exports = "-s EXPORTED_RUNTIME_METHODS='[\"cwrap\"]'";

if ($makeQ) {
    system("rm -rf CMakeFiles/");
    system("rm CMakeCache.txt");
}
 
 print "*************\nBuilding makefile...\n";
 my $cmakeCmd = "$EMCMAKE cmake ../cmake $cmake -DCMAKE_CXX_FLAGS=\"$FLAGS\"";
 system($cmakeCmd);

print "*************\nCompiling...\n";
my $makeCmd = "$EMMAKE make -j 8";
system($makeCmd);

print "*************\nLinking...\n";
my $ccCmd = "$EMCC $CHATTY libverovio.a $LFLAGS $FLAGS $embed $exports $extra_exports $output";
print "$ccCmd\n" if $CHATTY;

system($ccCmd);

if ($? == 0) {
	print " Done.\n";

	# the wrapper is necessary with closure 1 for avoiding to conflict with globals
	`cat $BUILD_DIR/verovio.js verovio-js-start.js verovio-proxy.js > $BUILD_DIR/$FILENAME`;

	# wrap the npm package (but not with wasm build and without humdrum for now)
	if ($wasmQ && !$lightQ && $nohumdrum) {
		print "Building $NPM package\n";
		# the cat order is different for the npm package because we want to wrap the Module in the init function
		`cat verovio-npm-start.js $BUILD_DIR/verovio.js verovio-proxy.js verovio-npm-end.js > $NPM/index.js`;
	}

	# create a gzip version
	`(cd $BUILD_DIR && gzip -c $FILENAME > $FILENAME.gz)`;
	print "$BUILD_DIR/$FILENAME.gz was created\n";

	# all good
	print "$BUILD_DIR/$FILENAME was created\n";

	# create also a zip file if version name is given
	if ($VERSION) {
		`(cd $BUILD_DIR && zip $FILENAME.zip $FILENAME)`;
		print "$BUILD_DIR/$FILENAME.zip was created\n";
	}
} else {
	print " Failed.\n";
}

exit 0;


###########################################################################


##############################
##
## syncSvgResources -- copy SVG resources for embedding in toolkit.
##

sub syncSvgResources {
	my ($exclusion) = @_;
	print "Syncing SVG resources...\n";

	# First clear old contents of data directory
	`rm -rf $DATA_DIR/*` if $DATA_DIR;

	# Then copy data directory contents
	`cp -r $VEROVIO_ROOT/data/* $DATA_DIR/`;

	# Remove .DS_Store from the data directory
	`find $DATA_DIR/ -name '.DS_Store' -type f -delete`;

	return unless $exclusion;
	my @list = split /[^A-Za-z0-9_]+/, $exclusion;
	foreach my $item (@list) {
		next unless $item;
		my @matches = glob "$DATA_DIR/$item*";
		if (@matches) {
			print "\tRemoving $item from embedded resources\n";
			`rm -rf $DATA_DIR/$item*`;
		}
	}
}
