#/*
# *  Copyright 2007-2010 hkrn <hikarin@users.sourceforge.jp>
# *
# *  Licensed 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.
# */
#
# $Id: Plugin.pm 1901 2009-12-31 02:40:20Z hikarin $
#

package Zeromin::Plugin;

use strict;
use base qw(Img0ch::Plugin);
use Module::Pluggable::Object qw();

sub new {
    my ( $zClass, $iObject, $key ) = @_;
    my $zPlugin;
    my $class = ref $iObject || '';

    if ( $class eq 'Img0ch::BBS' or $class eq 'Zeromin::BBS' ) {
        $zPlugin = $zClass->SUPER::new( $iObject, $key );
        $zPlugin->{__kernel} = $iObject->get_kernel();
    }
    elsif ( $class eq 'Img0ch::Kernel' or $class eq 'Img0ch::Maple' ) {
        require Img0ch::BBS;
        $zPlugin
            = $zClass->SUPER::new( Img0ch::BBS->new( $iObject, { id => 0 } ),
            $key );
        $zPlugin->{__kernel} = $iObject;
    }
    else {
        Img0ch::Kernel->throw_exception(
            'Img0ch::BBS or Img0ch::Maple(Img0ch::Kernel) not given');
    }

    my $type2num = $zPlugin->{_type2num};
    %{$type2num} = (
        %{$type2num},
        'zeromin.create.bbs'           => 100,
        'zeromin.remove.bbs'           => 101,
        'zeromin.update.bbs'           => 102,
        'zeromin.rebuild.bbs'          => 103,
        'zeromin.update.subject'       => 104,
        'zeromin.recreate.subject'     => 105,
        'zeromin.remove.thread'        => 106,
        'zeromin.replace.res'          => 107,
        'zeromin.erase.res'            => 108,
        'zeromin.remove.file'          => 109,
        'zeromin.stop.thread'          => 110,
        'zeromin.restart.thread'       => 111,
        'zeromin.restore.thread'       => 112,
        'zeromin.pool.thread'          => 113,
        'zeromin.remove.pool'          => 114,
        'zeromin.archive.thread'       => 115,
        'zeromin.remove.archive'       => 116,
        'zeromin.update.head'          => 117,
        'zeromin.update.meta'          => 118,
        'zeromin.update.foot'          => 119,
        'zeromin.update.banner.main'   => 120,
        'zeromin.update.banner.sub'    => 121,
        'zeromin.update.banner.mobile' => 122,
        'zeromin.update.thread'        => 123,
        'zeromin.update.setting'       => 124,
        'zeromin.repair.upload'        => 125,
        'zeromin.create.cap'           => 126,
        'zeromin.update.cap'           => 127,
        'zeromin.remove.cap'           => 128,
        'zeromin.create.user'          => 129,
        'zeromin.update.user'          => 130,
        'zeromin.remove.user'          => 131,
        'zeromin.create.cgroup'        => 132,
        'zeromin.update.cgroup'        => 133,
        'zeromin.remove.cgroup'        => 134,
        'zeromin.create.ugroup'        => 135,
        'zeromin.update.ugroup'        => 136,
        'zeromin.remove.ugroup'        => 137,
        'zeromin.create.category'      => 138,
        'zeromin.update.category'      => 139,
        'zeromin.remove.category'      => 140,
    );
    $zPlugin->{_type2num} = $type2num;

    my $num2type = {};
    while ( my ( $name, $value ) = each %{ $zPlugin->{_type2num} } ) {
        $num2type->{$value} = $name;
    }

    $zPlugin->{__load}        = {};
    $zPlugin->{__enabled}     = [];
    $zPlugin->{__disabled}    = [];
    $zPlugin->{__num2type}    = $num2type;
    $zPlugin->{__installed}   = 0;
    $zPlugin->{__uninstalled} = 0;
    $zPlugin->{__lastid}      = $zPlugin->{_repos}->get_int('I:P:E._');
    $zPlugin;
}

