#/*
# *  Copyright 2007 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: migrate.pm 610 2007-05-09 14:49:15Z hikarin $
#

package Zeromin::App::migrate;

use strict;

sub archive {
    my ($zApp) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->can_create_archive() or return { code => 1 };
    $zUser->can_remove_archive() or return { code => 1 };
    $zUser->can_update_archive() or return { code => 1 };

    require Zeromin::Archive;
    require Zeromin::BBS;
    my $iKernel = $zApp->kernel();
    my $zBBS    = Zeromin::BBS->new( $iKernel, { id => 0 } );
    my $bbs_all = $zBBS->all();
    foreach my $bbs (@$bbs_all) {
        my $iBBS = Img0ch::BBS->new( $iKernel, { id => $bbs->{id} } );
        my $zArchive = Zeromin::Archive->new($iBBS);
        _archive( $iKernel, $zArchive, $iBBS );
    }
    return { code => 0, archive => 1 };
}

sub _archive {
    my ( $iKernel, $zArchive, $iBBS ) = @_;
    my $base = $iBBS->path('kako');
    -d $base or return;

    local ( *DH, $! );
    opendir *DH, $base or $iKernel->throw_io_exception($base);
    my @dirs = readdir *DH;
    closedir *DH or $iKernel->throw_io_exception($base);

    require File::Copy;
    require File::Find;
    require File::Spec;
    my $hash = {};
    for my $dir ( File::Spec->no_upwards(@dirs) ) {
        $dir =~ /\A\d{3,3}\z/xms or next;
        $dir = File::Spec->catdir( $base, $dir );
        File::Find::find(
            {   bydepth  => 1,
                no_chdir => 1,
                untaint  => 1,
                wanted   => sub {
                    my $name = $File::Find::name;
                    -f $name
                        and $name =~ m{/(\d{9,10})\.html\z}xms
                        and $hash->{$1} = $name;
                },
            },
            $dir
        );
        for my $key ( keys %$hash ) {
            my $path = $zArchive->path($key);
            if ( !-d $path ) {
                require File::Path;
                File::Path::mkpath($path)
                    or $iKernel->throw_io_exception($path);
            }
            File::Copy::copy( $hash->{$key}, "${path}/${key}.html" )
                or $iKernel->throw_io_exception("${path}/${key}.html");
        }
        %$hash = ();
    }

    $zArchive->update();
    return;
}

sub pool {
    my ($zApp) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->can_create_pool()  or return { code => 1 };
    $zUser->can_recover_pool() or return { code => 1 };
    $zUser->can_remove_pool()  or return { code => 1 };

    require Zeromin::BBS;
    my $iKernel = $zApp->kernel();
    my $zBBS    = Zeromin::BBS->new( $iKernel, { id => 0 } );
    my $bbs_all = $zBBS->all();
    foreach my $bbs (@$bbs_all) {
        my $iBBS = Img0ch::BBS->new( $iKernel, { id => $bbs->{id} } );
        _pool( $iKernel, $iBBS );
    }
    return { code => 0, pool => 1 };
}

sub _pool {
    my ( $iKernel, $iBBS ) = @_;
    my $base = $iBBS->path('pool');
    -d $base or return;

    local ( *DH, $! );
    opendir *DH, $base or $iKernel->throw_io_exception($base);
    my @dirs = readdir *DH;
    closedir *DH or $iKernel->throw_io_exception($base);

    require File::Spec;
    require Zeromin::Thread;
    require Zeromin::Pool;
    for my $file ( File::Spec->no_upwards(@dirs) ) {
        $file =~ m{\A(\d{9,10})\.cgi\z}xms or next;
        my $key     = $1;
        my $zThread = Zeromin::Thread->new($iBBS);
        $zThread->load( File::Spec->catfile( $base, $file ) );
        my $zPool = Zeromin::Pool->new( $zThread, $key );
        $zPool->create();
    }

    return;
}

