/*
 *
 *          Copyright (c) 2014,2019  AT&T Knowledge Ventures
 *                     SPDX-License-Identifier: MIT
 */
package com.att.research.xacml.admin.view.windows;

import java.util.List;
import java.util.Map;

import oasis.names.tc.xacml._3_0.core.schema.wd_17.ApplyType;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.ConditionType;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.att.research.xacml.admin.XacmlAdminUI;
import com.att.research.xacml.admin.jpa.Datatype;
import com.att.research.xacml.admin.jpa.FunctionArgument;
import com.att.research.xacml.admin.jpa.FunctionDefinition;
import com.att.research.xacml.admin.util.JPAUtils;
import com.att.research.xacml.admin.view.events.ApplyParametersChangedListener;
import com.att.research.xacml.admin.view.events.ApplyParametersChangedNotifier;
import com.att.research.xacml.api.XACML3;
import com.vaadin.annotations.AutoGenerated;
import com.vaadin.data.Buffered.SourceException;
import com.vaadin.data.Container;
import com.vaadin.data.Container.Filter;
import com.vaadin.data.Item;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.data.util.filter.Compare;
import com.vaadin.data.util.filter.SimpleStringFilter;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.event.ItemClickEvent.ItemClickListener;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.ui.AbstractSelect.ItemCaptionMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Table;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;

public class ApplyEditorWindow extends Window implements ApplyParametersChangedNotifier {

	/*- VaadinEditorProperties={"grid":"RegularGrid,20","showGrid":true,"snapToGrid":true,"snapToObject":true,"movingGuides":false,"snappingDistance":10} */

	@AutoGenerated
	private VerticalLayout mainLayout;
	@AutoGenerated
	private Button buttonSelect;
	@AutoGenerated
	private Table tableFunction;
	@AutoGenerated
	private HorizontalLayout horizontalLayout_1;
	@AutoGenerated
	private CheckBox checkBoxFilterIsBag;
	@AutoGenerated
	private ComboBox comboBoxDatatypeFilter;
	@AutoGenerated
	private TextField textFieldFilter;
	@AutoGenerated
	private TextArea textAreaDescription;
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private static Log logger	= LogFactory.getLog(ApplyEditorWindow.class);
	private final ApplyEditorWindow self = this;
	private final ApplyParametersChangedNotifier notifier = new ApplyParametersChangedNotifier.BasicNotifier();
	private final BeanItemContainer<FunctionDefinition>	container = new BeanItemContainer<FunctionDefinition>(FunctionDefinition.class);
	private final ApplyType apply;
	private final ApplyType applyParent;
	private final FunctionArgument argument;
	private final Object parent;
	private boolean isSaved = false;
	private FunctionDefinition function = null;
	/**
	 * The constructor should first build the main layout, set the
	 * composition root and then do any custom initialization.
	 *
	 * The constructor will not be automatically regenerated by the
	 * visual editor.
	 * @param apply 
	 * @param parent 
	 * @param parentApply 
	 * @param argument 
	 */
	public ApplyEditorWindow(ApplyType apply, ApplyType parentApply, FunctionArgument argument, Object parent) {
		buildMainLayout();
		//setCompositionRoot(mainLayout);
		setContent(mainLayout);
		//
		// Save
		//
		this.apply = apply;
		this.applyParent = parentApply;
		this.argument = argument;
		this.parent = parent;
		logger.info(this.apply + " " + this.applyParent + " " + this.argument + " " + this.parent);
		//
		// Set our shortcuts
		//
		this.setCloseShortcut(KeyCode.ESCAPE);
		//
		// Initialize
		//
		this.textAreaDescription.setValue(apply.getDescription());
		this.textAreaDescription.setNullRepresentation("");
		this.initializeButton();
		this.initializeTable();
		this.initializeFilters();
		//
		// focus
		//
		this.textFieldFilter.focus();
	}
	