sub load {
    my ($zPlugin) = @_;

    if ( scalar caller eq 'Img0ch::Plugin' ) {
        shift @_;
        return $zPlugin->SUPER::load(@_);
    }

    my $iRepos   = $zPlugin->{_repos};
    my $bbs      = $zPlugin->{_bbs};
    my $enable   = $bbs ? "I:P.enabled.${bbs}" : 'I:P.enabled';
    my $disable  = $bbs ? "I:P.disabled.${bbs}" : 'I:P.disabled';
    my $enabled  = $iRepos->get($enable) || $iRepos->get('I:P.enabled');
    my $disabled = $iRepos->get($disable) || $iRepos->get('I:P.disabled');

    my $duplicate_check_hash = {};
    map { $duplicate_check_hash->{$_} = 1 } (
        index( $enabled, ',' )
        ? ( split ',', $enabled )
        : ($enabled)
    );
    my @enabled_plugins = grep { Img0ch::Kernel::intval($_) != 0 }
        keys %$duplicate_check_hash;

    %$duplicate_check_hash = ();
    map { $duplicate_check_hash->{$_} = 1 } (
        index( $disabled, ',' )
        ? ( split ',', $disabled )
        : ($disabled)
    );
    my @disabled_plugins = grep { Img0ch::Kernel::intval($_) != 0 }
        keys %$duplicate_check_hash;

    my $closure = sub {
        my ( $found, $enabled, $plugins ) = @_;
        for my $plugin ( @{$plugins} ) {
            my $class  = $iRepos->get("I:P:E.${plugin}.class");
            my $method = $iRepos->get("I:P:E.${plugin}.method");
            my $type   = $iRepos->get("I:P:E.${plugin}.type");
            $found->{$class} ||= {};

            # pid, type, enabled, installed
            $found->{$class}->{$method} = [ $plugin, $type, $enabled, 0 ];
        }
    };

    my $found = {};
    $closure->( $found, 1, \@enabled_plugins );
    $closure->( $found, 0, \@disabled_plugins );
    %{ $zPlugin->{__load} }     = %{$found};
    @{ $zPlugin->{__enabled} }  = @enabled_plugins;
    @{ $zPlugin->{__disabled} } = @disabled_plugins;
    return 1;
}

sub save {
    my ($zPlugin) = @_;
    my $iRepos = $zPlugin->{_repos};
    my $i        = $zPlugin->{__lastid} || 1;
    my $loaded   = $zPlugin->{__load};
    my $num2type = $zPlugin->{__num2type};
    my $typemap  = {};

    while ( my ( $class, $hash ) = each %{ $zPlugin->{__load} } ) {
        my $class_table = $loaded->{$class} || {};
        while ( my ( $method, $value ) = each %{$hash} ) {

            # for installing
            my $type = $num2type->{ $value->[1] } || '';
            $typemap->{$type} ||= [];
            if ( $value->[3] ) {
                push @{ $typemap->{$type} }, $i;
                $iRepos->set( "I:P:E.${i}.class",  $class );
                $iRepos->set( "I:P:E.${i}.method", $method );
                $iRepos->set( "I:P:E.${i}.type",   $type );

                # $value->[0] = $i; $i++;
                $value->[0]             = $i++;
                $value->[3]             = 0;
                $class_table->{$method} = $value;
            }
            else {
                push @{ $typemap->{$type} }, $value->[0];
            }
        }
        %{ $loaded->{$class} } = %{$class_table};
    }

    # $zPlugin->{__lastid} = $i; $i++
    $zPlugin->{__lastid} = $i++;
    $iRepos->set( 'I:P:E._', $i );
    %{ $zPlugin->{__load} } = %{$loaded};

    while ( my ( $type_name, $plugin_ids ) = each %{$typemap} ) {
        $type_name or next;
        @$plugin_ids = grep { Img0ch::Kernel::intval($_) != 0 } @$plugin_ids;
        $iRepos->set( "I:P.type.${type_name}", join( ',', @$plugin_ids ) );
    }

    my ( $enabled, $disabled ) = ( {}, {} );
    while ( my ( $class, $hash ) = each %{ $zPlugin->{__load} } ) {
        while ( my ( $method, $value ) = each %{$hash} ) {
            $value->[0] or next;
            if ( $value->[2] ) {
                $enabled->{ $value->[0] } = 1;
            }
            else {
                $disabled->{ $value->[0] } = 1;
            }
        }
    }

    my $bbs              = $zPlugin->{_bbs};
    my $enable           = $bbs ? "I:P.enabled.${bbs}" : 'I:P.enabled';
    my $disable          = $bbs ? "I:P.disabled.${bbs}" : 'I:P.disabled';
    my @enabled_plugins  = keys %$enabled;
    my @disabled_plugins = keys %$disabled;
    @{ $zPlugin->{__enabled} }  = @enabled_plugins;
    @{ $zPlugin->{__disabled} } = @disabled_plugins;
    $iRepos->set( $enable,  join( ',', @enabled_plugins ) );
    $iRepos->set( $disable, join( ',', @disabled_plugins ) );
    $iRepos->save();
    1;
}

