use strict;
use File::Find;
use utf8;

use gmm;
use converter;
use outputer;
use xls_master;
use xls;
use errata;
use sorting;
use downloader;
use util;

binmode(STDERR, "encoding(shiftjis)");

my $SRC_DIR = '../../src';
my $HEADER_DIR = '../../../../include/msgdata';
my $RES_DIR = '../../data';
my $STAFF = '../../../../src/demo/ending/stafflist_%s.dat';
my $GOMI = 'gomi_list_%s.txt';
my @END_COL = ( 'dummy' );
my $DOWN = "downloader.gmm";
my $LANG_MARK = "kor";
my $LANG = "LANG_KOREA";
my $LANG_ORG = "LANG_JAPAN";
my %LANG = (
	"LANG_ENGLISH" => { mark => "eng", local => "eng",  org => "LANG_JAPAN" },
	"LANG_FRANCE"  => { mark => "fra", local => "euro", org => "LANG_ENGLISH" },
	"LANG_GERMANY" => { mark => "ger", local => "euro", org => "LANG_ENGLISH" },
	"LANG_ITALY"   => { mark => "ita", local => "euro", org => "LANG_ENGLISH" },
	"LANG_SPAIN"   => { mark => "spa", local => "euro", org => "LANG_ENGLISH" }
);
for (my $col = 'A'; $col ne 'AA'; $col++) {
	push @END_COL, $col;
}
my %PLACE = (
	"ARG" => { no => 0,  country => "country009" },
	"AUS" => { no => 1,  country => "country012" },
	"BRA" => { no => 2,  country => "country028" },
	"CAN" => { no => 3,  country => "country036" },
	"CHN" => { no => 4,  country => "country043" },
	"DEU" => { no => 5,  country => "country077" },
	"ESP" => { no => 6,  country => "country193" },
	"FIN" => { no => 7,  country => "country070" },
	"FRA" => { no => 8,  country => "country071" },
	"GBR" => { no => 9,  country => "country219" },
	"IND" => { no => 10, country => "country094" },
	"ITA" => { no => 11, country => "country101" },
	"JPN" => { no => 12, country => "country103" },
	"NOR" => { no => 13, country => "country156" },
	"POL" => { no => 14, country => "country166" },
	"RUS" => { no => 15, country => "country172" },
	"SWE" => { no => 16, country => "country199" },
	"USA" => { no => 17, country => "country220" }
);
my %KIND = (
	title =>   { rel_ofs =>   0, indent => 0, color => "[FF:00:色:1]" },
	head =>    { rel_ofs =>  16, indent => 0 },
	type =>    { rel_ofs =>  64, indent => 0, color => "[FF:00:色:2]" },
	typesub => { rel_ofs =>  32, indent => 2, color => "[FF:00:色:2]" },
	typesp =>  { rel_ofs => 180, indent => 0, color => "[FF:00:色:2]" },
	name =>    { rel_ofs =>  21, indent => 4 },
	end =>     { rel_ofs =>  16, indent => 0 }
);
my %PM_VERSION = (
	diamond => "VERSION_DIAMOND",
	pearl => "VERSION_PEARL",
	"" => ""
);
my %COMM = (
	"95-TRADE_POKE_00" => 1,
	"95-TRADE_POKE_01" => 1,
	"95-TRADE_POKE_02" => 1,
	"95-TRADE_POKE_03" => 1,
	"95-TRADE_OYA_00" => 1,
	"95-TRADE_OYA_01" => 1,
	"95-TRADE_OYA_02" => 1,
	"95-TRADE_OYA_03" => 1,
);
# エクセルのinfoの部分をgmmに保存するコードのON/OFF。
my $INFO_TO_GMM = 0;
my $INFO_LANG = "LANG_ENGLISH";

my %CHECK_EXCEPT = (
	"pms_word08_01" => 1,
	"7-msg_union_greeting_01" => 1
);
my @CHECK_EXCEPT_REG = (
	qr/3-ZKN_COMMENT_02_NEW_\d{2}/,
	qr/[45]-ZKN_WORLD_(NAME|TYPE)_NEW_\d{2}/
);
my %EXIST_SPACE = (
	TOKUSEI_000 => 1, TOKUSEIINFO_000 => 1, TR_NONE => 1,
	UGGoods151 => 1, UGGoods152 => 1, UGGoods153 => 1, UGGoods154 => 1
);

# 禁断の手。初期エラーを設定したいときに使用する。
my $g_repo;
my $force_jpn;
my %diff;

main();
exit;