sub banner {
    my ($zApp) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->can_edit_banner() or return { code => 1 };

    require Unicode::Japanese;
    require Zeromin::Metadata;
    my $iKernel  = $zApp->kernel();
    my $zMeta    = Zeromin::Metadata->new($iKernel);
    my $base     = $iKernel->get_config()->get('BBSPath');
    my $encoding = $iKernel->get_encoding(1);
    my $ret      = { code => 0 };
    my $unijp    = Unicode::Japanese->new();

    my $main = $base . '/test/info/bannerpc.cgi';
    $ret->{main}
        = _banner( $iKernel, $zMeta, $unijp, $encoding, 'main_banner', $main )
        ? 1
        : 0;
    my $sub = $base . '/test/info/bannersub.cgi';
    $ret->{sub}
        = _banner( $iKernel, $zMeta, $unijp, $encoding, 'sub_banner', $main )
        ? 1
        : 0;
    my $mobile = $base . '/test/info/bannermb.cgi';
    $ret->{mobile}
        = _banner( $iKernel, $zMeta, $unijp, $encoding, 'mobile_banner',
        $mobile ) ? 1 : 0;

    return $ret;
}

sub category {
    my ($zApp) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->can_add_category()    or return { code => 1 };
    $zUser->can_edit_category()   or return { code => 1 };
    $zUser->can_remove_category() or return { code => 1 };

    require Unicode::Japanese;
    require Zeromin::Category;
    my $iKernel = $zApp->kernel();
    my $zCat    = Zeromin::Category->new($iKernel);
    my $base    = $iKernel->get_config()->get('BBSPath');
    my $path    = $base . '/test/info/category.cgi';
    my $unijp   = Unicode::Japanese->new();

    -r $path or return { code => 0, category => 0 };
    local ( $!, *FH );

    open *FH, "<${path}"    ## no critic
        or $iKernel->throw_io_exception($path);
    while ( my $line = <FH> ) {
        chomp $line;
        my ( undef, $name ) = split '<>', $line;
        $zCat->add( $unijp->set( $name, 'sjis' )->get() );
    }
    close *FH or $iKernel->throw_io_exception($path);
    $zCat->save();

    return { code => 0, category => 1 };
}

sub _banner {
    my ( $iKernel, $zMeta, $unijp, $encoding, $method, $path ) = @_;
    -r $path or return 0;
    local ( $!, *FH );
    open *FH, "<${path}"    ## no critic
        or $iKernel->throw_io_exception($path);
    my $color = <FH>;
    local $/ = undef;
    my $content = <FH>;
    $content = $unijp->set( $content, 'sjis' )->$encoding;
    $zMeta->$method( \$content );
    close *FH or $iKernel->throw_io_exception($path);
    return 1;
}

