#/*
# *  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: Setting.pm 1803 2008-11-02 16:55:25Z hikarin $
#

package Zeromin::Setting;

use strict;
use base qw(Img0ch::Setting);
use Config::Tiny qw();

my $subs = {
    'integer' => sub {
        my ( $value, $boolean ) = @_;
        return Img0ch::Kernel::intval($value);
    },
    'boolean' => sub {
        my ( $value, $boolean ) = @_;
        return $boolean eq $value ? 1 : 0;
    },
    'string' => sub { return $_[0] },
};

my $stringify = {
    'integer' => sub {
        my ( $value, $boolean ) = @_;
        return Img0ch::Kernel::intval($value) || '0';
    },
    'boolean' => sub {
        my ( $value, $boolean ) = @_;
        return $value ? $boolean : '';
    },
    'string' => sub { return $_[0] }
};

sub new {
    my ( $zClass, $iObject ) = @_;
    my $iBBS
        = ( ref $iObject || '' ) eq 'Zeromin::BBS'
        ? $iObject->parent()
        : $iObject;

    my $zSetting = $zClass->SUPER::new($iBBS);
    my $iKernel  = $zSetting->{_kernel};
    $zSetting->{__bbs}      = $iBBS;
    $zSetting->{__setting}  = {};
    $zSetting->{__encoding} = $iKernel->get_encoding(1);

    my $base = join '/', $iKernel->get_config()->get('SystemPath'),
        'setting.txt';
    my $config = Config::Tiny->new();
    my $info = $config->read($base) or $iKernel->throw_io_exception($base);
    $zSetting->{__info} = $info;

    while ( my ( $key, undef ) = each %{$info} ) {
        my $value = $zSetting->get_utf8($key);
        $zSetting->set( $key, $value );
    }

    $zSetting;
}

sub get { $_[0]->{__setting}->{ $_[1] } || $_[0]->SUPER::get( $_[1] ) }

sub gets {
    my ( $zSetting, $wanted ) = @_;
    my $info = $zSetting->{__info};
    my $ret  = {};

    foreach my $one ( @{$wanted} ) {
        $one or next;
        my $hash = $info->{$one};
        $hash or next;
        my $do = $subs->{ $hash->{type} };
        my $value = $zSetting->get($one) || $hash->{default};
        $ret->{$one} = $do->( $value, $hash->{boolean} );
    }

    $ret;
}

sub _get_normalized {
    my ( $get_method_type, $zSetting, $key, $unijp ) = @_;
    my $info  = $zSetting->{__info};
    my $hash  = $info->{$key} or return '';
    my $value = $zSetting->$get_method_type($key);

    if ( $unijp and $hash->{type} eq 'string' ) {
        $value = $unijp->set( $value, $zSetting->{__encoding} )->get();
    }
    return $value;
}

sub get_normalized { _get_normalized( 'get', @_ ) }

sub get_normalized_utf8 { _get_normalized( 'get_utf8', @_ ) }

sub get_default { shift->_get( shift, 'default' ) }

sub get_order { shift->_get( shift, 'order' ) }

sub get_require_privilege { shift->_get( shift, 'require' ) }

sub get_type { shift->_get( shift, 'type' ) }

sub is_open { shift->_get( shift, 'opened' ) }

sub _get {
    my ( $zSetting, $key, $name ) = @_;
    my $info = $zSetting->{__info};
    exists $info->{$key} ? $info->{$key}->{$name} : '';
}

sub set {
    my ( $zSetting, $key, $value ) = @_;
    my $info = $zSetting->{__info};
    my $hash = $info->{$key} || return '';
    my $do   = $subs->{ $hash->{type} };

    $value ||= '';
    $value =~ tr/\r\t//d;
    $value =~ s/\n+/,/g;
    $value =~ s/,+/,/g;
    $zSetting->{__setting}->{$key} = $do->( $value, $hash->{boolean} );
    return;
}

sub merge {
    my ( $zSetting, $iObject ) = @_;
    my $info  = $zSetting->{__info};
    my $merge = {};

    if ( UNIVERSAL::isa( $iObject, __PACKAGE__ ) ) {
        for my $key ( keys %{$info} ) {
            $merge->{$key} = $iObject->get($key);
        }
    }
    elsif ( UNIVERSAL::isa( $iObject, 'HASH' ) ) {
        %{$merge} = %{$iObject};
    }
    else {
        Carp::Clan::croak('Zeromin::Setting or HASH reference not given');
    }

    while ( my ( $key, $value ) = each %{$merge} ) {
        next if $key eq 'BBS_TITLE' or $key eq 'BBS_MODE';
        my $hash = $info->{$key};
        $hash or next;
        $zSetting->{__setting}->{$key} = $value || $hash->{default};
    }

    1;
}

