/**
 * 문장 구분을 위한 전처리기.
 * 형태소 분석기가 견고하려면 전처리기는 필수적이다. 요즈음 급증하는 online 문서를 많이 다루면서 형태소 분석기가 깨끗하지 못한
 * 문서를 처리해야 되는 경우가 많다. 이경우 전처리기에서 입력 문서를 먼저 처리해주면 형태소 분석기에서 더 쉽게 올바른 결과를 얻을
 * 수 있다. 전처리기에서 주로 하는 일은 문장구분이다. 즉, 입력 문서를 문장단위로 나누는 일이다. 이제까지 문장구분에 대해서는
 * 주로 영어권에서 연구되었다. 특히 영어에서는 마침표(.)가 문장이 끝날 때 사용되는가 약어 등의 다른 의미로 사용되는가에 대한 연구가
 * 많다. 여기에서는 문장구분이 될 수 있는 특수기호의 좌우 문자열을 가지고서 문장 구분을 하는 간단한 전처리기를 구현한다.
 * 
 * 크게 문장구분이 될 수 있는 특수기호는 물음표(?), 느낌표(!), 마침표(.)이다. 각각에 대한 처리는 아래와 같다.
 * 
 * 물음표나 느낌표가 나타났을 때
 * 	- 특수문자와 붙어서 사용되지 않은 경우에 문장구분을 한다. 여기에서 말하는 특수문자는 아스키코드값을 가지는 문자를 말한다.
 * 
 * 마침표가 나타났을 때
 * - 뒤에 숫자와 붙어서 사용된 경우는 무시한다.
 * - 마침표 바로 전이 영문이면 무시한다. 여기에서 영어약어사전을 이용하여 약어인 경우에만 무시하면 더 정확한 결과를 얻을 수 있을 것이다.
 * - 오른쪽에 마침표가 나오면 무시한다. 이것은 말줄임표에 대한 처리이다.
 * - 마침표 앞의 바로 앞의 단어의 길이가 2byte이하이면 마침표 앞 단어에서 문장구분을 하고, 그줄의 나머지를 또 다른 한 문장으로 처리한다. 이것은 '가.', '나.' 등과 같은 말머리표에 대한 처리이다.
 * - 그 이외에는 문장구분을 한다.
 * 
 * 위의 모든 경우에 대해서 괄호나 따옴표에 대한 처리가 필요하다. 위의 규칙을 적용하여 문장구분을 할 때 괄호나 따옴표 쌍이 끊어지지 않도록 한다.
 */

package kr.ac.kaist.swrc.jhannanum.module.sentence;

import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;

import kr.ac.kaist.swrc.jhannanum.module.Module;

/**
 * @author Sangwon Park
 *
 */
public class SentenceSplitter implements Module {

	private BufferedReader in = null;
	private PrintWriter out = null;
	
	final static private String MODULE_NAME = "SentenceSplitter";

	public String getName() {
		return MODULE_NAME;
	}

	public void initialize(Reader in, Writer out, String configFile) {
		if (in != null) {
			this.in = new BufferedReader(in);
		} else {
			this.in = null;
		}

		if (out != null) {
			this.out = new PrintWriter(out);
		} else {
			this.out = null;
		}
	}

	/**
	 * 앞 문장에 붙일 부호를 검사한다.
	 * @param c 확인할 문자
	 * @return 
	 */
	private boolean isSym(char c) {
		switch (c) {
		case ')': return true;
		case ']': return true;
		case '}': return true;
		case '?': return true;
		case '!': return true;
		case '.': return true;
		case '\'': return true;
		case '\"': return true;
		}
		return false;
	}

	public void run() throws Exception {
		String line = "";
		String[] words = null;
		boolean isBOS = true;

		// 한 문장을 읽음
		while ((line = in.readLine()) != null) {
			isBOS = true;
			
			if (line.equals("EOF")) {
				if (!isBOS) {
					out.write("EOS\n");
				}
				out.write("EOF\n\n");
				continue;
			}
			
			// 어절 단위로 잘라서 분석
			words = line.split("\\s");

			for (int i = 0; i < words.length; i++) {
				if (!words[i].matches(".*(\\.|\\!|\\?).*")) {
					// 어절 중 . ! ? 가 없는 경우
					if (isBOS) {
						out.write("BOS " + words[i] + " ");
					} else {
						out.write(words[i] + " ");
					}
					
					isBOS = false;
					
				} else {
					// 어절 중 . ! ? \n 가 있는 경우 문자 하나씩 확인하며 처리
					int beginIndex = 0;
					boolean isEOS = false;

					for (int j = 0 ; j < words[i].length(); j++) {
						char c = words[i].charAt(j);
						switch (c) {
						case '.':
							if (j > 0) {
								// 마침표 앞이 영문인 경우 무시
								if (Character.isLowerCase((words[i].charAt(j - 1))) || 
										Character.isUpperCase((words[i].charAt(j - 1))))  {
									continue;
								}

								// 마침표 열 다음에 공백이나 특수문자가 아니면 무시
								if (words[i].charAt(j - 1) == '.' && j != words[i].length() - 1) {
									continue;
								}
							}
							if (j < words[i].length() - 1) {
								// 마침표 뒤에 숫자가 있으면 무시 
								if (Character.isDigit(words[i].charAt(j + 1))) {
									continue;
								}

								// 마침표 열이 시작되면 무시
								if (words[i].charAt(j + 1) == '.') {
									continue;
								}
							}

							isEOS = true;
							break;
						case '!':
							isEOS = true;
							break;
						case '?':
							isEOS = true;
							break;
						}

						if (isEOS) {
							// 특수 문자를 문장에 포함시킴
							while (j < words[i].length() - 1) {
								if (isSym(words[i].charAt(j + 1))) {
									j++;
								} else {
									break;
								}
							}

							// 특수 문자가 처음 나타나는 위치 확인
							int k = j;
							for ( ; k >= 0; k--) {
								if (!isSym(words[i].charAt(k))) {
									break;
								}
							}

							if (isBOS) {
								out.write("BOS " + words[i].substring(beginIndex, k + 1) + " " + words[i].substring(k + 1, j + 1) + " EOS\n");
							}
							else {
								out.write(words[i].substring(beginIndex, k + 1) + " " + words[i].substring(k + 1, j + 1) + " EOS\n");
							}
							isBOS = true;

							beginIndex = j + 1;
							isEOS = false;

							out.flush();
						}
					}

					// 나머지 부분이 있으면 출력
					if (beginIndex < words[i].length()) {
						if (isBOS) {
							out.write("BOS " + words[i].substring(beginIndex, words[i].length()) + " ");
						}
						else { 
							out.write(words[i].substring(beginIndex, words[i].length()) + " ");
						}
						isBOS = false;
						out.flush();
					}
				}
			}
			if (!isBOS && words != null) {
				out.write(" EOS\n");
				out.flush();
			}
			
		}
	}

	public void setReader(Reader in) {
		if (in != null) {
			this.in = new BufferedReader(in);
		} else {
			this.in = null;
		}
	}
	
	public void setWriter(Writer out) {
		if (out != null) {
			this.out = new PrintWriter(out);
		} else {
			this.out = null;
		}
	}

	public void shutdown() {

	}
}