#/*
# *  Copyright 2007-2008 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: Cap.pm 1645 2008-02-06 15:40:18Z hikarin $
#

package Zeromin::Cap;

use strict;
use base qw(Img0ch::Cap);
use Digest::MD5 qw();

sub new {
    my ( $zClass, $iObject, $pass ) = @_;
    my $class = ref $iObject || '';
    my ( $iKernel, $zCap );

    $pass ||= '';
    if ( $class eq 'Img0ch::BBS' or $class eq 'Zeromin::BBS' ) {
        $iKernel      = $iObject->get_kernel();
        $zCap         = $zClass->SUPER::new( $iObject, $pass );
        $zCap->{_bbs} = $iObject->get_id();
    }
    elsif ( $class eq 'Img0ch::Kernel' or $class eq 'Img0ch::Maple' ) {
        $iKernel = $iObject;
        require Img0ch::BBS;
        $zCap
            = $zClass->SUPER::new( Img0ch::BBS->new( $iKernel, { id => 0 } ),
            $pass );
        $zCap->{_bbs} = 0;
    }
    else {
        Img0ch::Kernel->throw_exception(
            'Img0ch::BBS or Img0ch::Maple(Img0ch::Kernel) not given');
    }

    $zCap->{__encoding}   = $iKernel->get_encoding(1);
    $zCap->{__group_pool} = {};
    $zCap->{__cap_pool}   = {};
    $zCap->{__kernel}     = $iKernel;
    $zCap->{__group_id}   = $zCap->{_repos}->get_int('I:C:G._');
    $zCap;
}

sub save {
    my ($zCap) = @_;
    my $iRepos = $zCap->{_repos};

    my $cp = $zCap->{__cap_pool};
    for my $id ( keys %{$cp} ) {
        my $value = $cp->{$id};
        my $name = $value->{name} || '';
        $name =~ tr/\r\n//d;
        $iRepos->set( "I:C:I.${id}.name", $name );
        $iRepos->set( "I:C:I.${id}.raw",  $value->{raw} || '' );
        $iRepos->set( "I:C:I.${id}.gid",  $value->{gid} || 0 );
        $iRepos->set( "I:C:I.${name}.id", $id );
    }

    my $gp = $zCap->{__group_pool};
    for my $id ( keys %{$gp} ) {
        my $value = $gp->{$id};
        my $name = $value->{name} || '';
        $name =~ tr/\r\n//d;
        $iRepos->set( "I:C:G.${id}.name",      $name );
        $iRepos->set( "I:C:G.${id}.privilege", $value->{privilege} || 0 );
        $iRepos->set( "I:C:G.${name}.id",      $id );
        for my $bbs ( @{ $value->{bbs} } ) {
            $iRepos->set( "I:C:G:S.${id}.${bbs}", 1 );
        }
    }

    $iRepos->set( 'I:C:G._', $zCap->{__group_id} );
    $iRepos->save();
    return 1;
}

sub get_current_cap {
    {   name => $_[0]->{__name},
        id   => $_[0]->{__id},
        gid  => $_[0]->{__gid},
    };
}

sub add {
    my ( $zCap, $cap ) = @_;
    $zCap->get_group_name( $cap->{gid} ) or return 0;
    my $pass = delete $cap->{pass} || '';
    my $cid = Digest::MD5::md5_hex($pass);
    (          $zCap->get_cap_raw_pass($cid) eq $pass
            || $zCap->get_cap_raw_pass( Digest::MD5::md5_base64($pass) ) )
        and return 0;
    $cap->{raw} = $pass;
    $cap->{name} =~ tr/\r\n//d;
    $zCap->_plugin( 'zeromin.create.cap', [ $cid, $cap ] );
    $zCap->{__cap_pool}->{$cid} = $cap;
    1;
}

sub edit {
    my ( $zCap, $id, $cap ) = @_;
    $zCap->get_cap_name($id) || return 0;
    delete $cap->{pass};
    my $cp = $zCap->{__cap_pool};

    if ( !exists $cp->{$id} ) {
        my $iRepos = $zCap->{_repos};
        my $gid    = $iRepos->get("I:C:I.${id}.gid");
        if ($gid) {
            $cp->{$id} = {
                name => $iRepos->get("I:C:I.${id}.name"),
                pass => $iRepos->get("I:C:I.${id}.raw"),
                gid  => $gid,
            };
        }
        else {
            return 0;
        }
    }
    my $one = $cp->{$id};
    while ( my ( $key, $value ) = each %{$cap} ) {
        $one->{$key} = $value;
    }
    $zCap->_plugin( 'zeromin.update.cap', [ $id, $one ] );
    $zCap->{__cap_pool}->{$id} = $one;
    1;
}

