#!/usr/bin/perl

use strict;
use warnings;
use Image::ExifTool;
use Getopt::Long;
use Pod::Usage;
use Panotools::Script;

my $path_makefile;
my $d_inc = 15;
my $projection;
my $deg_fov;
my $crop_s;
my $help = 0;
my $verbose = 0;

my @argv_save = @ARGV;

GetOptions ('o|output=s' => \$path_makefile,
            't|time=s' => \$d_inc,
            'f|projection=i' => \$projection,
            'v|fov=s' => \$deg_fov,
            'k|selection=s' => \$crop_s,
            'l|loquacious' => \$verbose,
            'h|help' => \$help);

pod2usage (-verbose => 2) if $help;
pod2usage (2) unless (defined $path_makefile and scalar @ARGV > 1);

my $groups = [];

my $group_tmp = [];
my $previous_time;
my $previous_inc = 0;
for my $path_photo (@ARGV)
{
    my $exif_info = Image::ExifTool::ImageInfo ($path_photo);
    my $datetime = $exif_info->{'FileModificationDateTime'};
    $datetime = $exif_info->{'DateTimeOriginal'} if (defined $exif_info->{'DateTimeOriginal'});
    my $time_unix = Image::ExifTool::GetUnixTime ($datetime);
    $previous_time = $time_unix unless (defined $previous_time);
    my $inc = $time_unix - $previous_time;

    if (($inc - $previous_inc) > $d_inc)
    {
        push (@{$groups}, $group_tmp);
        $group_tmp = [];
    }
    push @{$group_tmp}, $path_photo;

    $previous_time = $time_unix;
    $previous_inc = $inc;
}
push (@{$groups}, $group_tmp);


my $rule_ptos = new File::Pto::Mk::Rule;
$rule_ptos->Targets ('pto');
my $rule_mks = new File::Pto::Mk::Rule;
$rule_mks->Targets ('mk');
my $rule_imgs = new File::Pto::Mk::Rule;
$rule_imgs->Targets ('images');

my @rules_pto;
my @rules_mk;
my @rules_ldr_blended;
my @rules_ldr_stacked_blended;
my @rules_hdr_blended;

for my $group (@{$groups})
{
    next if scalar @{$group} == 1;

    my $a = $group->[0];
    my $b = $group->[-1];
    $a =~ s/\.[[:alnum:]]+$//;
    $b =~ s/\.[[:alnum:]]+$//;
    $b =~ s/.*[\/\\]//;
    my $stub = "$a-$b";

    $rule_ptos->Prerequisites ("$stub.pto");
    $rule_mks->Prerequisites ("$stub.pto.mk");

    my $path_img;
    if (is_stacks (@{$group}))
    {
        $path_img = $stub ."_fused.tif";
    }
    else
    {
        $path_img = $stub .".tif";
    }
    $rule_imgs->Prerequisites ($path_img);

    print STDERR "$path_img: ". scalar @{$group} ." images\n" if $verbose;

    my $rule_pto = new File::Pto::Mk::Rule;
    $rule_pto->Targets ("$stub.pto");
    $rule_pto->Prerequisites (@{$group});
    $rule_pto->Command ('match-n-shift', '--stacks', '--align',
                            '$(AP_EXTRA_ARGS)', '--output', "$stub.pto");
    $rule_pto->Command ('--projection', $projection) if defined $projection;
    $rule_pto->Command ('--fov', $deg_fov) if defined $deg_fov;
    $rule_pto->Command ('--selection', $crop_s) if defined $crop_s;
    $rule_pto->Command (@{$group});

    push @rules_pto, $rule_pto;

    my $rule_mk = new File::Pto::Mk::Rule;
    $rule_mk->Targets ("$stub.pto.mk");
    $rule_mk->Prerequisites ("$stub.pto");
    $rule_mk->Command ('pto2mk', '-o', "$stub.pto.mk", '-p', $stub, "$stub.pto");

    push @rules_mk, $rule_mk;

    my $rule_ldr_blended = new File::Pto::Mk::Rule;
    $rule_ldr_blended->Targets ($stub .".tif");
    $rule_ldr_blended->Prerequisites ("$stub.pto", "$stub.pto.mk", @{$group});
    $rule_ldr_blended->Command ('make', '-e', '-f', "$stub.pto.mk", $stub .".tif", '$(MAKE_EXTRA_ARGS)');

    push @rules_ldr_blended, $rule_ldr_blended;

    my $rule_ldr_stacked_blended = new File::Pto::Mk::Rule;
    $rule_ldr_stacked_blended->Targets ($stub ."_fused.tif");
    $rule_ldr_stacked_blended->Prerequisites ("$stub.pto", "$stub.pto.mk", @{$group});
    $rule_ldr_stacked_blended->Command ('make', '-e', '-f', "$stub.pto.mk", $stub ."_fused.tif", '$(MAKE_EXTRA_ARGS)');

    push @rules_ldr_stacked_blended, $rule_ldr_stacked_blended;

    my $rule_hdr_blended = new File::Pto::Mk::Rule;
    $rule_hdr_blended->Targets ($stub ."_hdr.exr");
    $rule_hdr_blended->Prerequisites ("$stub.pto", "$stub.pto.mk", @{$group});
    $rule_hdr_blended->Command ('make', '-e', '-f', "$stub.pto.mk", $stub ."_hdr.exr", '$(MAKE_EXTRA_ARGS)');

    push @rules_hdr_blended, $rule_hdr_blended;
}

