package Cpanel::Easy::Utils::NameSpace;

# cpanel - Cpanel/Easy/Utils/NameSpace.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 Cpanel::StringFunc ();

=pod

=head1 Description

Utilities that handle EasyApache OptMod initialization.

=head1 API

The following sections contain a description of each API in this module.

=cut

sub ns_is_or_belongs_to_list {
    my ( $self, $ns, @search ) = @_;
    return if !$self->is_namespace($ns);

    for my $xns (@search) {
        my $real_xns = $xns !~ m{^Cpanel::Easy::} ? 'Cpanel::Easy::' . $xns : $xns;
        return 1 if $ns eq $real_xns || $ns =~ m{^$real_xns\:\:\w+};
    }

    return;
}

sub is_namespace {
    shift;
    return if !defined $_[0];
    goto &Cpanel::StringFunc::is_namespace;
}

sub get_file_from_namespace {
    shift;
    goto &Cpanel::StringFunc::get_file_from_namespace;
}

sub get_dir_from_namespace {
    shift;
    goto &Cpanel::StringFunc::get_dir_from_namespace;
}

sub get_main_and_name_from_ns {
    my ( $self, $ns ) = @_;
    return if !$self->is_namespace($ns);

    my @main = split( /::/, $ns );
    my $name = pop @main;

    return ( join( '::', @main ), $name );
}

sub get_ns_value_from_profile {
    my ( $self, $ns, $profile_setup ) = @_;
    return if !$self->is_namespace($ns);
    my ( $main, $name ) = $self->get_main_and_name_from_ns($ns);

    my $rc;

    if ( ref $profile_setup ne 'HASH' ) {
        if ( ref $self->{'working_profile'} eq 'HASH' ) {
            $profile_setup = $self->{'working_profile'};
        }
        else {
            $profile_setup = $self->deserialize( $self->determine_profile() );
        }
    }

    if ( $main eq 'Cpanel::Easy::' . $self->{'state_config'}{'main_name'} ) {
        if ( grep /^$name$/, @{ $self->{'state_config'}{'main_vers'} } ) {
            $profile_setup->{ $self->{'state_config'}{'main_name'} }{'version'} ||= '2_2';    # this should only be necessary on brand new cPanel installs
            $rc = $profile_setup->{ $self->{'state_config'}{'main_name'} }{'version'} eq $name ? 1 : 0;
        }
        else {
            $rc = $profile_setup->{ $self->{'state_config'}{'main_name'} }{'optmods'}{$name};
        }
    }
    else {
        $rc = $profile_setup->{$ns};
    }

    return $rc;
}

sub set_ns_value_in_profile {
    my ( $self, $ns, $profile_setup, $value ) = @_;
    return if !$self->is_namespace($ns);
    my ( $main, $name ) = $self->get_main_and_name_from_ns($ns);

    if ( $main eq 'Cpanel::Easy::' . $self->{'state_config'}{'main_name'} ) {
        if ( grep /^$name$/, @{ $self->{'state_config'}{'main_vers'} } ) {
            $profile_setup->{ $self->{'state_config'}{'main_name'} }{'version'} = $name;
        }
        else {
            $profile_setup->{ $self->{'state_config'}{'main_name'} }{'optmods'}{$name} = $value;
        }
    }
    else {
        $profile_setup->{$ns} = $value;
    }

    return 1;
}

sub get_easyconfig_hr_from_ns_via_state {
    my ( $self, $ns ) = @_;
    return if !$self->is_namespace($ns);
    my ( $main, $name ) = $self->get_main_and_name_from_ns($ns);

    my $rc;

    if ( $main eq 'Cpanel::Easy::' . $self->{'state_config'}{'main_name'} ) {
        if ( grep /^$name$/, @{ $self->{'state_config'}{'main_vers'} } ) {
            $rc = $self->{'state'}{'optmods'}{ $self->{'state_config'}{'main_name'} }{'_main'}{$name};
        }
        else {
            $rc = $self->{'state'}{'optmods'}{ $self->{'state_config'}{'main_name'} }{$name};
        }
    }
    else {
        $rc = $self->{'state'}{'optmods'}{$ns};
    }
    return $rc;
}

