package Memories::Photo;
use strict;
use Carp qw(cluck confess);
use base qw(Memories::DBI Maypole::Model::CDBI::Plain);
use Time::Piece;
use Image::Seek;
use constant PAGER_SYNTAX => "LimitXY";
__PACKAGE__->columns(Essential => qw(id title uploader uploaded x y));
__PACKAGE__->untaint_columns(printable => [qw/title/]);
__PACKAGE__->columns(TEMP => qw/exif_object/);
__PACKAGE__->set_sql(recent => q{
SELECT __ESSENTIAL__
FROM __TABLE__
ORDER BY uploaded DESC
LIMIT 4
});

__PACKAGE__->has_many(comments => "Memories::Comment");
__PACKAGE__->has_a(uploader => "Memories::User");
__PACKAGE__->has_a(uploaded => "Time::Piece",
    inflate => sub { Time::Piece->strptime(shift, "%Y-%m-%d %H:%M:%S") },
    deflate => 'datetime',
);

sub do_upload :Exported {
    my ($self, $r) = @_;
    my %upload = $r->upload("photo");

    # XXX Stop anonymous uploads!
    my $photo = $self->create({
        uploader => $r->user,
        uploaded => Time::Piece->new(),
        title => ($r->params->{title} || $upload{filename})
    });

    # Dump content
    if (!open OUT, ">". $photo->path("file")) {
        die "Can't write ".$photo->path("file")." because $!";
    }
    # XXX Check it's a JPEG, etc.
    # XXX Unzip ZIP file
    print OUT $upload{content};
    close OUT;
    my ($x, $y) = dim(image_info($photo->path));
    $photo->x($x); $photo->y($y);

    # Rotate?
    $photo->unrotate();

    $photo->make_thumb;
    $photo->add_tags($r->{params}{tags});
    $photo->add_to_imageseek_library;
    Memories->zap_cache();

    # Add system tags here
    my $tag = "date:".$photo->shot->ymd;
    $photo->add_to_system_tags({tag => Memories::SystemTag->find_or_create({name =>$tag}) });

    # Set it up to go again
    $r->objects([$photo]);
    $r->template("view");
    $r->message("Thanks for the upload! ".
        ($r->{params}{tags} ? "" 
        : "Don't forget to <a href=\"?".$r->config->uri_base."photo/view/".$photo->id."?active=tagedit\">tag your photo</a>"
        )
    ); 
}

sub view :Exported {
    my ($self, $r) = @_;
    if ($r->{session}{last_search}) {
        my $photo = $r->{objects}[0];
        # This is slightly inefficient
        my @search = split/,/, $r->{session}{last_search};
        my $found = -1;
        for my $i (0..$#search) {
            next unless $photo->id == $search[$i];
            $found = $i;
        }
        return unless $found > -1;
        $r->{template_args}{next} = $self->retrieve($search[$found+1]) 
            if $found+1 <= $#search;
        $r->{template_args}{prev} = $self->retrieve($search[$found-1])
            if $found-1 >= 0;
    }
}
sub upload :Exported {}

use Class::DBI::Plugin::Pager;
use Class::DBI::Plugin::AbstractCount;

sub recent :Exported {
    my ($self, $r) = @_;
    my $page = $r->params->{page} || 1;
    my $pager = $self->pager(
        per_page => Memories->config->{photos_per_page}, 
        page => $page,
        syntax => PAGER_SYNTAX,
        order_by => "uploaded desc"
    );
    $r->objects([$pager->retrieve_all ]);
    $r->{template_args}{pager} = $pager;
    $r->last_search;
}

sub add_comment :Exported {
    my ($self, $r, $photo) = @_;
    $r->template("view");
    $r->objects->[0]->add_to_comments({
        name => $r->params->{name},
        content => $r->params->{content}
    });
}

sub format { 
    "jpg" # For now
} 

use Cache::MemoryCache;
use Image::Info qw(dim image_info);
use Image::ExifTool;
my $cache = new Cache::MemoryCache( { 'namespace' => 'MemoriesInfo' });

sub add_to_imageseek_library {
    my $self = shift;
    Image::Seek::cleardb();
    my $img = Image::Imlib2->load($self->path("file"));

    Image::Seek::add_image($img, $self->id);
    # Merge this new one into the main database; there is a bit of a
    # race condition here. XXX
    Image::Seek::loaddb(Memories->config->{image_seek});
    Image::Seek::savedb(Memories->config->{image_seek});
}

sub recommended_tags {
    my $self = shift;
    my %tags = map { $_->name => $_ }
               map { $_->tags } 
               $self->find_similar(3);
    values %tags;
}

sub find_similar {
    my ($self, $count) = @_;
    Image::Seek::cleardb();
    Image::Seek::loaddb(Memories->config->{image_seek});
    my @res = map {$_->[0] } Image::Seek::query_id($self->id, $count);
    shift @res; # $self
    map { $self->retrieve($_) } @res;
}

