#
# Copyright(C) 2007-2012 National Institute of Information and Communications Technology
#

package tsv;

use strict;
use warnings;

use Encode;
use utf8;
binmode STDOUT, ":encoding(utf8)";

my $knpcmd = "echo %s | juman6 -B | knp3 -dpnd -tab -postprocess";

sub new ($$) {
    my ( $class, $self ) = @_;
    return bless $self, $class;
}

# JUMAN/KNPの結果を得る．
sub setknpresult($$) {
    my ( $pkg, $str ) = @_;
    my $knp;
    my $morph_i    = 0;
    my $bunsetsu_i = 0;
    my $line;
    my $lineCnt = 1;

    $str = encode( "euc-jp", $str );
    open my $in, "-|", sprintf( $knpcmd, $str ) or die;

    while ( $line = <$in> ) {

# KNP解析結果のヘッダーを処理する（ヘッダー以外でERROR:〜が含まれる場合があるかKNPの仕様確認が必要）
        if ( $lineCnt == 1 ) {
            my $index = rindex( $line, "ERROR:" );
            if ( $index != -1 ) {
                my @errmsg = split( "ERROR:", $line );
                $errmsg[1] =~ s/\\n|\n//;
                printf STDERR " skip: KNP ERROR: %s", $errmsg[1];
                $pkg->{knp} = $knp;
                return -1;
            }
        }

        $line = decode( "euc-jp", $line );
        chomp $line;

        if ( $line =~ /^EOS/ ) {
            last;
        }
        elsif ($line =~ /^\#/
            || $line =~ /^\+/
            || $line =~ /^\!/
            || $line =~ /^;/ )
        {
            next;
        }
        elsif ( $line =~ /^\*/ ) {
            my ( $deli, $dpnd, $others ) = split( /[\x{0020} ]/, $line, 3 );
            if ( $morph_i != 0 ) {
                $knp->{bnst}->[ $bunsetsu_i - 1 ] = $morph_i + 1;
            }
            $knp->{head}->[$bunsetsu_i] =
              substr( $dpnd, 0, length($dpnd) - 1 ) + 1;
            $knp->{type}->[$bunsetsu_i] = substr( $dpnd, length($dpnd) - 1 );
            $bunsetsu_i++;
        }
        else {
            my @morph = split( /[\x{0020} ]/, $line );
            $knp->{surf}->[$morph_i] = $morph[0];
            $knp->{base}->[$morph_i] = $morph[2];
            $knp->{cpos}->[$morph_i] = $morph[3];
            $knp->{fpos}->[$morph_i] = $morph[5];
            if ( $line =~ /<代表表記:([^>]+)>/ ) {
                $knp->{repr}->[$morph_i] = $1;
            }
            else {
                $knp->{repr}->[$morph_i] = "";
            }
            $morph_i++;
        }
        $lineCnt++;
    }
    close $in;

# 実行結果が未定義の場合、エラーとして処理する。正常処理時は"EOS"が記録されている
    if ( !defined($line) ) {
        $pkg->{knp} = $knp;
        return 0;
    }

    if ( $morph_i != 0 ) {
        $knp->{bnst}->[ $bunsetsu_i - 1 ] = $morph_i + 1;
        $pkg->{knp} = $knp;
        return 1;
    }

    # 形態素が取得できなかった場合
    $pkg->{knp} = $knp;
    return 0;
}