sub get_easyconfig_hr_from_ns_variations {
    my ( $self, $parent_ns, @list ) = @_;
    my %hr;

    my @std = $self->get_std_variation_of_ns_list( $parent_ns, 1 );
    my @spec;
    for my $vns (@list) {
        for my $std (@std) {
            push @spec, $vns . '::' . $std;
        }
    }

    for my $ns ( $parent_ns, $self->get_variations_of_ns_from_list( $parent_ns, @std, @spec ) ) {
        my $ea = $self->get_easyconfig_hr_from_ns( $ns, $parent_ns );

        if ( ref $ea eq 'HASH' ) {
            %hr = %{ $self->merge_easyconfig_hrs_into_one( \%hr, $ea ) };
        }
    }

    if ( exists $hr{'modself'} ) {
        if ( ref $hr{'modself'} eq 'CODE' ) {
            $hr{'modself'}->( $self, \%hr, $self->{'working_profile'} );
        }
    }

    if ( exists $hr{'cpversion'} ) {
        unless ( $self->meet_cpversion_requirements( $hr{'cpversion'} ) ) {
            $hr{'skip'}                       = 1;
            $hr{'treat_as_off_while_skipped'} = 1;
            $hr{'hastargz'}                   = 0;
        }
    }

    if ( exists $hr{'license_url'} && $hr{'license_url'} =~ m{^\S+\:\/\/} ) {
        $self->create_note_and_verify_on_from_license_url( \%hr, $self->{'working_profile'} );
    }

    if ( exists $hr{'implies'} && ref $hr{'implies'} eq 'HASH' ) {
        local $self->{'cache'}{'create_note_and_verify_on_from_implies'}{$parent_ns} =
          exists $self->{'cache'}{'create_note_and_verify_on_from_implies'}{$parent_ns}
          ? ( $self->{'cache'}{'create_note_and_verify_on_from_implies'}{$parent_ns} + 1 )
          : 1;

        if ( $self->{'cache'}{'create_note_and_verify_on_from_implies'}{$parent_ns} == 1 ) {
            $self->{'cache'}{'create_note_and_verify_on_from_implies'}{$parent_ns}++;
            $self->create_note_and_verify_on_from_implies( \%hr, $self->{'working_profile'} );
        }
    }

    return \%hr;
}

sub get_easyconfig_hr_from_ns {
    my ( $self, $ns, $parent_ns ) = @_;

    return if !$self->is_namespace($ns);
    $parent_ns = $ns if !$self->is_namespace($parent_ns);

    if ( exists $self->{'_'}{'do_not_modify: ns_variations_easyconfig_cache'}{$ns} ) {
        $self->unload_ns($ns);
    }

    eval qq{use $ns;};

    if ($@) {
        $self->{'easyconfig_hr_fetch_errors'}{$parent_ns}{$ns} = $@;
        return;
    }

    no strict 'refs';
    if ( ref ${ $parent_ns . '::easyconfig' } eq 'ARRAY' ) {
        my ( $meth, @args ) = @{ ${ $parent_ns . '::easyconfig' } };
        return if !$self->can($meth);
        ${ $parent_ns . '::easyconfig' } = $self->$meth(@args);
    }

    if ( ref ${ $parent_ns . '::easyconfig' } eq 'HASH' ) {
        $self->{'easyconf_version'}{$parent_ns}{$ns} = ${ $parent_ns . '::easyconfig' }->{'version'} || 0;    # or 'revision' ??
        $self->{'easyconf_version'}{$parent_ns}{$ns} =~ s{\$Rev\:(\d+)\s+\$}{$1};
        $self->{'_'}{'do_not_modify: ns_variations_easyconfig_cache'}{$ns} = ${ $parent_ns . '::easyconfig' };

        return ${ $parent_ns . '::easyconfig' };
    }

    return;
}

sub get_std_variation_of_ns_list {
    my ( $self, $parent_ns, $return_rawlist ) = @_;

    if ( !$self->{'working_profile'} ) {
        $self->{'working_profile'} = $self->deserialize( $self->determine_profile() );
    }

    # 'generic_os' and 'getos' lists needs to be in the same order
    my @std = (
        $self->{'generic_os'},

        # "$self->{'generic_os'}::$self->{'generic_os_version'}",
        "$self->{'generic_os'}::$self->{'cpu_bits'}",

        # "$self->{'generic_os'}::$self->{'cpu_bits'}::$self->{'generic_os_version'}",
        $self->{'getos'},

        # "$self->{'getos'}::$self->{'getos_version'}",
        "$self->{'getos'}::$self->{'cpu_bits'}",

        # "$self->{'getos'}::$self->{'cpu_bits'}::$self->{'getos_version'}"
    );

    my $v = $self->{'working_profile'}->{ $self->{'state_config'}{'main_name'} }{'version'};

    my $pns = $parent_ns;
    $pns =~ s/::[^\:]+$//;
    if ( $pns->can('versions') ) {
        if ( $self->{'working_profile'}{$pns} ) {
            for my $spec ( $pns->versions() ) {
                if ( $self->{'working_profile'}{ $pns . '::' . $spec } ) {
                    $v = $spec;
                    last;
                }
            }
        }
    }

    if ($v) {
        push @std, map { $v . '::' . $_ } @std;
        push @std, $v;
    }

    return @std if $return_rawlist;
    return $self->get_variations_of_ns_from_list( $parent_ns, @std );
}

