/*******************************************************************************
 * Copyright (c) 2010 Robert "Unlogic" Olofsson (unlogic@unlogic.se).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-3.0-standalone.html
 ******************************************************************************/
package se.unlogic.standardutils.dao.script;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MySQLScriptUtility implements ScriptUtility {

	private static final String MULTI_LINE_COMMENT_START_SYMBOL = "\\/\\*";
	private static final String MULTI_LINE_COMMENT_END_SYMBOL = "\\*\\/";

	private static final String LINE_COMMENT_START_SYMBOL = "^--|^#";
	private static final String LINE_COMMENT_END_SYMBOL = "\\n";

	private static final String STRING_TYPE1_SYMBOL = "\'";
	private static final String STRING_TYPE2_SYMBOL = "\"";

	private static final String STATEMENT_TERMINATOR = ";";

	public List<String> getStatements(String script) {

		List<String> statements = new ArrayList<String>();

		List<Symbol> allSymbols = new ArrayList<Symbol>();
		allSymbols.addAll(this.findSymbols(STRING_TYPE1_SYMBOL, script));
		allSymbols.addAll(this.findSymbols(STRING_TYPE2_SYMBOL, script));
		allSymbols.addAll(this.findSymbols(LINE_COMMENT_START_SYMBOL, script));
		allSymbols.addAll(this.findSymbols(LINE_COMMENT_END_SYMBOL, script));
		allSymbols.addAll(this.findSymbols(MULTI_LINE_COMMENT_START_SYMBOL, script));
		allSymbols.addAll(this.findSymbols(MULTI_LINE_COMMENT_END_SYMBOL, script));

		String str = script;
		int startIndex = 0;
		for(Symbol terminator : this.getValidSymbols(this.findSymbols(STATEMENT_TERMINATOR, script), this.createRegions(this.getValidSymbols(allSymbols)))) {
			str = script.substring(startIndex, terminator.getOffsets().getEnd());
			startIndex += str.length();
			statements.add(str);
		}

		return statements; 
	}

	/**
	 * Searches the sequence for symbols
	 * @param regex - A regular expression used to find the symbol (a sequence) within the sequence
	 * @param sequence - The haystack in which to search for symbols 
	 * @return
	 */
	private List<Symbol> findSymbols(String regex, String sequence) {
		List<Symbol> symbols = new ArrayList<Symbol>();
		Pattern pattern = Pattern.compile(regex,Pattern.MULTILINE);
		Matcher matcher = pattern.matcher(sequence);
		while(matcher.find()) {
			symbols.add(new Symbol(regex, new Offsets(matcher.start(),matcher.end())));
		}
		return symbols;
	}

	/**
	 * Creates regions between supplied symbols
	 * @param symbols
	 * @return
	 */
	private List<Region> createRegions(List<Symbol> symbols) {

		List<Region> regions = new ArrayList<Region>();

		Symbol stringType1Symbol = null;
		Symbol stringType2Symbol = null;
		Symbol lineCommentStartSymbol = null;
		Symbol multiLineCommentStartSymbol = null;

		for(Symbol symbol : symbols) {
			if(symbol.getKey().equals(STRING_TYPE1_SYMBOL)) {
				if(stringType1Symbol != null) {
					regions.add(new Region(stringType1Symbol, symbol));
					stringType1Symbol = null;
				} else {
					stringType1Symbol = symbol;
				}
			} else if(symbol.getKey().equals(STRING_TYPE2_SYMBOL)) {

				if(stringType2Symbol != null) {
					regions.add(new Region(stringType2Symbol, symbol));
					stringType2Symbol = null;
				} else {
					stringType2Symbol = symbol;
				}
			} else if(symbol.getKey().equals(LINE_COMMENT_END_SYMBOL) && lineCommentStartSymbol != null) {
				regions.add(new Region(lineCommentStartSymbol, symbol));
				lineCommentStartSymbol = null;
			} else if(symbol.getKey().equals(LINE_COMMENT_START_SYMBOL)) {
				lineCommentStartSymbol = symbol;
			} else if(symbol.getKey().equals(MULTI_LINE_COMMENT_END_SYMBOL) && multiLineCommentStartSymbol != null) {
				regions.add(new Region(multiLineCommentStartSymbol, symbol));
				multiLineCommentStartSymbol = null;
			} else if(symbol.getKey().equals(MULTI_LINE_COMMENT_START_SYMBOL)) {
				multiLineCommentStartSymbol = symbol;
			}

		}

		if(stringType1Symbol != null) {
			regions.add(new Region(stringType1Symbol, null));
			stringType1Symbol = null;
		}
		if(stringType2Symbol != null) {
			regions.add(new Region(stringType2Symbol, null));
			stringType2Symbol = null;
		}
		if(lineCommentStartSymbol != null) {
			regions.add(new Region(lineCommentStartSymbol, null));
			lineCommentStartSymbol = null;
		}
		if(multiLineCommentStartSymbol != null) {
			regions.add(new Region(multiLineCommentStartSymbol, null));
			multiLineCommentStartSymbol = null;
		}
		return regions;
	}

	/**
	 * Returns valid symbols from a collection of symbols
	 * @param symbols
	 * @return
	 */
	private List<Symbol> getValidSymbols(List<Symbol> symbols) {

		List<Symbol> validSymbols = new ArrayList<Symbol>();

		boolean withinStringType1 = false;
		boolean withinStringType2 = false;
		boolean withinLineComment = false;
		boolean withinMultiLineComment = false;

		Collections.sort(symbols, new Symbol.SymbolStartComparator());

		for(Symbol symbol : symbols) {
			if(!withinStringType1 && !withinStringType2 && !withinLineComment && !withinMultiLineComment) {
				validSymbols.add(symbol);
				if(symbol.getKey().equals(STRING_TYPE1_SYMBOL)) {
					withinStringType1 = true;
				} else if(symbol.getKey().equals(STRING_TYPE2_SYMBOL)) {
					withinStringType2 = true;
				} else if(symbol.getKey().equals(LINE_COMMENT_START_SYMBOL)) {
					withinLineComment = true;
				} else if(symbol.getKey().equals(MULTI_LINE_COMMENT_START_SYMBOL)) {
					withinMultiLineComment = true;
				}
			} else if(withinStringType1 && symbol.getKey().equals(STRING_TYPE1_SYMBOL)) {
				validSymbols.add(symbol);
				withinStringType1 = false;
			} else if(withinStringType2 && symbol.getKey().equals(STRING_TYPE2_SYMBOL)) {
				validSymbols.add(symbol);
				withinStringType2 = false;
			} else if(withinLineComment && symbol.getKey().equals(LINE_COMMENT_END_SYMBOL)) {
				validSymbols.add(symbol);
				withinLineComment = false;
			} else if(withinMultiLineComment && symbol.getKey().equals(MULTI_LINE_COMMENT_END_SYMBOL)) {
				validSymbols.add(symbol);
				withinMultiLineComment = false;
			}
		}
		return validSymbols;
	}

	/**
	 * Returns valid symbols from a set of symbols and regions
	 * @param symbols - symbols to validate against supplied regions
	 * @param invalidRegions - invalid regions. Symbols within these regions are considered invalid. 
	 * @return
	 */
	private List<Symbol> getValidSymbols(List<Symbol> symbols, List<Region> invalidRegions) {

		List<Symbol> validSymbols = new ArrayList<Symbol>();

		symbol:
			for(Symbol symbol : symbols) {
				for(Region invalidRegion : invalidRegions) {
					if(symbol.getOffsets().getStart() >= invalidRegion.getStart() && (invalidRegion.getEnd() == null || symbol.getOffsets().getStart() < invalidRegion.getEnd())) {
						continue symbol;
					}
				}
				validSymbols.add(symbol);
			}
		return validSymbols;
	}
}