	protected void initializeTable() {
		//
		// Setup GUI properties
		//
		this.tableFunction.setImmediate(true);
		this.tableFunction.setSelectable(true);
		this.tableFunction.setNullSelectionAllowed(false);
		this.tableFunction.setRequired(true);
		this.tableFunction.setRequiredError("You MUST select a function for the Apply");
		//
		// Set its data source
		//
		this.tableFunction.setContainerDataSource(this.container);
		this.tableFunction.setVisibleColumns(new Object[] {"xacmlid", "shortname", "datatypeBean", "isBagReturn"});
		this.tableFunction.setColumnHeaders(new String[] {"Function Xacml ID", "ID", "Return Data Type", "Return Bag?"});
		//
		// Determine appropriate filters
		//
		Datatype datatypeId = null;
		if (this.applyParent == null) {
			if (this.parent instanceof ConditionType) {
				//
				// Only boolean functions allowed
				//
				datatypeId = JPAUtils.findDatatype(XACML3.ID_DATATYPE_BOOLEAN);
			}
		} else {
			String parentFunction = this.applyParent.getFunctionId();
			this.function = JPAUtils.findFunction(parentFunction);
			if (this.function == null) {
				throw new IllegalArgumentException("applyParent's function is not found:" + parentFunction);
			}
			if (this.argument == null) {
				throw new IllegalArgumentException("Need to know what argument apply is ");
			}
			datatypeId = this.argument.getDatatypeBean();
		}
		Map<Datatype, List<FunctionDefinition>> functionMap = JPAUtils.getFunctionDatatypeMap();
		if (datatypeId == null) {
			//
			// All functions are available
			//
			for (Datatype id : functionMap.keySet()) {
				this.addTableEntries(functionMap.get(id));
			}
		} else {
			for (Datatype id : functionMap.keySet()) {
				if (id == null) {
					if (datatypeId == null) {
						this.addTableEntries(functionMap.get(id));
						break;
					}
					continue;
				}
				if (id.getId() == datatypeId.getId()) {
					this.addTableEntries(functionMap.get(id));
					break;
				}
			}
		}
		//
		// Setup double-click
		//
		this.tableFunction.addItemClickListener(new ItemClickListener() {
			private static final long serialVersionUID = 1L;

			@Override
			public void itemClick(ItemClickEvent event) {
				if (event.isDoubleClick()) {
					self.selected();
				}
			}
		});
		//
		// Value change listener
		//
		this.tableFunction.addValueChangeListener(new ValueChangeListener() {
			private static final long serialVersionUID = 1L;

			@Override
			public void valueChange(ValueChangeEvent event) {
				logger.info("valueChange " + self.tableFunction.getValue());
				if (self.tableFunction.getValue() != null) {
					self.buttonSelect.setEnabled(true);
				} else {
					self.buttonSelect.setEnabled(false);
				}
			}			
		});
		//
		// Select current value if possible
		//
		if (this.apply != null && this.apply.getFunctionId() != null && this.apply.getFunctionId().isEmpty() == false) {
			FunctionDefinition current = JPAUtils.findFunction(this.apply.getFunctionId());
			if (current != null) {
				this.tableFunction.select(current);
				this.tableFunction.setCurrentPageFirstItemId(current);
			} else {
				logger.warn("Could not find function in table for " + this.apply.getFunctionId());
			}
		} else {
			this.buttonSelect.setEnabled(false);
		}
	}
	
	protected void addTableEntries(List<FunctionDefinition> functions) {
		if (functions == null) {
			logger.warn("NULL list of functions, cannot add to table.");
			return;
		}
		for (FunctionDefinition function : functions) {
			//
			// Just check if this function is available for this
			// apply.
			//
//			if (XACMLFunctionValidator.isFunctionAvailable(function, this.apply, this.argument)) {
				this.container.addBean(function);
//			} else {
//				if (logger.isDebugEnabled()) {
//					logger.debug("Function not available: " + function);
//				}
//			}
		}
	}
	
	protected void initializeButton() {
		this.buttonSelect.addClickListener(new ClickListener() {
			private static final long serialVersionUID = 1L;

			@Override
			public void buttonClick(ClickEvent event) {
				self.selected();
			}
		});
	}
	
