#/*
# *  Copyright 2007-2009 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: Tag.pm 1826 2009-04-08 15:23:25Z hikarin $
#

package Img0ch::Tag;

use strict;
use Digest::MD5 qw();

sub new {
    my ( $iClass, $repos_name, $repos_prefix, $iBBS ) = @_;
    my $iKernel = $iBBS->get_kernel();
    my $iRepos  = $iKernel->get_repos( $iBBS->get_repos_path($repos_name) );

    my $bbs       = $iBBS->get_name();
    my $prefix    = join '.', $repos_prefix, $bbs;
    my $tag_cloud = {};
    my $ng_tag    = {};
    map { $ng_tag->{$_} = 1 } split "\n",
        $iRepos->get_binary("${prefix}.0.0.ng");
    map {
        my ( $name, $count ) = split "\t", $_;
        $tag_cloud->{$name} = $count;
    } split "\n", $iRepos->get_binary("${prefix}.0.0.tag");

    bless {
        _bbs     => $bbs,
        _kernel  => $iKernel,
        __ng     => $ng_tag,
        _prefix  => $prefix,
        _prefix2 => "${repos_prefix}.test",
        _repos   => $iRepos,
        __tags   => [],
        _tc      => $tag_cloud,
    }, $iClass;
}

sub load {
    my ($iTag) = @_;
    $iTag->{_repos}->load();
    return 1;
}

sub save {
    my ($iTag)    = @_;
    my $iKernel   = $iTag->{_kernel};
    my $iRepos    = $iTag->{_repos};
    my $tags      = $iTag->{__tags};
    my $prefix    = $iTag->{_prefix};
    my $prefix2   = $iTag->{_prefix2};
    my $ng_tags   = $iTag->{__ng};
    my $tag_cloud = $iTag->{_tc};

    my $tag_name_cache = {};
    my $encoding       = $iKernel->get_encoding(1);
DATA: for my $tag (@$tags) {
        my ( $key, $resno, $ip, $tag_names ) = @$tag;
        $resno = Img0ch::Kernel::intval($resno);

        my $tag_key = join '.', $prefix, $key, $resno;
        my $tag_data = [];
        map { push @$tag_data, [ split "\t", $_ ] }
            split "\n", $iRepos->get_binary($tag_key);

    NAME: for my $tag_name (
            do {
                my %h;
                map { $h{$_}++ } @$tag_names;
                keys %h;
            }
            )
        {
            $tag_name
                = $iKernel->get_encoded_str( $tag_name, 'utf8', $encoding );
            exists $ng_tags->{$tag_name} and next NAME;
            my $tag_name_key = join '.', $prefix2,
                Digest::MD5::md5_hex($tag_name);
            my $data = $tag_name_cache->{$tag_name_key}
                || [ split "\n", $iRepos->get_binary($tag_name_key) ];
            push @$data, join( "\t", $key, $resno );
            $tag_name_cache->{$tag_name_key} = $data;

            if ( !grep { $_->[0] eq $tag_name and $_->[1] eq $ip }
                @$tag_data )
            {
                push @$tag_data, [ $tag_name, $ip ];
                $tag_cloud->{$tag_name} ||= 0;
                $tag_cloud->{$tag_name}++;
            }
        }

        $iRepos->set_binary( $tag_key,
            join( "\n", map { join( "\t", @$_ ) } @$tag_data ) );
    }

    while ( my ( $tag_name_key, $data ) = each %$tag_name_cache ) {
        $iRepos->set_binary( $tag_name_key, join( "\n", @$data ) );
    }
    $iRepos->set_binary(
        "${prefix}.0.0.tag",
        join(
            "\n", map { join "\t", $_, $tag_cloud->{$_} } keys %$tag_cloud
        )
    );
    $iRepos->set_binary( "${prefix}.0.0.ng", join( "\n", keys %$ng_tags ) );

    %{ $iTag->{_tc} }    = %$tag_cloud;
    %{ $iTag->{__ng} }   = %$ng_tags;
    @{ $iTag->{__tags} } = ();
    $iRepos->save();
    return 1;

}