my $rule_all = new File::Pto::Mk::Rule;
$rule_all->Targets ('all');
$rule_all->Prerequisites ('images');

my $rule_phony = new File::Pto::Mk::Rule;
$rule_phony->Targets ('.PHONY');
$rule_phony->Prerequisites (qw/all images pto mk/);

open MAKE, ">", $path_makefile or die "cannot write-open $path_makefile";

print MAKE "# Created by Panotools::Script $Panotools::Script::VERSION\n";
print MAKE "# $0 ". join (' ', @argv_save). "\n\n";
print MAKE $rule_phony->Assemble;
print MAKE $rule_all->Assemble;
print MAKE $rule_ptos->Assemble;
print MAKE $rule_mks->Assemble;
print MAKE $rule_imgs->Assemble;
print MAKE "MAKE_EXTRA_ARGS=clean\n";
print MAKE "AP_EXTRA_ARGS=--clean\n\n";
print MAKE map {$_->Assemble} @rules_pto;
print MAKE map {$_->Assemble} @rules_mk;
print MAKE map {$_->Assemble} @rules_ldr_blended;
print MAKE map {$_->Assemble} @rules_ldr_stacked_blended;
print MAKE map {$_->Assemble} @rules_hdr_blended;

close MAKE;

sub is_stacks
{
    my $speeds = {};
    for my $path_photo (@_)
    {
        my $exif_info = Image::ExifTool::ImageInfo ($path_photo, 'ExposureTime', 'ShutterSpeed');
        my $et = $exif_info->{ExposureTime} || $exif_info->{ShutterSpeed} || 0;
        $speeds->{$et} = 'TRUE';
    }

    my $brackets = scalar keys (%{$speeds});
    return 0 if (scalar (@_) % $brackets);
    return 0 if ($brackets < 2);
    return 1;
}

package File::Pto::Mk::Rule;
use strict;
use warnings;

sub new
{
    my $class = shift;
    $class = ref $class || $class;
    my $self = bless {targets => [], prerequisites => [], command => []}, $class;
    return $self;
}

sub Assemble
{
    my $self = shift;
    return 0 unless scalar @{$self->{targets}};

    my $text;
    $text .= join ' ', (map { _quoteshell ($_)} @{$self->{targets}});
    $text .= ' : ';
    $text .= join ' ', (map { _quoteshell ($_)} @{$self->{prerequisites}});
    $text .= "\n\t" if scalar @{$self->{command}};
    $text .= join ' ', (map { _quoteshell ($_)} @{$self->{command}});
    $text .= "\n\n";
    return $text;
}

sub Targets
{
    my $self = shift;
    push @{$self->{targets}}, @_;
}

sub Prerequisites
{
    my $self = shift;
    push @{$self->{prerequisites}}, @_;
}

sub Command
{
    my $self = shift;
    push @{$self->{command}}, @_;
}

sub _quoteshell
{
    my $string = shift;
    $string =~ s/(['"\[\] `&*+~#:<?>|])/\\$1/g;
    return $string;
}

__END__

=head1 NAME

panostart - identify likely panorama sequences

=head1 SYNOPSIS

panostart [options] --output Makefile image1 image2 [...]

 Options:
  -o | --output name    Filename of created Makefile
  -t | --time           Number of seconds between panoramas
  -f | --projection     Panotools style input projection number. Use
                          0 for rectilinear, 2 for circular fisheye and
                          3 for full-frame fisheye images.
  -v | --fov            Horizontal field of view in degrees
  -k | --selection      Crop selection boundary, eg -459,2459,-57,2861
  -h | --help           Outputs help documentation.
  -l | --loquacious     Verbose output listing targets and numbers of images.

=head1 DESCRIPTION

B<panostart> takes a list of image files and creates a Makefile
containing rules to generate panoramas from the images.

=head1 LICENSE

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

=head1 SEE ALSO

L<http://hugin.sourceforge.net/>

=head1 AUTHOR

Bruno Postle - August 2008.

=cut