# JUMAN/KNPの結果を得る．
sub setknpresult2($$) {
    my ( $pkg, $str ) = @_;
    my $knp;
    my $morph_i    = 0;
    my $bunsetsu_i = 0;
    my $line;
    my $lineCnt = 1;
    my $len     = 0;

    $str = encode( "euc-jp", $str );
    open my $in, "-|", sprintf( $knpcmd, $str ) or die;

    while ( $line = <$in> ) {

# KNP解析結果のヘッダーを処理する（ヘッダー以外でERROR:〜が含まれる場合があるかKNPの仕様確認が必要）
        if ( $lineCnt == 1 ) {
            my $index = rindex( $line, "ERROR:" );
            if ( $index != -1 ) {
                my @errmsg = split( "ERROR:", $line );
                $errmsg[1] =~ s/\\n|\n//;
                printf STDERR " skip: KNP ERROR: %s", $errmsg[1];
                $pkg->{knp} = $knp;
                return -1;
            }
        }

        $line = decode( "euc-jp", $line );
        chomp $line;

        if ( $line =~ /^EOS/ ) {
            last;
        }
        if ( $line =~ /^\#/ ) {
            next;
        }
        if ( $line =~ /^\*/ ) {
            my ( $deli, $dpnd, $others ) = split( /[\x{0020} ]/, $line, 3 );
            if ( $morph_i != 0 ) {
                $knp->{bnst}->[ $bunsetsu_i - 1 ] = $morph_i + 1;
            }
            $knp->{head}->[$bunsetsu_i] =
              substr( $dpnd, 0, length($dpnd) - 1 ) + 1;
            $knp->{type}->[$bunsetsu_i] = substr( $dpnd, length($dpnd) - 1 );
            $knp->{string}->[$bunsetsu_i] = "";
            $bunsetsu_i++;
            next;
        }
        if (   $line =~ /^\+/
            || $line =~ /^\!/
            || $line =~ /^;/ )
        {
            next;
        }
        my @morph = split( /[\x{0020} ]/, $line );
        $knp->{surf}->[$morph_i] = $morph[0];
        $knp->{base}->[$morph_i] = $morph[2];
        $knp->{cpos}->[$morph_i] = $morph[3];
        $knp->{fpos}->[$morph_i] = $morph[5];
        if ( $line =~ /<代表表記:([^>]+)>/ ) {
            $knp->{repr}->[$morph_i] = $1;
        }
        else {
            $knp->{repr}->[$morph_i] = "";
        }
        $morph_i++;

        # 文節の位置を設定
        my $newlen = $len + length( $morph[0] );
        for ( my $i = $len ; $i < $newlen ; $i++ ) {
            $knp->{bunid}->[$i] = $bunsetsu_i - 1;
        }
        $len = $newlen;
        $knp->{bnst}->[ $bunsetsu_i - 1 ] = $morph_i + 1;
        $knp->{string}->[ $bunsetsu_i - 1 ] .= $morph[0];
        $lineCnt++;
    }
    close $in;

# 実行結果が未定義の場合、エラーとして処理する。正常処理時は"EOS"が記録されている
    if ( !defined($line) ) {
        $pkg->{knp} = $knp;
        return 0;
    }

    if ( $morph_i != 0 ) {
        $knp->{bnst}->[ $bunsetsu_i - 1 ] = $morph_i + 1;
        $pkg->{knp} = $knp;
        return 1;
    }

    # 形態素が取得できなかった場合
    $pkg->{knp} = $knp;
    return 0;
}

# 各項目の値を設定する．
sub setVal($$) {
    my ( $pkg, $val ) = @_;
    foreach ( keys %{$val} ) {
        if (   $_ eq "relevancy"
            || $_ eq "holder"
            || $_ eq "expression"
            || $_ eq "type"
            || $_ eq "target" )
        {

# このsplitを外すとin2tsv出力時に改行が1行にまとまってしまうので値は保持したままにする
# 行頭が\n、末尾が\nで終わっている項目や行中に￥ｎで改行されている項目も行数としてカウントするため分割する
            @{ $pkg->{$_} } = split( /\n|\\n|￥ｎ/, $val->{$_}, -1 );

            # 分割した項目の\nを削除する
            for ( my $i = 0 ; $i < @{ $pkg->{$_} } ; $i++ ) {
                $pkg->{$_}[$i] =~ s/\n|\\n|￥ｎ//;

                # [著者より前の文字を削除
                if ( $_ eq "holder" ) {
                    my $index = index( $pkg->{$_}[$i], "[著者" );
                    if ( $index > 0 ) {
                        substr( $pkg->{$_}[$i], 0, $index ) = "";
                    }
                }
            }
        }
        else {
            $pkg->{$_} = $val->{$_};

            # [著者より前の文字を削除
            if ( $_ eq "holder" ) {
                my $index = index( $pkg->{$_}, "[著者" );
                if ( $index > 0 ) {
                    substr( $pkg->{$_}, 0, $index ) = "";
                }
            }
        }

    }
}

