package smart_gs.swingui.toolbar.action;

import java.util.ArrayList;
import java.util.List;

import javax.swing.JOptionPane;

import smart_gs.logical.LineSegmentForEdit;
import smart_gs.util.GSLog;
import smart_gs.util.Pair;

public class LineSegEditorDeleteResultLinesMaker {
	String input = null;
	List<LineSegmentForEdit> linesForEditPre;
	List<LineSegmentForEdit> linesForEditPost;
	boolean canceled = false;
	List<Pair<Integer,Integer>> toDeleteIntervalsList;
	int index;
	char ch;
	GSLog log = GSLog.getInstance();

	public LineSegEditorDeleteResultLinesMaker(String input,	List<LineSegmentForEdit> linesForEdit) {
		this.linesForEditPre = linesForEdit;
		this.toDeleteIntervalsList = new ArrayList<Pair<Integer,Integer>>();
		this.input = input;

		if (this.input.length() == 0) {
			log.warn("Empty input. Canceled.");
			return;
		}
	}

	public void process() {
		if (this.input == null || this.input.length() == 0) {
			this.canceled = true;
			return;
		}
		
		this.index = 0;
		this.ch = this.input.charAt(0);
		
		this.canceled = read();
		if (this.canceled) return;
		this.canceled = semanticCheck();
		if (this.canceled) return;
		delete();
	}

	public boolean isCanceled() {
		return canceled;
	}
	
	private void delete() {
		LineSegmentForEdit[] linesForEditPreArray = (LineSegmentForEdit[])linesForEditPre.toArray(new LineSegmentForEdit[0]);
		for (Pair<Integer,Integer> p : this.toDeleteIntervalsList) {
			int start = p.getLeft();
			int end = p.getRight();
			for (int i = start; i<=end;i++) linesForEditPreArray[i] = null;
		}

		this.linesForEditPost = new ArrayList<LineSegmentForEdit>();
		for (LineSegmentForEdit line : linesForEditPreArray) {
			if (line != null) linesForEditPost.add(line.deepCopy());
		}
	}

	private boolean read() {
		boolean canceled = false;
		String errMsg;
		Sort errSort;

		while (!canceled) {
			errMsg  = null;
			errSort  = null;

			this.index = 0;
			this.ch = this.input.charAt(this.index);
			this.toDeleteIntervalsList.clear();
			try {
				checkIlliegalChar();
				startSymbol();
			} catch (ReadException e) {
				log.error(errMsg);
				errMsg = e.errMsg;
				errSort = e.sort;
			}

			//			Successfully read. No canceled by user.
			if (errSort == Sort.END_OF_INPUT) {
				canceled = false;
				break;
			} else {
				this.input = JOptionPane.showInputDialog(errMsg, this.input);
				//				Canceled by user.
				if (this.input == null) {
					canceled = true;
				} else if (this.input.length() == 0) {
					log.warn("Empty input. Canceled.");
					canceled = true;
				}
			}
		}
		return canceled;
	}
	
	private boolean semanticCheck() {
		boolean canceled = false;
		String errMsg = null;

		while (!canceled) {
			errMsg = null;
			for(int i =0; i< this.toDeleteIntervalsList.size(); i++) {
				Pair<Integer,Integer> p = this.toDeleteIntervalsList.get(i);
				int start = p.getLeft();
				int end = p.getRight();
				
				if (start > end) {
					errMsg = String.format("Starting number is larger than ending number in %d-th specification %d-%d",i+1, start, end);
					break;
				}
				
				int size = linesForEditPre.size();
				if (end >= size) {
					String[] s = this.input.split("[\t \n\r]+");
					errMsg = String.format("Line number %d of %d-th specification %s is larger than the largest line number %d.",end, i+1, s[i], size-1);
					break;
				}
			}

			// No semantic error. Thus, no cancellation.
			if (errMsg == null) {
				canceled = false;
				break;
			} else {
				// Something wrong semantically. Try again.
				this.input = JOptionPane.showInputDialog(errMsg, this.input);
				//				Canceled by user.
				if (this.input == null) {
					canceled = true;
				} else if (this.input.length() == 0) {
					log.warn("Empty input. Canceled.");
					canceled = true;
				} else {
					canceled = read();
				}
			}
		}
		return canceled;
	}