sub keyset {
    my ( $zSetting, $all ) = @_;
    my $info   = $zSetting->{__info};
    my $ret    = [];
    my $keyset = [
        map      { $_->[1] }
            sort { $a->[0] <=> $b->[0] }
            map  { [ $info->{$_}->{order}, $_ ] }
            keys %$info
    ];

    $all ||= 0;
    foreach my $key ( @{$keyset} ) {
        my $hash = $info->{$key};
        ( !$all and !$hash->{opened} ) and next;
        push @$ret, $key;
    }
    $ret;
}

sub type {
    my ( $zSetting, $key ) = @_;
    my $info = $zSetting->{__info};

    exists $info->{$key} ? $info->{$key} : 'string';
}

sub type_int {'integer'}

sub type_boolean {'boolean'}

sub type_string {'string'}

sub save {
    my ( $zSetting, $no_disk, $path ) = @_;
    my $iReposCommon = $zSetting->{_common};
    my $iReposSystem = $zSetting->{_system};
    my $bbs          = $zSetting->{_bbs};
    my $cache        = {};
    my $i            = 0;
    my $info         = $zSetting->{__info};
    my $keyset       = $zSetting->keyset(1);

    for my $key ( @{$keyset} ) {
        my $hash = $info->{$key};
        my $value = $zSetting->{__setting}->{$key} || '';
        $value = $stringify->{ $hash->{type} }->( $value, $hash->{boolean} );
        $cache->{$key} = $value;
        $iReposSystem->set( "I:S.${bbs}.${key}", $value );
        $iReposCommon->set( "I:S.${i}",          $key );
        $iReposSystem->remove($key);
        $iReposCommon->remove("I:S.${bbs}.${key}");
        $i++;
    }
    $iReposCommon->set( 'I:S._', $i );
    $iReposCommon->save();
    $iReposSystem->set( "I:S.${bbs}.utf8", 1 );
    $iReposSystem->save();

    $no_disk ||= 0;
    my $iBBS = $zSetting->{__bbs};
    if ( !$no_disk ) {
        my $iKernel = $zSetting->{_kernel};
        my $dir     = $iBBS->get_name();
        $path ||= $zSetting->path();

        my $fh = $iKernel->get_write_file_handle($path);
        print ${fh} $dir, '@', crypt( $dir, $dir ), "\n";

    OUTPUT_DISK:
        for my $key ( @{$keyset} ) {
            my $value = $iKernel->get_encoded_str( $cache->{$key},
                $iBBS->get_encoding(), 'utf8' );
            my $key_info = $info->{$key};
            $key_info->{opened} or next OUTPUT_DISK;
            if ( exists $key_info->{except} ) {
                $key_info->{except} eq $value and next OUTPUT_DISK;
            }
            if ( exists $key_info->{condition} ) {
                my $condition = $key_info->{condition};
                my ( $ckey, $cval ) = split ':', $condition, 2;
                my @cvals = split ',', ( $cval || '' );
                my $cached_value = $cache->{$ckey};
                map { $cached_value =~ qr/$_/ and next OUTPUT_DISK } @cvals;
            }
            print ${fh} $key, '=', $value, "\n"
                or $iKernel->throw_io_exception($path);
        }
        close $fh or $iKernel->throw_io_exception($path);
    }

    require Zeromin::Plugin;
    my $zPlugin = Zeromin::Plugin->new($iBBS);
    $zPlugin->do( 'zeromin.update.setting', $zSetting, [$iBBS] );
    1;
}

sub remove {
    my ( $zSetting, $no_disk ) = @_;
    my $path = $zSetting->path();

    %{ $zSetting->{__setting} } = ();
    $zSetting->flush();

    my $iRepos = $zSetting->{_common};
    $iRepos->iterate(
        sub {
            my ( $key, $value, $bbs ) = @_;
            $key =~ /\AI:S\.$bbs\./xms and return -1;
            return 0;
        },
        $zSetting->{_bbs}
    );
    $iRepos->save();

    if ( -e $path ) {
        unlink $path or $zSetting->{_kernel}->throw_io_exception($path);
    }

    return 1;
}

1;
__END__
