/*
 * Created on 2006-07-21
 * svnid:	$Id$
 */
package pl.edu.icm.yadda.dict;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Random;

import org.apache.log4j.Logger;

public class DictionaryServer {
	
	private static final Logger log = Logger.getLogger(DictionaryServer.class);
	
	private static final String COMM_SHUTDOWN = "shutdown";	// this command shutdowns server
	private static final String MSG_ERROR = "__ERROR__";
	
	private static final String PROP_SIZES			= "groupSizes";
	private static final String PROP_PROBABILITY	= "groupProbability";
	private static final String PROP_PORT			= "port";
	
	private int port;
	private boolean shutdown = false;
	private int parts;				// into how many parts words are split
	private String[][] words;		// all dictionary words (split for groups with different probability)
	private Random random = new Random();
	
	
	/* 
	 * array of probabilities of words' groups
	 * nth position contains sum of probabilities of groups 0 to n
	 */
	private int[] probabilities;
	
	public DictionaryServer() {
	}
	
	public static void main(String[] args) {
		try {
			DictionaryServer server = createDictionary(args);
			if (server != null) {
				server.start();
			}
		} catch (DictionaryException e) {
			log.error(e.getMessage());
		} catch (Exception e) {
			log.error("Error occured.", e);
		}
	}

	private void start() {
		try {
			ServerSocket serviceSocket = new ServerSocket(port);
			while (!shutdown) {
				Socket clientSocket = serviceSocket.accept();
				BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
				PrintStream out = new PrintStream(clientSocket.getOutputStream());
				process(in, out);
				clientSocket.close();
			}
			serviceSocket.close();
		} catch (Exception e) {
			log.error("Server exits due to an error.", e);
		}
	}
	
	private void process(BufferedReader in, PrintStream out) throws Exception {
		String req = in.readLine();
		String ret = null;
		log.debug("Received ["+req+"]");
		if (COMM_SHUTDOWN.equals(req)) {
			log.info("Received shutdown command. Server exits.");
			shutdown = true;
		} else {
			int count = atoi(req);
			if (count<=0) {
				ret = MSG_ERROR + " '"+req+"' is not valid words number (should be grater than 0)";
			} else {
				ret = generateWords(count);
			}
			out.println(ret);
		}
	}
	
	private String generateWords(int count) {
		StringBuffer ret = new StringBuffer();
		int[] stats = new int[parts];
		for (int i=0; i<count; i++) {
			int proc = random.nextInt(100);
			for (int j=0; j<parts; j++) {
				if (proc<probabilities[j]) {	// choose word from jth part
					int w = random.nextInt(words[j].length);
					ret.append(" ").append(words[j][w]);
					stats[j]++;
					break;
				}
			}
		}
		log.info("Statistics: "+count+" words generated "+arrayToString(stats));
		return ret.toString().trim();
	}
	