	protected void initializeFilters() {
		this.textFieldFilter.setImmediate(true);
		this.textFieldFilter.addTextChangeListener(new TextChangeListener() {
			private static final long serialVersionUID = 1L;
			SimpleStringFilter currentFilter = null;
			
			@Override
			public void textChange(TextChangeEvent event) {
				//
				// Remove current filter
				//
				if (this.currentFilter != null) {
					self.container.removeContainerFilter(this.currentFilter);
					this.currentFilter = null;
				}
				//
				// Get the text
				//
				String value = event.getText();
				if (value != null && value.length() > 0) {
					//
					// Add the new filter
					//
					this.currentFilter = new SimpleStringFilter("shortname", value, true, false);
					self.container.addContainerFilter(this.currentFilter);
				}
			}
		});
		
		this.comboBoxDatatypeFilter.setContainerDataSource(((XacmlAdminUI) UI.getCurrent()).getDatatypes());
		this.comboBoxDatatypeFilter.setImmediate(true);
		this.comboBoxDatatypeFilter.setItemCaptionMode(ItemCaptionMode.PROPERTY);
		this.comboBoxDatatypeFilter.setItemCaptionPropertyId("xacmlId");
		this.comboBoxDatatypeFilter.setNullSelectionAllowed(true);
		this.comboBoxDatatypeFilter.addValueChangeListener(new ValueChangeListener() {
			private static final long serialVersionUID = 1L;
			Container.Filter currentFilter = null;

			@Override
			public void valueChange(ValueChangeEvent event) {
				//
				// Remove current filter
				//
				if (this.currentFilter != null) {
					self.container.removeContainerFilter(this.currentFilter);
					this.currentFilter = null;
				}
				//
				// Get the current selection
				//
				Object id = self.comboBoxDatatypeFilter.getValue();
				if (id == null) {
					return;
				}
				//
				// Setup the filter
				//
				final Datatype datatype = ((XacmlAdminUI) UI.getCurrent()).getDatatypes().getItem(id).getEntity();
				this.currentFilter = new Container.Filter() {
					private static final long serialVersionUID = 1L;

					@Override
					public boolean passesFilter(Object itemId, Item item) throws UnsupportedOperationException {
						if (itemId instanceof FunctionDefinition) {
							Datatype dt = ((FunctionDefinition) itemId).getDatatypeBean();
							if (dt == null) {
								return false;
							}
							return (dt.getXacmlId().equals(datatype.getXacmlId()));
						}
						return false;
					}
					
					@Override
					public boolean appliesToProperty(Object propertyId) {
						if (propertyId != null && propertyId.toString().equals("datatypeBean")) {
							return true;
						}
						return false;
					}
				};
				self.container.addContainerFilter(this.currentFilter);
			}
		});
		
		this.checkBoxFilterIsBag.setImmediate(true);
		this.checkBoxFilterIsBag.addValueChangeListener(new ValueChangeListener() {
			private static final long serialVersionUID = 1L;
			Filter currentFilter = null;

			@Override
			public void valueChange(ValueChangeEvent event) {
				//
				// Remove current filter
				//
				if (this.currentFilter != null) {
					self.container.removeContainerFilter(this.currentFilter);
					this.currentFilter = null;
				}
				//
				// Is it checked?
				//
				if (self.checkBoxFilterIsBag.getValue() == false) {
					//
					// Nope, get out of here
					//
					return;
				}
				//
				// Add the filter
				//
				this.currentFilter = new Compare.Equal("isBagReturn", true);
				self.container.addContainerFilter(this.currentFilter);
			}
		});
	}
	
	protected	void	selected() {
		//
		// Is there a selected function?
		//
		try {
			//
			// Run the commit
			//
			this.textAreaDescription.commit();
			this.tableFunction.commit();
			//
			// Commit worked, get the selected function
			//
			Object id = this.tableFunction.getValue();
			//
			// Sanity check, it *should* never be null
			// unless someone changed the initialization code.
			//
			if (id == null || ! (id instanceof FunctionDefinition)) {
				throw new InvalidValueException(this.tableFunction.getRequiredError());
			}
			//
			// Get the actual function and save it into the apply
			//
			this.function = (FunctionDefinition) id;
			this.apply.setDescription(this.textAreaDescription.getValue());
			this.apply.setFunctionId(function.getXacmlid());
		} catch (SourceException | InvalidValueException e) {
			//
			// Vaadin GUI will display message
			//
			return;
		}
		/**
		//
		// Make sure the arguments are good
		//
		final ApplyType copyApply = XACMLObjectCopy.copy(this.apply);
		final ApplyArgumentsEditorWindow window = new ApplyArgumentsEditorWindow(copyApply, this.function);
		window.setCaption("Define Arguments for " + this.function.getShortname());
		window.setModal(true);
		window.addCloseListener(new CloseListener() {
			private static final long serialVersionUID = 1L;

			@Override
			public void windowClose(CloseEvent e) {
				//
				// Did the user click save?
				//
				if (window.isSaved() == false) {
					return;
				}
				//
				// Save our arguments
				//
				self.apply.getExpression().clear();
				self.apply.getExpression().addAll(copyApply.getExpression());
				//
				// We are saved
				//
				self.isSaved = true;
				//
				// Fire
				//
				self.fireEvent(self.apply, self.applyParent, self.argument, self.parent);
				//
				// Close the apply editor window
				//
				self.close();
			}
		});
		window.center();
		UI.getCurrent().addWindow(window);
		**/
		//
		// We are saved
		//
		self.isSaved = true;
		//
		// Fire
		//
		self.fireEvent(self.apply, self.applyParent, self.argument, self.parent);
		//
		// Close the apply editor window
		//
		self.close();
	}

