/*******************************************************************************
 * Copyright 2014 Rafael Garcia Moreno.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.bladecoder.engineeditor.ui.panels;

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.fadeIn;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.fadeOut;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.sequence;

import java.util.ArrayList;

import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.List;
import com.badlogic.gdx.scenes.scene2d.ui.List.ListStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox.SelectBoxStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.TextField;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.bladecoder.engineeditor.common.EditorLogger;

public class EditableSelectBox<T> extends Table {
	static final Vector2 temp = new Vector2();

	private TextField input;
	private TextButton showListButton;
	private SelectList<T> selectList;

	private T[] items;

	private boolean disabled;

	public EditableSelectBox(Skin skin) {
		super(skin);

		input = new TextField("", skin);
		showListButton = new TextButton(">", skin);
		selectList = new SelectList<>(skin, input);

		add(input);
		add(showListButton);

		addListener(new ClickListener() {
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
				if (pointer == 0 && button != 0)
					return false;

				if (disabled)
					return false;

//				if (selectList.getStage() == null)
				showList();

				return true;
			}

			@Override
			public boolean keyUp(InputEvent event, int keycode) {
				if (keycode == Keys.ENTER) {
					setSelectedIndex(selectList.list.getSelectedIndex());
					hideList();
					input.setCursorPosition(input.getText().length());
				} else if (keycode == Keys.UP) {
					int idx = selectList.list.getSelectedIndex();

					if (idx > 0)
						selectList.list.setSelectedIndex(idx - 1);

					return true;
				} else if (keycode == Keys.DOWN) {
					int idx = selectList.list.getSelectedIndex();

					if (idx < selectList.list.getItems().size - 1)
						selectList.list.setSelectedIndex(idx + 1);

					return true;
				} else {
					if (selectList.getStage() == null && selectList.list.getItems().size > 0) {
						showList();
					}

					filterItems(input.getText());
				}

				return false;
			}
		});

		// selectList.getList().addListener(new ChangeListener() {
		// @Override
		// public void changed(ChangeEvent event, Actor actor) {
		// fire(event);
		// }
		// });

		input.setProgrammaticChangeEvents(true);
		input.addListener(new ChangeListener() {

			@Override
			public void changed(ChangeEvent event, Actor actor) {
				fire(event);
			}
		});
	}

	public String getSelected() {
		return input.getText();
	}

	public void setSelected(String str) {
		input.setText(str);
	}

	public int getSelectedIndex() {
		return selectList.list.getSelectedIndex();
	}

	public void setSelectedIndex(int idx) {
		if (selectList.list.getItems().size == 0)
			return;

		input.setText(selectList.list.getItems().get(idx).toString());
	}

	public TextField getInput() {
		return input;
	}

	public final void setItems(T[] newItems) {

		items = newItems;

		setListItems(items);
	}

	private final void setListItems(T[] newItems) {
		if (newItems == null)
			throw new IllegalArgumentException("newItems cannot be null.");

		float oldPrefWidth = getPrefWidth();

		selectList.list.setItems(newItems);
		if (newItems.length > 0)
			selectList.list.setSelectedIndex(0);
		else
			selectList.list.setSelectedIndex(-1);

		invalidate();
		if (oldPrefWidth != getPrefWidth())
			invalidateHierarchy();
	}

	public void showList() {
		if (selectList.list.getItems().size == 0)
			return;

		if (selectList.list.getSelectedIndex() >= selectList.list.getItems().size)
			selectList.list.setSelectedIndex(selectList.list.getItems().size - 1);

		selectList.show(getStage());
	}

	public void hideList() {
		selectList.hide();
		setListItems(items);
	}

	@SuppressWarnings("unchecked")
	private void filterItems(String s) {

		if (s == null || s.isEmpty()) {
			setListItems(items);
		} else {

			ArrayList<T> filtered = new ArrayList<>();

			String sl = s.toLowerCase();

			for (T item : items) {
				if (item.toString().toLowerCase().contains(sl))
					filtered.add(item);
			}

			setListItems((T[]) filtered.toArray(new String[filtered.size()]));
		}

		selectList.hide();
		selectList.invalidate();
		showList();
	}

	protected void onShow(Actor selectBoxList, boolean below) {
		selectBoxList.getColor().a = 0;
		selectBoxList.addAction(fadeIn(0.3f, Interpolation.fade));
	}

	static class SelectList<T> extends ScrollPane {
		private final TextField selectBox;
		private int selectedIndex;
		int maxListCount;
		private final Vector2 screenPosition = new Vector2();
		final List<T> list;
		private InputListener hideListener;
		private Actor previousScrollFocus;

		public SelectList(Skin skin, final TextField inputBox) {
			super(null, skin.get(SelectBoxStyle.class).scrollStyle);
			this.selectBox = inputBox;

			setOverscroll(false, false);
			setFadeScrollBars(false);

			ListStyle listStyle = skin.get(SelectBoxStyle.class).listStyle;

			list = new List<>(listStyle);
			list.setTouchable(Touchable.disabled);
			setActor(list);

			list.addListener(new ClickListener() {
				@Override
				public void clicked(InputEvent event, float x, float y) {
					selectBox.setText(list.getSelected().toString());
					selectedIndex = list.getSelectedIndex();
					hide();
				}

				@Override
				public boolean mouseMoved(InputEvent event, float x, float y) {
					list.setSelectedIndex(
							Math.min(list.getItems().size - 1, (int) ((list.getHeight() - y) / list.getItemHeight())));
					return true;
				}
			});

			addListener(new InputListener() {
				@Override
				public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
					if (toActor == null || !isAscendantOf(toActor))
						if (selectedIndex < list.getItems().size)
							list.setSelectedIndex(selectedIndex);
						else
							EditorLogger.error("EditableSelectBox:exit selectedIndex outOfBounds: " + selectedIndex);
				}
			});

			hideListener = new InputListener() {
				@Override
				public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
					Actor target = event.getTarget();
					if (isAscendantOf(target))
						return false;

					if (selectedIndex < list.getItems().size)
						list.setSelectedIndex(selectedIndex);
					else
						EditorLogger.error("EditableSelectBox:touchDown selectedIndex outOfBounds: " + selectedIndex);

					hide();
					return false;
				}

				@Override
				public boolean keyDown(InputEvent event, int keycode) {
					if (keycode == Keys.ESCAPE)
						hide();
					return false;
				}
			};
		}

		public void show(Stage stage) {
			if (list.isTouchable())
				return;

			stage.removeCaptureListener(hideListener);
			stage.addCaptureListener(hideListener);
			stage.addActor(this);

			selectBox.localToStageCoordinates(screenPosition.set(0, 0));

			// Show the list above or below the select box, limited to a number
			// of items and the available height in the stage.
			float itemHeight = list.getItemHeight();
			float height = itemHeight
					* (maxListCount <= 0 ? list.getItems().size : Math.min(maxListCount, list.getItems().size));
			Drawable scrollPaneBackground = getStyle().background;
			if (scrollPaneBackground != null)
				height += scrollPaneBackground.getTopHeight() + scrollPaneBackground.getBottomHeight();
			Drawable listBackground = list.getStyle().background;
			if (listBackground != null)
				height += listBackground.getTopHeight() + listBackground.getBottomHeight();

			float heightBelow = screenPosition.y;
			float heightAbove = stage.getCamera().viewportHeight - screenPosition.y - selectBox.getHeight();
			boolean below = true;
			if (height > heightBelow) {
				if (heightAbove > heightBelow) {
					below = false;
					height = Math.min(height, heightAbove);
				} else
					height = heightBelow;
			}

			if (below)
				setY(screenPosition.y - height);
			else
				setY(screenPosition.y + selectBox.getHeight());
			setX(screenPosition.x);
			setSize(Math.max(getPrefWidth(), selectBox.getWidth()), height);

			validate();
			scrollTo(0, list.getHeight() - selectedIndex * itemHeight - itemHeight / 2, 0, 0, true, true);
			updateVisualScroll();

			previousScrollFocus = null;
			Actor actor = stage.getScrollFocus();
			if (actor != null && !actor.isDescendantOf(this))
				previousScrollFocus = actor;
			stage.setScrollFocus(this);

			list.setTouchable(Touchable.enabled);
			clearActions();
			// getColor().a = 0;
			// addAction(fadeIn(0.3f, Interpolation.fade));
		}

		public void hide() {
			if (!list.isTouchable() || !hasParent())
				return;
			list.setTouchable(Touchable.disabled);

			Stage stage = getStage();
			if (stage != null) {
				stage.removeCaptureListener(hideListener);
				if (previousScrollFocus != null && previousScrollFocus.getStage() == null)
					previousScrollFocus = null;
				Actor actor = stage.getScrollFocus();
				if (actor == null || isAscendantOf(actor))
					stage.setScrollFocus(previousScrollFocus);
			}

			clearActions();
			getColor().a = 1;
			addAction(sequence(fadeOut(0.15f, Interpolation.fade), Actions.removeActor()));
		}

		@Override
		public void draw(Batch batch, float parentAlpha) {
			selectBox.localToStageCoordinates(temp.set(0, 0));
			if (!temp.equals(screenPosition))
				hide();
			super.draw(batch, parentAlpha);
		}

		@Override
		public void act(float delta) {
			super.act(delta);
			toFront();
		}

		public List<T> getList() {
			return list;
		}

		public int getSelectedIndex() {
			return list.getSelectedIndex();
		}
	}
}