# 内部処理用にすべての項目を出力する．
sub printTSV($) {
    my ($pkg) = @_;
    if ( !defined( $pkg->{num} ) ) {
        $pkg->{num} = 0;
    }

    # 改行コードは\nで出力する
    # TOPIC（Ver1.1では未使用項目）
    printf STDOUT "%s",   $pkg->{topic};
    printf STDOUT "\t%s", $pkg->{sampleID}++;
    printf STDOUT "\t%s", $pkg->{documentID};
    printf STDOUT "\t%s", $pkg->{sentenceID};
    printf STDOUT "\t%s", $pkg->{sentence};
    printf STDOUT "\t";

    # 評価関連性（Ver1.1では未使用項目）
    #	for (my $i = 0; $i <= $pkg->{num} ; $i++){
    #	    printf STDOUT "%s", $pkg->{relevancy}->[$i];
    #	    if ($i < $pkg->{num}) {
    #	        printf STDOUT "\\n";
    #	    }
    #	}
    printf STDOUT "\t";

    # 評価保持者
    for ( my $i = 0 ; $i <= $pkg->{num} ; $i++ ) {
        printf STDOUT "%s", $pkg->{holder}->[$i];
        if ( $i < $pkg->{num} ) {
            printf STDOUT "\\n";
        }
    }
    printf STDOUT "\t";

    # 評価表現
    for ( my $i = 0 ; $i <= $pkg->{num} ; $i++ ) {
        printf STDOUT "%s", $pkg->{expression}->[$i];
        if ( $i < $pkg->{num} ) {
            printf STDOUT "\\n";
        }
    }
    printf STDOUT "\t";

    # 評価タイプ
    for ( my $i = 0 ; $i <= $pkg->{num} ; $i++ ) {
        printf STDOUT "%s", $pkg->{type}->[$i];
        if ( $i < $pkg->{num} ) {
            printf STDOUT "\\n";
        }
    }

    # 評価対象（Ver1.1では未使用項目）
    #	printf STDOUT "\t";
    #	for (my $i = 0; $i <= $pkg->{num} ; $i++){
    #	    printf STDOUT "%s", $pkg->{target}->[$i];
    #	    if ($i < $pkg->{num}) {
    #	        printf STDOUT "\\n";
    #	    }
    #	}
    printf STDOUT "\t%s:%s:%s:%s", join( "_", @{ $pkg->{knp}->{surf} } ),
      join( "_", @{ $pkg->{knp}->{base} } ),
      join( "_", @{ $pkg->{knp}->{cpos} } ),
      join( "_", @{ $pkg->{knp}->{fpos} } );
    printf STDOUT "\t%s:%s:%s", join( "_", @{ $pkg->{knp}->{bnst} } ),
      join( "_", @{ $pkg->{knp}->{head} } ),
      join( "_", @{ $pkg->{knp}->{type} } );
    printf STDOUT "\t%s", $pkg->{context};

    #	printf STDOUT "\t%s", join( "_", @{ $pkg->{knp}->{repr} } );
    #	printf STDOUT "\n";
    printf STDOUT "\n";

# 処理行数を返却する（{num}+1は数行に跨る場合の開始行を出力するための処理）
#   return $pkg->{num}+1;
    return 1;
}

# 抽出結果を確認用に出力する．
sub printOUT($) {
    my ($pkg) = @_;
    if ( !defined( $pkg->{num} ) ) {
        $pkg->{num} = 0;
    }
    for ( my $i = 0 ; $i <= $pkg->{num} ; $i++ ) {
        printf STDOUT "%s",   $pkg->{documentID};
        printf STDOUT "\t%s", $pkg->{sentenceID};

        #	printf STDOUT "\t%s", $pkg->{topic};
        #	printf STDOUT "\t%s", $pkg->{relevancy}->[$i];
        #	printf STDOUT "\t%s", $pkg->{sentence};
        printf STDOUT "\t%s", $pkg->{holder}->[$i];
        printf STDOUT "\t%s", $pkg->{type}->[$i];
        printf STDOUT "\t%s", $pkg->{expression}->[$i];

        #	printf STDOUT "\t%s", $pkg->{target}->[$i];
        printf STDOUT "\n";
    }
    return $pkg->{num} + 1;
}