#===========================================================
# メイン処理。
#===========================================================
sub main {
	my ($flist, $conv, $conv_org, %out, $reg);
	my (%gomi_list, %gomi_list_c, $visible_gomi, @gomi_idx_empty);
	my (%sort_data, $staff_dat, $staff_ofs, $src_dir);
	my %OPT = (
		"VISIBLE_GOMI" => \$visible_gomi,
		"FORCE_JPN" => \$force_jpn
	);

	# utf8のBOM。
	print "\x{ef}\x{bb}\x{bf}";
	binmode STDOUT, "utf8";

	if (!(1 <= @ARGV && @ARGV <= 2)) {
		return 1;
	}
	for my $i (@ARGV[1 .. 1]) {
		if (defined($OPT{$i})) {
			${$OPT{$i}} = 1;
		}
	}

	if ($ARGV[0] ne $LANG) {
		die "言語指定がおかしい。\n";
	}
	$src_dir = "$SRC_DIR/$LANG_MARK";

	$conv = new converter($LANG);
	$conv_org = new converter($LANG_ORG);

	# エラー出力対象のメッセージリストを作成。
	if (open DIFF, "diff.txt") {
		my (@diff);

		@diff = <DIFF>;
		close DIFF;
		chomp @diff;
		for my $i (@diff) {
			my (@term);

			@term = split(/\t/, $i);
			$diff{$term[0]}{$term[1]} = 1;
		}
	}

	# 既存ゴミリストから、既存ハッシュと空きリストを作る。
	if (open(GOMI_LIST, sprintf($GOMI, $LANG_MARK))) {
		my ($prev_idx);

		$prev_idx = "ZZ";
		while (<GOMI_LIST>) {
			if (/^$/) {
				last;
			}
		}
		while (<GOMI_LIST>) {
			my ($c_idx, $msg_id);

			if (!/^([A-Z]{3}): \(.*\), (.*)$/) {
				die "ゴミリストの書式が変。\n";
			}
			($c_idx, $msg_id) = ($1, $2);
			for (my $idx = ++$prev_idx; $idx ne $c_idx; $idx++) {
				push @gomi_idx_empty, $idx;
			}
			$gomi_list_c{$msg_id} = $c_idx;
			$prev_idx = $c_idx;
		}
		for (my $idx = ++$prev_idx; $idx ne "AAAA"; $idx++) {
			push @gomi_idx_empty, $idx;
		}
		close GOMI_LIST;
	} else {
		for (my $idx = "AAA"; $idx ne "AAAA"; $idx++) {
			push @gomi_idx_empty, $idx;
		}
	}

if (0) {
# ジオネットのソートは韓国では必要ない。
	# 地域ソートのテーブルのため、対応する国コードを入れておく。
	for my $i (keys %PLACE) {
		my (%data);

		$data{name} = $i;
		$data{country} = $PLACE{$i}{country};
		$sort_data{geonet}{place}[$PLACE{$i}{no}] = \%data;
	}
}

	# スタッフリストデータのヘッダ作成。
	$staff_dat .= "#ifdef __STAFFLIST_DAT__\t// 単一のファイルからのみincludeを許可する\n\n";
	$staff_dat .= "#include \"msgdata\\msg_stafflist.h\"\n\n";
	$staff_dat .= "#define ENDING_STRID_BLNK    (0xffff)\n\n";
	$staff_dat .= "static const struct {\n";
	$staff_dat .= "\tu16 strID;\n";
	$staff_dat .= "\tu16 height;\n";
	$staff_dat .= "\tu16 centeringFlag;\n";
	$staff_dat .= "} StaffListDataTable[] = {\n";

	print STDERR ">>>gmmファイル処理\n";
	$flist = get_file_list($src_dir);
	$reg = qr/^$src_dir\//;
	for my $i (@{$flist}) {
		my $gmm = new gmm($i);

		print STDERR "===$i===\n";

		for (my $iter = $gmm->newIter($LANG); !$iter->over(); $iter->next()) {
			my ($res, $repo, $text, $msg_id, $org_fname, $str, $text_org, $use_tag_list,
				$scroll, $win_con, $need_wait, $line_limit);

			$text = $iter->getText();
			$text_org = $iter->getText($LANG_ORG);
			$msg_id = $iter->getMsgId();
			$use_tag_list = $iter->getUseTagList();
			$scroll = $iter->getScroll();
			$win_con = $iter->getWindowContext();
			$need_wait = $iter->getNeedWait();
			$line_limit = $iter->getLineLimit();
			if ($line_limit == 0) {
				$line_limit = 1;
			}
			$org_fname = $iter->getOrgFileName();
			$org_fname =~ s/\\/\//g;

			$str = $i;
			$str =~ s/$reg//;

			if ($win_con eq "garbage") {
				my ($info, $pre_gomi, $gomi_len, $dummy);

				# ゴミのはず。
				$info = "($str), $msg_id";
				if ($gomi_list_c{$msg_id}) {
					$pre_gomi = $gomi_list_c{$msg_id};
				} else {
					$pre_gomi = shift @gomi_idx_empty;
				}
				$res = $conv_org->exec($text_org, \$dummy);
				if ($visible_gomi) {
					# lengthはEOMを含んでいるので。
					if ($res->{length} - 1 < 4) {
						print errata::out("日本短い", $info);
						$gomi_len = 1;
					} else {
						$gomi_len = ($res->{length} - 1) - 3;
					}
					$text = $pre_gomi . make_gomi_msg($gomi_len);
					print errata::out("ムラムラ", $info);
				} else {
					$text = " " x ($res->{length} - 1);
				}
				$gomi_list{$pre_gomi} = $info;

				$res = $conv->exec($text, \$dummy);
			} else {
				# スタッフリストは前処理。
				if ($str eq "stafflist.gmm") {
					if ($text ne "") {
						stafflist(\$text, \$use_tag_list, $msg_id, \$staff_dat, \$staff_ofs);
					} else {
						# 空っぽになるとコンパイルエラーになるので、対策。
						$staff_dat .= "\t{ ENDING_STRID_0000,     0, TRUE },\n";
					}
				}

				# デバッグ用は行数無視。
				if ($str eq "debug.gmm") {
					$line_limit = undef;
				}

				$res = convert($text, $text_org, $conv, $conv_org, $str, $msg_id,
					use_tag_list => $use_tag_list, scroll => $scroll, win_con => $win_con,
					need_wait => $need_wait, line_limit => $line_limit, comm => $COMM{$msg_id});

				# ソートに送るデータ作り。
				if ($str eq "typename.gmm" && $msg_id ne "0-TYPENAME_009") {
					push @{$sort_data{word}[2]}, { msg_id => $msg_id, text => $text };
				}
if (0) {
# ジオネットのソートは韓国では必要ない。
				if ($str eq "wifi.gmm") {
					if ($msg_id =~ /^\d\d?-([A-Z]{3})(\d{2})$/) {
						my ($plc, $no) = ($1, $2);

						if (exists($PLACE{$plc}) && $no ne "00") {
							push @{$sort_data{geonet}{place}[$PLACE{$plc}{no}]{data}}, { def => "$plc$no", text => $text };
						}
					}
					if ($msg_id =~ /^18-country(\d{3})$/) {
						my ($no) = $1;

						if ($no ne "000") {
							push @{$sort_data{geonet}{country}}, { def => "country$no", text => $text };
						}
					}
				}
}
			}

			if ($msg_id !~ s/^\d+-//) {
				die "error: メッセージIDの先頭が数値でない\n";
			}
			if (!defined($out{$org_fname})) {
				if ($org_fname eq $DOWN) {
					$out{$org_fname} = new outputer($org_fname, "result");
				} else {
					$out{$org_fname} = new outputer($org_fname);
				}
			}
			$out{$org_fname}->add($msg_id, $res);
		}
		$gmm->delete();
	}

	# スタッフリストデータのフッタ作成。
	$staff_dat .= "};\n\n";
	$staff_dat .= "#endif\n";

	print STDERR ">>>xlsファイル処理\n";
	my %xls_convert = (
		A => \&xls_convert_type_a,
		B => \&xls_convert_type_b,
		E => \&xls_convert_type_e,
		F => \&xls_convert_type_f
	);
	for my $i (@xls_master::master) {

		print STDERR "===$i->{fname}===\n";

		$xls_convert{$i->{type}}($i, $src_dir, \%out, $conv, $conv_org, \%sort_data);
	}

	print STDERR ">>>出力処理\n";
	for my $i (sort keys %out) {
		print STDERR "===$i===\n";
		$out{$i}->exec($HEADER_DIR, "$RES_DIR/$LANG_MARK");
	}

	# ゴミリスト出力。
	if (!open(GOMI, sprintf(">$GOMI", $LANG_MARK))) {
		die "error: 開けません。\n";
	}
	binmode(GOMI, "encoding(shiftjis)");
	print GOMI "# このファイルはコンバータで使用しますので、内容を変更しないで下さい。\n\n";
	for my $i (sort keys %gomi_list) {
		print GOMI "$i: $gomi_list{$i}\n";
	}
	close GOMI;

	# ポケモン名のソート処理。
	sorting::pokeName($sort_data{poke_name}, $LANG_MARK);
	# 簡易会話ワードのソート処理。
	sorting::simpleWord($sort_data{word}, $LANG_MARK, $LANG);
if (0) {
# ジオネットのソートは韓国では必要ない。
	# ジオネットの国名と地域名のソート処理。
	sorting::geonet($sort_data{geonet}, $LANG_MARK);
}
	# ダウンローダー用メッセージをコンバート。
	downloader::reverseConv($DOWN, $out{$DOWN}->getMsgIdList(),
		$out{$DOWN}->getResult(), $LANG_MARK);
	# スタッフリスト用データを保存。
	util::updateAndSave(sprintf($STAFF, $LANG_MARK), $staff_dat, "encoding(shiftjis)");
}