sub to_json {
    my ( $iTag, $key ) = @_;
    defined &{'Img0ch::Request::Interface::get_json'}
        or require Img0ch::Request::Interface;

    my $tags = $iTag->{_tc};
    my $json = { meta => { bbs => $iTag->{_bbs} } };
    if ($key) {
        my $temp = {};
        for my $tag ( keys %$tags ) {
            my $where = $iTag->search($tag);
            my @wants = grep { $_->[0] eq $key } @$where;
            for my $want (@wants) {
                my $resno = $want->[1];
                $temp->{$resno} ||= {};
                $temp->{$resno}->{$tag} = 1;
            }
        }
        while ( my ( $resno, $data ) = each %$temp ) {
            $json->{$resno} = [ keys %$data ];
        }
        $json->{meta}->{key} = $key;
    }
    else {
        $json->{root} = $tags;
    }

    return Img0ch::Request::Interface->get_json($json);
}

sub get_utf8 {
    my ( $iTag, $key, $resno ) = @_;
    my $iRepos = $iTag->{_repos};
    my $find_key = join '.', $iTag->{_prefix}, $key,
        Img0ch::Kernel::intval($resno);
    my $data = $iRepos->get_binary($find_key);

    my $ret = {};
    for my $tag_info ( split "\n", $data ) {
        my ( $tag_name, $ip ) = split "\t", $tag_info;
        $ret->{$tag_name} ||= [ 0, [] ];
        $ret->{$tag_name}->[0]++;
        push @{ $ret->{$tag_name}->[1] }, $ip;
    }

    return $ret;
}

sub get_tags_utf8 { return { %{ $_[0]->{_tc} } } }

sub get_ng_tag_utf8 { return [ keys %{ $_[0]->{__ng} } ] }

sub get_ng_tag {
    my ( $iTag, $key, $resno ) = @_;
    my $iKernel  = $iTag->{_kernel};
    my $encoding = $iKernel->get_encoding(1);
    return [ map { $iKernel->get_encoded_str( $_, $encoding, 'utf8' ) }
            @{ $iTag->get_ng_tag_utf8() } ];
}

sub add {
    my ( $iTag, $key, $resno, $ip, $tags ) = @_;
    $tags or return;
    ref $tags or $tags = [$tags];
    scalar(@$tags) or return;
    push @{ $iTag->{__tags} }, [ $key, $resno, $ip, $tags ];
    return;
}

sub set {
    my ( $iTag, $key, $resno, $ip, $tags ) = @_;
    $iTag->remove( $key, $resno );
    $iTag->add( $key, $resno, $ip, $tags );
    return;
}

sub set_ng_tag {
    my ( $iTag, $tag_name ) = @_;
    my $iKernel = $iTag->{_kernel};
    my $tag_names = ref $tag_name ? $tag_name : [$tag_name];
NGTAG:
    for my $tag_name (@$tag_names) {
        $tag_name =~ tr/\r\t\n//d;
        $tag_name or next NGTAG;
        $tag_name
            = $iKernel->get_encoded_str( $tag_name, $iKernel->get_encoding(1),
            'utf8' );
        $iTag->{__ng}->{$tag_name} = 1;
    }
    return;
}

sub remove {
    my ( $iTag, $key, $resno ) = @_;
    map { $iTag->remove_by_tag( $_, $key, $resno, 1 ) }
        keys %{ $iTag->get( $key, $resno ) };
    $iTag->{_repos}->remove( join '.', $iTag->{_prefix}, $key,
        Img0ch::Kernel::intval($resno) );
    return 1;
}

