import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.*;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class DictionaryGeneratorMain {

	@Option(name = "--size", aliases = {"-s"}, usage = "size of automaton", metaVar = "<size>", required = true)
	private int size;

	@Option(name = "--wordsCount", aliases = {"-wc"}, usage = "number of words", metaVar = "<words count>",
			required = true)
	private int words;

	@Option(name = "--result", aliases = {"-r"}, usage = "write result automaton to this file",
			metaVar = "<result file>")
	private String resultFilePath = "test.dct";

	@Option(name = "--log", aliases = {"-l"}, usage = "write log to this file", metaVar = "<log>")
	private String logFile;

	@Option(name = "--percent", aliases = {"-p"}, usage = "percent of noisy data", metaVar = "<noisy percent>")
	private int p = 0;

	@Option(name = "--alphabet", aliases = {"-a"}, usage = "alphabet size", metaVar = "<alphabet size>")
	private int alphabetSize = 2;

	@Option(name = "--startlength", aliases = {"-sl"}, usage = "length of the smallest generated word",
			metaVar = "<smallest word length>")
	private int startLength = 3;

	private static Logger logger = Logger.getLogger("Logger");

	private void launch(String... args) {
		CmdLineParser parser = new CmdLineParser(this);
		try {
			parser.parseArgument(args);
		} catch (CmdLineException e) {
			System.err.println(e.getMessage());
			System.err.print("Usage ");
			parser.printSingleLineUsage(System.err);
			System.err.println();
			parser.printUsage(System.err);
			return;
		}

		boolean noisyMode = p > 0;

		if (logFile != null) {
			try {
				FileHandler fh = new FileHandler(logFile, false);
				logger.addHandler(fh);
				fh.setFormatter(new SimpleFormatter());
				logger.setUseParentHandlers(false);
				System.out.println("Log file: " + logFile);
			} catch (Exception e) {
				System.err.println("Problem with log file: " + logFile + ". " + e.getMessage());
				return;
			}
		}

		Random random = new Random();
		logger.info("Starting generating file " + resultFilePath);
		Automaton automaton = new Automaton(size);
		for (int number = 1; number < size; number++) {
			int parentNum;
			do {
				parentNum = random.nextInt(number);
			} while (!hasFreeEdge(automaton, parentNum));
			Node parentNode = automaton.getState(parentNum);
			int label;
			do {
				label = random.nextInt(alphabetSize);
			} while (parentNode.getChild(String.valueOf(label)) != null);
			automaton.addTransition(parentNum, number, String.valueOf(label));
		}

		for (int number = 0; number < size; number++) {
			Node curNode = automaton.getState(number);

			for (int i = 0; i < alphabetSize; i++) {
				if (curNode.getChild(String.valueOf(i)) == null) {
					automaton.addTransition(number, random.nextInt(size), String.valueOf(i));
				}
			}

			Node.Status status;
			if (random.nextInt(2) == 1) {
				status = Node.Status.ACCEPTABLE;
			} else {
				status = Node.Status.REJECTABLE;
			}
			curNode.setStatus(status);
		}
		logger.info("Automaton succesfully generated: \n" + automaton.toString());
		logger.info("Generating words for: " + resultFilePath);
		try (PrintWriter pw = new PrintWriter(new File(resultFilePath))) {
			pw.println(words + " " + alphabetSize);

			Set<String> wordsSet = new HashSet<>();
			StringBuilder path;
			Node curNode;

			int WordsLessThenCurrentLengthCount = 2;
			int length = startLength;
			int currentWordNumber = 0;

			int noisyWords = 0;
			int noisyWordsCount = noisyMode ? (int) Math.round((words / 100.0) * p) : 0;

			while (currentWordNumber < words) {
				if (currentWordNumber == WordsLessThenCurrentLengthCount) {
					WordsLessThenCurrentLengthCount *= 2;
					length++;
				}
				path = new StringBuilder();
				curNode = automaton.getStart();
				for (int letter = 0; letter < length; letter++) {
					String label = String.valueOf(random.nextInt(alphabetSize));
					curNode = curNode.getChild(label);
					path.append(label).append(" ");
				}
				if (curNode.isAcceptable()) {
					path = new StringBuilder("1 ").append(length).append(" ").append(path);
				} else {
					path = new StringBuilder("0 ").append(length).append(" ").append(path);
				}
				if (!wordsSet.contains(path.toString())) {
					wordsSet.add(path.toString());
					currentWordNumber++;
				}
			}

			List<String> wordsList = new ArrayList<>();
			wordsList.addAll(wordsSet);
			Collections.shuffle(wordsList);
			Set<String> changedWords = new HashSet<>();
			Iterator<String> iter = wordsList.iterator();
			while (iter.hasNext()) {
				String word = iter.next();
				if (noisyWords < noisyWordsCount) {
					changedWords.add(((word.charAt(0) == '1') ? "0" : "1") + word.substring(1));
					noisyWords++;
					iter.remove();
				} else {
					break;
				}
			}

			wordsList.addAll(changedWords);
			Collections.shuffle(wordsList);
			for (String word : wordsList) {
				pw.println(word);
			}
		} catch (FileNotFoundException e) {
			logger.info("Some problem with result file " + resultFilePath + ": " + e.getMessage());
			e.printStackTrace();
		}
	}

	private boolean hasFreeEdge(Automaton automaton, int nodeNumber) {
		boolean ans = false;
		for (int i = 0; i < alphabetSize; i++) {
			ans |= (automaton.getState(nodeNumber).getChild(String.valueOf(i)) == null);
		}
		return ans;
	}

	private void run(String... args) {
		Locale.setDefault(Locale.US);
		try {
			launch(args);
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	public static void main(String... args) {
		new DictionaryGeneratorMain().run(args);
	}

}