sub get {
    my ( $zPlugin, $class ) = @_;
    my $found = $zPlugin->{__load};
    exists $found->{$class} ? $found->{$class} : {};
}

sub get_id {
    my ( $zPlugin, $qualified, $bbs ) = @_;
    defined $bbs or $bbs = $zPlugin->{_bbs};
    my ( $cls, $mtd ) = _split($qualified);
    my $id = 0;

    while ( my ( $class, $hash ) = each %{ $zPlugin->{__load} } ) {
        while ( my ( $method, $value ) = each %{$hash} ) {
            if ( $class eq $cls and $method eq $mtd ) {
                $id = $value->[0];
            }
        }
    }
    $id and return $id;

    my $iRepos   = $zPlugin->{_repos};
    my $enable   = $bbs ? "I:P.enabled.${bbs}" : 'I:P.enabled';
    my $disable  = $bbs ? "I:P.disabled.${bbs}" : 'I:P.disabled';
    my $enabled  = $iRepos->get($enable);
    my $disabled = $iRepos->get($disable);

    my @enabled_plugins = (
        index( $enabled, ',' )
        ? ( split ',', $enabled )
        : ($enabled)
    );
    my @disabled_plugins = (
        index( $disabled, ',' )
        ? ( split ',', $disabled )
        : ($disabled)
    );

    my $closure = sub {
        my ( $id, $cls, $mtd, $plugins ) = @_;
        for my $plugin ( @{$plugins} ) {
            my $class  = $iRepos->get("I:P:E.${plugin}.class");
            my $method = $iRepos->get("I:P:E.${plugin}.method");
            if ( $class eq $cls and $method eq $cls ) {
                $$id = $plugin;
                last;
            }
        }
    };

    $closure->( \$id, $cls, $mtd, \@enabled_plugins );
    $id and return $id;
    $closure->( \$id, $cls, $mtd, \@disabled_plugins );
    return $id;
}

sub get_plugin {
    my ( $zPlugin, $id ) = @_;
    my $iRepos = $zPlugin->{_repos};
    my $class  = $iRepos->get("I:P:E.${id}.class");
    my $method = $iRepos->get("I:P:E.${id}.method");
    my $type   = $zPlugin->get_type_num( $iRepos->get("I:P:E.${id}.type") );
    return ( $class, $method, $type );
}

sub get_type_num { $_[0]->{_type2num}->{ $_[1] } }

sub get_type { $_[0]->{__num2type}->{ $_[1] } }

sub set_bbs_id { $_[0]->{_bbs} = $_[1]; return; }

