# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
package ModPerl::BuildMM;

use strict;
use warnings;

use ExtUtils::MakeMaker ();
use Cwd ();
use File::Spec::Functions qw(catdir catfile splitdir);
use File::Basename;
use File::Find;

use Apache2::Build ();
use ModPerl::MM;
use constant WIN32 => Apache2::Build::WIN32;
use constant CYGWIN => Apache2::Build::CYGWIN;

our %PM; #add files to installation

# MM methods that this package overrides
no strict 'refs';
my $stash = \%{__PACKAGE__ . '::MY::'};
my @methods = grep *{$stash->{$_}}{CODE}, keys %$stash;
ModPerl::MM::override_eu_mm_mv_all_methods(@methods);
use strict 'refs';

my $apache_test_dir = catdir Cwd::getcwd(), "Apache-Test", "lib";

#to override MakeMaker MOD_INSTALL macro
sub mod_install {
    q{$(PERL) -I$(INST_LIB) -I$(PERL_LIB) \\}."\n" .
    qq{-I$apache_test_dir -MModPerl::BuildMM \\}."\n" .
    q{-e "ExtUtils::Install::install({@ARGV},'$(VERBINST)',0,'$(UNINST)');"}."\n";
}

my $build;

sub build_config {
    my $key = shift;
    $build ||= Apache2::Build->build_config;
    return $build unless $key;
    $build->{$key};
}

#the parent WriteMakefile moves MY:: methods into a different class
#so alias them each time WriteMakefile is called in a subdir

sub my_import {
    no strict 'refs';
    my $stash = \%{__PACKAGE__ . '::MY::'};
    for my $sym (keys %$stash) {
        next unless *{$stash->{$sym}}{CODE};
        my $name = "MY::$sym";
        undef &$name if defined &$name;
        *$name = *{$stash->{$sym}}{CODE};
    }
}

sub WriteMakefile {
    my %args = @_;

    $build ||= build_config();
    ModPerl::MM::my_import(__PACKAGE__);

    my $inc;
    $inc = $args{INC} if $args{INC};
    $inc = " " . $build->inc;
    if (my $glue_inc = $build->{MP_XS_GLUE_DIR}) {
        for (split /\s+/, $glue_inc) {
            $inc .= " -I$_";
        }
    }

    my $libs;
    my @libs = ();
    push @libs, $args{LIBS} if $args{LIBS};
    if (Apache2::Build::BUILD_APREXT) {
        # in order to decouple APR/APR::* from mod_perl.so,
        # link these modules against the static MP_APR_LIB lib,
        # rather than the mod_perl lib (which would demand mod_perl.so
        # be available). For other modules, use mod_perl.lib as
        # usual. This is done for APR in xs/APR/APR/Makefile.PL.
        my $name = $args{NAME};
        if ($name =~ /^APR::\w+$/) {
            # For cygwin compatibility, the order of the libs should be
            # <mod_perl libs> <apache libs>
            @libs = ($build->mp_apr_lib, $build->apache_libs);
        }
        else {
            @libs = ($build->modperl_libs, $build->apache_libs);
        }
    }
    else {
        @libs = ($build->modperl_libs, $build->apache_libs);
    }
    $libs = join ' ', @libs;

    my $ccflags;
    $ccflags = $args{CCFLAGS} if $args{CCFLAGS};
    $ccflags = " " . $build->perl_ccopts . $build->ap_ccopts;

    my $optimize;
    $optimize = $args{OPTIMIZE} if $args{OPTIMIZE};
    $optimize = " " . $build->perl_config('optimize');

    my $lddlflags;
    $lddlflags = $args{LDDLFLAGS} if $args{LDDLFLAGS};
    $lddlflags = " " . $build->perl_config('lddlflags');

    my %dynamic_lib;
    %dynamic_lib = %{ $args{dynamic_lib}||{} } if $args{dynamic_lib};
    $dynamic_lib{OTHERLDFLAGS} = $build->otherldflags;

    my @opts = (
        INC         => $inc,
        CCFLAGS     => $ccflags,
        OPTIMIZE    => $optimize,
        LDDLFLAGS   => $lddlflags,
        LIBS        => $libs,
        dynamic_lib => \%dynamic_lib,
    );

    my @typemaps;
    push @typemaps, $args{TYPEMAPS} if $args{TYPEMAPS};
    my $pwd = Cwd::fastcwd();
    for ('xs', $pwd, "$pwd/..") {
        my $typemap = $build->file_path("$_/typemap");
        if (-e $typemap) {
            push @typemaps, $typemap;
        }
    }
    push @opts, TYPEMAPS => \@typemaps if @typemaps;

    my $clean_files = (exists $args{clean} && exists $args{clean}{FILES}) ?
        $args{clean}{FILES} : '';
    $clean_files .= " glue_pods"; # cleanup the dependency target
    $args{clean}{FILES} = $clean_files;

    ExtUtils::MakeMaker::WriteMakefile(@opts, %args);
}