sub user {
    my ($zApp) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->can_add_category()    or return { code => 1 };
    $zUser->can_edit_category()   or return { code => 1 };
    $zUser->can_remove_category() or return { code => 1 };

    require Unicode::Japanese;
    my $iKernel = $zApp->kernel();
    my $base    = $iKernel->get_config()->get('BBSPath');
    my $ret     = { code => 0, user => 0, ugroup => 0 };
    my $unijp   = Unicode::Japanese->new();

    my $ugroup = $base . '/test/info/group.cgi';
    if ( -r $ugroup ) {
        require Img0ch::BBS;
        local ( $!, *FH );
        open *FH, "<${ugroup}"    ## no critic
            or $iKernel->throw_io_exception($ugroup);
        while ( my $line = <FH> ) {
            chomp $line;
            my ( $gname, $bbs_dir, undef, $rt ) = split '<>', $line;
            $zUser->get_group_id($gname) and next;

            my $role = {};
            my $bbs_id
                = Img0ch::BBS->new( $iKernel, { bbs => $bbs_dir } )->get_id();
            $rt = join ',', ( 1 .. 81 ) if $rt eq '*';
            _ugroup_update_privilege( $role, $rt );
            $zUser->add_group(
                {   bbs       => $bbs_id,
                    name      => $unijp->set( $gname, 'sjis' )->get(),
                    privilege => {
                        user     => $role->{user},
                        cap      => $role->{cap},
                        bbs      => $role->{bbs},
                        thread   => $role->{thread},
                        res      => $role->{res},
                        pool     => $role->{pool},
                        archive  => $role->{archive},
                        setting  => $role->{setting},
                        meta     => $role->{meta},
                        subject  => $role->{subject},
                        control  => $role->{control},
                        category => $role->{category},
                        view     => $role->{view},
                        plugin   => $role->{plugin},
                    }
                }
            );
        }
        close *FH or $iKernel->throw_io_exception($ugroup);
        $zUser->save();
        $ret->{ugroup} = 1;
    }

    my $user = $base . '/test/info/users.cgi';
    if ( -r $user ) {
        local ( $!, *FH );
        open *FH, "<${user}"    ## no critic
            or $iKernel->throw_io_exception($user);
        while ( my $line = <FH> ) {
            chomp $line;
            my ( $uname, $pass, $gname, undef ) = split '<>', $line;
            $gname = $unijp->set($gname)->get();
            $zUser->add(
                {   name => $unijp->set( $uname, 'sjis' )->get(),
                    mail => 'dev@slash.null',
                    pass => $pass,
                    gid  => $zUser->get_group_id($gname)
                }
            );
        }
        close *FH or $iKernel->throw_io_exception($ugroup);
        $zUser->save();
        $ret->{user} = 1;
    }
    return $ret;
}

sub cap {
    my ($zApp) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->can_create_cap() or return { code => 1 };
    $zUser->can_edit_cap()   or return { code => 1 };
    $zUser->can_remove_cap() or return { code => 1 };

    require Unicode::Japanese;
    require Zeromin::Cap;
    my $iKernel  = $zApp->kernel();
    my $zCap     = Zeromin::Cap->new($iKernel);
    my $base     = $iKernel->get_config()->get('BBSPath');
    my $encoding = $iKernel->get_encoding(1);
    my $ret      = { code => 0, cap => 0, cgroup => 0 };
    my $unijp    = Unicode::Japanese->new();

    my $cgroup = $base . '/test/info/groupc.cgi';
    if ( -r $cgroup ) {
        require Img0ch::BBS;
        local ( $!, *FH );
        open *FH, "<${cgroup}"    ## no critic
            or $iKernel->throw_io_exception($cgroup);
        while ( my $line = <FH> ) {
            chomp $line;
            my ( $name, $bbs_dir, undef, $rt ) = split '<>', $line;
            $zCap->get_group_id($name) and next;

            my $privilege = 0;
            my $bbs_id
                = Img0ch::BBS->new( $iKernel, { bbs => $bbs_dir } )->get_id();
            _cgroup_update_privilege( \$privilege, $rt );
            $zCap->add_group(
                {   bbs       => $bbs_id,
                    name      => $unijp->set( $name, 'sjis' )->get(),
                    privilege => $privilege
                }
            );
        }
        close *FH or $iKernel->throw_io_exception($cgroup);
        $zCap->save();
        $ret->{cgroup} = 1;
    }

    my $cap = $base . '/test/info/caps.cgi';
    if ( -r $cap ) {
        local ( $!, *FH );
        open *FH, "<${cap}"    ## no critic
            or $iKernel->throw_io_exception($cap);
        while ( my $line = <FH> ) {
            chomp $line;
            my ( undef, $cname, $pass, $gname, undef ) = split '<>', $line;
            $gname = $unijp->set( $gname, 'sjis' )->get();
            $zCap->add(
                {   name => $unijp->set( $cname, 'sjis' )->$encoding,
                    pass => $pass,
                    gid  => $zCap->get_group_id($gname)
                }
            );
        }
        close *FH or $iKernel->throw_io_exception($cap);
        $zCap->save();
        $ret->{cap} = 1;
    }
    return $ret;
}