#===========================================================
# スタッフリスト処理。
#===========================================================
sub stafflist {
	my ($text, $use_tag_list, $msg_id, $staff_dat, $staff_ofs) = @_;
	my ($kind, $ver, @opt, $cont, $center, $msg_id_true);
	my %OPT_CHECK = (
		cont => \$cont,
		center => \$center,
		"" => 1
	);

	if (${$text} !~ s/\s*\[3:0F:staff:_(?:([^_]*)_?)(?:([^_]*)_?)?(?:([^_]*)_?)?(?:([^_]*)_?)?\]$//) {
		die "$msg_id: スタッフリストのフッタが付いてない。\n";
	}
	( $kind, $ver, @opt ) = ( $KIND{$1}, $PM_VERSION{$2}, $3, $4 );

	if (!defined($kind)) {
		die "$1: 定義のない種類です。\n";
	}
	if (!defined($ver)) {
		die "$2: 定義のないバージョンです。\n";
	}
	for my $i (@opt) {
		if (!defined($OPT_CHECK{$i})) {
			die "$i: 定義のないオプションです。\n";
		}
		if (ref $OPT_CHECK{$i}) {
			${$OPT_CHECK{$i}} = 1;
		}
	}

	if ($kind->{color}) {
		my $use;

		${$text} = $kind->{color} . ${$text};
		if (${$use_tag_list}) {
			${$use_tag_list} .= '/';
		}
		$use = $kind->{color};
		$use =~ s/\[(FF:00:)色:(\d+)\]/$1$2-1/;
		${$use_tag_list} .= $use;
	}
	if ($kind->{indent}) {
		${$text} = (" " x $kind->{indent}) . ${$text};
	}
	if ($ver) {
		${$staff_dat} .= "#if ( PM_VERSION == $ver )\n";
	}
	$msg_id_true = $msg_id;
	$msg_id_true =~ s/^\d+-//;
	if ($cont) {
		${$staff_ofs} += 16;
	} else {
		${$staff_ofs} += $kind->{rel_ofs};
	}
	${$staff_dat} .= "\t{ $msg_id_true, " . sprintf("%5d", ${$staff_ofs}) . ", " . ($center ? "TRUE" : "FALSE") . " },\n";
	if ($ver) {
		${$staff_dat} .= "#endif\n\n";
	}
}

