package kr.ac.kaist.swrc.jhannanum.hannanum;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;

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

/**
 * 한나눔 형태소 분석기.
 * @author Sangwon Park
 */
public class HanNanum {
    final static private int MODE_LOCAL = 0x00;
    final static private int MODE_NETWORK = 0x01;
    final static private String HANNANUM_EOF = "EOF";
    
    final static public int DEFAULT_PORT = 6000;
    final static public String DEFAULT_HOST = "hudoni.kaist.ac.kr";
    
    /** port number */
    private int port = 0;
    
    /** host name */
    private String host = null;
    
	/** client socket */
    private Socket socket = null;
    
    /** socket output stream */
    private PrintWriter out = null;
    
    /** socket input reader */
    private BufferedReader in = null; 
    
    /** process mode */
    private int mode = 0;
    
    private ArrayList<Module> workflow = null;
    private ArrayList<Thread> moduleThreads = null;
       
    /**
     * 로컬 모드 생성자.
     */
    public HanNanum() {
    	mode = MODE_LOCAL;
    	
    	workflow = new ArrayList<Module>();
    	moduleThreads = new ArrayList<Thread>();
    }

    /**
     * 네트워크 모드 생성자.
     * @param host	한나눔 서버 주소
     * @param port	한나눔 서버 포트
     * @throws IOException 
     * @throws UnknownHostException 
     */
    public HanNanum(String host, int port) throws UnknownHostException, IOException {
    	mode = MODE_NETWORK;
    	
    	this.host = host;
    	this.port = port;
    	
    	connect(host, port);
    }

    public void activateWorkflow() throws Exception {
    	PipedWriter pipeOut = new PipedWriter();
		PipedReader pipeIn = new PipedReader(pipeOut);
		
    	workflow.get(workflow.size() - 1).setWriter(pipeOut);
    	this.in = new BufferedReader(pipeIn);
    	
    	this.moduleThreads.clear();
    	
    	for (Module module : workflow) {
    		Thread th = new ModuleThread(module);
    		moduleThreads.add(th);
    		th.start();
    	}
    }

    public void addModule(Module module, String configFile) throws Exception {
    	PipedWriter pipeOut = new PipedWriter();
		PipedReader pipeIn = new PipedReader(pipeOut);
		
    	if (workflow.size() == 0) {
    		module.initialize(pipeIn, null, configFile);
    		workflow.add(module);
    		
    		this.out = new PrintWriter(pipeOut);
    		return;
    	}
    	
    	Module prevModule = workflow.get(workflow.size() - 1);
    	
   		prevModule.setWriter(pipeOut);
   		module.initialize(pipeIn, null, configFile);
   		
   		workflow.add(module);
    }
      
    /**
     * 형태소 분석을 시작한다.
     * @param br	분석할 데이터
     * @return	분석 결과
     * @throws Exception 
     */
    public String analyze(String str) throws Exception {
    	switch (mode) {
    	case MODE_LOCAL:
    		return processOnLocal(str);
    	case MODE_NETWORK:
    		return processOnNetwork(str);   
    	}
    	return null;
    }
    
    /**
     * 형태소 분석기를 닫는다.
     * 네트워크 모드일 경우 네트워크 관련 자원을 정리한다.
     */
    public void close() {
    	switch (mode) {
    	case MODE_LOCAL:
    		for (Thread th : moduleThreads) {
    			th.interrupt();
    		}
    		moduleThreads.clear();
    		break; 
    	case MODE_NETWORK:
    		try {
    			out.close();
    			in.close();
    			socket.close();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		break;
    	}
    }
    
    /**
     * 한나눔 서버에 접속한다.
     * @param host	한나눔 서버 주소
     * @param port	한나눔 서버 포트
     * @throws UnknownHostException
     * @throws IOException
     */
    private void connect(String host, int port) throws UnknownHostException, IOException {
    	socket = new Socket(host, port);
		out = new PrintWriter(socket.getOutputStream(), true);
		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    }
    
    /**
     * 현재 프로그램이 동작하는 로컬 컴퓨터에서 분석을 진행한다. 
     * @param br	분석할 데이터
     * @return		분석 결과
     * @throws Exception 
     */
    private String processOnLocal(String str) throws Exception {
    	String result = "";
    	
    	out.write(str);
    	out.write("\n" + HANNANUM_EOF + "\n");
    	out.flush();
    	
    	String line = null;
    	while ((line = in.readLine()) != null) {
    		if (line.equals(HANNANUM_EOF)) {
    			while (in.ready()) {
    				in.readLine();
    			}
    			break;
    		} else {
    			result += line + '\n';
    		}
    	}
    	
    	return result;
    }
    
    /**
     * 한나눔 서버로 데이터를 전송한 후 서버에서 분석한 결과를 돌려받는다.
     * @param br	분석할 데이터
     * @return		분석 결과
     * @throws UnknownHostException
     * @throws IOException
     */
    private String processOnNetwork(String str) throws UnknownHostException, IOException {
    	String line = null;
    	String result = "";

    	out.write(str);
    	out.println(HANNANUM_EOF + ".");
    	out.flush();
    	
    	while ((line = in.readLine()) != null) {
    		if (line.startsWith("@" + HANNANUM_EOF)) {
    			in.readLine();
    			in.readLine();
    			in.readLine();
    			in.readLine();
    			in.readLine();
    			in.readLine();
    			break;
    		} else {
    			result += line + "\n";
    		}
    	}
    	return result;
    }
    
    public void shutdownWorkflow() throws Exception {
    	for (Module module : workflow) {
    		module.shutdown();
    	}
    }
}