#/*
# *  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: Template.pm 1901 2009-12-31 02:40:20Z hikarin $
#

package Img0ch::Template;

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

sub new {
    my ( $iClass, $iObject, $args ) = @_;
    $args or $args = { version => Img0ch::Kernel->VERSION() };
    my ( $iConfig, $iKernel, $bbs );
    my $class   = ref $iObject || '';
    my $setting = {};
    my $tmpldir = $args->{dir} || 'default';

    if ( $class eq 'Img0ch::BBS' ) {
        $iKernel = $iObject->get_kernel();
        $iConfig = $iKernel->get_config();
        $bbs     = $iObject->get_name();
        if ( my $iSetting = $args->{setting} ) {
            %{$setting} = %{ $iSetting->get_hash() };
            $tmpldir = delete $setting->{'BBS_TEMPLATE'} || 'default';
        }
    }
    elsif ( $class eq 'Img0ch::Kernel' or $class eq 'Img0ch::Maple' ) {
        $iConfig = $iObject->get_config();
        $iKernel = $iObject;
        $bbs     = '';
    }
    else {
        Img0ch::Kernel->throw_exception(
            'Img0ch::BBS or Img0ch::Maple(Img0ch::Kernel) not given');
    }

    my $uri       = $iConfig->get('Server');
    my $tmplcache = $iConfig->get('TemplateCacheRoot');
    my $tmplcmode = $iConfig->get_int('TemplateCacheRootMode');
    my $tmplbase  = $iConfig->get('TemplatePath')
        || $iConfig->get('ShellPath');
    my $file = join '.', $args->{file},
        ( $iConfig->get('TemplateFileExtension') || 'ht' );
    my $filename = join '/', $tmplbase, $tmpldir, $file;
    -r $filename or $filename = join '/', $tmplbase, 'default', $file;

    my $iRequest = $args->{request};
    my $iTemplate = bless { __request => $iRequest }, $iClass;
    my $cgipath
        = $iRequest
        ? $iRequest->get_app_uri()
        : Img0ch::Request->get_app_uri2($iConfig);

    $iTemplate->{__engine} = _load(
        $iConfig->get('TemplateEngine') || 'HTML::Template',
        {   kernel   => $iKernel,
            dir      => $tmpldir,
            cachedir => $tmplcache,
            cmode    => $tmplcmode,
            filename => $filename,
            bbs      => $bbs,
            tmplpath => $tmplbase,
            setting  => $setting,
            static   => $iConfig->get('Static'),
            uri      => $uri,
            cgipath  => $cgipath,
            version =>
                ( $iRequest ? $iRequest->signature() : $args->{version} ),
        },
    );
    $iTemplate->{__kernel} = $iKernel;

    $iTemplate->param(
        {   SecuredVersion => Digest::MD5::md5_hex(
                Img0ch::Kernel->VERSION(),
                $iConfig->get('Seed')
            ),
        }
    );

    if ( my $iSetting = $args->{setting} ) {
        my $mode   = $iSetting->get('BBS_MODE');
        my $res    = $mode eq 'picture' ? 1 : 0;
        my $thread = ( $iSetting->is_checked('BBS_IMG_UPLOAD_THREAD')
                || $mode eq 'news' ) ? 1 : 0;
        $iTemplate->param(
            {   Uploadable => ( $res || $thread ),
                UploadableFromRes    => $res,
                UploadableFromThread => $thread,
            }
        );
    }
    return $iTemplate;
}

sub param { shift->{__engine}->param(@_) }

sub to_string {
    my ( $iTemplate, $filter ) = @_;
    my $buffer = $iTemplate->{__engine}->render();

    return ( ref $filter || '' ) eq 'CODE' ? \$filter->($buffer) : $buffer;
}

sub flush {
    my ( $iTemplate, $filter ) = @_;
    my $iRequest = $iTemplate->{__request};
    my $buffer   = $iTemplate->to_string($filter);
    $iRequest ? $iRequest->print($buffer) : print $$buffer;
    return;
}

sub save {
    my ( $iTemplate, $path, $filter ) = @_;
    my $iKernel = $iTemplate->{__kernel};
    my $buffer  = $iTemplate->{__engine}->render();

    my $fh = $iKernel->get_write_file_handle($path);
    print {$fh} (
        ( ref $filter || '' ) eq 'CODE'
        ? $filter->($buffer)
        : $$buffer
    ) or $iKernel->throw_io_exception($path);
    close $fh or $iKernel->throw_io_exception($path);
    1;
}

sub remove_cache { shift->{__engine}->remove_cache(@_) }

