#!/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
-m      Modularize emscripten build

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, $modularizeQ);
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,
	'm|modularize' => \$modularizeQ,
	'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 (STACK_SIZE) 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 .= " -std=c++$cpp";

my $LFLAGS =  " -s ASM_JS=1";
$LFLAGS .= " -s INITIAL_MEMORY=256MB";
$LFLAGS .= " -s STACK_SIZE=128MB";
$LFLAGS .= " -s WASM=0";
$LFLAGS .= " --memory-init-file 0";

# Check that -w and -l and not enabled together
die "ERROR: option -w cannot be enabled with -l.\n" if ($wasmQ && $lightQ);

# Always wasm build with the humdrum toolkit
if (!$nohumdrum) {
	$wasmQ = 1
}

# Process command-line options:
if ($wasmQ) {
	print "Creating WASM toolkit version\n";

	# Debugging flags, see:
	#    https://emscripten.org/docs/porting/Debugging.html

	# We can try to find the cause of integer overflow runtime errors with
	# $FLAGS = " -g4 -O0 --emit-symbol-map";

	# SAFEHEAP=1 :: Adds additional memory access checks,
	# and will give clear errors for problems like
	# dereferencing 0 and memory alignment issues.
	#$LFLAGS .= " -s SAFEHEAP=1";

	# 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 STACK_SIZE=256MB";
	$LFLAGS .= " -s SINGLE_FILE=1";
	$LFLAGS .= " -s INCOMING_MODULE_JS_API=onRuntimeInitialized";
	$FLAGS_NAME = "-wasm";
}

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

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

if (!$nohumdrum) {
	print "Creating toolkit version with humdrum\n";
	$FLAGS_NAME = "-hum";
}

if($modularizeQ) {
    print "Creating modularized emscripten build\n";
    $FLAGS_NAME .= "-modularize";
}

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

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

my $DATA_DIR  = "data";
my $BUILD_DIR = "build";
my $OUTPUT_DIR = $BUILD_DIR;
$OUTPUT_DIR .= "/$VERSION" if $VERSION;

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

`mkdir -p $BUILD_DIR`;
`mkdir -p $OUTPUT_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 .= "'_enableLog',";
$exports .= "'_enableLogToBuffer',";
$exports .= "'_vrvToolkit_constructor',";
$exports .= "'_vrvToolkit_destructor',";
$exports .= "'_vrvToolkit_edit',";
$exports .= "'_vrvToolkit_editInfo',";
$exports .= "'_vrvToolkit_getAvailableOptions',";
$exports .= "'_vrvToolkit_getDefaultOptions',";
$exports .= "'_vrvToolkit_getDescriptiveFeatures',";
$exports .= "'_vrvToolkit_getElementAttr',";
$exports .= "'_vrvToolkit_getElementsAtTime',";
$exports .= "'_vrvToolkit_getExpansionIdsForElement',";
$exports .= "'_vrvToolkit_getHumdrum',";
$exports .= "'_vrvToolkit_convertHumdrumToHumdrum',";
$exports .= "'_vrvToolkit_convertHumdrumToMIDI',";
$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_renderToExpansionMap',";
$exports .= "'_vrvToolkit_renderToMIDI',";
$exports .= "'_vrvToolkit_renderToPAE',";
$exports .= "'_vrvToolkit_renderToSVG',";
$exports .= "'_vrvToolkit_renderToTimemap',";
$exports .= "'_vrvToolkit_resetOptions',";
$exports .= "'_vrvToolkit_resetXmlIdSeed',";
$exports .= "'_vrvToolkit_select',";
$exports .= "'_vrvToolkit_setOptions',";
$exports .= "'_vrvToolkit_validatePAE',";
$exports .= "'_malloc',";
$exports .= "'_free'";
$exports .= "]\"";

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

my $modularize = $modularizeQ ? "-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME=\"'createVerovioModule'\"" : "";

if ($makeQ) {
	system("rm -rf CMakeFiles/");
	system("rm CMakeCache.txt");
}

print "*************\nBuilding makefile...\n";
my $cmakeCmd = "$EMCMAKE cmake ../cmake $cmake -DCMAKE_CXX_FLAGS=\"$FLAGS\"";
print "$cmakeCmd\n" if $CHATTY;
system($cmakeCmd) == 0 or die "system $cmakeCmd failed: $?";

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

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

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

    # skip prebundled wasm module with verovio toolkit for modularized builds
    if(!$modularizeQ) {
        `npm --prefix npm install`;
        `npm --prefix npm run prebundle -- -o "\$(pwd)/$OUTPUT_DIR/$FILENAME"`;

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

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

        # create also a zip file if version name is given
        if ($VERSION) {
            `(cd $OUTPUT_DIR && zip $FILENAME.zip $FILENAME)`;
            print "$OUTPUT_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*`;
		}
	}
}