my %always_dynamic = map { $_, 1 }
  qw(ModPerl::Const Apache2::Const APR::Const APR APR::PerlIO);

sub ModPerl::BuildMM::MY::constants {
    my $self = shift;
    $build ||= build_config();

    #"discover" xs modules.  since there is no list hardwired
    #any module can be unpacked in the mod_perl-2.xx directory
    #and built static

    #this stunt also make it possible to leave .xs files where
    #they are, unlike 1.xx where *.xs live in src/modules/perl
    #and are copied to subdir/ if DYNAMIC=1

    if ($build->{MP_STATIC_EXTS}) {
        #skip .xs -> .so if we are linking static
        my $name = $self->{NAME};
        unless ($always_dynamic{$name}) {
            if (my ($xs) = keys %{ $self->{XS} }) {
                $self->{HAS_LINK_CODE} = 0;
                print "$name will be linked static\n";
                #propagate static xs module to src/modules/perl/Makefile
                $build->{XS}->{$name} =
                  join '/', Cwd::fastcwd(), $xs;
                $build->save;
            }
        }
    }

    $self->MM::constants;
}

sub ModPerl::BuildMM::MY::top_targets {
    my $self = shift;
    my $string = $self->MM::top_targets;

    return $string;
}

sub ModPerl::BuildMM::MY::postamble {
    my $self = shift;

    my $doc_root = catdir Cwd::getcwd(), "docs", "api";

    my @targets = ();

    # reasons for glueing pods to the respective .pm files:
    # - manpages will get installed over the mp1 manpages and vice
    #   versa. glueing pods avoids creation of manpages, but may be we
    #   could just tell make to skip manpages creation?
    # if pods are installed directly they need to be also redirected,
    # some into Apache2/ others (e.g. Apache2) not

    # add the code to glue the existing pods to the .pm files in blib.
    # create a dependency on pm_to_blib subdirs linkext targets to
    # allow 'make -j'
    require ExtUtils::MakeMaker;
    my $mm_ver = $ExtUtils::MakeMaker::VERSION;
    $mm_ver =~ s/_.*//; # handle dev versions like 6.30_01
    my $pm_to_blib = ($mm_ver >= 6.22 && $mm_ver <= 6.25)
        ? "pm_to_blib.ts"
        : "pm_to_blib";
    my @target = ("glue_pods: $pm_to_blib subdirs linkext");

    if (-d $doc_root) {
        my $build = build_config();

        # those living in modperl-2.0/lib are already nicely mapped
        my %pms = %{ $self->{PM} };

        my $cwd = Cwd::getcwd();
        my $blib_dir = catdir qw(blib lib);

        # those autogenerated under WrapXS/
        # those living under xs/
        # those living under ModPerl-Registry/lib/
        my @src = ('WrapXS', 'xs', catdir(qw(ModPerl-Registry lib)));

        for my $base (@src) {
            chdir $base;
            my @files = ();
            find({ no_chdir => 1,
                   wanted => sub { push @files, $_ if /.pm$/ },
                 }, ".");
            chdir $cwd;

            for (@files) {
                my $pm = catfile $base, $_;
                my $blib;
                if ($base =~ /^(xs|WrapXS)/) {
                    my @segm = splitdir $_;
                    splice @segm, -2, 1; # xs/APR/Const/Const.pm
                    splice @segm, -2, 1 if /APR.pm/; # odd case
                    $blib = catfile $blib_dir, @segm;
                }
                else {
                    $blib = catfile $blib_dir, $_;
                }
                $pms{$pm} = $blib;
            }
        }

        while (my ($pm, $blib) = each %pms) {
            $pm   =~ s|/\./|/|g; # clean the path
            $blib =~ s|/\./|/|g; # clean the path
            my @segm = splitdir $blib;
            for my $i (1..2) {
                # try APR.pm and APR/Bucket.pm
                my $pod = catdir(@segm[-$i .. -1]);
                $pod =~ s/\.pm/\.pod/;
                my $podpath = catfile $doc_root, $pod;
                next unless -r $podpath;

                push @target,
                    '$(FULLPERL) -I$(INST_LIB) ' .
                    "-I$apache_test_dir -MModPerl::BuildMM " .
                    "-e ModPerl::BuildMM::glue_pod $pm $podpath $blib";

                # Win32 doesn't normally install man pages
                # and Cygwin doesn't allow '::' in file names
                next if WIN32 || CYGWIN;

                # manify while we're at it
                my (undef, $man, undef) = $blib =~ m!(blib/lib/)(.*)(\.pm)!;
                $man =~ s!/!::!g;

                push @target,
                    '$(NOECHO) $(POD2MAN_EXE) --section=3 ' .
                    "$podpath \$(INST_MAN3DIR)/$man.\$(MAN3EXT)"
            }
        }

        push @target, $self->{NOECHO} . '$(TOUCH) $@';
    }
    else {
        # we don't have the docs sub-cvs repository extracted, skip
        # the docs gluing
        push @target, $self->{NOECHO} . '$(NOOP)';
    }
    push @targets, join "\n\t", @target;

#    # next target: cleanup the dependency file
#    @target = ('glue_pods_clean:');
#    push @target, '$(RM_F) glue_pods';
#    push @targets, join "\n\t", @target;

    return join "\n\n", @targets, '';
}