#===========================================================
# ゴミメッセージを作る。
#===========================================================
sub make_gomi_msg {
	my ($gomi_len) = @_;

	return "ムラ" x ($gomi_len / 2) . "ム" x ($gomi_len % 2);
}

#===========================================================
# ファイルリスト取得。
#===========================================================
sub get_file_list {
	my ($dir) = @_;
	my $sub;
	my @list;

	$sub = sub {
		if ($File::Find::name =~ /\.gmm$/) {
			push @list, $File::Find::name;
		}
	};
	find($sub, $dir);
	return \@list;
}

#===========================================================
# １つもらった値を返すだけの関数。
#===========================================================
sub return_through {
	return $_[0];
}

#===========================================================
# エクセルのタイプＡ。
#===========================================================
sub xls_convert_type_a {
	my ($val, $src_dir, $out, $conv, $conv_org, $sort_data) = @_;
	my ($xls, $data, @mst, %msg_order);
	my ($c_msg_id, $c_text_org, $c_text, $c_end);

	($c_msg_id, undef, $c_text_org, undef, $c_text, $c_end) = (0 .. 100);

	# マスターデータの作成。
	for my $i (@{$val->{data}}) {
		my $org_fname = $i->{org_fname};
		my $sort_no = $i->{sort_no};
		my %comm_check;

		if ($i->{comm_check}) {
			for my $j (@{$i->{comm_check}}) {
				$comm_check{$j} = 1;
			}
		}
		$out->{$org_fname} = new outputer($org_fname);
		for my $j (@{$i->{msg_id}}) {
			push @mst, { msg_id => $j, org_fname => $org_fname };
			if (defined($msg_order{$j})) {
				die "メッセージIDがすでに存在しています。\n";
			}
			$msg_order{$j} = { id => $#mst, sort_no => $sort_no,
				comm_check => $comm_check{$j} };
		}
	}

	# 翻訳データ取得。
	$xls = new xls("$src_dir/$val->{fname}");
	$data = $xls->getData(1, $END_COL[$c_end]);
	if (@mst != @{$data}) {
		die "マスターと翻訳ファイルの数が一致しない。\n";
	}

	# コンバート処理。
	for my $i (@{$data}) {
		my ($str, $repo, $res, $msg_id, $mst_data);
		my ($sort_no, $comm_check);

		$msg_id = $i->[$c_msg_id];
		if (!defined($msg_order{$msg_id})) {
			die "マスターに存在しないメッセージID。\n";
		}
		$mst_data = $mst[$msg_order{$msg_id}{id}];
		$sort_no = $msg_order{$msg_id}{sort_no};
		$comm_check = $msg_order{$msg_id}{comm_check};

		check_space($msg_id, $i->[$c_text]);
		$mst_data->{data} = convert($i->[$c_text], $i->[$c_text_org], $conv, $conv_org,
			$val->{fname}, $msg_id, comm => $comm_check);

		# ソートに送るデータ作り。
		if (defined($sort_no)) {
			push @{$sort_data->{word}[$sort_no]}, { msg_id => $msg_id, text => $i->[$c_text] };
		}
	}
	# 出力データ処理。
	for my $i (@mst) {
		if (!defined($i->{data})) {
			die "翻訳ファイルに存在しないメッセージID。\n";
		}
		$out->{$i->{org_fname}}->add($i->{msg_id}, $i->{data});
	}
}

