# This code was forked from the LiveJournal project owned and operated
# by Live Journal, Inc. The code has been modified and expanded by
# Dreamwidth Studios, LLC. These files were originally licensed under
# the terms of the license supplied by Live Journal, Inc, which can
# currently be found at:
#
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
#
# In accordance with the original license, this code and all its
# modifications are provided under the GNU General Public License.
# A copy of that license can be found in the LICENSE file included as
# part of this distribution.

package LJ::BetaFeatures;

use strict;
use Carp qw(croak);

use LJ::ModuleLoader;

my %HANDLER_OF_KEY = ();                                                 # key -> handler object
my @HANDLER_LIST   = LJ::ModuleLoader->module_subclasses(__PACKAGE__);
my @DW_HANDLER_LIST = LJ::ModuleLoader->module_subclasses("DW::BetaFeatures");

foreach my $handler ( @HANDLER_LIST, @DW_HANDLER_LIST ) {
    eval "use $handler";
    die "Error loading handler '$handler': $@" if $@;
}

sub get_handler {
    my $class = shift;
    my $key   = shift;

    my $handler = $HANDLER_OF_KEY{$key};
    return $handler if $handler;

    my $use_key = 'default';
    foreach my $cl ( @HANDLER_LIST, @DW_HANDLER_LIST ) {
        my $subcl = lc( ( split( "::", $cl ) )[-1] );
        next if $subcl eq 'default';
        next unless $subcl eq $key;

        $HANDLER_OF_KEY{$key} = $cl->new($key);
        last;
    }

    # have one now?
    $handler = $HANDLER_OF_KEY{$key};
    return $handler if $handler;

    # need to instantiate default and register that
    $handler = ( __PACKAGE__ . "::default" )->new($key);
    return $HANDLER_OF_KEY{$key} = $handler;
}

sub add_to_beta {
    my $class = shift;
    my ( $u, $key ) = @_;

    my $handler = $class->get_handler($key);
    die "No handler for beta."                      unless $handler;
    die "This beta test is inactive."               unless $handler->is_active;
    die "You do not have access to this beta test." unless $handler->user_can_add($u);

    # add the cap value if they're adding it
    unless ( $u->in_class( $class->cap_name ) ) {
        $u->add_to_class( $class->cap_name );
    }

    my $propval  = $u->prop( $class->prop_name ) // '';
    my @features = split( /\s*,\s*/, $propval );
    return 1 if grep { $_ eq $key } @features;

    push @features, $key;
    my $newval = join( ",", @features );
    $u->set_prop( $class->prop_name => $newval );
    $handler->add_to_beta($u);
    return 1;
}

sub remove_from_beta {
    my $class = shift;
    my ( $u, $key ) = @_;

    my $handler = $class->get_handler($key);
    die "No handler for beta." unless $handler;

    # we can just return if they're not beta testing anything
    return 1 unless $u->in_class( $class->cap_name );

    # remove the feature from the prop list
    my $propval  = $u->prop( $class->prop_name );
    my @features = split( /\s*,\s*/, $propval );
    my @newkeys  = ();
    @newkeys = grep { $_ ne $key } @features;

    # they're a member of no active beta tests?
    unless (@newkeys) {
        $u->clear_prop( $class->prop_name );
        $handler->remove_from_beta($u);
        $u->remove_from_class( $class->cap_name );
        return 1;
    }

    # we have something to set
    my $newval = join( ",", @newkeys );

    # they're already in the cap class
    $u->set_prop( $class->prop_name => $newval );
    $handler->remove_from_beta($u);
    return 1;
}

sub user_in_beta {
    my ( $class, $u, $key ) = @_;

    my $key_handler = $class->get_handler($key);
    return 1 if $key_handler->is_sitewide_beta;
    return 0 unless $u;

    # is the cap set?
    return 0 unless $u->in_class( $class->cap_name );

    # cap is set, what does their prop say?
    my $propval = $u->prop( $class->prop_name );
    unless ($propval) {
        $u->remove_from_class( $class->cap_name );
        return 0;
    }

    # they have some prop value set, which features
    # are they testing?
    my @features = split( /\s*,\s*/, $propval );

    my $dirty   = 0;
    my $ret_val = 0;
    my @newkeys = ();
    foreach my $fkey (@features) {
        my $handler = $class->get_handler($fkey);
        unless ( $handler->is_active($fkey) ) {
            $dirty = 1;
            next;
        }

        # they should still be in this active fkey
        push @newkeys, $fkey;

        # is this the key that we're looking for?
        $ret_val = 1 if $fkey eq $key;
    }

    # now we know if they are in the requested class
    # -- we'll only proceed further if we need to clean
    #    up their prop value
    return $ret_val unless $dirty;

    # we need to change their prop value

    # they're a member of no active beta tests?
    unless (@newkeys) {
        $u->clear_prop( $class->prop_name );
        $u->remove_from_class( $class->cap_name );
        return $ret_val;    # should be 0
    }

    # we have something to set
    my $newval = join( ",", @newkeys );

    # they're already in the cap class
    $u->set_prop( $class->prop_name => $newval );
    return $ret_val;        # could be 1 or 0
}

sub prop_name { 'betafeatures_list' }
sub cap_name  { 'betafeatures' }

1;
