package    #
    inc::Sereal::BuildTools;
use strict;
use warnings;

use Config;
use constant OSNAME   => $^O;

my %bare_minimum_files= map { $_ => 1 } qw{
    typemap
    ppport.h
    srl_stack.h
    srl_common.h
    srl_inline.h
    srl_taginfo.h
    srl_protocol.h
    srl_reader_error.h
    srl_reader_types.h
    inc/Sereal/BuildTools.pm
    inc/Devel/CheckLib.pm
    inc/Sereal
    inc/Devel
    inc
};

sub link_files {
    my $shared_dir= shift;
    my $mode= shift || "";
    my $exclude_tests= $mode eq "without_tests";
    my $tests_only= $mode eq "tests_only";
    my $bare_minimum= $mode eq "bare_minimum";

    # This fires from a git source tree only.
    # Right now, all devs are on Linux. Feel free to make portable.
    eval {
        # overwrite by default
        require File::Find;
        require File::Path;
        require File::Spec;
        File::Find::find( {
                no_chdir => 1,
                wanted   => sub {
                    my $f= $_;
                    s/^\Q$shared_dir\E\/?// or die $_;
                    return unless $_;
                    return if $exclude_tests && m#^/?t/#;
                    return if $tests_only    && !m#^/?t/#;
                    return if $bare_minimum  && !exists $bare_minimum_files{$_};

                    if ( -d $f ) {
                        File::Path::mkpath($_);
                    }
                    elsif ( -f $f ) {
                        return if $f =~ /(?:\.bak|\.sw[po]|~)$/;
                        my @d= File::Spec->splitdir($_);
                        my $fname= pop @d;
                        my $ref= join "/", ("..") x scalar(@d);
                        my $subd= join "/", @d;
                        chdir $subd if length($ref);
                        my $srcfname= join( "/", grep length, $ref, $shared_dir, $subd, $fname );
                        if ( OSNAME eq 'MSWin32' ) {
                            die "link($srcfname, $fname) failed: $!"
                                unless link( $srcfname, $fname );    #only NTFS implements it
                        }
                        else {
                            symlink( $srcfname, $fname );
                        }
                        chdir($ref) if length($ref);
                    }
                },
            },
            $shared_dir
        );
        1;
    } or warn $@;
}

sub generate_constant_includes {

    # no-op
}

# Prefer external csnappy and miniz libraries over the bundled ones.
sub check_external_libraries {
    my ( $libs, $defines, $objects, $subdirs )= @_;
    require Devel::CheckLib;

    $$libs .= ' -L/usr/local/lib'
        if $^O =~ /openbsd/;

    if (
           !$ENV{SEREAL_USE_BUNDLED_LIBS}
        && !$ENV{SEREAL_USE_BUNDLED_CSNAPPY}
        && Devel::CheckLib::check_lib(
            lib    => 'csnappy',
            header => 'csnappy.h'
        ) )
    {
        print "Using installed csnappy library\n";
        $$libs    .= ' -lcsnappy';
        $$defines .= ' -DHAVE_CSNAPPY';
    }
    else {
        print "Using bundled csnappy code\n";
    }

    if (
           !$ENV{SEREAL_USE_BUNDLED_LIBS}
        && !$ENV{SEREAL_USE_BUNDLED_MINIZ}
        && Devel::CheckLib::check_lib(
            lib    => 'miniz',
            header => 'miniz.h'
        ) )
    {
        print "Using installed miniz library\n";
        $$libs    .= ' -lminiz';
        $$defines .= ' -DHAVE_MINIZ';
    }
    else {
        print "Using bundled miniz code\n";
        $$objects .= ' miniz$(OBJ_EXT)';
    }

    if (
           !$ENV{SEREAL_USE_BUNDLED_LIBS}
        && !$ENV{SEREAL_USE_BUNDLED_ZSTD}
        && Devel::CheckLib::check_lib(
            lib    => 'zstd',
            header => 'zstd.h'
        ) )
    {
        print "Using installed zstd library\n";
        $$libs    .= ' -lzstd';
        $$defines .= ' -DHAVE_ZSTD';
    }
    else {
        print "Using bundled zstd code\n";
        push @{$subdirs}, 'zstd';
        $$objects .= ' zstd/libzstd$(OBJ_EXT)';
    }
}

sub build_defines {
    my (@defs)= @_;

    my $defines= join(
        " ", map { "-D$_" . ( defined $ENV{$_} ? "=$ENV{$_}" : '' ) }
            grep { exists $ENV{$_} } ( qw(NOINLINE DEBUG MEMDEBUG NDEBUG), @defs ) );

    $defines .= " -DNDEBUG" unless $ENV{DEBUG};

    if ($Config{usequadmath}) {
        $defines .= " -DHAS_QUADMATH";
    } elsif ($Config{uselongdouble}) {
        $defines .= " -DHAS_LONGDOUBLE";
    }

    if ( $Config{osname} eq 'hpux' && not $Config{gccversion} ) {

        # HP-UX cc does not support inline.
        # Or rather, it does, but it depends on the compiler flags,
        # assumedly -AC99 instead of -Ae would work.
        # But we cannot change the compiler config too much from
        # the one that was used to compile Perl,
        # so we just fake the inline away.
        $defines .= " -Dinline= ";
    }

    $defines .= " -DMINIZ_NO_STDIO";
    if ($ENV{USE_UNALIGNED}) {
        $defines .= " -DMINIZ_USE_UNALIGNED_LOAD_AND_STORE=1";
        $defines .= " -DMINIZ_UNALIGNED_USE_MEMCOPY";
    }

    return $defines;
}