# 空白行を出力する
sub printOUTBlank($) {
    my ($pkg) = @_;
    printf STDOUT "";    #topic;
    printf STDOUT "\t%s", $pkg->{sampleID};      #sampleID
    printf STDOUT "\t%s", $pkg->{documentID};    #documentID
    printf STDOUT "\t%s", $pkg->{sentenceID};    #sentenceID
    printf STDOUT "\t空白文";
    printf STDOUT "\t";                          # 評価関連性
    printf STDOUT "\t";                          # 評価保持者
    printf STDOUT "\t";                          # 評価表現
    printf STDOUT "\t";                          # 評価タイプ

    #	printf STDOUT "\t"; # 評価対象
    printf STDOUT "\t空白_文:空白だ_文:形容詞_名詞:*_普通名詞";
    printf STDOUT "\t3:0:D";
    printf STDOUT "\t";
    printf STDOUT "\t空白/くうはくa_文/ぶん";
    printf STDOUT "\n";
}

# Relevancyの値をチェックする．
sub checkRelevancy($) {
    my ($rel) = @_;
    if (   $rel =~ /[0-9\.\-]+/
        || $rel eq "+"
        || $rel eq "-"
        || $rel eq "x"
        || $rel eq "" )
    {
        if ( $rel eq "+" || $rel eq "-" || $rel eq "x" ) {
            $rel = 2;
        }
        return $rel;
    }
    else {

        #	printf STDERR "NG:Relevancy=%s\n", $rel;
        return;
    }

}

# Typeの値をチェックする．
sub checkType($) {
    my ($type) = @_;

    if (   $type eq ""
        || $type eq "当為"
        || $type eq "当為０"
        || $type eq "当為+"
        || $type eq "当為-"
        || $type eq "要望"
        || $type eq "要望０"
        || $type eq "要望+"
        || $type eq "要望-"
        || $type eq "感情+"
        || $type eq "感情-"
        || $type eq "批評+"
        || $type eq "批評-"
        || $type eq "メリット+"
        || $type eq "メリット-"
        || $type eq "採否+"
        || $type eq "採否-"
        || $type eq "出来事+"
        || $type eq "出来事-" )
    {
        return 0;
    }
    else {
        printf STDERR " skip: Type Error [%s]", $type;

        #	printf STDERR "NG:Type=[%s]\n", $type;
        return -1;
    }
}