#===========================================================
# エクセルのタイプＢ。
#===========================================================
sub xls_convert_type_b {
	my ($val, $src_dir, $out, $conv, $conv_org, $sort_data) = @_;
	my ($mst_data, $other, %mst, %mst_order, %col, %col_org, $xls, $data,
		$fname_a, $gmm_a, $iter_a, $fname_b, $gmm_b, $iter_b);
	my ($c_msg_id, $c_name_org, $c_name, $c_wei, $c_hei,
		$c_cmmt0_org, $c_cmmt0, $c_cmmt1_org, $c_cmmt1, $c_end);
	my %NEED_F = (weight => 1, height => 1);

	($c_msg_id, $c_name_org, undef, $c_name, $c_wei, $c_hei,
		$c_cmmt0_org, undef, $c_cmmt0, $c_cmmt1_org, undef, $c_cmmt1, $c_end) = (0 .. 100);
	%col = (name => $c_name, cmmt0 => $c_cmmt0, cmmt1 => $c_cmmt1,
		weight => $c_wei, height => $c_hei);
	%col_org = (name => $c_name_org, cmmt0 => $c_cmmt0_org, cmmt1 => $c_cmmt1_org,
		weight => $c_wei, height => $c_hei);

	$mst_data = $val->{data};
	$other = $mst_data->{msg_id_pre}{other};

	# マスターデータの作成。
	for my $i (@{$mst_data->{msg_id}}) {
		my ($msg_id, $patch);

		$msg_id = "$mst_data->{msg_id_pre}{name}$i";
		push @{$mst{name}}, { msg_id => $msg_id };
		$mst_order{name}{$msg_id} = $#{$mst{name}};

		if ($mst_data->{patch}{$i} ne "name_only") {
			for my $j (keys %{$other}) {
				$msg_id = "$other->{$j}$i";
				push @{$mst{$j}}, { msg_id => $msg_id };
				$mst_order{$j}{$msg_id} = $#{$mst{$j}};
			}
		}
	}

	# 翻訳データ取得。
	$xls = new xls("$src_dir/$val->{fname}");
	$data = $xls->getData(1, $END_COL[$c_end]);
	if (@{$mst{name}} != @{$data}) {
		die "マスターと翻訳ファイルの数が一致しない。\n";
	}

	if ($INFO_TO_GMM) {
		$fname_a = "gmm/$val->{fname}";
		$fname_a =~ s/\.xls$/_dia.gmm/;
		$gmm_a = new gmm($fname_a);
		$iter_a = $gmm_a->newIter($INFO_LANG);
		$fname_b = "gmm/$val->{fname}";
		$fname_b =~ s/\.xls$/_per.gmm/;
		$gmm_b = new gmm($fname_b);
		$iter_b = $gmm_b->newIter($INFO_LANG);
	}

	# コンバート処理。
	for my $i (@{$data}) {
		my ($msg_id, $tmp, $mst, $no, $save_msg_id);

		if ($INFO_TO_GMM && !$iter_a->over()) {
			if ($i->[$c_msg_id] ne $iter_a->getMsgId()) {
				die "gmmにinfo書き出しで失敗。\n";
			}
			if ($i->[$c_msg_id] ne $iter_b->getMsgId()) {
				die "gmmにinfo書き出しで失敗。\n";
			}
			$iter_a->setText($i->[$col{cmmt0}]);
			$iter_a->next();
			$iter_b->setText($i->[$col{cmmt1}]);
			$iter_b->next();
		}

		# 名前処理。
		if ($i->[$c_msg_id] !~ /^text(.*)$/) {
			die "メッセージIDが変。\n";
		}
		$no = $1;
		$tmp = sprintf("%03d", $no);
		$msg_id = $save_msg_id = "$mst_data->{msg_id_pre}{name}$tmp";
		if (!defined($mst_order{name}{$msg_id})) {
			die "マスターに存在しないメッセージID。\n";
		}
		$mst = $mst{name}[$mst_order{name}{$msg_id}];

		check_space($msg_id, $i->[$col{name}]);
		$mst->{data} = convert($i->[$col{name}], $i->[$col_org{name}], $conv, $conv_org,
			$val->{fname}, $msg_id, comm => 1);

		# その他処理。
		for my $j (keys %{$other}) {
			$msg_id = "$other->{$j}$tmp";
			if (defined($mst_order{$j}{$msg_id})) {
				my ($text);

				$text = $i->[$col{$j}];
				# 高さと重さの頭揃えに対応。重さは整数部４桁、高さは整数部３桁。
				# 高さの最高は整数部２桁であるが、プログラム側の都合で、全数字が５つに
				# なるように調整している。
				if ($NEED_F{$j}) {
					if ($text !~ /^([\d\?]+)[\.][\d\?](?:m|kg)$/) {
						die "重さか高さの形式が違っています。\n";
					}
					$text = ('_SPCNUM_' x (3 - length($1))) . $text;
				}
				$mst = $mst{$j}[$mst_order{$j}{$msg_id}];
				$mst->{data} = convert($text, $i->[$col_org{$j}], $conv, $conv_org,
					$val->{fname}, $msg_id);
			}
		}

		# ソートに送るデータ作り。
		if ($no != 0 && $no != 494 && $no != 495) {
			if ($i->[$col{name}] ne "") {
				push @{$sort_data->{poke_name}}, { no => $no, text => $i->[$col{name}] };
				push @{$sort_data->{word}[0]}, { msg_id => $save_msg_id, text => $i->[$col{name}] };
			} else {
				push @{$sort_data->{poke_name}}, { no => $no, text => $i->[$c_name_org] };
				push @{$sort_data->{word}[0]}, { msg_id => $save_msg_id, text => $i->[$c_name_org] };
			}
		}
	}
	if ($INFO_TO_GMM) {
		$gmm_a->print($fname_a);
		$gmm_b->print($fname_b);
	}
	# 出力データ処理。
	for my $i (keys %{$mst_data->{org_fname}}) {
		my $org_fname = $mst_data->{org_fname}{$i};

		$out->{$org_fname} = new outputer($org_fname);
		for my $j (@{$mst{$i}}) {
			if (!defined($j->{data})) {
				die "翻訳ファイルに存在しないメッセージID。\n";
			}
			$out->{$org_fname}->add($j->{msg_id}, $j->{data});
		}
	}
}