sub get_variations_of_ns_from_list {
    my ( $self, $parent_ns, @variations ) = @_;
    return map { $self->get_variation_of_ns( $parent_ns, $_ ) }
      grep { defined($_) && $_ ne '' && $_ !~ m/^::|::::|::$/g } @variations;
}

sub get_variation_of_ns {
    my ( $self, $parent_ns, $graft_in ) = @_;
    $graft_in .= '::' if $graft_in !~ m{::$};

    if ( $parent_ns =~ m{::} ) {
        $parent_ns =~ s{(.*::)(\w+)}{$1$graft_in$2};
    }
    else {
        $parent_ns = $graft_in . $parent_ns;    # probably never used in Cpanel::Easy but just in case it ever is moved :)
    }

    return $parent_ns;
}

sub ns_is_include_that_has_versions {
    my ( $self, $ns ) = @_;

    return if !$self->is_namespace($ns);

    if ( grep /^$ns$/, @{ $self->{'state_config'}{'include'} } ) {
        if ( $ns->can('versions') ) {
            return 1 if $ns->versions();
        }
    }

    return;
}

sub ns_is_include_kid_and_parent_is_off {
    my ( $self, $ns, $profile_hr ) = @_;

    return if !$self->is_namespace($ns);
    my ( $main, $name ) = $self->get_main_and_name_from_ns($ns);

    if ( grep( /^$main$/, @{ $self->{'state_config'}{'include'} } ) && $name ) {
        return 1 if !$self->get_ns_value_from_profile( $main, $profile_hr );
    }

    return;
}

sub unload_ns {
    my ( $self, $ns ) = @_;
    require Cpanel::CPAN::Module::Refresh;
    Cpanel::CPAN::Module::Refresh->unload_module( $self->get_file_from_namespace( $ns, 1 ) );
}

=pod

=head2 Cpanel::Easy::Utils::NameSpace::meet_cpversion_requirements()

Processes the optional 'cpversion' requirements hash in an OptMod to
determine if it should be available to the administrator given their
current cPanel & WHM version.

  Input:
    Hash reference -- 'cpversion' hash reference

  Output:
    Boolean value -- 1 if requirements are met, 0 if not
=cut

sub meet_cpversion_requirements {
    my $self    = shift;
    my $r       = shift;
    my $cpver   = $self->get_cpanel_version();
    my $allowed = 0;

    unless ( defined $r && ref $r eq 'ARRAY' ) {
        $self->print_alert(q{Developer Error: Invalid cpversion OptMod hash structure});
        return 0;
    }

    return 1 if $#$r < 0;    # no requirements defined, must be OK

    # sort tiers in increasing order to make checks easier
    my @versions = sort { $a->{'tier'} <=> $b->{'tier'} } @$r;

    # current cpanel version came before the oldest specified version
    return 0 if $self->cmp_cpanel_tier( $cpver, '<', $versions[0]->{'tier'} );

    # current cpanel version is newer the latest specified version
    return 1 if $self->cmp_cpanel_tier( $cpver, '>', $versions[$#versions]->{'tier'} );

    # not before, not after, must be somewhere in between
    for my $ver (@versions) {

        # same tier?  if not, move to next comparison
        next unless $self->cmp_cpanel_tier( $cpver, '==', $ver->{'tier'} );

        # found same tier, if there's a minversion, compare it
        if ( exists $ver->{'minversion'} ) {
            last if Cpanel::Version::Compare::compare( $cpver, '<', $ver->{'minversion'} );
        }

        # otherwise, cpanel version is ok
        $allowed = 1;
        last;
    }

    return $allowed;
}

1;

__END__
