# 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::BuildOptions;

use strict;
use warnings;

use Apache2::Build ();
use Apache::TestTrace;
my $param_qr = qr([\s=]+);

use constant VERBOSE => 1;
use constant UNKNOWN_FATAL => 2;

use File::Spec;

sub init {
    my ($self, $build) = @_;

    #@ARGV should override what's in .makepl_args.mod_perl2
    #but @ARGV might also override the default MP_OPTS_FILE
    #so snag that first
    parse($build, [grep { /^MP_OPTIONS_FILE/ } @ARGV]);
    parse_file($build);
    parse_argv($build);

    # if AP_PREFIX is used apxs and apr-config from the apache build
    # tree won't work, so it can't co-exist with APXS and APR_CONFIG
    # options
    if ($build->{MP_AP_PREFIX} and $build->{MP_APXS}) {
        error "You need to pass either MP_AP_PREFIX or MP_APXS, but not both";
        die "\n";
    }
    if ($build->{MP_AP_PREFIX} and $build->{MP_APR_CONFIG}) {
        error "You need to pass either MP_AP_PREFIX or MP_APR_CONFIG, " .
            "but not both";
        die "\n";
    }

    if ($build->{MP_DEBUG} and $build->{MP_USE_GTOP} and !$build->find_gtop) {
        error "Can't find libgtop, resetting MP_USE_GTOP=0";
        $build->{MP_USE_GTOP} = 0;
    }

    unless ($build->{MP_USE_DSO} or $build->{MP_USE_STATIC}) {
        # default to DSO
        $build->{MP_USE_DSO} = 1;
    }

    $build->{MP_GENERATE_XS} = 1 unless exists $build->{MP_GENERATE_XS};

    # define MP_COMPAT_1X unless explicitly told to disable it
    $build->{MP_COMPAT_1X} = 1
        unless exists $build->{MP_COMPAT_1X} && !$build->{MP_COMPAT_1X};

    # make a last ditch effort to find apxs in $ENV{PATH}
    if (!$build->{MP_AP_PREFIX} && !$build->{MP_APXS}) {

        my @paths = split(/:/, $ENV{PATH});
        my $potential_apxs;
        while (!$potential_apxs) {

            last if scalar(@paths) == 0; # don't loop endlessly
            $potential_apxs = File::Spec->catfile(shift @paths, 'apxs');
            if (-e $potential_apxs && -x $potential_apxs) {

                $build->{MP_APXS} = $potential_apxs;
                print "MP_APXS unspecified, using $potential_apxs\n\n";
            } else {
                undef $potential_apxs;
            }
        }
    }
}

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

    $opts = VERBOSE|UNKNOWN_FATAL unless defined $opts;
    my $table = table();
    my @unknown;
    my $continue = "";

    my @data = ();
    for (@$lines) {
        chomp;
        s/^\s+//; s/\s+$//;
        next if /^\#/ || /^$/;
        last if /^__END__/;

        # more than one entry on the same line (but make sure to leave
        # -DMP_* alone)
        push @data, split /(?=\WMP_)/, $_;
    }

    for (@data) {
        #XXX: this "parser" should be more robust

        s/^\s+//; s/\s+$//;

        $_ = "$continue $_" if $continue;

        #example: +"MP_CCOPTS=-Werror" if $] >= 5.007
        if (s/^\+//) {
            $_ = eval $_;
        }

        if (/^MP_/) {
            my ($key, $val) = split $param_qr, $_, 2;
            $val ||= "" unless defined $val && $val eq '0';
            $continue = $val =~ s/\\$// ? $key : "";

            if (!$table->{$key} and $opts & UNKNOWN_FATAL) {
                my $usage = usage();
                die "Unknown Option: $key\nUsage:\n$usage\n";
            }

            if ($key eq 'MP_APXS') {
                $val = File::Spec->canonpath(File::Spec->rel2abs($val));
            }

            if ($key eq 'MP_AP_PREFIX') {
                $val = File::Spec->canonpath(File::Spec->rel2abs($val));

                if (Apache2::Build::WIN32()) {
                    # MP_AP_PREFIX may not contain spaces
                    require Win32;
                    $val = Win32::GetShortPathName($val);
                }

                if (!$val || !-d $val) {
                    error "MP_AP_PREFIX must point to a valid directory.";
                    die "\n";
                }
            }

            if ($table->{$key}->{append}){
                $self->{$key} = join " ", grep $_, $self->{$key}, $val;
            }
            else {
                $self->{$key} = $val;
            }

            print "   $key = $val\n" if $opts & VERBOSE;
        }
        else {
            push @unknown, $_;
        }
    }

    return \@unknown;
}