sub build_optimize {
    my $cc_flags= shift || {};

    my $catch_violations= exists $cc_flags->{catch_violations} ? $cc_flags->{catch_violations} : 1;

    my $OPTIMIZE;

    my $clang= 0;
    if ( $Config{gccversion} ) {
        $OPTIMIZE= '-O3';
        if ( $Config{gccversion} =~ /[Cc]lang/ ) {    # clang.
            $clang= 1;
        }

        my $gccversion= 0;
        if ( $Config{gccversion} =~ /^(\d+\.\d+)/ ) {
            $gccversion= $1;
        }

        if ( $] < 5.035005 && $catch_violations && ( $clang || $gccversion >= 4.3 ) ) {

            # v5.35.5 enables some C99 features including mixed declarations and code,
            # and uses them in inline.h, hence we can't enable this warning flag
            # without generating false positive warnings.
            # Earlier versions of perl support older compilers that are strict C89,
            # hence we still need to avoid mixed declarations and code, hence
            # enable this warning on earlier perls so that we can still spot problems.

            # -Werror= introduced in GCC 4.3
            # For trapping C++ // comments we would need -std=c89 (aka -ansi)
            # but that may be asking too much of different platforms.
            $OPTIMIZE .= ' -Werror=declaration-after-statement ';
        }

    }
    elsif ( $Config{osname} eq 'MSWin32' ) {
        $OPTIMIZE= '-O2 -W4';
    }
    else {
        $OPTIMIZE= $Config{optimize};
    }

    if ( $ENV{DEBUG} ) {
        $OPTIMIZE .= ' -g';
        if ( $ENV{DEBUG} > 0 && $Config{gccversion} ) {
            $OPTIMIZE .= ' -Wextra'      if $ENV{DEBUG} > 1;
            $OPTIMIZE .= ' -pedantic'    if $ENV{DEBUG} > 5;                  # not pretty
            $OPTIMIZE .= ' -Weverything' if ( $ENV{DEBUG} > 6 && $clang );    # really not pretty
        }
    }

    # silence warning about STMT_START and STMT_END.
    $OPTIMIZE .= " -Wno-compound-token-split-by-macro"
        if $clang;

    return $OPTIMIZE;
}

sub WriteMakefile {
    require ExtUtils::MakeMaker;

    #Original by Alexandr Ciornii, modified by Yves Orton
    my %params= @_;
    my $eumm_version= $ExtUtils::MakeMaker::VERSION;
    $eumm_version= eval $eumm_version;
    die "EXTRA_META is deprecated" if exists $params{EXTRA_META};
    die "License not specified" if not exists $params{LICENSE};
    if ( $params{TEST_REQUIRES} and $eumm_version < 6.6303 ) {
        $params{BUILD_REQUIRES}=
            { %{ $params{BUILD_REQUIRES} || {} }, %{ $params{TEST_REQUIRES} } };
        delete $params{TEST_REQUIRES};
    }
    if ( $params{BUILD_REQUIRES} and $eumm_version < 6.5503 ) {

        #EUMM 6.5502 has problems with BUILD_REQUIRES
        $params{PREREQ_PM}= { %{ $params{PREREQ_PM} || {} }, %{ $params{BUILD_REQUIRES} } };
        delete $params{BUILD_REQUIRES};
    }
    if ( $params{CONFIGURE_REQUIRES} and $eumm_version < 6.52 ) {
        $params{PREREQ_PM}= { %{ $params{PREREQ_PM} || {} }, %{ $params{CONFIGURE_REQUIRES} } };
        delete $params{CONFIGURE_REQUIRES};
    }
    delete $params{MIN_PERL_VERSION} if $eumm_version < 6.48;
    delete $params{META_MERGE}       if $eumm_version < 6.46;
    delete $params{META_ADD}         if $eumm_version < 6.46;
    delete $params{LICENSE}          if $eumm_version < 6.31;
    delete $params{AUTHOR}           if $] < 5.005;
    delete $params{ABSTRACT_FROM}    if $] < 5.005;
    delete $params{BINARY_LOCATION}  if $] < 5.005;
    delete $params{OPTIMIZE}         if $^O eq 'MSWin32';

    ExtUtils::MakeMaker::WriteMakefile(%params);
}

sub MY::postamble {
  return <<'MAKE_FRAG';
zstd/libzstd$(OBJ_EXT): zstd/Makefile
	cd zstd && $(MAKE) all
MAKE_FRAG
}

1;