sub hostlog {
    my ($zApp) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->can_create_bbs()      or return { code => 1 };
    $zUser->can_remove_bbs()      or return { code => 1 };
    $zUser->can_view_thread_log() or return { code => 1 };

    require Zeromin::BBS;
    my $iKernel = $zApp->kernel();
    my $zBBS    = Zeromin::BBS->new( $iKernel, { id => 0 } );
    my $bbs_all = $zBBS->all();

    foreach my $bbs (@$bbs_all) {
        my $iBBS = Img0ch::BBS->new( $iKernel, { id => $bbs->{id} } );
        _hostlog( $zApp, $iBBS ) or return { code => 1 };
    }
    return { code => 0 };
}

sub _hostlog {
    my ( $zApp, $iBBS ) = @_;
    my $iKernel = $zApp->kernel();

    require Time::Local;
    require Img0ch::Log;
    require Img0ch::Thread;
    my $iConfig = $iKernel->get_config();
    my ( $id, $dir ) = ( $iBBS->get_id(), $iBBS->get_name() );
    my $path = join '/', $iConfig->get('BBSPath'), $dir, 'log/HOSTs.cgi';
    -r $path or return { code => 0 };

    my $idx    = {};
    my $thread = {};
    my $log    = {};
    my $datdir = join '/', $iConfig->get('BBSPath'), $dir, 'dat';
    _index( $iKernel, $datdir, $idx );

    local ( $!, *FH );
    local $SIG{__WARN__} = sub { };
    open *FH, "<${path}" or    ## no critic
        $iKernel->throw_io_exception($path);
    while ( my $line = <FH> ) {
        chomp $line;
        my ( $stamp, $key, undef, $host ) = split '<>', $line;
        my $resno = $idx->{ ( join '-', $key, $stamp ) } || next;

        my $iLog = $log->{$key};
        if ($iLog) {
            $iLog = Img0ch::Log->new( $iBBS, $key );
            $iLog->load();
            $log->{$key} = $iLog;
        }
        my $iThread = $thread->{$key};
        if ( !$iThread ) {
            $iThread = Img0ch::Thread->new( $iBBS, $key );
            $iThread->load();
            $thread->{$key} = $iThread;
        }

        my $article = $iThread->get($resno);
        if ($article) {
            my $ip;
            if ( $host =~ /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/xms ) {
                $ip = "$1.$2.$3.$4";
            }
            else {
                $ip = join '.', unpack( 'C*', gethostbyname $host );
            }
            $host =~ /\s([\w\-]+)\z/xms;
            my $serial = $1 || '';
            $iLog->set( $resno, [ $stamp, $ip, $serial, '' ] );
        }
    }
    close *FH or $iKernel->throw_io_exception($path);
    map { $_->save() } values %$log;
    return 1;
}

=head1
sub ngword {
    my ( $zClass, $zApp ) = @_;
    my $zConfig = ZeroCH::Config->instance;
    my $path = $zConfig->BBSPath . '/test/info/ngword.raw.cgi';
    return 0 unless -f $path;
    require Digest::MD5;
    require ShiftJIS::Regexp;
    require ZeroCH::Filter::NGWord;
    my $md5 = Digest::MD5->new;
    open my $fh, '<', $path or $zClass->io_error($path);
    while (my $word = <$fh>) {
        chomp $word;
        my $regex = ShiftJIS::Regexp::re($word);
        my $ngword = ZeroCH::Filter::NGWord->new;
        my $id = $md5->add(0)->add($word)->hexdigest;
        $ngword->id($id);
        $ngword->bbs(0);
        $ngword->entry($word);
        $ngword->regex($regex);
        $ngword->flag(1);
        $ngword->to('');
        $ngword->save;
    }
    close $fh or $zClass->io_error($path);
    1
}
=cut

