#/*
# *  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: Action.pm 2002 2010-02-21 13:28:07Z hikarin $
#

package Zeromin::Log::Action;

use strict;
use File::Basename qw();
use File::Find qw();
use File::Path qw();

BEGIN {
    if ( defined $Storable::VERSION or eval 'use Storable qw(); 1' ) {
        *Zeromin::Log::Action::freeze = \&Storable::nfreeze;
        *Zeromin::Log::Action::thaw   = \&Storable::thaw;
    }
    else {
        defined $FreezeThaw::VERSION or require FreezeThaw;
        *Zeromin::Log::Action::freeze = \&FreezeThaw::freeze;
        *Zeromin::Log::Action::thaw   = \&FreezeThaw::thaw;
    }
}

sub new {
    my ( $zClass, $iKernel ) = @_;

    my $date = time();
    my ( undef, undef, undef, $day, $month, $year ) = localtime($date);
    $year  += 1900;
    $month += 1;

    my $path       = join '/', $year, $month, $day, 'zeromin';
    my $repos_path = $iKernel->get_repos_path($path);
    my $repos_dir  = $repos_path;
    $repos_dir =~ s{/zeromin.\w+\z}{}xms;
    if ( !-d $repos_dir ) {
        File::Path::mkpath($repos_dir)
            or $iKernel->throw_io_exception($repos_dir);
    }
    my $iRepos = $iKernel->get_repos_nocache($repos_path);

    bless {
        __kernel => $iKernel,
        __log    => [],
        __rs     => $iRepos,
    }, $zClass;
}

sub load {1}

sub save {
    my ($zLogAct) = @_;
    my $iRepos    = $zLogAct->{__rs};
    my $line      = $zLogAct->{__log};
    $line->[0] or return 0;

    for my $one (@$line) {
        my $ip = join '.', unpack 'C4', $one->[4];
        my $prefix = join '.', 'I:L:A', $one->[0], $one->[1];
        my $param = $one->[7] || [];
        $iRepos->set( "${prefix}.class",  $one->[2] );
        $iRepos->set( "${prefix}.user",   $one->[3] );
        $iRepos->set( "${prefix}.ip",     $ip );
        $iRepos->set( "${prefix}.ok",     $one->[5] );
        $iRepos->set( "${prefix}.reason", $one->[6] );
        $iRepos->set_binary( "${prefix}.param", freeze($param) );
    }

    $iRepos->save();
    @{ $zLogAct->{__log} } = ();
    1;
}

sub add {
    my ( $zLogAct, @args ) = @_;
    push @{ $zLogAct->{__log} }, [ time(), int( rand(99999) ), @args ];
    return;
}

sub get {
    my ( $zLogAct, $date, $zUser ) = @_;
    my $iRepos = $zLogAct->{__rs};
    my $prefix = "I:L:A.${date}";
    my $user   = $iRepos->get("${prefix}.user");

    if ($user) {
        $zUser and $user = $zUser->get_user_name($user);
        {   class  => $iRepos->get("${prefix}.class"),
            date   => $date,
            ip     => $iRepos->get("${prefix}.ip"),
            ok     => $iRepos->get_int("${prefix}.ok"),
            reason => sprintf(
                $iRepos->get("${prefix}.reason"),
                @{ thaw( $iRepos->get_binary("${prefix}.param") ) },
            ),
            user => $user,
        };
    }
    else {
        {   class  => 'Anonymous',
            date   => 0,
            ip     => '0.0.0.0',
            ok     => 0,
            reason => '',
            user   => 0,
        };
    }
}

sub remove {
    my ( $zLogAct, $date ) = @_;
    my $iRepos = $zLogAct->{__rs};
    my $prefix = "I:L:A.${date}";
    my $user   = $iRepos->remove("${prefix}.user");

    if ($user) {
        $iRepos->remove("${prefix}.class");
        $iRepos->remove("${prefix}.ip");
        $iRepos->remove("${prefix}.ok");
        $iRepos->remove("${prefix}.reason");
        $iRepos->remove("${prefix}.param");
        return 1;
    }
    return 0;
}