sub remove {
    my ( $zCap, $cid ) = @_;
    my $cp = $zCap->{__cap_pool};
    exists $cp->{$cid} and delete $cp->{$cid};

    my $iRepos = $zCap->{_repos};
    my $name = $iRepos->remove("I:C:I.${cid}.name") || '';
    $iRepos->remove("I:C:I.${cid}.raw");
    $iRepos->remove("I:C:I.${cid}.gid");
    $iRepos->remove("I:C:I.${name}.id");
    $zCap->_plugin( 'zeromin.remove.cap', [$cid] );
    1;
}

sub get_cap {
    my ( $zCap, $id, $unijp, $encoding ) = @_;
    my $iRepos = $zCap->{_repos};
    my $gid    = $iRepos->get("I:C:I.${id}.gid");

    if ($gid) {
        my $name = $iRepos->get("I:C:I.${id}.name");
        $unijp and $name = $unijp->set( $name, $encoding )->get();
        {   name  => $name,
            id    => $id,
            gid   => $gid,
            gname => $zCap->get_group_name($gid),
        };
    }
    else {
        {   name  => '',
            id    => undef,
            gid   => 0,
            gname => '',
        };
    }
}

sub get_caps {
    my ( $zCap, $unijp ) = @_;
    my $iRepos   = $zCap->{_repos};
    my $cache    = {};
    my $encoding = $zCap->{__encoding};
    my $ret      = [];

    $iRepos->iterate(
        sub {
            my ( $key, $value, $cache ) = @_;
            if ( $key =~ /\AI:C:I\.(.+?)\.(\w+)\z/xms ) {
                $2 eq 'id' and return 0;
                $cache->{$1} ||= {};
                $cache->{$1}->{$2} = $$value;
            }
            return 0;
        },
        $cache
    );

    while ( my ( $id, $info ) = each %{$cache} ) {
        my $gid  = $info->{gid};
        my $name = $info->{name};
        $unijp and $name = $unijp->set( $name, $encoding )->get();
        push @$ret,
            {
            name  => $name,
            id    => $id,
            gid   => $gid,
            gname => $zCap->get_group_name($gid)
            };
    }
    $ret;
}