# 各項目をチェックする．
sub checkTSV($) {
    my ($pkg) = @_;

    my $num = 0;

    if ( !defined( $pkg->{topic} ) ) {
        $pkg->{topic} = "";
    }
    if ( !defined( $pkg->{sampleID} ) ) {
        $pkg->{sampleID} = "";
    }
    if ( !defined( $pkg->{documentID} ) ) {
        $pkg->{documentID} = "";
    }
    if ( !defined( $pkg->{sentenceID} ) ) {
        $pkg->{sentenceID} = "";
    }
    if ( !defined( $pkg->{sentence} ) ) {
        $pkg->{sentence} = "";
    }
    if ( !defined( $pkg->{knp} ) ) {
        $pkg->{knp}->{surf}->[0]   = "",
          $pkg->{knp}->{base}->[0] = "",
          $pkg->{knp}->{cpos}->[0] = "",
          $pkg->{knp}->{fpos}->[0] = "",
          $pkg->{knp}->{bnst}->[0] = "",
          $pkg->{knp}->{head}->[0] = "",
          $pkg->{knp}->{type}->[0] = "",
          $pkg->{knp}->{repr}->[0] = "",
          ;
    }
    if ( !defined( $pkg->{context} ) ) {
        $pkg->{context} = "";
    }
    if ( defined( $pkg->{relevancy} ) ) {
        if ( $#{ $pkg->{relevancy} } > $num ) {
            $num = $#{ $pkg->{relevancy} };
        }
    }
    if ( defined( $pkg->{holder} ) ) {
        if ( $#{ $pkg->{holder} } > $num ) {
            $num = $#{ $pkg->{holder} };
        }
    }
    if ( defined( $pkg->{expression} ) ) {
        if ( $#{ $pkg->{expression} } > $num ) {
            $num = $#{ $pkg->{expression} };
        }
    }
    if ( defined( $pkg->{type} ) ) {
        if ( $#{ $pkg->{type} } > $num ) {
            $num = $#{ $pkg->{type} };
        }
    }
    if ( defined( $pkg->{target} ) ) {
        if ( $#{ $pkg->{target} } > $num ) {
            $num = $#{ $pkg->{target} };
        }
    }
    for ( my $i = 0 ; $i <= $num ; $i++ ) {
        if ( !defined( $pkg->{relevancy}->[$i] ) ) {
            $pkg->{relevancy}->[$i] = "";
        }
        else {
            if ( defined( checkRelevancy( $pkg->{relevancy}->[$i] ) ) ) {
                $pkg->{relevancy}->[$i] =
                  checkRelevancy( $pkg->{relevancy}->[$i] );
            }
            else {
                return -1;
            }
        }
        if ( !defined( $pkg->{holder}->[$i] ) ) {
            $pkg->{holder}->[$i] = "";

# 評価表現と評価タイプのみ設定されている場合、評価保持者に[不定]を設定する
            if (   "" eq $pkg->{holder}->[$i]
                && defined( $pkg->{expression}->[$i] )
                && defined( $pkg->{type}->[$i] ) )
            {
                $pkg->{holder}->[$i] = "[不定]";
            }
        }
        if ( !defined( $pkg->{expression}->[$i] ) ) {
            $pkg->{expression}->[$i] = "";
        }
        if ( !defined( $pkg->{type}->[$i] ) ) {
            $pkg->{type}->[$i] = "";
        }
        else {
            if ( checkType( $pkg->{type}->[$i] ) == -1 ) {
                return -1;
            }
        }
    }

# 評価表現（expression）が改行で区切られて二つ以上ある場合、
# 評価保持者（holder）、評価タイプ（type）、評価対象(target）も
# 評価表現の数と同じ数がないといけない
# Ver1.1では評価対象（target）を未使用のためコメントアウト
    if (
           $#{ $pkg->{expression} } != $#{ $pkg->{holder} }
        || $#{ $pkg->{expression} } != $#{ $pkg->{type} }

        #	|| $#{ $pkg->{expression} } != $#{ $pkg->{target} }
      )
    {
        printf STDERR
          " skip: Element number mismatch. holder:%d\ttype:%d\texp:%d",
          $#{ $pkg->{holder} }, $#{ $pkg->{type} }, $#{ $pkg->{expression} };
        return -1;
    }

# 評価保持者（holder）が[x][y]表記の場合に追加する自動改行がある場合のチェック
    my $expElm    = "";
    my $holderElm = "";
    my $typeElm   = "";
    my $targetElm = "";

    for ( my $i = 0 ; $i <= $#{ $pkg->{expression} } ; $i++ ) {
        $expElm    .= $pkg->{expression}[$i] . "\\n";
        $holderElm .= $pkg->{holder}[$i] . "\\n";
        $typeElm   .= $pkg->{type}[$i] . "\\n";

        #$targetElm .= $pkg->{target}[$i]."\\n";
    }

    my @expElmNum = split( /\\n/, $expElm );
    my @holderElm = split( /\\n/, $holderElm );
    my @typeElm   = split( /\\n/, $typeElm );

    #my @targetElm = split(/\\n/ , $targetElm);

    if (
           $#expElmNum != $#holderElm
        || $#expElmNum != $#typeElm

        #	|| $#expElmNum != $#typeElm
      )
    {
        printf STDERR
          " skip: Element number mismatch. holder:%d\ttype:%d\texp:%d",
          $#holderElm + 1, $#typeElm + 1, $#expElmNum + 1;
        return -1;
    }

    $pkg->{num} = $num;
    return 0;
}

# 半角英数字、半角記号を全角に置換する処理
sub valueChangeZenkaku() {
    my ($s) = @_;

    $s =~ s/\t/ /g;
    $s =~ tr/A-Za-z/Ａ-Ｚａ-ｚ/;
    $s =~ tr/0-9/０-９/;
    $s =~
tr{!"#$%&'()*+,-./:;<=>?@[\\]^_`|~｡､･｢｣\{\}[]}{！”＃＄％＆’（）＊＋，−．／：；＜＝＞？＠［￥］＾＿｀｜〜。、・「」｛｝};
    return $s;
}

1;

