#!/usr/bin/perl
#
# Parse ly note notation and apply a transposition to them. This is a
# very primitive lexer, so will cause unexpected changes in LilyPond
# input. Restrict the input to note sequences:
#
# % echo 'b4 e dis e fis cis fis2 e4 dis cis dis e2 b' | ./transpose t=1
# c4 f e f g d g2 f4 e d e f2 c
#
# Notes must be supplied on standard input, as the argument list is
# reserved for an arbitrary list of transposition and inversion
# operations:
#
# % echo b | ./transpose t=2 i
# b

use strict;
use warnings;

use Getopt::Long qw(GetOptions);
use Music::LilyPond::Scale::Chromatic ();

my @operations;
my $is_quiet = 0;

########################################################################
#
# MAIN

GetOptions( 'quiet|q' => \$is_quiet );

for my $operation (@ARGV) {
  my ( $key, $value ) = split '=', $operation, 2;
  next unless defined $key;
  push @operations,
    { op => $key, ( defined $value ? ( value => $value ) : () ) };
}

my $note_count       = 0;
my $accidental_count = 0;

while ( my $data = <STDIN> ) {
LOOP: {
    # in-line comments - TODO support those horrid multiline comments?
    # Easier: just say no.
    if ( $data =~ m/\G(\s*%\s.*$)/cgm ) {
      print $1;
      redo LOOP;
    }
    # Double-quoted strings (^"Adagio" and the like)
    if ( $data =~ m/\G("( (?: \\"|[^"] )*? )"\s*)/cgx ) {
      print $1;
      redo LOOP;
    }
    # Notable keywords that take note values we should not trample
    if ( $data =~ m/\G(\\(?:key|relative)\s+\w)/cg ) {
      print $1;
      redo LOOP;
    }
    # Other keywords
    if ( $data =~ m/\G(\\[a-zA-Z]+\s*)/cg ) {
      print $1;
      redo LOOP;
    }
    if ( $data =~ m/\G([cdefgab](?:[ie]s)*)/cg ) {
      my $note = Music::LilyPond::Scale::Chromatic->new($1);

      for my $op_ref (@operations) {
        if ( $op_ref->{op} eq 't' or $op_ref->{op} =~ m/^trans/ ) {
          $note->transpose( $op_ref->{value} );
        } elsif ( $op_ref->{op} eq 'i' or $op_ref->{op} =~ m/^inv/ ) {
          $note->invert( exists $op_ref->{value} ? $op_ref->{value} : () );
        } else {
          die "unknown operator: " . $op_ref->{key} . "\n";
        }
      }

      print $note->as_string;
      ++$note_count;
      ++$accidental_count unless $note->get_accidental == 0;
      redo LOOP;
    }
    # Skip over non-note and non-special-to-LilyPond values
    if ( $data =~ m/\G([^cdefgab%\\"]+|\s+)/cg ) {
      print $1;
      redo LOOP;
    }
  }
}

warn
  "info: stats: note_count=$note_count, accidental_count=$accidental_count\n"
  unless $is_quiet;