sub unrotate {
    my $self = shift;
    my $orient = $self->exif_info->{EXIF}->{Orientation};
    return if $orient !~/Rotate (\d+)/i;
    my $steps = $1/90;
    my $img = Image::Imlib2->load($self->path("file"));
    $img->image_orientate($steps);
    $img->save($self->path("file"));
}

sub exif_info {
    my $self = shift;
    my $info = $self->exif_object;
    return $info if $info;
    # Get it from the Cache
    if (!($info = $cache->get($self->id))) {
        # Create it
        $info = $self->_exif_info;
        $cache->set($self->id, $info);
    }
    $self->exif_object($info);
    $info;
}

sub _exif_info {
    my $exifTool = new Image::ExifTool;
    $exifTool->Options(Group0 => ['EXIF', 'MakerNotes', 'Composite']);
    my $info = $exifTool->ImageInfo(shift->path);
    my $hash = {};
    foreach my $tag ($exifTool->GetFoundTags('Group0')) {
         my $group = $exifTool->GetGroup($tag);
         my $val = $info->{$tag};
         next if ref $val eq 'SCALAR';
         next if $val =~ /^[0\s]*$/;
         $hash->{$group}->{$exifTool->GetDescription($tag)} = $val;
    }
    return $hash;
}

# XXX Cache this
sub dimensions { join "x", $_[0]->x, $_[0]->y }

sub is_bigger {
    my ($self, $size) = @_;
    return 1 if $size eq "full";
    my ($w, $h) = ($self->x, $self->y);
    my ($w2, $h2) = split /x/, $size;
    return 1 if $w > $w2 or $h > $h2;
    return 0;
}

sub sized_url { # Use this rather than ->path from TT
    my ($self, $size) = @_;
    my $url = Memories->config->{data_store_external};
    my $resized = Memories->config->{sizes}->[$size];
    if (!$resized) { cluck "Asked for crazy size $size"; return; }
    if ($resized eq "full") { return $self->path("url") }
    $self->scale($resized) 
        unless -e $self->path( file => $resized );
    return $self->path(url => $resized);
}

sub path { 
    my ($self, $is_url, $scale) = @_;
    my $path =
        Memories->config->{$is_url eq "url" ? "data_store_external" : "data_store" };
    if ($scale) { $path .= "$scale/" }
    # Make dir if it doesn't exist, save trouble later
    use File::Path;
    if ($is_url ne "url") {mkpath($path);}
    $path .= $self->id.".".$self->format;
    return $path;
}

sub thumb_url { shift->path(url => Memories->config->{thumb_size}); }
sub make_thumb { shift->scale(Memories->config->{thumb_size}, 1); }

use Image::Imlib2;
sub scale {
    my ($self, $scale, $swap) = @_;
    my ($x, $y) = split /x/, $scale;
    # Find out smaller axis
    my ($cur_x, $cur_y) = ($self->x, $self->y);
    if (!$swap) {
        if ($cur_x < $cur_y) { $y = 0 } else { $x = 0 }
    } else {
        if ($cur_x > $cur_y) { $y = 0 } else { $x = 0 }
    }
    my $img = Image::Imlib2->load($self->path("file"));
    unless ($img) {
        cluck "Couldn't open image file ".$self->path("file");
        return;
    }
    $img = $img->create_scaled_image($x, $y);
    $img->image_set_format("jpeg");
    my $file = $self->path( file => $scale );
    $img->save($file);
    if ($!) {
        cluck "Couldn't write image file $file ($!)"; 
        return;
    }
}

use Text::Balanced qw(extract_multiple extract_quotelike);
sub edit_tags :Exported {
    my ($self, $r) = @_;
    my $photo = $r->objects->[0];
    my %params = %{$r->params};
    for (keys %params) { 
        next unless /delete_(\d+)/;
        my $tagging = Memories::Tagging->retrieve($1) or next;
        next unless $tagging->photo->id == $photo->id;
        $tagging->delete;
    }
    $photo->add_tags($params{newtags});
    $r->template("view");
}

sub add_tags {
    my ($photo, $tagstring) = @_;

    for my $tag (map { s/^"|"$//g; $_} extract_multiple(lc $tagstring, [ \&extract_quotelike, qr/([^\s]+)/ ], undef,1)) {
        $photo->add_to_tags({tag => Memories::Tag->find_or_create({name =>$tag}) })
    }
}

sub shot {
  my $self = shift;
  my $exif = $self->exif_info->{EXIF};
  my ($dt) =
    grep {$_ and /[^ 0:]/} 
        ($exif->{ 'Shooting Date/Time' },
         $exif->{ 'Date/Time Of Digitization' },
         $exif->{ 'Date/Time Of Last Modification' });
  if (!$dt) { return $self->uploaded }
  return Time::Piece->strptime($dt, "%Y:%m:%d %T") || $self->uploaded;
}
1;