sub threadlog {
    my ($zApp) = @_;
    my $zUser = $zApp->user() || return { code => 1 };
    $zUser->can_create_bbs()      or return { code => 1 };
    $zUser->can_remove_bbs()      or return { code => 1 };
    $zUser->can_view_create_log() or return { code => 1 };

    require Zeromin::BBS;
    my $iKernel = $zApp->kernel();
    my $zBBS    = Zeromin::BBS->new( $iKernel, { id => 0 } );
    my $bbs_all = $zBBS->all();

    foreach my $bbs (@$bbs_all) {
        my $iBBS = Img0ch::BBS->new( $iKernel, { id => $bbs->{id} } );
        _threadlog( $zApp, $iBBS ) or return { code => 1 };
    }
    return { code => 0 };
}

sub _threadlog {
    my ( $zApp, $iBBS ) = @_;
    my $iKernel = $zApp->kernel();

    require Time::Local;
    require Img0ch::Log;
    require Img0ch::Thread;
    my $iConfig = $iKernel->get_config();
    my ( $id, $dir ) = ( $iBBS->get_id(), $iBBS->get_name() );
    my $path = join '/', $iConfig->get('BBSPath'), $dir, 'log/IP.cgi';
    -r $path or return { code => 0 };

    my $log = {};
    local ( $!, *FH );
    open *FH, "<${path}" or    ## no critic
        $iKernel->throw_io_exception($path);

    while ( my $line = <FH> ) {
        chomp $line;
        my ( undef, $key, undef, $host ) = split '<>', $line;
        my $ip = '';
        if ( $host =~ /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/xms ) {
            $ip = "$1.$2.$3.$4";
        }
        else {
            $ip = join '.', unpack( 'C*', gethostbyname $host );
        }
        $host =~ /\s([\w\-]+)\z/xms;
        my $serial = $1 || '';
        my $iLog = $log->{$key};
        if ($iLog) {
            $iLog = Img0ch::Log->new( $iBBS, $key );
            $iLog->load();
            $log->{$key} = $iLog;
        }
        $iLog->set( 1, [ $key, $ip, $serial, '' ] );
    }
    close *FH or $iKernel->throw_io_exception($path);
    map { $_->save() } values %$log;
    return 1;
}

sub _cgroup_update_privilege {
    my ( $role, $rt ) = @_;
    my $auth = {};
    my @r = index( $rt, ',' ) >= 0 ? split( ',', $rt ) : ($rt);
    map { $auth->{$_} = 1 } @r;
    $$role = 1;
    map { exists $auth->{$_} or $$role = 0 } ( 1 .. 7 );
    $auth->{15} and $$role += 2;
    $auth->{8}  and $$role += 4;
    $auth->{18} and $$role += 8;
    $auth->{10} and $$role += 16;
    $auth->{11} and $$role += 32;
    $auth->{13} and $$role += 64;
    $auth->{13} and $$role += 128;
    $auth->{15} and $$role += 256;
    $auth->{17} and $$role += 512;
    return;
}

sub _index {
    my ( $iKernel, $datdir, $index ) = @_;

    local ( $!, *DH );
    opendir *DH, $datdir or $iKernel->throw_io_exception($datdir);
    my @datfiles = readdir *DH;
    closedir *DH or $iKernel->throw_io_exception($datdir);

DAT_SCAN:
    for my $datfile (@datfiles) {
        $datfile =~ /\A(\d{9,10})\.dat\z/xms or next DAT_SCAN;
        my $key     = $1;
        my $datpath = join '/', $datdir, $datfile;
        my $i       = 0;
        local ( $!, *FH );

        open *FH, "<${datpath}"    ## no critic
            or $iKernel->throw_io_exception($datpath);
    DAT_READ:
        while ( my $line = <FH> ) {
            $i++;
            $line =~ /\A.*?<>.*?<>(.*?)<>.*?<>.*\z/xms or next DAT_READ;
            my $date = $1;
            $date =~ /.*?(\d+).*?(\d+).*?(\d+).*?(\d+).*?(\d+).*?(\d+)/xms
                or next DAT_READ;
            my ( $year, $month, $day, $hour, $minute, $second )
                = map { Img0ch::Kernel::intval($_) }
                ( $1, $2, $3, $4, $5, $6 );
            my $stamp = Time::Local::timelocal( $second, $minute, $hour, $day,
                $month - 1, $year - 1900 );
            $index->{ ( join '-', $key, $stamp ) } = $i;
        }
        close *FH or $iKernel->throw_io_exception($datpath);
    }
    return;
}