sub copy {
    my ( $zPluginTo, $zPluginFrom ) = @_;
    my $found = {};

    while ( my ( $class, $hash ) = each %{ $zPluginFrom->{__load} } ) {
        $found->{$class} ||= {};
        while ( my ( $method, $value ) = each %{$hash} ) {
            $found->{$class}->{$method}
                = [ $value->[0], $value->[1], $value->[2] ];
        }
    }
    %{ $zPluginTo->{__load} } = %{$found};
    1;
}

sub all {
    my ( $zPlugin, $zPluginMergeFrom ) = @_;
    my $load     = $zPlugin->{__load};
    my $mf_load  = $zPluginMergeFrom->{__load};
    my $num2type = $zPlugin->{__num2type};
    my $ret      = {};

    while ( my ( $class, $entry1 ) = each %{$load} ) {
        $class or next;
        my $mf_class_entry = $mf_load->{$class} || {};
        while ( my ( $method, $entry2 ) = each %{$entry1} ) {
            $method or next;
            my $mf_method_entry = $mf_class_entry->{$method} || [];
            $ret->{ $entry2->[0] } = {
                class   => $class,
                method  => $method,
                type    => $entry2->[1],
                enabled => ( $mf_method_entry->[2] || $entry2->[2] ),
            };
        }
    }

    $ret;
}

sub filter_with_page {
    my ( $zPlugin, $zPluginMergeFrom, $query, $item_per_page, $offset ) = @_;
    my $classes = {};
    my $content = [];
    my $plugins = $zPlugin->all($zPluginMergeFrom);

    while ( my ( $id, $entry ) = each %$plugins ) {
        my $class = $entry->{class};
        $classes->{$class} = 1;
        ( $query and $class ne $query ) and next;
        push @$content,
            {
            id      => $id,
            class   => $class,
            method  => $entry->{method},
            type    => $entry->{type},
            enabled => $entry->{enabled}
            };
    }

    my $class = [];
    map { push @$class, { name => $_ } } sort keys %$classes;

    defined $Data::Page::VERSION or require Data::Page;
    my $page = Data::Page->new( scalar @$content, $item_per_page, $offset );
    return ( [ $page->splice($content) ], $page, $class );
}

sub retrive {
    my ( $zPlugin, $class, $bbs, $key ) = @_;
    $class ||= '';
    my $id
        = $class =~ /\A\d+\z/xms
        ? $class
        : $zPlugin->get_id( $class, $bbs );
    $zPlugin->SUPER::retrive( $id, $bbs, $key );
}

sub store {
    my ( $zPlugin, $class, $bbs, $key, $data ) = @_;
    $class ||= '';
    my $id
        = $class =~ /\A\d+\z/xms
        ? $class
        : $zPlugin->get_id( $class, $bbs );
    $zPlugin->SUPER::store( $id, $bbs, $key, $data );
    return;
}

sub enable { _switch( @_, 1 ) }

sub disable { _switch( @_, 0 ) }

sub is_enabled {
    my ( $zPlugin, $class ) = @_;
    my ( $cls, $mtd )
        = $class =~ /\A(\d+)\z/xms
        ? $zPlugin->get_plugin($1)
        : _split($class);
    $zPlugin->{__load}->{$cls}->{$mtd}->[2] ? 1 : 0;
}

sub search {
    my ( $zPlugin, $path, $uninstall ) = @_;
    my $i = 0;
    $path ||= ['Img0ch::Plugin'];
    my $mpo = Module::Pluggable::Object->new(
        inner       => 0,
        only        => qr/\A.*::Install\z/xms,
        search_path => $path,
        require     => 1,
    );

    my ( $method, $attr );
    if ($uninstall) {
        $method = 'uninstall';
        $attr   = '__uninstalled';
    }
    else {
        $method = 'install';
        $attr   = '__installed';
    }

    for my $plugin ( $mpo->plugins() ) {
        my $inst = UNIVERSAL::can( $plugin, $method ) || next;
        $inst->($zPlugin);
    }
    my $affected = $zPlugin->{$attr};
    $zPlugin->{$attr} = 0;

    return $affected;
}