sub parse_file {
    my $self = shift;

    my $fh;
    my @dirs = qw(./ ../ ./. ../.);
    push @dirs, "$ENV{HOME}/." if exists $ENV{HOME};
    my @files = map { $_ . 'makepl_args.mod_perl2' } @dirs;
    unshift @files, $self->{MP_OPTIONS_FILE} if $self->{MP_OPTIONS_FILE};

    for my $file (@files) {
        if (open $fh, $file) {
            $self->{MP_OPTIONS_FILE} = $file;
            last;
        }
        $fh = undef;
    }

    return unless $fh;

    print "Reading Makefile.PL args from $self->{MP_OPTIONS_FILE}\n";
    my $unknowns = parse($self, [<$fh>]);
    push @ARGV, @$unknowns if $unknowns;

    close $fh;
}

sub parse_argv {
    my $self = shift;
    return unless @ARGV;

    my @args = @ARGV;
    @ARGV = ();

    print "Reading Makefile.PL args from \@ARGV\n";
    my $unknowns = parse($self, \@args);
    push @ARGV, @$unknowns if $unknowns;
}

sub usage {
    my $table = table();
    my @opts = map { "$_ - $table->{$_}->{val}" } sort keys %$table;
    join "\n", @opts;
}

sub parse_table {
    my ($fh) = @_;
    my %table;
    local $_;

    while (<$fh>) {
        chomp;
        s/^\s+//; s/\s+$//;
        next if /^\#/ || /^$/;
        last if /^__END__/;
        my ($key, $append, $val) = split /\s+/, $_, 3;
        $table{'MP_' . $key} = { append => $append, val => $val };
    }

    return \%table;
}

my $Table;

sub table {
    $Table ||= parse_table(\*DATA);
}

1;

# __DATA__ format:
# key        append     description
# where:
#     key:    is the option name
#     append: is whether we want to replace a default option (0)
#             or append the arg to the option (1)
#     desc:   description for this option

__DATA__
USE_GTOP       0    Link with libgtop and enable libgtop reporting
DEBUG          0    Turning on debugging (-g -lperld) and tracing
MAINTAINER     0    Maintainer mode: DEBUG=1 -DAP_DEBUG -Wall ...
CCOPTS         1    Add to compiler flags
TRACE          0    Turn on tracing
USE_DSO        0    Build mod_perl as a dso
USE_STATIC     0    Build mod_perl static
PROMPT_DEFAULT 0    Accept default value for all would-be prompts
OPTIONS_FILE   0    Read options from given file
STATIC_EXTS    0    Build Apache2::*.xs as static extensions
APXS           0    Path to apxs
AP_DESTDIR     0    Destination for Apache specific mod_perl bits
AP_PREFIX      0    Apache installation or source tree prefix
AP_CONFIGURE   0    Apache ./configure arguments
APR_CONFIG     0    Path to apr-config
APU_CONFIG     0    Path to apu-config
XS_GLUE_DIR    1    Directories containing extension glue
INCLUDE_DIR    1    Add directories to search for header files
GENERATE_XS    0    Generate XS code based on httpd version
LIBNAME        0    Name of the modperl dso library (default is  mod_perl)
COMPAT_1X      0    Compile-time mod_perl 1.0 backcompat (default is  on)
APR_LIB        0    Lib used to build APR::* on Win32 (default is aprext)
NONTHREADED_OK 0    Using a non-threaded perl is okay with httpd >=2.3