sub _load {
    my ( $pkg, $argument ) = @_;
    my $class = 'Img0ch::Template::'
        . +{
        'ClearSilver'              => 'CS',
        'HTML::Template'           => 'HT',
        'HTML::Template::Compiled' => 'HTC',
        'Template::Toolkit'        => 'TT',
        }->{$pkg}
        || 'HT';
    defined &{"${class}::new"} or Img0ch::Kernel->load_module($class);
    return $class->new($argument);
}

1;
__END__

=head1 NAME

Img0ch::Template - テンプレートを管理するクラス

=head1 SYNOPSYS

  use Img0ch::Template

  my $iTemplate = Img0ch::Template->new($iKernel, {
      file => 'foo'
  });
  $iTemplate->param({
      foo => 'bar'
  });

  # 結果を標準出力に出力する
  $iTemplate->flush();
  # 結果をindex.htmlとして保存する
  $iTemplate->save('index.html')

=head1 DESCRIPTION

単体のテンプレートファイルを1つのオブジェクトとするクラスです。

=head2 new

=over 4

=item Arguments

$iBBS or $iKernel, $argument

=item Return Value

$iTemplate (Img0ch::Template itself)

=back

I<Img0ch::Template>のオブジェクトを作成します。

第一引数は$iBBS(I<Img0ch::BBS>)か$iKernel(I<Img0ch::Kernel>)を渡す
必要があります。それ以外の値を渡すとcroak()を呼出、例外を発行します。

第二引数はハッシュを渡す必要があります。指定可能な値は以下の通りです。

=over 4

=item file

必須のオプションです。拡張子を除いたテンプレートのファイルを指定します。

=item dir

テンプレートを格納するディレクトリを指定します。指定されない場合、
I<Img0ch::BBS>が渡されているならI<BBS_TEMPLATE>の値を、
I<Img0ch::Kernel>が渡されているならI<default>を値にとります。

=item setting

I<Img0ch::Setting>を引数として渡します。
設定された場合、I<Img0ch::Setting>の内容をI<get_hash()>で
全て展開し、テンプレートのパラメータとして使えるようにします。

=item version

バージョンの文字列を指定します。指定された場合、
テンプレートのパラメータにI<VERSION>が使えるようになります。

=back

このとき自動的にparam()に渡され、
全てのテンプレートに利用できる値は以下の通りです。

=over 4

=item BaseURI

img0ch-config.cgiのI<Server>と同じ値を返します。

=item BBSPath

img0ch-config.cgiのI<BBSPath>と同じ値を返します。

=item BBSURI

L<BaseURI>に掲示板のディレクトリを付加したURLを返します。

=item BBS

掲示板のディレクトリ名を返します。

=item CGIURI

あればimg0ch-config.cgiのI<CGIServer>と同じ値、
無ければL<BaseURI>に"test"ディレクトリを付加したURLを返します。

=item StaticURI

img0ch-config.cgiのI<Static>と同じ値を返します。

=item TimeStamp

現在の時刻のタイムスタンプを返します。

第一引数にI<Img0ch::BBS>が渡された場合、
SETTING.TXTにある全ての値を利用することが出来ます。

=item VERSION

使用しているバージョンの文字列を返します。

=item Version

使用しているバージョンの数値(x.x.x)を返します。

=item SecuredVersion

使用しているバージョンとimg0ch-config.cgiのI<Seed>を
MD5関数に渡して得られた結果を返します。

=item Uploadable

掲示板がファイルのアップロードに対応しているかの真偽値を返します。
対応していたら真、対応していない場合は偽を返します。

I<UploadableFromRes>またはI<UploadableFromThread>の
どちらかが真を返した場合必ずこのタグは真を返します。
逆に両方共偽を返した場合必ずこのタグは偽を返します。

=item UploadableFromRes

掲示板がレス投稿時のファイルのアップロードに対応しているかの真偽値を返します。
対応していたら真、対応していない場合は偽を返します。

=item UploadableFromThread

掲示板がスレッド作成時のファイルのアップロードに対応しているかの真偽値を返します。
対応していたら真、対応していない場合は偽を返します。

=back

=head2 flush

=over 4

=item Arguments

$code_reference?

=item Return Value

1

=back

テンプレートを展開した結果を標準出力に書き込みます。
I<$code_reference>が渡されている場合、テンプレートを展開した
スカラーのリファレンスが渡されます。結果をI<print()>に出力するため、
返り値にリファレンスではないスカラーを返す必要があります。

=head2 save

=over 4

=item Arguments

$path_to_file, $code_reference?

=item Return Value

1

=back

テンプレートを展開した結果をI<$path_to_file>に書き込みます。
I<$code_reference>が渡されている場合、テンプレートを展開した
スカラーのリファレンスが渡されます。結果をI<print()>に出力するため、
返り値にリファレンスではないスカラーを返す必要があります。

=head1 AUTHOR

hkrn E<lt>hikarin@users.sourceforge.jpE<gt>

=cut
