package tv.dyndns.kishibe.qmaclone.client.creation;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import tv.dyndns.kishibe.qmaclone.client.constant.Constant;
import tv.dyndns.kishibe.qmaclone.client.game.ProblemType;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketProblem;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketWrongAnswer;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.gwt.user.client.ui.IsWidget;
public class WrongAnswerPresenter {
private static final int MERGE_THRESHOLD = 200;
private final WrongAnswerView view;
public WrongAnswerPresenter(WrongAnswerView view) {
this.view = Preconditions.checkNotNull(view);
}
public IsWidget asWidget() {
return view;
}
/**
* 文字列を整数に変換する。
*
* @param s
* 変換元の文字列。
* @return 変換後の整数。整数に変換できない場合は0。
*/
private int tryParse(String s) {
try {
return (int) Math.rint(Double.valueOf(s));
} catch (NumberFormatException e) {
return 0;
}
}
private List<PacketWrongAnswer> normalizeClickAnswers(List<PacketWrongAnswer> answers) {
Map<String, Integer> normalizedAnswerToCount = Maps.newHashMap();
for (PacketWrongAnswer answer : answers) {
String[] coord = answer.answer.split(" ");
if (coord.length != 2) {
continue;
}
int x = MoreObjects.firstNonNull(tryParse(coord[0]), 0);
int y = MoreObjects.firstNonNull(tryParse(coord[1]), 0);
String minKey = answer.answer;
for (Entry<String, Integer> entry : normalizedAnswerToCount.entrySet()) {
String[] dstCoord = entry.getKey().split(" ");
int dstX = MoreObjects.firstNonNull(tryParse(dstCoord[0]), 0);
int dstY = MoreObjects.firstNonNull(tryParse(dstCoord[1]), 0);
if ((x - dstX) * (x - dstX) + (y - dstY) * (y - dstY) <= MERGE_THRESHOLD) {
minKey = entry.getKey();
break;
}
}
int count = MoreObjects.firstNonNull(normalizedAnswerToCount.get(minKey), Integer.valueOf(0))
+ answer.count;
normalizedAnswerToCount.put(minKey, count);
}
List<PacketWrongAnswer> normalized = Lists.newArrayList();
for (Entry<String, Integer> entry : normalizedAnswerToCount.entrySet()) {
PacketWrongAnswer answer = new PacketWrongAnswer();
answer.answer = entry.getKey();
answer.count = entry.getValue();
normalized.add(answer);
}
return normalized;
}
private String extractFileName(String fileName) {
if (!fileName.startsWith("http") || !fileName.contains("://")) {
return fileName;
}
int offset = fileName.lastIndexOf('/') + 1;
return fileName.substring(offset);
}
/**
* 解答文字列の正規化を行う。実際に行うのはURLからのファイル名の抽出と、解答のソート
*
* @param answer
* @return
*/
private String normalizeAnswerString(String answer) {
List<String> pairs = Lists.newArrayList();
// 複数回答に対応する
for (String pair : Splitter.on(Constant.DELIMITER_GENERAL).split(answer)) {
List<String> elements = Lists.newArrayList();
// 線結びに対応する
for (String element : Splitter.on(Constant.DELIMITER_KUMIAWASE_PAIR).split(pair)) {
// URLはファイル名のみ表示する
elements.add(extractFileName(element));
}
pairs.add(Joiner.on(Constant.DELIMITER_KUMIAWASE_PAIR).join(elements));
}
return Joiner.on(Constant.DELIMITER_GENERAL).join(pairs);
}
/**
* 複数回答をソートする
*
* @param answer
* @return
*/
private String sortMultipleStrings(String answer) {
List<String> pairs = Lists.newArrayList(Splitter.on(Constant.DELIMITER_GENERAL).split(answer));
Collections.sort(pairs);
return Joiner.on(Constant.DELIMITER_GENERAL).join(pairs);
}
/**
* 正規化後の解答の回答数を再度集計する
*
* @param answers
* @return 再集計結果。結果は解答文字列でソートされている
*/
private List<PacketWrongAnswer> recountAnswers(List<PacketWrongAnswer> answers) {
Map<String, Integer> normalizedAnswerToCount = Maps.newHashMap();
for (PacketWrongAnswer answer : answers) {
int count = MoreObjects.firstNonNull(normalizedAnswerToCount.get(answer.answer),
Integer.valueOf(0))
+ answer.count;
normalizedAnswerToCount.put(answer.answer, count);
}
List<PacketWrongAnswer> normalized = Lists.newArrayList();
for (Entry<String, Integer> entry : normalizedAnswerToCount.entrySet()) {
PacketWrongAnswer answer = new PacketWrongAnswer();
answer.answer = entry.getKey();
answer.count = entry.getValue();
normalized.add(answer);
}
Collections.sort(normalized, new Comparator<PacketWrongAnswer>() {
@Override
public int compare(PacketWrongAnswer o1, PacketWrongAnswer o2) {
return ComparisonChain.start().compare(o1.count, o2.count, Ordering.natural().reverse())
.compare(o1.answer, o2.answer).result();
}
});
return normalized;
}
@VisibleForTesting
List<PacketWrongAnswer> normalize(List<PacketWrongAnswer> wrongAnswers, PacketProblem problem) {
// 画像クリッククイズは近い座標をマージする
if (problem.type == ProblemType.Click) {
wrongAnswers = normalizeClickAnswers(wrongAnswers);
}
// 各解答文字列の正規化を行う
for (PacketWrongAnswer wrongAnswer : wrongAnswers) {
wrongAnswer.answer = normalizeAnswerString(wrongAnswer.answer);
}
// 各解答文字列をソートする。順番当てはソートしない。
// BugTrack-QMAClone/633 - QMAClone wiki
// http://kishibe.dyndns.tv/qmaclone/wiki/wiki.cgi?page=BugTrack%2DQMAClone%2F633
if (problem.type != ProblemType.Junban) {
for (PacketWrongAnswer wrongAnswer : wrongAnswers) {
wrongAnswer.answer = sortMultipleStrings(wrongAnswer.answer);
}
}
return recountAnswers(wrongAnswers);
}
public void setWrongAnswers(List<PacketWrongAnswer> wrongAnswers, PacketProblem problem) {
view.setAnswer(normalize(wrongAnswers, problem));
}
}