#!/usr/bin/perl

=head1 NAME

CSSJ::Builder - コンテントビルダ

=head2 概要

断片化された変換結果を結合します。

通常、プログラマがこのパッケージを直接使う必要はありません。

=head2 作者

$Date: 2006/03/30 10:58:01 $ MIYABE Tatsuhiko

=cut
package CSSJ::Builder;

require Exporter;
@ISA	= qw(Exporter);
@EXPORT_OK	= qw(build);

use strict;
use File::Temp;
use Symbol;
use CSSJ::Helpers;
use CSSJ::Fragment;
use CSSJ::CTIP;

=head2 build

C<build IOHANDLE OUTPUTHANDLE ERROR_FUNC PROGRESS_FUNC CONTENT_LENGTH_FUNC>

レスポンスからデータを構築します。

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=item OUTPUTHANDLE 出力先ハンドル

=item ERROR_FUNC エラーコールバック関数。省略する場合はundef。

=item PROGRESS_FUNC 進行状況コールバック関数。省略する場合はundef。

=item CONTENT_LENGTH_FUNC Content-Lengthコールバック関数。省略する場合はundef。

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub build (**$$$) {
  my $fp = shift;
  my $out = shift;
  my $errorFunc = shift;
  my $progressFunc = shift;
  my $contentLengthFunc = shift;
  $fp = Symbol::qualify_to_ref($fp, caller());
  $out = Symbol::qualify_to_ref($out, caller());
  my $builder = new CSSJ::Builder($fp, $out, $errorFunc, $progressFunc, $contentLengthFunc);
  my $err;
  while ($err = $builder->next()) {
  };
  defined($err) or return undef;
  return 1; 
}

=head1 CSSJ::Builder

C<new CSSJ::Builder IOHANDLE OUTHANDLE ERROR_FUNC PROGRESS_FUNC CONTENT_LENGTH_FUNC>

ビルダーを構築します。

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=item OUTPUTHANDLE 出力先ハンドル

=item ERROR_FUNC エラーコールバック関数。省略する場合はundef。

=item PROGRESS_FUNC 進行状況コールバック関数。省略する場合はundef。

=item CONTENT_LENGTH_FUNC Content-Lengthコールバック関数。省略する場合はundef。

=back

=cut
sub new ($**$$$) {
  my $class = shift;
  my $fp = shift;
  my $out = shift;
  my $errorFunc = shift;
  my $progressFunc = shift;
  my $contentLengthFunc = shift;
  $fp = Symbol::qualify_to_ref($fp, caller());
  $out = Symbol::qualify_to_ref($out, caller());
  
  my $tempFile;
  my $tempFileName;
  ($tempFile, $tempFileName) = File::Temp::tempfile();
  binmode $tempFile;
  
  my $self = {
    'FP' => $fp,
    'OUT' => $out,
    'errorFunc' => $errorFunc,
    'progressFunc' => $progressFunc,
    'contentLengthFunc' => $contentLengthFunc,
    'tempFile' => $tempFile,
    'tempFileName' => $tempFileName,
    'frgs' => [ ],
    'first' => undef,
    'last' => undef,
    'onMemory' => 0,
    'length' => 0,
    'segment' => 0
  };
  
  bless $self, $class;
  return $self;
}

=head1 CSSJ::Builder->next

C<next>

次のビルドタスクを実行します。

=head2 戻り値

B<次がある場合は1,終わった場合は0,エラーの場合はundef>

=cut
sub next ($) {
  my $self = shift;
  
  my $fp = $self->{FP};
  my $out = $self->{OUT};
  my $errorFunc = $self->{errorFunc};
  my $progressFunc = $self->{progressFunc};
  my $contentLengthFunc = $self->{contentLengthFunc};
  my $tempFile = $self->{tempFile};
  my $tempFileName = $self->{tempFileName};
  my $frgs = $self->{frgs};
  my $first = $self->{first};
  my $last = $self->{last};
  my $onMemory = \$self->{onMemory};
  my $length = \$self->{length};
  my $segment = \$self->{segment};
  
  my %next = CSSJ::CTIP::res_next($fp);
  if (! %next) {close($tempFile); unlink($tempFileName); return undef;}
  
  if ($next{type} != CSSJ::CTIP::RES_END) {
    my $type = $next{type};
    if ($type == CSSJ::CTIP::RES_ADD) {
      my $id = @$frgs;
      my $frg = new CSSJ::Fragment($id);
      $frgs->[$id] = $frg;
      if (!$first) {
        $first = $frg;
      }
      else {
        $last->{next} = $frg;
        $frg->{prev} = $last;
      }
      $last = $frg;
    } elsif ($type == CSSJ::CTIP::RES_INSERT) {
      my $anchorId = $next{anchorId};
      my $id = @$frgs;
      my $anchor = $frgs->[$anchorId];
      my $frg = new CSSJ::Fragment($id);
      $frgs->[$id] = $frg;
      $frg->{prev} = $anchor->{prev};
      $frg->{next} = $anchor;
      $anchor->{prev}->{next} = $frg;
      $anchor->{prev} = $frg;
      if ($first->{id} == $anchor->{id}) {
        $first = $frg;
      }
    } elsif ($type == CSSJ::CTIP::RES_ERROR) {
      if (defined($errorFunc)) {
        &$errorFunc($next{level}, $next{error});
      }
    } elsif ($type == CSSJ::CTIP::RES_DATA) {
      if (defined($progressFunc)) {
        &$progressFunc($next{progress});
      }
      my $frg = $frgs->[$next{id}];
      my $written = $frg->write($tempFile, $onMemory, $segment, $next{bytes});
      if (!defined($written)) {close($tempFile); unlink($tempFileName); return undef;}
      $$length += $written;
    }
    
    $self->{first} = $first;
    $self->{last} = $last;
    return 1;
  }
  else {
    if (defined($contentLengthFunc)) {
      &$contentLengthFunc($$length);
    }
    
    my $saveout = select($out);
    $| = 1;
    $| = 0;
    select($saveout);
    
    open($tempFile, "<$tempFileName");
    binmode $tempFile;
    
    my $frg = $first;
    while (defined($frg)) {
      if (!defined($frg->flush($tempFile, $out))) {close($tempFile); unlink($tempFileName); return undef;}
      $frg = $frg->{next};
    }
  	close($tempFile);
    unlink($tempFileName);
  	
    return 0;
  }
}