sub remove_ng_tag {
    my ( $iTag, $tag_name ) = @_;
    if ($tag_name) {
        my $iKernel = $iTag->{_kernel};
        ref $tag_name or $tag_name = [$tag_name];
        for my $tag_name (@$tag_name) {
            $tag_name = $iKernel->get_encoded_str( $tag_name,
                $iKernel->get_encoding(1), 'utf8' );
            delete $iTag->{__ng}->{$tag_name};
        }
    }
    else {
        %{ $iTag->{__ng} } = ();
    }
    return 1;
}

sub remove_by_tag {
    my ( $iTag, $name, $key, $resno, $skip_rebuild ) = @_;
    my $iKernel = $iTag->{_kernel};
    my $iRepos  = $iTag->{_repos};

    $name = $iKernel->get_encoded_str( $name, 'utf8',
        $iKernel->get_encoding(1) );
    $resno = Img0ch::Kernel::intval($resno);
    my $find_key = join '.', $iTag->{_prefix2}, Digest::MD5::md5_hex($name);
    my @result = @{ $iTag->search($name) };

    if ( $key and $resno ) {
        my @ret = grep { $_->[0] != $key or $_->[1] != $resno } @result;
        my $count = scalar @ret;
        if ($count) {
            $iRepos->set_binary( $find_key,
                join( "\n", map { join "\t", @$_ } @ret ) );
            $iTag->{_tc}->{$name} = $count;
        }
        else {
            $iRepos->remove($find_key);
            delete $iTag->{_tc}->{$name};
            delete $iTag->{__ng}->{$name};
        }
        if ( !$skip_rebuild ) {
            my $tag_key = join '.', $iTag->{_prefix}, $key, $resno;
            my $tag_data = [];
            for my $data ( split "\n", $iRepos->get_binary($tag_key) ) {
                my @data = split "\t", $data;
                $data[0] eq $name and next;
                push @$tag_data, \@data;
            }
            $iRepos->set_binary( $tag_key,
                join( "\n", map { join( "\t", @$_ ) } @$tag_data ) );
        }
    }
    else {
        for my $where (@result) {
            my ( $key, $resno ) = @$where;
            my $tags = $iTag->get( $key, $resno );
            $iTag->remove( $key, $resno );
            delete $tags->{$name};
            while ( my ( $name, $info ) = each %$tags ) {
                map { $iTag->add( $key, $resno, $_, $name ) } @{ $info->[1] };
            }
        }
        $iRepos->remove($find_key);
        delete $iTag->{_tc}->{$name};
    }

    return 1;
}

sub search {
    my ( $iTag, $name ) = @_;
    my $iKernel = $iTag->{_kernel};
    my $iRepos  = $iTag->{_repos};

    $name = $iKernel->get_encoded_str( $name, 'utf8',
        $iKernel->get_encoding(1) );
    my $key = join '.', $iTag->{_prefix2}, Digest::MD5::md5_hex($name);
    my $data = $iRepos->get_binary($key);

    my $ret  = [];
    my $hash = {};
    map { $hash->{ $_->[0] } ||= {}; $hash->{ $_->[0] }->{ $_->[1] } = 1 }
        map { [ split "\t", $_ ] } split "\n", $data;
    map {
        my $key = $_;
        map { push @$ret, [ $key, $_ ] } keys %{ $hash->{$_} }
    } keys %$hash;

    return $ret;
}

sub define_utf8_method {
    my ($iClass) = @_;
    no strict 'refs';
    for my $method (qw(get get_tags)) {
        my $utf8_method = $method . '_utf8';
        *{"${iClass}::${method}"} = sub {
            my ( $iTag, $key, $resno ) = @_;
            my $iKernel  = $iTag->{_kernel};
            my $encoding = $iKernel->get_encoding(1);
            my $ret      = {};
            my %result   = %{ $iTag ->${utf8_method}( $key, $resno ) };
            while ( my ( $tag_name, $data ) = each %result ) {
                $tag_name = $iKernel->get_encoded_str( $tag_name, $encoding,
                    'utf8' );
                $ret->{$tag_name} = $data;
            }
            return $ret;
        };
    }
}

1;
__END__