sub install {
    my ( $zPlugin, $hash ) = @_;
    my $iRepos   = $zPlugin->{_repos};
    my $class    = $hash->{package};
    my $loaded   = $zPlugin->{__load}->{$class} || {};
    my $type2num = $zPlugin->{_type2num};
    my $merge    = {};

    while ( my ( $method, $type ) = each %{ $hash->{methods} } ) {
        my $num   = $type2num->{$type};
        my $count = 0;
        $num or next;
        if ( exists $loaded->{$method} ) {
            $merge->{$method} = $loaded->{$method};
            $merge->{$method}->[1] = $num;
        }
        else {

            # pid, type, default is disabled, installing
            $merge->{$method} = [ 0, $num, 0, 1 ];
            $zPlugin->{__installed}++;
        }
    }
    my $temp = $zPlugin->{__load}->{$class} || {};
    $zPlugin->{__load}->{$class} = { %{$temp}, %{$merge} };

    1;
}

sub uninstall {
    my ( $zPlugin, $hash ) = @_;
    my $iRepos   = $zPlugin->{_repos};
    my $class    = $hash->{package};
    my $type2num = $zPlugin->{_type2num};

    while ( my ( $method, $type ) = each %{ $hash->{methods} } ) {
        my $id = $zPlugin->get_id("${class}::${method}") || return 0;
        $iRepos->remove("I:P:E.${id}.class");
        $iRepos->remove("I:P:E.${id}.method");
        $iRepos->remove("I:P:E.${id}.type");
        delete $zPlugin->{__load}->{$class}->{$method};
        $zPlugin->{__uninstalled}++;
    }
    delete $zPlugin->{__load}->{$class};

    1;
}

sub set_form {
    my ( $zPlugin, $id, $bbs, $key ) = @_;
    $bbs ||= $zPlugin->{_bbs} || 0;
    $key ||= $zPlugin->{_key} || 0;
    require Zeromin::Plugin::Config;
    my $zPConfig = Zeromin::Plugin::Config->new( $zPlugin, $id, $bbs, $key );
    return $zPConfig;
}

sub remove_from_bbs {
    my ( $zPlugin, $bbs_id ) = @_;
    $bbs_id ||= $zPlugin->{_bbs};

    require Img0ch::BBS;
    my $iBBS = Img0ch::BBS->new( $zPlugin->{__kernel}, { id => $bbs_id } );
    my $bbs_dir = $iBBS->get_name() or return 0;

    my $iReposEntries = $zPlugin->{_repos};
    $iReposEntries->remove("I:P.enabled.${bbs_dir}");
    $iReposEntries->remove("I:P.disabled.${bbs_dir}");
    $iReposEntries->save();
    my $iReposData = $zPlugin->{_data};
    $iReposData->iterate(
        sub {
            my ( $key, $value, $bbs_dir ) = @_;
            $key =~ /\AI:P:S\.[^\.]+\.$bbs_dir/xms and return -1;
            return 0;
        },
        $bbs_dir
    );
    $iReposData->save();
    return 1;
}

sub _split { my @ret = ( $_[0] || '.::.' ) =~ /\A(.+)::(.+)\z/xms }

sub _switch {
    my ( $zPlugin, $class, $bit ) = @_;
    my ( $cls, $mtd )
        = $class =~ /\A(\d+)\z/xms
        ? $zPlugin->get_plugin($1)
        : _split($class);
    exists $zPlugin->{__load}->{$cls} or return 0;
    exists $zPlugin->{__load}->{$cls}->{$mtd} or return 0;
    $zPlugin->{__load}->{$cls}->{$mtd}->[2] = $bit;
    return 1;
}

1;
__END__
