#!/usr/bin/perl -w

use PDF::API2;
use PDF::API2::Basic::PDF::File;
use PDF::API2::Basic::PDF::Utils;

my $fileid = 'Xournal source';
sub append( $$$ );

if ( scalar @ARGV == 0 )
  {
  print STDERR "Usage:\n  $0 myfile.(pdf|xoj)\n";
  exit;
  }

my $base = $ARGV[0];
my $xoj = ( $ARGV[0] =~ /\.xoj(?:\.gz)?$/ );
$base =~ s/\.xoj(?:\.gz)?$/.pdf/;
map { die "$base.$_ already existent" if -e "$base.$_" } ( 'pdf', 'xoj' );

my $spdf = { };
$spdf = PDF::API2::Basic::PDF::File->open( $base, 1 ) if not $xoj and -e $base;

my $updated = 0;
my $olddata;
my $annotated = 0;
if ( $spdf->{' prev'} )
  {
  $annotated = defined $spdf->{' prev'}->{' prev'};
  print STDERR "found ".( $annotated ? 'two' : 'one' )." PDF update(s)\n";

  $spdf->{Root}->realise;

  if ( $spdf->{Root}->{Names} )
    {
    $spdf->{Root}->{Names}->realise;
    my $D = bless $spdf->{Root}->{Names}, 'PDF::API2::Names';

    if ( my $f = $D->get( 'EmbeddedFiles', $fileid ) )
      {
      print STDERR "found embedded Xournal source\n";

      $f->realise;
      $f->{EF}->realise;
      $f->{EF}->{F}->realise;

      print STDERR "extracting Xournal source\n";
      my $s = bless $f->{EF}->{F}, 'PDF::API2::Basic::PDF::Dict';
      $s->read_stream;

      open OUT, "> $base.xoj";
      $updated = 1;
      print OUT $s->{' stream'};
      close OUT;

      print STDERR "stripping most recent PDF update(s), keeping Xournals annotations\n";
      my $p1 = ${[sort { $a <=> $b } grep { $_ } map { $$_[0] } values %{$spdf->{' xref'}} ]}[0];
      my $p2 = $annotated ? ${[sort { $a <=> $b } grep { $_ } map { $$_[0] } values %{$spdf->{' prev'}->{' xref'}} ]}[0] : 0;
#      if ( $annotated )
        {
        seek $spdf->{' INFILE'}, $p2, 0;
        read $spdf->{' INFILE'}, $olddata, $p1 - $p2; 
        }
      truncate $spdf->{' OUTFILE'}, $p2;
      close $spdf->{' OUTFILE'};
      $spdf->release;
      }
    }
  }

my $arg = $base . ( ( $updated or not $annotated and not $spdf->{Root} ) ? '.xoj' : '' );

my $stdsize;
if ( $xoj or ( not $annotated and not $updated and not $spdf->{Root} ) )
  {
  if ( $xoj )
    { `cp $ARGV[0] $base.xoj`; }
  else
    {	# use your favourite .xoj here
    print STDERR "writing standard Xournal file, creating new documents with given name is not yet supported by Xournal\n";
    open OUT, "> $arg";
    print OUT <<EOF;
<?xml version="1.0" standalone="no"?>
<xournal version="0.4.2.1">
<page width="595.00" height="842.00">
<background type="solid" color="white" style="lined" />
<layer/>
</page>
</xournal>
EOF
    close OUT;
    $stdsize = -s $arg;
    }
  }

print STDERR "starting Xournal\n";
print "xournal $arg\n";
`xournal $arg 2>/dev/null`;

die "could not find Xournal source file '$arg'" unless -e $arg;
if ( defined $stdsize and ( -s $arg == $stdsize and not -e "$base.pdf" ) )
  {
  print STDERR "removing '$arg', apparently no changes saved\n";
  unlink $arg;
  exit;
  }

if ( -e $base.'.pdf' )
  {
  print STDERR "found Xournal export\n";
  `mv $base.pdf $base`;
  }