	public enum Sort {
	    END_OF_INPUT, SYNTAX_ERR
	}
	
	class ReadException extends Exception {
		public Sort sort;
		public String errMsg;
		ReadException(Sort sort1, String msg) {
			sort = sort1;
			errMsg = msg; 
		}
	}

	public boolean getVerdict() {
		return this.canceled;
	}

	private void startSymbol() throws ReadException {
		if (isWhiteSpace(this.ch)) {whiteSpaces(); startSymbol();}
		if (isNumeral(this.ch)) {indexInterval(); startSymbol();}
		throwSyntaxErr();
	}
	
	private void whiteSpaces() throws ReadException {
		while (isWhiteSpace(this.ch)) {
			if (isEndOfInput()) throwEndOfInput();
			this.index++;
			this.ch = input.charAt(this.index);
			checkIlliegalChar();
		}
	}

	private void indexInterval() throws ReadException {
		Integer start = null;
		Integer end = null;
		StringBuffer startIndexString = new StringBuffer();
		StringBuffer endIndexString = new StringBuffer();

		// read startIndexString
		while (isNumeral(this.ch)) {
			startIndexString.append(this.ch);
			if(isEndOfInput()) {
				start = new Integer(new String(startIndexString));
				end = new Integer(new String(startIndexString));
				this.toDeleteIntervalsList.add(new Pair<Integer,Integer>(start,end));
				throwEndOfInput();
			} 
			this.index++;
			this.ch = input.charAt(this.index);
			checkIlliegalChar();
		}
		
		if(isWhiteSpace(this.ch)) {
			start = new Integer(new String(startIndexString));
			end = new Integer(new String(startIndexString));
			this.toDeleteIntervalsList.add(new Pair<Integer,Integer>(start,end));
			return;
		}
		
		// At this point, start *should* end with '-'
		if (this.ch != '-') throwSyntaxErr();
		
		// this.ch is '-'. Advance to read the end of the interval.
		if (isEndOfInput()) throwSyntaxErr();
		index++;
		this.ch = input.charAt(this.index);
		checkIlliegalChar();
		
		// At this point, an numeral must come.
		// Read endIndexString
		if (!isNumeral(this.ch)) throwSyntaxErr();
		
		while (isNumeral(this.ch)) {
			endIndexString.append(this.ch);
			if(isEndOfInput()) {
				start = new Integer(new String(startIndexString));
				end = new Integer(new String(endIndexString));
				this.toDeleteIntervalsList.add(new Pair<Integer,Integer>(start,end));
				throwEndOfInput();
			} 
			this.index++;
			this.ch = input.charAt(this.index);
			checkIlliegalChar();
		}
		
		start = new Integer(new String(startIndexString));
		end = new Integer(new String(endIndexString));	
		this.toDeleteIntervalsList.add(new Pair<Integer,Integer>(start,end));
	}
	
	private boolean isEndOfInput () {
		return this.index >= this.input.length()-1;
	}

	
	private void throwEndOfInput () throws ReadException {
		ReadException e = new ReadException(Sort.END_OF_INPUT,"");
		throw e;
	}
	
	private void throwSyntaxErr () throws ReadException {
		ReadException e = new ReadException(Sort.SYNTAX_ERR,String.format("Syntax error at %d-th character: %s ...\n",this.index+1,this.input.substring(index,Math.min(index+4,this.input.length()))));
		throw e;
	}
	
	private void checkIlliegalChar () throws ReadException {
		if (isIllegalChar(this.ch)) {
			ReadException e = new ReadException(Sort.SYNTAX_ERR,String.format("%d-th character %c is illegal: %s ...\n",this.index+1,this.ch, this.input.substring(index,Math.min(index+4, this.input.length()))));
			throw e;
		}
	}
	
	private boolean isWhiteSpace(char ch) {
		return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
	}
	
	private boolean isNumeral(char ch) {
		return '0' <= ch && ch <= '9';
	}
	
	private boolean isIllegalChar(char ch) {
		return ! (isWhiteSpace(ch) || isNumeral(ch) || ch == '-');
	}

	public List<LineSegmentForEdit> getLines() {
		return linesForEditPost;
	}

}
