#!/usr/bin/perl -w

use strict;
use Data::Dumper;
use Opt;
use Lilypond;

my @INCLUDEPATH = (
    ".", "include",
    "..", "../include",
    "../..", "../../include",
);
my $current_dir = qx/basename `pwd`/;
chomp $current_dir;

# default options values
my %opt = (
    count  => 1,
    eps    => 0,
    header => "",
    paper  => "",
    size   => 0,
    midi   => 0,
    dir    => "%d%p%s",
    file   => "%d%n.ly",
    sep    => "_",
);

=encoding utf8
=pod

=head1 NAME

mk_ly.pl - a program to make ly files from given templates

=head1 SYNOPSIS

mk_ly.pl
 [count=number_of_pieces]
 [eps=y or 0]
 [header=file_name]
 [paper=paper_size]
 [size=staff_size]
 [score=score_template]
 [midi=y or 0]
 [dir=output_dir_template]
 [file=output_file_name_template]

=head1 DESCRIPTION

=head1 OPTIONS

=head2 count

 The number (integer) of pieces this work consists of.
 Default value = 1.

=head2 eps

 If given and = "y", include the following line:
 \include "lilypond-book-preamble.ly"
 Default value = 0.

=head2 header

 If given, the line "\include "<header>"
 will be prsent in the resulting files.
 Default value = "".

=head2 paper

 If given, the line "\inlucde "sz_<paper>.ily"
 will be prsent in the resulting files.
 "sz_<paper>.ily" should contain a \paper block describing the
 paper size, margins etc.
 Default value = "".

=head2 size

 If given and > 0, the line "#(set-global-staff-size <size>)"
 will be prsent in the resulting files.
 Default value = 0.

=head2 score

 A shorthand for the \score block to be written to the files.
 See below.

=head2 midi

 If given and = "y", a \score << ... \midi { } >> block will be included
 in the resulting files.
 Default value = "".

=head2 dir

 If given, create the files according to this directory name template.
 Default value "%d%p%s".

=head2 file

 If given, create files according to this file name template.
 Default value "%d%n.ly".

=head2 sep

 In the template processing, add <sep> before the substituted value.
 Default = "_".

=head1 Score shorthand

=head1 Substitutions

 dir and file may contain template codes, which will be replaced
 with the following values.

=over

=item %d the current directory name (basename `pwd`)

=item %p the paper option value

=item %s the size option value

=item %n the piece number (1 up to the count option value)

=back

=cut

my $pre =
'\include "score_glb.ily"

%%%%%%%%%%

\include "%d.instr_ily"
clefTen = { \clef "treble_8" }
\include "%d%n.ily"
\include "%d.staff_ily"

';

sub template($;$) {
    my $str = shift;
    my $d = $current_dir;
    my $p = $opt{paper};
    my $s = $opt{size};
    my $n = shift;

    my $S = $opt{sep};

    $str =~ s/%d/$S$d/g if ($d);
    $str =~ s/%p/$S$p/g if ($p);
    $str =~ s/%s/$S$s/g if ($s);
    $str =~ s/%n/$S$n/g if ($n);
    if ($S) {
	$str =~ s/^$S//;
    }

    $str;
}

sub template_find($;$) {
    my $str = shift;
    my $n = shift;

    my @line = split(/\n/, $str, -1);
    for (@line) {
	if (m/\\include \"(.*)\"$/) {
	    my $file = $1;
	    $file = template($file, $n);
	    my $tst = find_file($file);
	    if ($tst eq "") {
		die("included file ($file) in pre not found");
	    }
	    $_ = "\\include \"$tst\"";
	}
    }
    $str = join("\n", @line) . "\n";

    $str;
}

sub try_file($) {
    my $file = shift;

    if (!-e $file) {
	return "file does not exist";
    }
    if (!-r $file) {
	return "file is not readable";
    }
    "";
}

sub find_file($) {
    my $name = shift;

    # where to search for file with name $name

    for my $dir (@INCLUDEPATH) {
	my $path;
	if ($dir eq ".") {
	    $path = $name;
	} else {
	    $path = "$dir/$name";
	}
	#print "testing $path\n";
	my $str = try_file($path);
	if ($str eq "") {
	    #print "$path: found\n";
	    return $path;
	}
	#print "$path: $str\n";
    }
    #print "$name: not found\n";
    return "";
}