sub retrive_by_day {
    my ( $zLogAct, $year, $month, $day, $zUser ) = @_;
    my $iKernel = $zLogAct->{__kernel};
    my $path    = $iKernel->get_repos_path("${year}/${month}/${day}/zeromin");
    return $zLogAct->_retrive( [$path], $iKernel, $zUser );
}

sub retrive_by_day_with_page {
    my ( $zLogAct, $year, $month, $day, $zUser, $item_per_page, $offset )
        = @_;
    return _page( $zLogAct->retrive_by_day( $year, $month, $day, $zUser ),
        $item_per_page, $offset );
}

sub retrive_by_month {
    my ( $zLogAct, $year, $month, $zUser ) = @_;
    return $zLogAct->all(
        $zUser, '',
        File::Basename::dirname(
            $zLogAct->{__kernel}->get_repos_path("${year}/${month}/")
        )
    );
}

sub retrive_by_month_with_page {
    my ( $zLogAct, $year, $month, $zUser, $item_per_page, $offset ) = @_;
    return _page( $zLogAct->retrive_by_month( $year, $month, $zUser ),
        $item_per_page, $offset );
}

sub all {
    my ( $zLogAct, $zUser, $regex, $start_path ) = @_;
    my $iKernel = $zLogAct->{__kernel};
    $regex ||= qr{/zeromin\.}xms;

    my $path = $start_path
        || File::Basename::dirname( $iKernel->get_repos_path(0) );
    my $repositories = [];
    File::Find::find(
        {   bydepth  => 1,
            no_chdir => 1,
            untaint  => 1,
            wanted   => sub {
                $File::Find::name =~ $regex
                    and push @$repositories, $File::Find::name;
                }
        },
        $path,
    );
    return $zLogAct->_retrive( $repositories, $iKernel, $zUser );
}

sub all_with_page {
    my ( $zLogAct, $zUser, $regex, $item_per_page, $offset ) = @_;
    return _page( $zLogAct->all( $zUser, $regex ), $item_per_page, $offset );
}

sub to_array {
    my ($zLogAct) = @_;
    my $date_table = {};

    $zLogAct->{__rs}->iterate(
        sub {
            my ( $key, $value, $date_table ) = @_;
            if ( $key =~ /\AI:L:A\.(\d+\.\d+)\.\w+\z/xms ) {
                $date_table->{$1} = 1;
            }
            return 0;
        },
        $date_table
    );
    return [ keys %$date_table ];
}

sub to_array_with_page {
    my ( $zLogAct, $item_per_page, $offset ) = @_;
    return _page( $zLogAct->to_array(), $item_per_page, $offset );
}

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

sub _retrive {
    my ( $zLogAct, $repositories, $iKernel, $zUser ) = @_;
    my $ret  = [];
    my $seen = {};

    for my $iRepos ( map { $iKernel->get_repos_nocache($_) } @$repositories )
    {
        $iRepos->iterate(
            sub {
                my ( $key, $value, $seen ) = @_;
                if ( $key =~ m{\AI:L:A\.(\d+?)\.(.+?)\.(\w+)\z}xms ) {
                    my ( $date, $mil, $name ) = ( $1, $2, $3 );
                    my $prefix = "I:L:A.${date}.${mil}";
                    exists $seen->{$prefix} and return 0;
                    $seen->{$prefix} = $date;
                }
                return 0;
            },
            $seen,
        );
        while ( my ( $prefix, $date ) = each %$seen ) {
            my $user   = $iRepos->get("${prefix}.user")   or next;
            my $class  = $iRepos->get("${prefix}.class")  or next;
            my $ip     = $iRepos->get("${prefix}.ip")     or next;
            my $ok     = $iRepos->get("${prefix}.ok");
            my $reason = $iRepos->get("${prefix}.reason") or next;
            my $param  = $iRepos->get("${prefix}.param");
            push @$ret,
                {
                class  => $class,
                date   => $date,
                ip     => $ip,
                ok     => $ok,
                reason => sprintf(
                    $iRepos->get("${prefix}.reason"),
                    @{ thaw( $iRepos->get_binary("${prefix}.param") ) },
                ),
                user => ( $zUser ? $zUser->get_user_name($user) : $user ),
                };
        }
    }

    @$ret = sort { $b->{date} cmp $a->{date} } @$ret;
    return $ret;
}

1;
__END__