sub _ugroup_update_privilege {
    my ( $role, $rt ) = @_;
    my $auth = {};
    my @r = index( $rt, ',' ) >= 0 ? split( ',', $rt ) : ($rt);
    map { $auth->{$_} = 1 } @r;
    $role->{user} = 0;
    exists $auth->{9}  and $role->{user} += 1;
    exists $auth->{11} and $role->{user} += 2;
    exists $auth->{12} and $role->{user} += 4;
    exists $auth->{3}  and $role->{user} += 8;
    exists $auth->{5}  and $role->{user} += 16;
    exists $auth->{6}  and $role->{user} += 32;
    $role->{cap} = 0;
    exists $auth->{21} and $role->{cap} += 1;
    exists $auth->{23} and $role->{cap} += 2;
    exists $auth->{24} and $role->{cap} += 4;
    exists $auth->{15} and $role->{cap} += 8;
    exists $auth->{17} and $role->{cap} += 16;
    exists $auth->{18} and $role->{cap} += 32;
    $role->{bbs} = 0;
    exists $auth->{27} and $role->{bbs} += 1;
    exists $auth->{55} and $role->{bbs} += 2;
    exists $auth->{32} and $role->{bbs} += 4;
    exists $auth->{57} and $role->{bbs} += 8;
    $role->{setting} = 0;
    exists $auth->{29} and $role->{setting} = 127;
    $role->{thread} = 0;
    exists $auth->{34} and $role->{thread} += 1;
    exists $auth->{79} and $role->{thread} += 2;
    exists $auth->{37} and $role->{thread} += 4;
    exists $auth->{39} and $role->{thread} += 8;
    exists $auth->{40} and $role->{thread} += 16;
    $role->{pool} = 0;
    exists $auth->{35} and $role->{pool} += 1;
    exists $auth->{78} and $role->{pool} += 2;
    exists $auth->{41} and $role->{pool} += 4;
    $role->{res} = 0;
    exists $auth->{48} and $role->{res} += 1;
    exists $auth->{49} and $role->{res} += 2;
    exists $auth->{45} and $role->{res} += 4;
    $role->{archive} = 0;
    exists $auth->{51} and $role->{archive} += 1;
    exists $auth->{58} and $role->{archive} += 2;
    exists $auth->{53} and $role->{archive} += 4;
    $role->{subject} = 0;
    exists $auth->{56} and $role->{subject} += 1;
    exists $auth->{57} and $role->{subject} += 2;
    $role->{meta} = 0;
    exists $auth->{62} and $role->{meta} += 1;
    exists $auth->{66} and $role->{meta} += 2;
    exists $auth->{64} and $role->{meta} += 4;
    exists $auth->{68} and $role->{meta} += 8;
    $role->{view} = 0;
    exists $auth->{74} and $role->{view} += 1;
    exists $auth->{75} and $role->{view} += 2;
    exists $auth->{80} and $role->{view} += 4;
    $role->{plugin} = 0;
    ( exists $auth->{27} and $auth->{32} ) and $role->{plugin} += 7;
    $role->{category} = 0;
    exists $auth->{81} and $role->{category} += 7;
    $role->{control} = 0;
    exists $auth->{70} and $role->{control} += 28;
    exists $auth->{72} and $role->{control} += 3;
    return;
}

1;
__END__