sub find_ily($$) {
    my $str = shift;
    my $n = shift;

    my @line = split(/\n/, $str);
    my @fld = grep /%n/, @line;
    my @file = ();
    for (@fld) {
	if (m/^\\include \"(.*)\"$/) {
	    push @file, find_file(template($1, $n));
	}
    }
    $str = join(" ", @file);

    $str;
}
sub find_variables($) {
    my $file = shift;
    my $fh;

    my $lyr = {};
    my $mus = {};

    if ( !open($fh, "<", $file) ) {
	printf(STDERR "cannot open $file for reading\n");
	return ();
    }
    while(<$fh>) {
	if (m/^lyr([a-zA-Z]+)\s+=(.*)/) {
	    my $name = $1;
	    my $rest = $2;
	    $$lyr{$name} = 1 unless ($rest =~ m/tacet/);

	} elsif (m/^mus([a-zA-Z]+)\s+=(.*)$/) {
	    my $name = $1;
	    my $rest = $2;
	    $$mus{$name} = 1 unless ($rest =~ m/tacet/);
	}
    }
    close($fh);

    ($lyr, $mus);
}

sub proc_opt() {
    my $dir = template($opt{dir});


    if ($dir) {
	if (! -e $dir) {
	    mkdir($dir) || die("cannot make directory \"$dir\"\n");
	}
	chdir($dir);
	if (! -e "Makefile") {
	    my $str = find_file("Makefile");
	    if (!$str) {
		print "Makefile: file not found\n";
		return;
	    }
	    `ln -s $str .`;
	}
    }

    {
	my $count = $opt{count};
	if ($count !~ m/^\d+$/) {
	    printf( STDERR "illegal count <%s>\n", $count );
	    return;
	}
    }

    my $eps = "";
    if ($opt{eps} eq "y") {
	$eps = "\n\\include \"lilypond-book-preamble.ly\"\n";
    }

    my $header = "";
    if ($opt{header}) {
	my $str = find_file($opt{header});
	if (!$str) {
	    print "$header: file not found\n";
	    return;
	}
	$header = "\\include \"$str\"\n";
    }

    my $paper = "";
    if ($opt{paper}) {
	$paper = $opt{paper};
	my $name = "sz_$paper.ily";
	my $str = find_file($name);
	if (!$str) {
	    print "$paper: paper size not found\n";
	    return;
	}
	$paper = "\\include \"$str\"\n";
    }

    my $size = "";
    if ($opt{size}) {
	my $sz = $opt{size};
	if ($sz =~ m/^(\d+(\.\d*)?)|(\.\d+)$/) {
	    $size = sprintf "#(set-global-staff-size %s)\n", $sz;
	} else {
	    $size = "";
	    printf( STDERR "illegal size <%s>\n", $sz );
	    return;
	}
    }

    my $data;
    {
	my $score_code = $opt{score};
	$data = Lilypond::parse_score_code($score_code);
	if ($data eq "") {
	    return;
	}
	#print Dumper($data);
    }

    my $numlen = length($opt{count});
    if ($numlen < 2) { $numlen = 2; }
    for (my $ix = 1; $ix <= $opt{count}; $ix++) {
	my $num = sprintf("%0${numlen}d", $ix);
	my $file = template($opt{file}, $num);

	my $ily = find_ily($pre, $num);
	my ($lyr, $mus) = find_variables($ily);

	my $score = "\\version \"2.25.1\"\n";
	$score .= $eps;
	$score .= $header;
	$score .= "\n";
	$score .= $paper;
	$score .= $size;
	$score .= "\n";
	$score .= template_find($pre, $num);
	{
	    my $str = Lilypond::mk_score($data, $num, $lyr, $mus);
	    if ($str eq "") {
		return;
	    }
	    $score .= $str;
	    if ($opt{midi} eq "y") {
		my $midi = Lilypond::mk_midi($data);
		if ($midi eq "") {
		    return;
		}
		$score .= "\n" . $midi;
	    }
	}

	my $fh;
	if (open($fh, ">", $file)) {
	    print $fh $score;
	    close($fh);
	} else {
	    if ($dir) {
		die("cannot open $dir/$file\n");
	    } else {
		die("cannot open $file\n");
	    }
	}
    }
    if ($dir) {
	chdir($current_dir);
    }
}

sub main() {
    my @argv = Opt::opt(\%opt);
    if (@argv) {
	print STDERR "unknown arguments: <", join("> <", @argv), ">\n";
	return;
    }

    proc_opt();
}

main();

__END__
