package Cpanel::Easy::Utils::Profile;

# cpanel - Cpanel/Easy/Utils/Profile.pm           Copyright(c) 2014 cPanel, Inc.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cpanel license. Unauthorized copying is prohibited

use strict;
use warnings;
no warnings qw( redefine );
use File::Basename     ();
use YAML::Syck         ();
use Cpanel::Easy::PHP5 ();

# Fix optmods specifically known to be an issue 2.2 <-> 2.4
# because their reverse flags differ in their 2_4::,
#
# they are explictly handled:
#   Cpanel::Easy::Apache::2_4::Asis    (reverse => 0)
#   Cpanel::Easy::Apache::2_4::Env     (reverse => 0)
#   Cpanel::Easy::Apache::2_4::Version (reverse => 0)
#
# It is possible to determine these modules programatically,
# but at this time we're handling them explicitly in the function.
#
# No attempt is made to check the profile against the current running
# httpd compiled modules; if this is desired the require query functions
# are contained in Cpanel::AdvConfig::Apache::modules module.
#
# Input Parameters:
#  profile_ref   => $profile_ref
#  wanted_apache => $wanted_apache_version_key
# Output:
#  scalar: 'nomajor', '22_to_24', '24_to_22'
#

sub check_apache_upgrade_direction {
    my $self   = shift;
    my %params = @_;
    my $status = 'nomajor';

    my $wp_ref        = $params{'profile_ref'};
    my $wanted_apache = $params{'wanted_apache'};

    # noop if params are not provided as expected
    return $status if not $wp_ref or not $wanted_apache;

    # not a ternary for clarity
    if ( $self->is_upgrade_22_to_24($wanted_apache) ) {
        $status = '22_to_24';
    }
    elsif ( $self->is_downgrade_24_to_22($wanted_apache) ) {
        $status = '24_to_22';
    }

    return $status;
}

sub check_and_fix_reverse_optmods {
    my $self   = shift;
    my %params = @_;

    my $wp_ref        = $params{'profile_ref'};
    my $wanted_apache = $params{'wanted_apache'};

    my $status = $self->check_apache_upgrade_direction(@_);

    # do if 2.2 <-> 2.4
    if ( $status ne 'nomajor' ) {
        $wp_ref->{Apache}->{optmods}->{Asis}    = ( 1 == $wp_ref->{Apache}->{optmods}->{Asis} )    ? 0 : 1;
        $wp_ref->{Apache}->{optmods}->{Env}     = ( 1 == $wp_ref->{Apache}->{optmods}->{Env} )     ? 0 : 1;
        $wp_ref->{Apache}->{optmods}->{Version} = ( 1 == $wp_ref->{Apache}->{optmods}->{Version} ) ? 0 : 1;
    }

    return $status;
}

# Parses a profile filename into constiuent parts.  If any part is missing,
# it will fill in the blanks.
sub parse_profile_filename {
    my $self = shift;
    my $path = shift;

    # strip all null characters
    $path =~ s/\0//g;

    my ( $name, $dir, $ext ) = File::Basename::fileparse( $path, qr/\.[^\.]*\z/xms );
    return () unless $name;                      # specified a directory, not filename or path
    return () unless $name =~ /\A(\w+)\z/xms;    # only allow finite characters in file name

    # the _main and the initial cpanel (optional) profile don't exist in custom directory,
    # so don't force it there
    if ( $path ne $self->{'profile_main'} && $path ne '/etc/cp_easyapache_profile.yaml' && $dir ne $self->{'runlog_dir'} ) {
        $dir = $self->{'profile_custom_dir'} if ( !$dir || $dir =~ m{^\./} );
        $ext = '.yaml' unless $ext;
    }

    $dir =~ s{/\z}{}xms;

    my $fullpath = sprintf( '%s/%s%s', $dir, $name, $ext );

    return ( $name, $ext, $dir, $fullpath );
}