sub glue_pod {

    die "expecting 3 arguments: pm, pod, dst" unless @ARGV == 3;
    my ($pm, $pod, $dst) = @ARGV;

    # it's possible that the .pm file is not existing
    # (e.g. ThreadMutex.pm is not created on unless
    # $apr_config->{HAS_THREADS})
    return unless -e $pm && -e $dst;

    # have we already glued the doc?
    exit 0 unless -s $pm == -s $dst;

    # ExtUtils::Install::pm_to_blib removes the 'w' perms, so we can't
    # just append the doc there
    my $orig_mode = (stat $dst)[2];
    my $rw_mode   = 0666;

    chmod $rw_mode, $dst      or die "Can't chmod $rw_mode $dst: $!";
    open my $pod_fh, "<$pod"  or die "Can't open $pod: $!";
    open my $dst_fh, ">>$dst" or die "Can't open $dst: $!";
    print $dst_fh "\n"; # must add one line separation
    print $dst_fh (<$pod_fh>);
    close $pod_fh;
    close $dst_fh;
    # restore the perms
    chmod $orig_mode, $dst    or die "Can't chmod $orig_mode $dst: $!";
}

sub ModPerl::BuildMM::MY::post_initialize {
    my $self = shift;
    $build ||= build_config();
    my $pm = $self->{PM};

    while (my ($k, $v) = each %PM) {
        if (-e $k) {
            $pm->{$k} = $v;
        }
    }

    # prefix typemap with Apache2/ so when installed in the
    # perl-lib-tree it won't be picked by non-mod_perl modules
    if (exists $pm->{'lib/typemap'} ) {
        $pm->{'lib/typemap'} = '$(INST_ARCHLIB)/auto/Apache2/typemap';
    }

    '';
}

my $apr_config;

sub ModPerl::BuildMM::MY::libscan {
    my ($self, $path) = @_;

    $apr_config ||= $build->get_apr_config();

    if ($path =~ m/(Thread|Global)Mutex/) {
        return unless $apr_config->{HAS_THREADS};
    }

    return '' if $path =~ /DummyVersions.pm/;

    return '' if $path =~ m/\.pl$/;
    return '' if $path =~ m/~$/;
    return '' if $path =~ /\B\.svn\b/;

    $path;
}

1;

=head1 NAME

ModPerl::BuildMM -- A "subclass" of ModPerl::MM used for building mod_perl 2.0

=head1 SYNOPSIS

  use ModPerl::BuildMM;
  
  # ModPerl::BuildMM takes care of doing all the dirty job of overriding 
  ModPerl::BuildMM::WriteMakefile(...);

  # if there is a need to extend the methods 
  sub MY::postamble {
      my $self = shift;
  
      my $string = $self->ModPerl::BuildMM::MY::postamble;
  
      $string .= "\nmydist : manifest tardist\n";
  
      return $string;
  }


=head1 DESCRIPTION

C<ModPerl::BuildMM> is a "subclass" of C<ModPerl::MM> used for
building mod_perl 2.0. Refer to
C<L<ModPerl::MM|docs::2.0::api::ModPerl::MM>> manpage.

=head1 OVERRIDEN METHODS

C<ModPerl::BuildMM> overrides the following methods:

=head2 C<ModPerl::BuildMM::MY::constants>


=head2 C<ModPerl::BuildMM::MY::top_targets>


=head2 C<ModPerl::BuildMM::MY::postamble>


=head2 C<ModPerl::BuildMM::MY::post_initialize>


=head2 C<ModPerl::BuildMM::MY::libscan>


=cut