	@Override
	public boolean addListener(ApplyParametersChangedListener listener) {
		return this.notifier.addListener(listener);
	}

	@Override
	public boolean removeListener(ApplyParametersChangedListener listener) {
		return this.notifier.removeListener(listener);
	}

	@Override
	public void fireEvent(ApplyType apply, ApplyType parent, FunctionArgument argument, Object container) {
		this.notifier.fireEvent(apply, parent, argument, container);
	}

	public boolean isSaved() {
		return this.isSaved;
	}
	
	public FunctionDefinition	getSelectedFunction() {
		return this.function;
	}
	
	@AutoGenerated
	private VerticalLayout buildMainLayout() {
		// common part: create layout
		mainLayout = new VerticalLayout();
		mainLayout.setImmediate(false);
		mainLayout.setWidth("-1px");
		mainLayout.setHeight("-1px");
		mainLayout.setMargin(true);
		mainLayout.setSpacing(true);
		
		// top-level component properties
		setWidth("-1px");
		setHeight("-1px");
		
		// horizontalLayout_1
		horizontalLayout_1 = buildHorizontalLayout_1();
		mainLayout.addComponent(horizontalLayout_1);
		
		// tableFunction
		tableFunction = new Table();
		tableFunction.setCaption("Select A Function");
		tableFunction.setImmediate(false);
		tableFunction.setWidth("100.0%");
		tableFunction.setHeight("-1px");
		tableFunction.setInvalidAllowed(false);
		tableFunction.setRequired(true);
		mainLayout.addComponent(tableFunction);
		mainLayout.setExpandRatio(tableFunction, 1.0f);
		
		// buttonSelect
		buttonSelect = new Button();
		buttonSelect.setCaption("Select and Continue");
		buttonSelect.setImmediate(true);
		buttonSelect.setWidth("-1px");
		buttonSelect.setHeight("-1px");
		mainLayout.addComponent(buttonSelect);
		mainLayout.setComponentAlignment(buttonSelect, new Alignment(48));
		
		return mainLayout;
	}

	@AutoGenerated
	private HorizontalLayout buildHorizontalLayout_1() {
		// common part: create layout
		horizontalLayout_1 = new HorizontalLayout();
		horizontalLayout_1.setImmediate(false);
		horizontalLayout_1.setWidth("-1px");
		horizontalLayout_1.setHeight("-1px");
		horizontalLayout_1.setMargin(false);
		horizontalLayout_1.setSpacing(true);
		
		// textAreaDescription
		textAreaDescription = new TextArea();
		textAreaDescription.setCaption("Enter A Description");
		textAreaDescription.setImmediate(false);
		textAreaDescription.setWidth("50.0%");
		textAreaDescription.setHeight("-1px");
		horizontalLayout_1.addComponent(textAreaDescription);
		
		// textFieldFilter
		textFieldFilter = new TextField();
		textFieldFilter.setCaption("Filter Function By ID");
		textFieldFilter.setImmediate(false);
		textFieldFilter.setWidth("-1px");
		textFieldFilter.setHeight("-1px");
		horizontalLayout_1.addComponent(textFieldFilter);
		horizontalLayout_1.setComponentAlignment(textFieldFilter,
				new Alignment(9));
		
		// comboBoxDatatypeFilter
		comboBoxDatatypeFilter = new ComboBox();
		comboBoxDatatypeFilter.setCaption("Filter By Data Type");
		comboBoxDatatypeFilter.setImmediate(false);
		comboBoxDatatypeFilter.setWidth("-1px");
		comboBoxDatatypeFilter.setHeight("-1px");
		horizontalLayout_1.addComponent(comboBoxDatatypeFilter);
		horizontalLayout_1.setComponentAlignment(comboBoxDatatypeFilter,
				new Alignment(9));
		
		// checkBoxFilterIsBag
		checkBoxFilterIsBag = new CheckBox();
		checkBoxFilterIsBag.setCaption("Is Bag Filter");
		checkBoxFilterIsBag.setImmediate(false);
		checkBoxFilterIsBag.setWidth("-1px");
		checkBoxFilterIsBag.setHeight("-1px");
		horizontalLayout_1.addComponent(checkBoxFilterIsBag);
		horizontalLayout_1.setComponentAlignment(checkBoxFilterIsBag,
				new Alignment(9));
		
		return horizontalLayout_1;
	}

}