elsif ( defined $olddata )
  {
  print STDERR "re-attaching old Xournal annotations\n";
  die 'could not find originial PDF file' unless -e $base;
  open OUT, ">> $base";
  print OUT $olddata;
  close OUT;
  }
elsif ( -e $ARGV[0] )
  {
  print STDERR "could not find Xournal export\n";
  exit;
  }

print STDERR "attaching Xournal source\n";
append( $base, $base.'.xoj', $fileid );

unlink "$base.xoj";



sub append( $$$ )
  {
  my ( $pdfname, $filename, $description ) = ( shift, shift, shift );
  my $spdf = PDF::API2::Basic::PDF::File->open( $pdfname, 1 );
  $description = $filename unless $description;

  $spdf->{Root}->realise;
  $spdf->{Root} = $spdf->new_obj( $spdf->{Root} );

  if ( defined $spdf->{Root}->{Names} )
    {
    $spdf->{Root}->{Names}->realise;
    $spdf->{Root}->{Names} = $spdf->new_obj( $spdf->{Root}->{Names} );
    }
  else
    { $spdf->{Root}->{Names} = PDF::API2::Basic::PDF::Dict->new; }
  $spdf->{Root}->{Names}->{' parent'} = $spdf;
  bless $spdf->{Root}->{Names}, 'PDF::API2::Names';

  my $file = PDF::API2::Basic::PDF::Dict->new;
  @$file{'Type', ' streamfile', 'Filter'} = (
    PDFName( 'EmbeddedFile' ),
    $filename,
    PDFArray( PDFName( 'FlateDecode' ) )
  ); 

  my $descr = PDF::API2::Basic::PDF::Dict->new;
  @$descr{qw/Type F EF/} = (
    PDFName( 'Filespec' ),
    PDFStr( $filename ),
    PDF::API2::Basic::PDF::Dict->new
  );
  $descr->{EF}->{F} = $file;

  $spdf->{Root}->{Names}->set( 'EmbeddedFiles', $description, $descr );
  $spdf->new_obj( $descr );
  $spdf->new_obj( $file );
  $spdf->append_file;
  $spdf->release;
  }

package PDF::API2::Names;

BEGIN {
  use strict;
  use vars qw(@ISA $VERSION);
  @ISA = qw(PDF::API2::Basic::PDF::Dict);

  use PDF::API2::Basic::PDF::Utils;
}

sub dictionary
  {
  my $self = shift;
  my $root = $self;
  my $cat = shift;
  my $create = shift || 0;

  $self->{' names'} = { } unless defined $self->{' names'} and ref $self->{' names'} eq 'HASH';
  if ( defined $root and not defined $self->{' names'}->{$cat} and defined $self->{$cat} )
    {
    $root->realise unless $root->{' realised'};
    my @nodes = ( $root->{$cat} );
    my %names = ( );

    while ( my $node = shift @nodes )
      {
      $node->realise unless $node->{' realised'};
      if ( defined $node->{Kids} )
        {
        $node->{Kids}->realise unless $node->{Kids}->{' realised'};
        push @nodes, @{$node->{Kids}->val};
        }
      elsif ( defined $node->{Names} )
        {
        $node->{Names}->realise unless $node->{Names}->{' realised'};
        for ( my $i = 0; $i < scalar @{$node->{Names}->val}; $i += 2 ) { $names{$node->{Names}->val->[$i]->val} = $node->{Names}->val->[$i + 1]; }
        }

      $self->{' parent'}->remove_obj( $node );
      }

    $self->{' names'}->{$cat} = \%names if scalar keys %names > 0 or $create;
    }

  return $self->{' names'}->{$cat};
  }

sub remove
  {
  my $self = shift;
  $self->{' parent'}->remove_obj( $self );
  return;
  }