#===========================================================
# タイプＣのトレーナー名圧縮。
# ※韓国版では使用していない。
#===========================================================
sub type_c_compress {
	my ($res) = @_;

	$res->{data} = converter::pokeStrCompress($res->{data});
	$res->{length} = length($res->{data}) / 2;
	if (8 < $res->{length}) {
		die "トレーナー名のバッファオーバーです。\n";
	}
	return $res;
}

#===========================================================
# エクセルのタイプＥ。
#===========================================================
sub xls_convert_type_e {
	my ($val, $src_dir, $out, $conv, $conv_org, $sort_data) = @_;
	my ($xls, $data, %mst_order, %mst, $fname, $gmm, $iter);
	my ($c_msg_id, $c_name_org, $c_name, $c_info_org, $c_info, $c_end);

	($c_msg_id, undef, $c_name_org, undef, $c_name,
		$c_info_org, undef, $c_info, $c_end) = (0 .. 100);

	# マスターデータの作成。
	for my $i (@{$val->{data}}) {
		my (%org, $info_id, $repeat, %name_move_next);

		$org{name} = $i->{org_fname}{name};
		$org{info} = $i->{org_fname}{info};
		for my $j (@{$i->{msg_id}}) {
			my ($msg_id, $patch, $next);

			$patch = $i->{patch}{$j};
			if (!$repeat && ref($patch) eq "HASH" && $patch->{type} eq "name_move_next") {
				$next = 1;
			} else {
				my $tmp;

				$tmp = $j;
				$tmp =~ s/_0(\d+)$/_$1/;
				$msg_id = "$i->{msg_id_pre}{name}$tmp";
				push @{$mst{$org{name}}}, { msg_id => $msg_id };
			}

			if ($repeat) {
				$mst_order{$msg_id} = { org_fname => \%org, info_len => $i->{info_len},
					sort_no => $i->{sort_no}, msg_id_pre => $i->{msg_id_pre}{name}, no => $j,
					id => $#{$mst{$org{name}}}, info_id => $info_id };
				$repeat = 0;
				next;
			} else {
				undef($info_id);
				if (defined($org{info})) {
					if ($patch ne "name_only") {
						push @{$mst{$org{info}}}, { msg_id => "$i->{msg_id_pre}{info}$j" };
						$info_id = $#{$mst{$org{info}}};
					}
				}
				if ($next) {
					$name_move_next{$patch->{pre_msg_id}} = { id => $j, info_id => $info_id };
				} else {
					$mst_order{$msg_id} = { org_fname => \%org, info_len => $i->{info_len},
						sort_no => $i->{sort_no}, msg_id_pre => $i->{msg_id_pre}{name}, no => $j,
						id => $#{$mst{$org{name}}}, info_id => $info_id };
				}
			}
			if (defined($name_move_next{$j})) {
				$repeat = 1;
				$info_id = $name_move_next{$j}{info_id};
				$j = $name_move_next{$j}{id};
				redo;
			}
		}
	}

	# 翻訳データ取得。
	$xls = new xls("$src_dir/$val->{fname}");
	$data = $xls->getData(1, $END_COL[$c_end]);
	if (scalar(keys %mst_order) != @{$data}) {
		die "マスターと翻訳ファイルの数が一致しない。\n";
	}

	if ($INFO_TO_GMM) {
		$fname = "gmm/$val->{fname}";
		$fname =~ s/\.xls$/.gmm/;
		$gmm = new gmm($fname);
		$iter = $gmm->newIter($INFO_LANG);
	}

	# コンバート処理。
	for my $i (@{$data}) {
		my ($msg_id, $mst_order, $org, $no);

		if ($INFO_TO_GMM && !$iter->over()) {
			if ($i->[$c_msg_id] ne $iter->getMsgId()) {
				die "gmmにinfo書き出しで失敗。\n";
			}
			$iter->setText($i->[$c_info]);
			$iter->next();
		}

		# 名前処理。
		$msg_id = $i->[$c_msg_id];
		if (!defined($mst_order{$msg_id})) {
			die "マスターに存在しないメッセージID。\n";
		}
		$mst_order = $mst_order{$msg_id};
		$org = $mst_order->{org_fname};
		$no = $mst_order->{no};

		check_space($msg_id, $i->[$c_name]);
		$mst{$org->{name}}[$mst_order->{id}]{data} = convert($i->[$c_name], $i->[$c_name_org], $conv, $conv_org,
			$val->{fname}, $msg_id);
		if (defined($mst_order->{info_id})) {
			$mst{$org->{info}}[$mst_order->{info_id}]{data} = convert($i->[$c_info], $i->[$c_info_org], $conv, $conv_org,
				$val->{fname}, $mst{$org->{info}}[$mst_order->{info_id}]{msg_id}, limit => $mst_order->{info_len});
		}

		# ソートに送るデータ作り。
		if (defined($mst_order->{sort_no}) &&
			($mst_order->{msg_id_pre} eq "WAZA_" ? $no != 0 && $no != 449 && $no != 464 && $no != 465 :
														$no != 0 && $no != 121 && $no != 123))
		{
			push @{$sort_data->{word}[$mst_order->{sort_no}]}, { msg_id => $msg_id, text => $i->[$c_name] };
		}
	}
	if ($INFO_TO_GMM) {
		$gmm->print($fname);
	}
	# 出力データ処理。
	for my $i (keys %mst) {
		$out->{$i} = new outputer($i);
		for my $j (@{$mst{$i}}) {
			if (!defined($j->{data})) {
				die "翻訳ファイルに存在しないメッセージID。\n";
			}
			$out->{$i}->add($j->{msg_id}, $j->{data});
		}
	}
}

#===========================================================
# エクセルのタイプＦ。
#===========================================================
sub xls_convert_type_f {
	my ($val, $src_dir, $out, $conv, $conv_org) = @_;
	my ($xls, $data, %mst_order, %mst, $fname, $gmm, $iter);
	my ($c_msg_id, $c_name_org, $c_name, $c_info_org, $c_info, $c_end);

	($c_msg_id, undef, $c_name_org, undef, $c_name,
		$c_info_org, undef, $c_info, $c_end) = (0 .. 100);

	# マスターデータの作成。
	for my $i (@{$val->{data}}) {
		my ($org_fname);

		$org_fname = $i->{org_fname};
		for my $j (@{$i->{data}}) {
			my ($msg_id, $msg_id_info);

			for my $k (@{$j->{msg_id}}) {
				$msg_id = "$j->{msg_id_pre}{name}$k->{id}";
				$msg_id_info = "$j->{msg_id_pre}{info}$k->{id}";
				$mst_order{$msg_id} = { name_id => $k->{name_id}, info_id => $k->{info_id},
					org_fname => $org_fname, reg => $j->{msg_id_pre}{reg}};
				$mst{$org_fname}[$k->{name_id}] = { msg_id => $msg_id };
				if (defined($k->{info_id})) {
					$mst{$org_fname}[$k->{info_id}] = { msg_id => $msg_id_info };
				}
			}
		}
	}

	# 翻訳データ取得。
	$xls = new xls("$src_dir/$val->{fname}");
	$data = $xls->getData(1, $END_COL[$c_end]);
	if (scalar(keys %mst_order) != @{$data}) {
		die "マスターと翻訳ファイルの数が一致しない。\n";
	}

	if ($INFO_TO_GMM) {
		$fname = "gmm/$val->{fname}";
		$fname =~ s/\.xls$/.gmm/;
		$gmm = new gmm($fname);
		$iter = $gmm->newIter($INFO_LANG);
	}

	# コンバート処理。
	for my $i (@{$data}) {
		my ($msg_id, $order);

		if ($INFO_TO_GMM && !$iter->over()) {
			if ($i->[$c_msg_id] ne $iter->getMsgId()) {
				die "gmmにinfo書き出しで失敗。\n";
			}
			$iter->setText($i->[$c_info]);
			$iter->next();
		}

		$msg_id = $i->[$c_msg_id];
		$order = $mst_order{$msg_id};
		if (!defined($order)) {
			die "マスターに存在しないメッセージID。\n";
		}
		check_space($msg_id, $i->[$c_name]);
		$mst{$order->{org_fname}}[$order->{name_id}]{data} =
			convert($i->[$c_name], $i->[$c_name_org], $conv, $conv_org,
				$val->{fname}, $msg_id);
		if (defined($order->{info_id})) {
			$mst{$order->{org_fname}}[$order->{info_id}]{data} =
				convert($i->[$c_info], $i->[$c_info_org], $conv, $conv_org,
					$val->{fname}, $mst{$order->{org_fname}}[$order->{info_id}]{msg_id});
		}
	}
	if ($INFO_TO_GMM) {
		$gmm->print($fname);
	}
	# 出力データ処理。
	for my $i (keys %mst) {
		$out->{$i} = new outputer($i);
		for my $j (@{$mst{$i}}) {
			if (!defined($j->{data})) {
				die "翻訳ファイルに存在しないメッセージID。\n";
			}
			$out->{$i}->add($j->{msg_id}, $j->{data});
		}
	}
}

#===========================================================
# コンバート処理。
#===========================================================
sub convert {
	my ($text, $text_org, $conv, $conv_org, $fname, $msg_id, %prm) = @_;
	my ($str, $repo, $res);

	$repo = $g_repo;
	$g_repo = "";
	$str = "($fname), $msg_id";

	if (!$force_jpn && $text ne "") {
		my ($except);

		for my $i (@CHECK_EXCEPT_REG) {
			if ($msg_id =~ /$i/) {
				$except = 1;
				last;
			}
		}
		if (!$except) {
			$except = $CHECK_EXCEPT{$msg_id};
		}
		$res = $conv->exec($text, \$repo, $except, $prm{use_tag_list}, $prm{scroll}, $prm{win_con},
			$prm{need_wait}, $prm{limit}, $prm{line_limit}, $prm{comm});
		if ($fname eq "debug.gmm" || scalar(keys %diff) && !$diff{$fname}{$msg_id}) {
			$repo = "";
		}
		if ($repo ne "") {
			print "$str\n";
		}
	} else {
		$res = $conv_org->exec($text_org, \$repo);
		if ($fname eq "debug.gmm" || scalar(keys %diff) && !$diff{$fname}{$msg_id}) {
			$repo = "";
		}
		if (!$force_jpn && $fname ne "debug.gmm" && $fname ne "stafflist.gmm") {
			print errata::out("元使用", $str);
		} elsif ($repo ne "") {
			print "$str\n";
		}
	}
	if ($repo ne "") {
		$repo =~ s/^/\t/mg;
		print $repo;
	}
	return $res;
}

#===========================================================
# 文頭文末のスペースと改行がないかチェック。
#===========================================================
sub check_space {
	my ($msg_id, $text) = @_;

	if (!exists($EXIST_SPACE{$msg_id}) && ($text =~ /^ / || $text =~ / $/) ||
		$text =~ /\n/)
	{
		$g_repo .= errata::out("スペース");
	}
}