sub get_caps_with_page {
    my ( $zCap, $unijp, $item_per_page, $offset ) = @_;

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

sub get_cap_pass {
    my ( $zCap, $name ) = @_;
    while ( my ( $cid, $value ) = each %{ $zCap->{__cap_pool} } ) {
        ( $value->{name} || '' ) eq $name and return $cid;
    }

    my $iRepos = $zCap->{_repos};
    my $id     = $iRepos->get("I:C:I.${name}.id");
    $id and return $id;

    $iRepos->iterate(
        sub {
            my ( $key, $value, $name, $id ) = @_;
            if ( $key =~ /\AI:C:I\.(.+?)\.name\z/xms and $$value eq $name ) {
                $$id = $1;
            }
            return 0;
        },
        $name,
        \$id
    );
    return $id || '';
}

*get_cap_id = \&get_cap_pass;

sub get_cap_name { shift->_get( 'cap', 'name', @_ ) }

sub get_cap_raw_pass { shift->_get( 'cap', 'raw', @_ ) }

sub get_cap_gid { shift->_get( 'cap', 'gid', @_ ) }

sub get_cap_belongs { shift->_get( 'cap', 'bbs', @_ ) }

sub add_group {
    my ( $zCap, $group ) = @_;
    my $iKernel = $zCap->{__kernel};
    my $bbs     = $group->{bbs};
    my $seen    = {};
    defined $bbs or $bbs = [ $zCap->{_bbs} ];
    ( ref $bbs || '' ) eq 'ARRAY' or $bbs = [$bbs];
    for my $scope (@$bbs) {
        $scope = Img0ch::Kernel::intval($scope);
        require Img0ch::BBS;
        my $iBBS = Img0ch::BBS->new( $iKernel, { id => $scope } );
        $seen->{ $iBBS->get_id() } = 1;
    }
    $group->{name} =~ tr/\r\n//d;
    $group->{bbs} = [ grep { $_ != 0 } keys %$seen ];
    @{ $group->{bbs} } or @{ $group->{bbs} } = (0);

    my $gid = ++$zCap->{__group_id};
    $zCap->_plugin( 'zeromin.create.cgroup', [ $gid, $group ] );
    $zCap->{__group_pool}->{$gid} = $group;
    return $gid;
}

sub edit_group {
    my ( $zCap, $gid, $group ) = @_;
    my $gp = $zCap->{__group_pool};

    if ( exists $group->{bbs} ) {
        my $iKernel = $zCap->{__kernel};
        my $bbs     = $group->{bbs};
        my $seen    = {};
        ( ref $bbs || '' ) eq 'ARRAY' or $bbs = [$bbs];

        for my $scope (@$bbs) {
            $scope = Img0ch::Kernel::intval($scope);
            if ($scope) {
                my $iBBS = Img0ch::BBS->new( $iKernel, { id => $scope } );
                my $id = $iBBS->get_id() || return 0;
                $seen->{$id} = 1;
            }
            else {
                $seen->{$scope} = 1;
            }
        }
        $group->{bbs} = [ grep { $_ != 0 } keys %$seen ];
        @{ $group->{bbs} } or @{ $group->{bbs} } = (0);

        $zCap->{_repos}->iterate(
            sub {
                my ( $key, $value, $gid ) = @_;
                $key =~ /\AI:C:G:S\.$gid\./xms and return -1;
                return 0;
            },
            $gid
        );
    }
    if ( !exists $gp->{$gid} ) {
        my $name = $zCap->{_repos}->get("I:C:G.${gid}.name");
        if ($name) {
            $gp->{$gid} = {
                bbs  => $group->{bbs},
                name => $name,
            };
        }
        else {
            return 0;
        }
    }
    my $one = $gp->{$gid};
    while ( my ( $key, $value ) = each %{$group} ) {
        $one->{$key} = $value;
    }
    $zCap->_plugin( 'zeromin.update.cgroup', [ $gid, $one ] );
    $zCap->{__group_pool}->{$gid} = $one;
    1;
}

sub remove_group {
    my ( $zCap, $gid ) = @_;
    my $iRepos = $zCap->{_repos};
    my $count  = 0;
    my $gp     = $zCap->{__group_pool};
    $iRepos->iterate(
        sub {
            my ( $key, $value, $gid, $count ) = @_;
            if ( $key =~ /\AI:C:I\.(?:.+)\.gid\z/ and $$value == $gid ) {
                $$count++;
            }
            return 0;
        },
        $gid,
        \$count
    );
    $count and return 0;

    exists $gp->{$gid} and delete $gp->{$gid};
    my $name = $iRepos->remove("I:C:G.${gid}.name");
    $iRepos->remove("I:C:G.${gid}.privilege");
    $iRepos->remove("I:C:G.${name}.id");
    $iRepos->iterate(
        sub {
            my ( $key, $value, $gid ) = @_;
            $key =~ /\AI:C:G:S\.$gid/xms and return -1;
            return 0;
        },
        $gid
    );
    $zCap->_plugin( 'zeromin.remove.cgroup', [$gid] );
    1;
}

sub get_group {
    my ( $zCap, $gid ) = @_;
    my $iRepos = $zCap->{_repos};
    my $name   = $iRepos->get("I:C:G.${gid}.name");

    if ($name) {
        my ( $count, $scope_stack ) = ( 0, [] );
        $iRepos->iterate(
            sub {
                my ( $key, $value, $gid, $count ) = @_;
                if ( $key =~ /\AI:C:I\.(?:.+)\.gid\z/ and $$value == $gid ) {
                    $$count++;
                }
                return 0;
            },
            $gid,
            \$count
        );
        $iRepos->iterate(
            sub {
                my ( $key, $value, $gid, $scope_stack ) = @_;
                if ( $key =~ /\AI:C:G:S\.$gid\.(.+?)\z/xms ) {
                    push @$scope_stack, $1;
                }
                return 0;
            },
            $gid,
            $scope_stack
        );
        {   bbs       => $scope_stack,
            count     => $count,
            id        => $gid,
            name      => $name,
            privilege => $iRepos->get_int("I:C:G.${gid}.privilege"),
        };
    }
    else {
        {   bbs       => [0],
            count     => 0,
            id        => 0,
            name      => '',
            privilege => 0,
        };
    }
}

sub get_groups {
    my ($zCap)      = @_;
    my $iRepos      = $zCap->{_repos};
    my $group_cache = {};
    my $scope_cache = {};
    my $ret         = [];

    $iRepos->iterate(
        sub {
            my ( $key, $value, $group_cache, $scope_cache ) = @_;
            if ( $key =~ /\AI:C:I\.(?:.+)\.gid\z/ ) {
                $group_cache->{$$value} ||= {};
                $group_cache->{$$value}->{count}++;
            }
            elsif ( $key =~ /\AI:C:G\.(.+?)\.(\w+)\z/xms ) {
                my ( $gid, $key ) = ( $1, $2 );
                $key eq 'id' and return 0;
                $group_cache->{$gid} ||= {};
                $group_cache->{$gid}->{$key} = $$value;
            }
            elsif ( $key =~ /\AI:C:G:S\.(.+?)\.(.+)\z/xms ) {
                my ( $gid, $bbs ) = ( $1, $2 );
                $scope_cache->{$gid} ||= [];
                push @{ $scope_cache->{$gid} }, $bbs;
            }
            return 0;
        },
        $group_cache,
        $scope_cache
    );

    while ( my ( $id, $info ) = each %{$group_cache} ) {
        push @$ret,
            {
            bbs => ( $scope_cache->{$id} || [0] ),
            count     => Img0ch::Kernel::intval( $info->{count} ),
            id        => $id,
            name      => $info->{name},
            privilege => $info->{privilege},
            };
    }
    $ret;
}

sub get_groups_with_page {
    my ( $zCap, $item_per_page, $offset ) = @_;

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

sub get_group_id {
    my ( $zCap, $name ) = @_;
    while ( my ( $gid, $value ) = each %{ $zCap->{__group_pool} } ) {
        ( $value->{name} || '' ) eq $name and return $gid;
    }

    my $iRepos = $zCap->{_repos};
    my $id     = $iRepos->get("I:C:G.${name}.id");
    $id and return $id;

    $iRepos->iterate(
        sub {
            my ( $key, $value, $name, $id ) = @_;
            if ( $key =~ /\AI:C:G\.(.+?)\.name\z/xms and $$value eq $name ) {
                $$id = $1;
            }
            return 0;
        },
        $name,
        \$id
    );
    return $id || 0;
}

sub get_group_name { shift->_get( 'group', 'name', @_ ) }

sub get_group_belongs {
    Img0ch::Kernel::intval( shift->_get( 'group', 'bbs', @_ ) );
}

sub remove_from_bbs {
    my ( $zCap, $bbs_id ) = @_;
    $bbs_id ||= $zCap->{_bbs};
    my $iRepos = $zCap->{_repos};
    $iRepos->iterate(
        sub {
            my ( $key, $value, $bbs_id ) = @_;
            $key =~ /\AI:C:G:S\.[^\.]+\.$bbs_id\z/xms and return -1;
            return 0;
        },
        $bbs_id
    );
    $iRepos->save();
    return 1;
}

sub _get {
    my ( $zCap, $type, $accessor, $id ) = @_;
    my $p = $zCap->{"__${type}_pool"};
    $id ||= '';

    if ( $p->{$id} ) {
        $p->{$id}->{$accessor};
    }
    else {
        my $prefix = { 'cap' => 'I:C:I', 'group' => 'I:C:G' }->{$type};
        my $iRepos = $zCap->{_repos};
        my $ret    = $iRepos->get("${prefix}.${id}.${accessor}");
        if ( !$ret ) {
            my $new_id;
            if ( defined $MIME::Base64::VERSION
                or eval 'require MIME::Base64; 1' )
            {
                $new_id
                    = MIME::Base64::encode_base64( pack( 'H*', $id ), '' );
            }
            else {
                defined $MIME::Base64::Perl::VERSION
                    or require MIME::Base64::Perl;
                $new_id
                    = MIME::Base64::Perl::encode_base64( pack( 'H*', $id ),
                    '' );
            }
            $new_id =~ tr/=//d;
            $ret = $iRepos->get("${prefix}.${new_id}.${accessor}");
        }
        return $ret;
    }
}

sub _plugin {
    my ( $zCap, $at, $argument ) = @_;
    my $iKernel = $zCap->{__kernel};
    require Zeromin::Plugin;
    my $zPlugin = Zeromin::Plugin->new($iKernel);
    $zPlugin->do( $at, $zCap, $argument );
    return;
}

1;
__END__