	private void init(File dictFile, File configFile) throws Exception {
		try {
			Properties conf = new Properties();
			conf.load(new FileInputStream(configFile));
			String p = conf.getProperty(PROP_PORT);
			port = atoi(p);
			if (port<=0) {
				exit("Invalid port number ("+p+")");
			}
			int[] sizes = getPropertyArray(conf, PROP_SIZES);
			int[] probs = getPropertyArray(conf, PROP_PROBABILITY);
			if (sizes.length != probs.length) {
				throw new DictionaryException("Invalid configuration: number of parts different than number of probabilities");
			}
			words = new String[sizes.length+1][];	// +1 because there is another part - the rest
			probabilities = new int[sizes.length+1];
			probabilities[sizes.length] = 100;	// the last part
			BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(dictFile)));
			int count = 0;
			for (int i=0; i<sizes.length; i++) {
				words[i] = new String[sizes[i]];
				probabilities[i] = (i>0 ? probabilities[i-1] : 0) + probs[i];
				if (probabilities[i]>100) {
					throw new DictionaryException("Invalid configuration: sum of probabilities greater than 100");
				}				
				for (int j=0; j<sizes[i]; j++) {
					words[i][j] = reader.readLine();
					if (words[i][j]==null) {
						throw new DictionaryException("Invalid configuration: number of words in dictionary ("+count+") is smaller than sum of all parts' sizes.");
					}
					count++;
				}
			}
			ArrayList lastPart = new ArrayList(50000);
			for (String w=reader.readLine(); w!=null; w=reader.readLine()) {
				lastPart.add(w);
				count++;
			}
			if (count==0) throw new DictionaryException("Dictionary is empty!");
			parts = sizes.length;
			if (lastPart.isEmpty()) {	// sum of probabilities of specified parts must == 100
				if (probabilities[sizes.length-1] != 100) {
					throw new DictionaryException("Dictionary has exactly the same number of words as sum of specified parts but probabilities doesn't sum to 100");
				}
			} else {
				words[sizes.length] = (String[]) lastPart.toArray(new String[lastPart.size()]);
				parts++;
			}
			String msg = "Dictionary successfully created ("+count+" words, port "+port+"). Words are split into "+parts+" parts:";
			for (int i=0; i<sizes.length; i++) {
				msg += "\n\t"+sizes[i]+" words - "+probs[i]+"%";
			}
			if (words[sizes.length]!=null) {
				msg += "\n\t"+words[sizes.length].length+" words - "+(100-(sizes.length>0?probabilities[sizes.length-1]:0))+"%";
			}
			log.info(msg);
		} catch (DictionaryException de) {
			throw de;
		} catch (Exception e) {
			throw new Exception("Creating dictionary failed.", e); 
		}
	}
	
	private final int[] getPropertyArray(Properties prop, String name) throws Exception {
		String s = prop.getProperty(name);
		if (s==null) {
			throw new DictionaryException("Invalid configuration file: property '"+name+"' missing");
		}
		String[] arr = s.split("\\s+");
		if (arr.length==1 && "".equals(arr[0])) {
			arr = new String[0];
		}
		int[] ret = new int[arr.length];
		for (int i=0; i<arr.length; i++) {
			ret[i] = atoi(arr[i]);
			if (ret[i]<=0) {
				throw new DictionaryException("Invalid '"+name+"' configuration property: '"+arr[i]+"' is not positive integer");
			}
		}
		return ret;
	}
	
	private static DictionaryServer createDictionary(String[] args) throws Exception {
		if (args.length != 2) {
			log.error("Usage: DictionaryServer dictionaryFile configFile port");
		}
		File dictFile = new File(args[0]);
		if (!dictFile.exists()) {
			exit("Dictionary file ["+dictFile.getAbsolutePath()+"] doesn't exist.");
		}
		File configFile = new File(args[1]);
		if (!configFile.exists()) {
			exit("Dictionary file ["+configFile.getAbsolutePath()+"] doesn't exist.");
		}
		DictionaryServer server = new DictionaryServer();
		server.init(dictFile, configFile);
		return server;
	}
	
	private static final int atoi(String s) {
		try {
			return Integer.parseInt(s);
		} catch (NumberFormatException e) {
			return -1;
		}
	}

	private static void exit(String msg) {
		log.error(msg);
		System.exit(1);
	}
	
	private static String arrayToString(int[] arr) {
		StringBuffer msg = new StringBuffer().append("(");
		for (int i=0; i<arr.length; i++) {
			msg.append(i>0?", ":"").append(arr[i]);
		}
		msg.append(")");
		return msg.toString();
	}
	
}

class DictionaryException extends Exception {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1344952553075137347L;

	/**
	 * 
	 */
	public DictionaryException() {
		super();
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param message
	 * @param cause
	 */
	public DictionaryException(String message, Throwable cause) {
		super(message, cause);
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param message
	 */
	public DictionaryException(String message) {
		super(message);
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param cause
	 */
	public DictionaryException(Throwable cause) {
		super(cause);
		// TODO Auto-generated constructor stub
	}
	
}