sub mergeNames
  {
  my $self = shift;
  my $src = shift;
  my $ignore = shift || 0;

#  my @categories = qw# Dests AP JavaScript Pages Templates IDS URLS EmbeddedFiles AlternatePresentations Renditions #;
  my @categories = qw# Dests #;
  foreach my $cat ( @categories )
    {
    if ( defined $self->{' prefix'} )
      {
      if ( defined $self->dictionary( $cat, 0 ) )
        {
        foreach my $key ( keys %{$self->{' names'}->{$cat}} )
          {
          $self->{' names'}->{$cat}->{$self->{' prefix'}.$key} = $self->{' names'}->{$cat}->{$key};
          delete $self->{' names'}->{$cat}->{$key};
          }
        }
      delete $self->{' prefix'};
      }

    next if not defined $src->dictionary( $cat );
    $self->dictionary( $cat, 1 );

    foreach my $key ( keys %{$src->{' names'}->{$cat}} )
      {
      die "duplicate key '$key' in '$cat' tree" if not $ignore and defined $self->{' names'}->{( defined $src->{' prefix'} ? $src->{' prefix'} : '' ).$key};
      $self->{' names'}->{$cat}->{( defined $src->{' prefix'} ? $src->{' prefix'} : '' ).$key} = $src->{' names'}->{$cat}->{$key};
      }
    }
  }

sub set
  {
  my ( $self, $cat, $key, $value ) = ( shift, shift, shift, shift );

  $self->dictionary( $cat, 1 );
  $self->{' names'}->{$cat}->{$key} = $value;
  }

sub get
  {
  my ( $self, $cat, $key ) = ( shift, shift, shift, shift );

  $self->dictionary( $cat, 0 );
  return ( defined $self->{' names'}->{$cat} ? $self->{' names'}->{$cat}->{$key} : undef );
  }

sub outobjdeep
  {
  my ( $self, @opts ) = @_;

  if ( $self->{' names'} )
    {

    foreach my $key ( keys %{$self->{' names'}} )
      {
      my @names = sort keys %{$self->{' names'}->{$key}};
      if ( scalar @names == 0 )
        {
        delete $self->{' names'}->{$key};
        delete $self->{$key};
        next;
        }

      my @layer = ( );
      while ( my @ss = splice @names, 0, 5, ( ) )
        {
        for ( my $i = 0; $i <= $#ss; $i += 2 ) { splice @ss, $i + 1, 0, ( $self->{' names'}->{$key}->{$ss[$i]} ); $ss[$i] = PDFStr( $ss[$i] ); }

        my $d = PDF::API2::Basic::PDF::Dict->new( );
        @$d{'Names', 'Limits', ' parent'} = ( PDFArray( @ss ), PDFArray( PDFStr( $ss[0]->val ), PDFStr( $ss[$#ss - 1]->val ) ), $self->{' parent'} );
        $self->{' parent'}->new_obj( $d );

        push @layer, $d;
        }

      while ( scalar @layer > 1 )
        {
        my @l = ( );
        while ( my @kids = splice @layer, 0, 5, ( ) )
          {
          if ( scalar @kids == 1 ) { push @l, $kids[0]; last; }
          my $d = PDF::API2::Basic::PDF::Dict->new( );
          @$d{'Kids', 'Limits', ' parent'} = ( PDFArray( @kids ), PDFArray( PDFStr( $kids[0]->{Limits}->val->[0]->val ), PDFStr( $kids[$#kids]->{Limits}->val->[1]->val ) ), $self->{' parent'} );
          $self->{' parent'}->new_obj( $d );
          push @l, $d;
          }
        @layer = @l;
        }

      delete $layer[0]->{Limits};

      if ( scalar keys %{$self->{' names'}->{$key}} > 0 ) { $self->{$key} = $layer[0]; }
      else { delete $self->{$key}; }
      }
    }

  foreach my $k ( qw[ names prefix ] )
    {
    $self->{" $k"} = undef;
    delete( $self->{" $k"} );
    }

  $self->SUPER::outobjdeep( @opts );
  }

1;