# Looks for things that we consider to be the "major" features in a profile
sub get_major_features {
    my $self    = shift;
    my %args    = @_;
    my $profile = $args{'hashref'};
    my @features;
    die "Programmer Error: 'hashref' argument required" unless $profile;

    {
        # Apache
        my $ns      = $profile->{'Apache'}{'version'};
        my $version = $ns;
        $version =~ s/\_/\./;
        push @features, { ns => "Cpanel::Easy::Apache::$ns", name => "Apache", version => $version };
    }

    # PHP version
    my $phpns = 'Cpanel::Easy::PHP5';
    if ( $profile->{$phpns} ) {
        my @php_versions = Cpanel::Easy::PHP5::versions();

        # Make sure the version in the profile itself is in our list,
        # even if it's not supported, or else it won't be shown at
        # all.
        push @php_versions, map { my $v = $_; $v =~ s/${phpns}:://; $v } grep { m/${phpns}::[\d_]+/ && $profile->{$_} } keys %$profile;

        my @enabled = grep { my $ns = "${phpns}::$_"; $profile->{$ns} } sort { $a gt $b } @php_versions;
        if (@enabled) {
            my $version = "5.$enabled[0]";
            $version =~ s/_\d+//g;
            push @features, { ns => "${phpns}::$enabled[0]", name => "PHP", version => $version };
        }
    }

    {
        # Which mpm: mock this data to use MPM selection code
        local $self->{'working_profile'} = $profile;
        my @mpms = qw( Event Itk Prefork Worker );
        my $ns   = "Cpanel::Easy::Apache";
        local $self->{'state'} = { 'order' => [ map { sprintf( '%s::MPM%s', $ns, $_ ) } @mpms ] };
        my $mpm = ucfirst( $self->get_configured_mpm() );

        # Converting 'Itk' to uppercase to keep the display names consistent
        my $mpmDisplayVersion = $mpm eq "Itk" ? uc $mpm : $mpm;

        push @features, { ns => "${ns}::MPM$mpm", name => "MPM", version => $mpmDisplayVersion };
    }

    # Ruid2?
    if ( defined $profile->{'Cpanel::Easy::ModRuid2'} && $profile->{'Cpanel::Easy::ModRuid2'} ) {
        push @features, { ns => "Cpanel::Easy::ModRuid2", name => "Mod", version => "Ruid2" };
    }

    # Tomcat?
    if ( defined $profile->{'Cpanel::Easy::Tomcat::7_0'} && $profile->{'Cpanel::Easy::Tomcat::7_0'} ) {
        push @features, { ns => "Cpanel::Easy::Tomcat::7_0", name => "Tomcat", version => "7.0" };
    }

    return \@features;
}

# Returns helpful feature information that the user might want to know more about
sub get_feature_info {
    my $self = shift;
    my $hr   = shift;
    my %data;

    $data{'name'}      = $hr->{'name'};
    $data{'note'}      = $hr->{'note'} if defined $hr->{'note'};
    $data{'support'}   = $hr->{'support'} if defined $hr->{'support'};
    $data{'ensurepkg'} = [ sort { $a cmp $b } @{ $hr->{'ensurepkg'} } ] if defined $hr->{'ensurepkg'};
    $data{'url'}       = $hr->{'url'} if defined $hr->{'url'};

    return \%data;
}

# Retrieves the contents of a profile, and converts into a human-readable version.
# NOTE: This assumes all profiles are in the profile directory.
sub get_all_features {
    my $self    = shift;
    my %args    = @_;
    my $profile = $args{'profile'};

    # Validate input
    die "Programmer Error: 'profile' argument required" unless $profile;
    my $path = ( $self->parse_profile_filename($profile) )[3];
    die "Profile name specified is missing a filename: $profile" unless $path;

    my $profile_ref = YAML::Syck::LoadFile($path);    # this can die, just like we do

    $profile_ref = $profile_ref->{'profile'} if defined $profile_ref->{'profile'};    # build logs store this in diff location

    # Get the major features first.
    my $major_features = $self->get_major_features( hashref => $profile_ref );

    # Now piece together the %features hash
    my $apache = ( grep { $_->{'name'} eq 'Apache' } @$major_features )[0];
    my $mpm    = ( grep { $_->{'name'} eq 'MPM' } @$major_features )[0];

    my %features = (
        'webserver' => {
            'name'    => $apache->{'name'},
            'ns'      => $apache->{'ns'},
            'version' => $apache->{'version'},
            'mpm'     => $mpm->{'version'}
        }
    );

    my $php = ( grep { $_->{'name'} eq 'PHP' } @$major_features )[0];
    if ($php) {
        $features{'php'} = {
            'name'    => $php->{'name'},
            'ns'      => $php->{'ns'},
            'version' => $php->{'version'},
        };
    }

    $features{'profile'} = Cpanel::Encoder::html_encode_str( $profile_ref->{'_meta'}{'name'} );

    # Now that we got the major things out of the way, fill in
    # the blanks
    while ( my ( $key, $val ) = each(%$profile_ref) ) {
        if ( $key =~ /^Apache$/ ) {
            while ( my ( $optns, $optval ) = each( %{ $val->{'optmods'} } ) ) {
                my $hr = $self->get_easyconfig_hr_from_ns_variations( 'Cpanel::Easy::Apache::' . $optns );
                next if $hr->{'skip'};
                $optval = int( !$optval ) if $hr->{'reverse'};
                next unless $optval;

                my $ref = $self->get_feature_info($hr);

                # the URLs in feature info are often templated, so try to convert it
                if ( defined $ref->{'url'} && $ref->{'url'} =~ /\[%/ ) {
                    $ref->{'url'} =~ s/\[\% apache_uri_version \%\]/$features{webserver}{version}/g;
                }

                push @{ $features{'webserver'}{'features'} }, { ns => "Cpanel::Easy::Apache::$optns", %$ref };
            }
        }
        elsif ( $key =~ /::PHP5/ ) {
            next unless defined $features{'php'};    # php not enabled
            next if $key =~ /::PHP5$/;               # we don't use this optmod entry
            next if $key =~ /::PHP5::\d+_\d+$/;      # already recorded php version

            my $hr = $self->get_easyconfig_hr_from_ns_variations($key);
            next if $hr->{'skip'};                   # option forceably disabled
            $val = int( !$val ) if $hr->{'reverse'};
            next unless $val;                        # option not selected

            my $ref = $self->get_feature_info($hr);
            push @{ $features{'php'}{'features'} }, { ns => $key, %$ref };
        }
        elsif ( $key =~ /^Cpanel::Easy::/ ) {
            my $hr = $self->get_easyconfig_hr_from_ns_variations($key);
            next if $hr->{'skip'};
            $val = int( !$val ) if $hr->{'reverse'};
            next unless $val;                        # option not selected

            my $ref = $self->get_feature_info($hr);
            push @{ $features{'extras'}{'features'} }, { ns => $key, %$ref };
        }
    }

    # sort each feature alphabetically for easier viewing
    for my $top (qw( webserver php extras )) {
        next unless defined $features{$top};
        if ( defined $features{$top}{'features'} ) {
            $features{$top}{'features'} = [ sort { $a->{'name'} cmp $b->{'name'} } @{ $features{$top}{'features'} } ];
        }
    }

    # Since 'extras' are optional optmods, we need a way to help
    # callers determine if there are any
    $features{'extras'}{'name'} = "Additional OptMods" if defined $features{'extras'};

    return \%features;
}

sub get_full_apache_version {
    my $version_key = shift;
    my $apache_hr   = shift;

    if ( $apache_hr->{$version_key}->{'version'} ) {
        return $apache_hr->{$version_key}->{'version'};
    }

    if ( $apache_hr->{$version_key}->{'src_cd2'} ) {
        my $src_version_key = $apache_hr->{$version_key}->{'src_cd2'};
        $src_version_key =~ s/^(apache|httpd)[_-]//i;
        return $src_version_key;
    }

    return $version_key;
}

1;
