/**
 * This file is part of pwt.
 *
 * pwt is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * pwt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with pwt. If not,
 * see <http://www.gnu.org/licenses/>.
 */
package fr.putnami.pwt.core.widget.client.mask;

import com.google.common.collect.Lists;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.ValueBoxBase;

import java.util.Iterator;
import java.util.List;

public class MaskValueBoxHelper
	implements KeyUpHandler, KeyDownHandler, KeyPressHandler, FocusHandler, BlurHandler, MouseUpHandler {

	public abstract static class TokenHelper extends Timer {

		protected MaskValueBoxHelper maskHelper;

		protected String placeHolder = "";
		protected boolean optional = false;

		protected String token;

		private int keyDown;

		public void setPlaceHolder(String placeHolder) {
			this.placeHolder = placeHolder;
		}

		public void setToken(String token) {
			this.token = token;
		}

		@Override
		public void run() {
			if (this.internalRun()) {
				this.schedule(this.maskHelper.repeatDelayMilis);
			}
		}

		private boolean internalRun() {
			boolean r = this.handleKeyDown(this.keyDown);
			this.maskHelper.refreshValueBox();
			return r;
		}

		public void onKeyDown(KeyDownEvent event) {
			this.keyDown = event.getNativeKeyCode();
			boolean preventDefault = false;
			switch (event.getNativeKeyCode()) {
				case KeyCodes.KEY_DELETE:
				case KeyCodes.KEY_BACKSPACE:
					if (this.token != null) {
						this.reset();
						this.maskHelper.refreshValueBox();
						event.preventDefault();
					}
					break;
				case KeyCodes.KEY_DOWN:
				case KeyCodes.KEY_UP:
					event.preventDefault();
					break;
				case KeyCodes.KEY_LEFT:
					preventDefault = this.maskHelper.focusPrevious();
					break;
				case KeyCodes.KEY_RIGHT:
					preventDefault = this.maskHelper.focusNext();

					break;
				case KeyCodes.KEY_TAB:
					if (event.isShiftKeyDown()) {
						preventDefault = this.maskHelper.focusPrevious();
					} else {
						preventDefault = this.maskHelper.focusNext();
					}
					break;
				default:
					break;
			}

			if (preventDefault) {
				event.preventDefault();
			}
			if (this.handleKeyDown(this.keyDown)) {
				this.maskHelper.refreshValueBox();
				this.schedule(this.maskHelper.initialDelayMilis);
			}
		}

		public void reset() {
			this.token = null;
			this.cancel();
		}

		protected void focus(boolean forward) {
		}

		protected String flush() {
			return this.token == null ? "" : this.token;
		}

		protected boolean handleKeyDown(int keyDown) {
			return true;
		}

		protected boolean handleKeyPress(KeyPressEvent event) {
			return handleKeyPress(event.getCharCode());
		}

		protected abstract boolean handleKeyPress(char charCode);
	}

	private final List<TokenHelper> helpers = Lists.newArrayList();
	private final ValueBoxBase<String> valueBox;

	private TokenHelper currentHelper;

	private int initialDelayMilis = 500;
	private int repeatDelayMilis = 50;

	private boolean cursorToReset;

	public MaskValueBoxHelper(ValueBoxBase<String> valueBox) {
		this.valueBox = valueBox;

		valueBox.addKeyDownHandler(this);
		valueBox.addKeyUpHandler(this);
		valueBox.addKeyPressHandler(this);
		valueBox.addBlurHandler(this);
		valueBox.addFocusHandler(this);
		valueBox.addMouseUpHandler(this);
	}

	public void reset() {
		this.helpers.clear();
	}

	public void addTokenHelper(TokenHelper helper) {
		this.helpers.add(helper);
		helper.maskHelper = this;
	}

	@Override
	public void onBlur(BlurEvent event) {
		for (TokenHelper helper : this.helpers) {
			helper.cancel();
		}
	}

	@Override
	public void onFocus(FocusEvent event) {
		this.parseTokens();
		this.cursorToReset = true;
	}

	@Override
	public void onMouseUp(MouseUpEvent event) {
		this.focusOnCursor();
	}

	@Override
	public void onKeyDown(KeyDownEvent event) {
		if (this.cursorToReset) {
			this.focusOnCursor();
			this.cursorToReset = false;
		}
		if (this.currentHelper != null) {
			this.currentHelper.onKeyDown(event);
		}
	}

	@Override
	public void onKeyUp(KeyUpEvent event) {
		if (this.currentHelper != null) {
			this.currentHelper.cancel();
		}
	}

	@Override
	public void onKeyPress(KeyPressEvent event) {
		int i = helpers.indexOf(currentHelper);
		if (currentHelper != null) {
			if (currentHelper.handleKeyPress(event)) {
				refreshValueBox();
				event.preventDefault();
			} else if (1 + i < helpers.size()) {
				if (!helpers.get(1 + i).handleKeyPress(event)
					&& 2 + i < helpers.size()) {
					focus(i + 1);
					helpers.get(2 + i).handleKeyPress(event);
				} else {
					focus(i + 1);
				}
			} else {
				event.preventDefault();
			}
		}
	}

	private void parseTokens() {
		String value = this.valueBox.getValue();
		for (TokenHelper helper : this.helpers) {
			helper.reset();
		}
		if (value == null) {
			return;
		}
		Iterator<TokenHelper> helperIterator = this.helpers.iterator();
		TokenHelper helper = helperIterator.next();
		for (int i = 0; i < value.length(); i++) {
			char character = value.charAt(i);
			if (!helper.handleKeyPress(character)) {
				while (!helper.handleKeyPress(character) && helperIterator.hasNext()) {
					helper = helperIterator.next();
				}
			}
		}
	}

	private void focusOnCursor() {
		int cursorPosition = this.valueBox.getCursorPos();
		int cnt = 0;
		for (TokenHelper helper : this.helpers) {
			String token = helper.flush();
			int tokenLenght = token != null ? token.length() : 0;
			if (cursorPosition <= cnt + tokenLenght) {
				this.currentHelper = helper;
				this.focus(this.helpers.indexOf(this.currentHelper));
				return;
			}
			cnt += tokenLenght;
		}
		this.focus(0);
	}

	boolean focusNext() {
		if (this.currentHelper == null) {
			return this.focus(0);
		}
		return this.focus(this.helpers.indexOf(this.currentHelper) + 1);
	}

	boolean focusPrevious() {
		if (this.currentHelper == null) {
			return this.focus(0);
		}
		return this.focus(this.helpers.indexOf(this.currentHelper) - 1);
	}

	private boolean focus(int index) {
		for (TokenHelper helper : this.helpers) {
			helper.cancel();
		}
		if (index < 0) {
			this.currentHelper = this.helpers.get(0);
			return false;
		} else if (index + 1 > this.helpers.size()) {
			this.currentHelper = this.helpers.get(this.helpers.size() - 1);
			return false;
		}
		TokenHelper helper = this.helpers.get(index);
		if (this.currentHelper != helper) {
			boolean forward = index > this.helpers.indexOf(this.currentHelper);
			this.currentHelper = helper;
			helper.focus(forward);
			this.highlight();
		}
		return true;
	}

	private void highlight() {
		if (this.currentHelper == null) {
			return;
		}
		String tbVal = this.valueBox.getValue();
		if (tbVal.length() == 0) {
			return;
		}
		int start = 0;
		int helperIndex = this.helpers.indexOf(this.currentHelper);
		for (int i = 0; i <= helperIndex; i++) {
			if (i > 0) {
				start += this.helpers.get(i - 1).flush().length();
			}
		}
		int end = this.currentHelper.flush().length();
		// end = tbVal != null && tbVal.length() >= end ? end : tbVal.length();
		// start = end > start ? start : end;
		this.valueBox.setSelectionRange(start, end);
	}

	private void refreshValueBox() {
		StringBuilder sb = new StringBuilder();
		for (TokenHelper helper : this.helpers) {
			sb.append(helper.flush());
		}
		this.valueBox.setValue(sb.toString(), true);
		this.highlight();
	}

}