/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */

package org.tiefaces.components.websheet.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.AjaxBehaviorEvent;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.primefaces.component.datatable.DataTable;
import org.primefaces.context.RequestContext;
import org.tiefaces.common.FacesUtility;
import org.tiefaces.common.TieConstants;
import org.tiefaces.components.websheet.TieWebSheetBean;
import org.tiefaces.components.websheet.configuration.SheetConfiguration;
import org.tiefaces.components.websheet.dataobjects.CellFormAttributes;
import org.tiefaces.components.websheet.dataobjects.FacesCell;
import org.tiefaces.components.websheet.dataobjects.FacesRow;
import org.tiefaces.components.websheet.dataobjects.TieCell;
import org.tiefaces.components.websheet.utility.CellControlsUtility;
import org.tiefaces.components.websheet.utility.CellUtility;
import org.tiefaces.components.websheet.utility.ConfigurationUtility;
import org.tiefaces.components.websheet.utility.SaveAttrsUtility;

/**
 * The Class ValidationHandler.
 */
public class ValidationHandler {

	/** The parent. */
	private TieWebSheetBean parent = null;

	/** logger. */
	private static final Logger LOG = Logger.getLogger(ValidationHandler.class.getName());

	/**
	 * Instantiates a new validation handler.
	 *
	 * @param pparent
	 *            the parent
	 */
	public ValidationHandler(final TieWebSheetBean pparent) {
		super();
		this.parent = pparent;

	}

	/**
	 * Refresh after status changed.
	 *
	 * @param oldStatus
	 *            the old status
	 * @param newStatus
	 *            the new status
	 * @param formRow
	 *            the form row
	 * @param formCol
	 *            the form col
	 * @param cell
	 *            the cell
	 * @param updateGui
	 *            update gui flag.
	 *            if true then update gui.
	 */
	private void refreshAfterStatusChanged(final boolean oldStatus, final boolean newStatus, final int formRow,
			final int formCol, final FacesCell cell, final boolean updateGui) {

		if (!newStatus) {
			cell.setErrormsg("");
		}
		cell.setInvalid(newStatus);
		if (updateGui && (oldStatus != newStatus) && (parent.getWebFormClientId() != null)) {
			RequestContext.getCurrentInstance()
					.update(parent.getWebFormClientId() + ":" + (formRow) + ":group" + (formCol));
		}

	}

	/**
	 * Validate with row col in current page.
	 *
	 * @param row
	 *            the row
	 * @param col
	 *            the col
	 * @param updateGui
	 * 			  if true then update gui.
	 * @return true, if successful
	 */
	public boolean validateWithRowColInCurrentPage(final int row, final int col, boolean updateGui) {

		//until now passEmptyCheck has one to one relation to submitMode
		//e.g. when passEmptyCheck = false, then submitMode = true.

		boolean submitMode = parent.getSubmitMode();
		boolean passEmptyCheck = !submitMode;

		int topRow = parent.getCurrent().getCurrentTopRow();
		int leftCol = parent.getCurrent().getCurrentLeftColumn();
		boolean pass = true;

		FacesRow fRow = CellUtility.getFacesRowFromBodyRow(row, parent.getBodyRows(), topRow);
		if (fRow == null) {
			return pass;
		}

		FacesCell cell = CellUtility.getFacesCellFromBodyRow(row, col, parent.getBodyRows(), topRow, leftCol);
		if (cell == null) {
			return pass;
		}

		Cell poiCell = parent.getCellHelper().getPoiCellWithRowColFromCurrentPage(row, col);
		boolean oldStatus = cell.isInvalid();

		String value = CellUtility.getCellValueWithoutFormat(poiCell);
		if (value == null) {
			value = "";
		} else {
			value = value.trim();
		}

		if (passEmptyCheck && value.isEmpty()) {
			refreshAfterStatusChanged(oldStatus, false, row - topRow, col - leftCol, cell, updateGui);
			return pass;
		}

		if (((parent.isOnlyValidateInSubmitMode() && submitMode ) || !parent.isOnlyValidateInSubmitMode())
		&& !validateByTieWebSheetValidationBean(poiCell, topRow, leftCol, cell, value, updateGui)) {
			return false;
		}

		SheetConfiguration sheetConfig = parent.getSheetConfigMap().get(parent.getCurrent().getCurrentTabName());
		List<CellFormAttributes> cellAttributes = CellControlsUtility.findCellValidateAttributes(
				parent.getCellAttributesMap().getCellValidateAttributes(), fRow.getOriginRowIndex(), poiCell);

		if (parent.isAdvancedContext() && parent.getConfigAdvancedContext().getErrorSuffix() != null
				&& !checkErrorMessageFromObjectInContext(row - topRow, col - leftCol, cell, poiCell, value,
						sheetConfig, updateGui)) {
			return false;
		}

		if (cellAttributes != null) {
			pass = validateAllRulesForSingleCell(row - topRow, col - leftCol, cell, poiCell, value, sheetConfig,
					cellAttributes, updateGui);
		}

		if (pass) {
			refreshAfterStatusChanged(oldStatus, false, row - topRow, col - leftCol, cell, updateGui);
		}
		return pass;

	}

	/**
	 * Validate by tie web sheet validation bean.
	 *
	 * @param poiCell the poi cell
	 * @param topRow the top row
	 * @param leftCol the left col
	 * @param cell the cell
	 * @param value the value
	 * @param updateGui the update gui
	 * @return true, if successful
	 */
	private boolean validateByTieWebSheetValidationBean(final Cell poiCell, final int topRow, final int leftCol,
			final FacesCell cell, final String value, boolean updateGui) {
		if (parent.getTieWebSheetValidationBean() != null) {
			String errormsg = null;
			String fullName = ConfigurationUtility.getFullNameFromRow(poiCell.getRow());
			String saveAttr = SaveAttrsUtility.prepareContextAndAttrsForCell(poiCell, fullName, parent.getCellHelper());
			if (saveAttr != null) {
				int row = poiCell.getRowIndex();
				int col = poiCell.getColumnIndex();
				errormsg = parent.getTieWebSheetValidationBean().validate(
						parent.getSerialDataContext().getDataContext(), saveAttr, ConfigurationUtility
								.getFullNameFromRow(poiCell.getRow()), poiCell.getSheet().getSheetName(),
						row, col, value);
				if ((errormsg != null) && (!errormsg.isEmpty())) {
					cell.setErrormsg(errormsg);
					refreshAfterStatusChanged(false, true, row - topRow, col - leftCol, cell, updateGui);
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Check error message from object in context.
	 *
	 * @param formRow the form row
	 * @param formCol the form col
	 * @param cell the cell
	 * @param poiCell the poi cell
	 * @param value the value
	 * @param sheetConfig the sheet config
	 * @param updateGui the update gui
	 * @return true, if successful
	 */
	private boolean checkErrorMessageFromObjectInContext(final int formRow, final int formCol, final FacesCell cell,
			final Cell poiCell, final String value, final SheetConfiguration sheetConfig, boolean updateGui) {

		@SuppressWarnings("unchecked")
		HashMap<String, TieCell> tieCells = (HashMap<String, TieCell>) parent.getSerialDataContext().getDataContext()
				.get("tiecells");

		if (tieCells != null) {

			TieCell tieCell = tieCells.get(CellUtility.getSkeyFromPoiCell(poiCell));

			if (tieCell != null && tieCell.getContextObject() != null) {

				String errorMethod = tieCell.getMethodStr() + parent.getConfigAdvancedContext().getErrorSuffix();

				String errorMessage = CellControlsUtility.getObjectPropertyValue(tieCell.getContextObject(),
						errorMethod, true);

				if (errorMessage != null && !errorMessage.isEmpty()) {
					cell.setErrormsg(errorMessage);
					LOG.log(Level.INFO, "Validation failed for sheet {0} row {1} column {2} : {3}",
							new Object[] { poiCell.getSheet().getSheetName(), poiCell.getRowIndex(),
									poiCell.getColumnIndex(), errorMessage });
					refreshAfterStatusChanged(false, true, formRow, formCol, cell, updateGui);
					return false;
				}

			}
		}

		return true;

	}

	/**
	 * Validate all rules for single cell.
	 *
	 * @param formRow            the form row
	 * @param formCol            the form col
	 * @param cell            the cell
	 * @param poiCell            the poi cell
	 * @param value            the value
	 * @param sheetConfig            the sheet config
	 * @param cellAttributes            the cell attributes
	 * @param updateGui the update gui
	 * @return true, if successful
	 */
	private boolean validateAllRulesForSingleCell(final int formRow, final int formCol, final FacesCell cell,
			final Cell poiCell, final String value, final SheetConfiguration sheetConfig,
			final List<CellFormAttributes> cellAttributes, boolean updateGui) {
		Sheet sheet1 = parent.getWb().getSheet(sheetConfig.getSheetName());
		for (CellFormAttributes attr : cellAttributes) {
			boolean pass = doValidation(value, attr, poiCell.getRowIndex(), poiCell.getColumnIndex(), sheet1);
			if (!pass) {
				String errmsg = attr.getMessage();
				if (errmsg == null) {
					errmsg = TieConstants.DEFALT_MSG_INVALID_INPUT;
				}
				cell.setErrormsg(errmsg);
				LOG.log(Level.INFO, "Validation failed for sheet {0} row {1} column {2} : {3}", new Object[] {
						poiCell.getSheet().getSheetName(), poiCell.getRowIndex(), poiCell.getColumnIndex(), errmsg });
				refreshAfterStatusChanged(false, true, formRow, formCol, cell, updateGui);
				return false;
			}

		}
		return true;
	}

	/**
	 * Do validation.
	 *
	 * @param value            the value
	 * @param attr            the attr
	 * @param rowIndex            the row index
	 * @param colIndex the col index
	 * @param sheet            the sheet
	 * @return true, if successful
	 */
	private boolean doValidation(final Object value, final CellFormAttributes attr, final int rowIndex,
			final int colIndex, final Sheet sheet) {
		boolean pass;

		String attrValue = attr.getValue();
		attrValue = attrValue.replace("$value", value.toString() + "").replace("$rowIndex", rowIndex + "")
				.replace("$colIndex", colIndex + "").replace("$sheetName", sheet.getSheetName());
		attrValue = ConfigurationUtility.replaceExpressionWithCellValue(attrValue, rowIndex, sheet);
		if (attrValue.contains(TieConstants.EL_START)) {
			Object returnObj = FacesUtility.evaluateExpression(attrValue, Object.class);
			attrValue = returnObj.toString();
			pass = Boolean.parseBoolean(attrValue);
		} else {
			pass = parent.getCellHelper().evalBoolExpression(attrValue);
		}
		return pass;

	}

	/**
	 * Validate cell.
	 *
	 * @param target
	 *            the target
	 * @return true, if successful
	 */
	public final boolean validateCell(final UIComponent target) {

		int[] rowcol = CellUtility.getRowColFromComponentAttributes(target);
		int row = rowcol[0];
		int col = rowcol[1];
		return validateWithRowColInCurrentPage(row, col, true);
	}

	/**
	 * Validate current page.
	 *
	 * @return true, if successful
	 */
	public final boolean validateCurrentPage() {
		boolean allpass = true;

		int top = parent.getCurrent().getCurrentTopRow();
		for (int irow = 0; irow < parent.getBodyRows().size(); irow++) {
			if (!validateRowInCurrentPage(irow + top, false)) {
				allpass = false;
			}
		}
		return allpass;
	}

	/**
	 * Validate row in current page.
	 *
	 * @param irow the irow
	 * @param updateGui the update gui
	 * @return true, if successful
	 */
	public final boolean validateRowInCurrentPage(final int irow, final boolean updateGui) {
		SheetConfiguration sheetConfig = parent.getSheetConfigMap().get(parent.getCurrent().getCurrentTabName());
		return this.validateRow(irow, sheetConfig, updateGui);
	}

	/**
	 * Validate row.
	 *
	 * @param irow the irow
	 * @param sheetConfig the sheet config
	 * @param updateGui the update gui
	 * @return true, if successful
	 */
	private boolean validateRow(final int irow, final SheetConfiguration sheetConfig, boolean updateGui) {
		boolean pass = true;
		if (sheetConfig == null) {
			return pass;
		}
		int top = sheetConfig.getBodyCellRange().getTopRow();
		List<FacesCell> cellRow = parent.getBodyRows().get(irow - top).getCells();
		for (int index = 0; index < cellRow.size(); index++) {
			FacesCell fcell = cellRow.get(index);
			if ((fcell != null) && (!validateWithRowColInCurrentPage(irow, fcell.getColumnIndex(), updateGui))) {
				pass = false;
			}
		}
		return pass;
	}

	/**
	 * Triggered when value in cells changed. e.g. user edit cell.
	 *
	 * @param event
	 *            ajax event.
	 */
	public void valueChangeEvent(final AjaxBehaviorEvent event) {

		try {
			FacesContext facesContext = FacesContext.getCurrentInstance();
			String tblName = parent.getWebFormClientId();
			UIComponent target = event.getComponent();
			boolean pass = validateCell(target);
			if (pass) {
				// to improve performance, re-validate current row only
				// page validation take times. will happen when change tab(page)
				// or
				// reload page.
				int[] rowcol = CellUtility.getRowColFromComponentAttributes(target);
				validateRowInCurrentPage(rowcol[0], true);
				refreshCachedCellsInCurrentPage(facesContext, tblName);
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, "Validation error:" + ex.getLocalizedMessage(), ex);
		}
	}

	/**
	 * Refresh cached cells in current page.
	 *
	 * @param facesContext
	 *            the faces context
	 * @param tblName
	 *            the tbl name
	 */
	private void refreshCachedCellsInCurrentPage(final FacesContext facesContext, final String tblName) {
		// refresh current page calculation fields
		UIComponent s = facesContext.getViewRoot().findComponent(tblName);
		if (s == null) {
			return;
		}
		DataTable webDataTable = (DataTable) s;
		int first = webDataTable.getFirst();
		int rowsToRender = webDataTable.getRowsToRender();
		int rowCounts = webDataTable.getRowCount();
		int top = parent.getCurrent().getCurrentTopRow();
		int left = parent.getCurrent().getCurrentLeftColumn();

		for (int i = first; i <= (first + rowsToRender); i++) {
			if (i < rowCounts) {
				refreshCachedCellsInRow(tblName, top, left, i);
			}

		}
	}

	/**
	 * Refresh cached cells in row.
	 *
	 * @param tblName
	 *            the tbl name
	 * @param top
	 *            the top
	 * @param left
	 *            the left
	 * @param i
	 *            the i
	 */
	private void refreshCachedCellsInRow(final String tblName, final int top, final int left, final int i) {
		FacesRow dataRow = parent.getBodyRows().get(i);
		int isize = dataRow.getCells().size();
		for (int index = 0; index < isize; index++) {
			FacesCell fcell = dataRow.getCells().get(index);
			Cell poiCell = parent.getCellHelper().getPoiCellWithRowColFromCurrentPage(i + top, index + left);
			if (poiCell != null) {
				parent.getHelper().getWebSheetLoader().refreshCachedCell(tblName, i, index, poiCell, fcell);
			}
		}
	}

	/**
	 * set submit mode flag with javascript for holding in client side.
	 *
	 * @param fullflag
	 *            true or false
	 */
	public void setSubmitModeInView(final Boolean fullflag) {

		if (FacesContext.getCurrentInstance() != null) {
			Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
			if (viewMap != null) {
				Boolean flag = (Boolean) viewMap.get(TieConstants.SUBMITMODE);
				if ((flag == null) || (!flag.equals(fullflag))) {
					viewMap.put(TieConstants.SUBMITMODE, fullflag);
				}
			}
		}
	}

	/**
	 * triggered validation process before actions like save or submit.
	 *
	 * @return true (pass) false (failed)
	 */
	public boolean preValidation() {

		String currentTabName = parent.getCurrent().getCurrentTabName();
		String tabName = null;
		String firstInvalidTabName = null;
		boolean reload = false;
		for (Map.Entry<String, SheetConfiguration> entry : parent.getSheetConfigMap().entrySet()) {
			tabName = entry.getKey();
			// if not reload and tabname==current then skip reloading.
			if (reload || (!tabName.equals(currentTabName))) {
				parent.getWebSheetLoader().prepareWorkShee(tabName);
				reload = true;
			}
			if (!parent.getValidationHandler().validateCurrentPage() &&
				(firstInvalidTabName == null)) {
				firstInvalidTabName = tabName;
			}
		}
		if (firstInvalidTabName != null)  {
			if (!tabName.equals(firstInvalidTabName)) {
				parent.getHelper().getWebSheetLoader().loadWorkSheet(firstInvalidTabName);
			}
			return false;
		}
		return true;
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.dataobjects;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

/**
 * Cell Attributes Map. Contain several related maps for cell.
 *
 * @author Jason Jiang
 *
 */
public class CellAttributesMap implements Serializable {
	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = -1080211494689904695L;

	/**
	 * hold comments for the current sheet config form. key is $$ - for normal
	 * comment $ - empty method comment $xxx -- method comment while xxx is
	 * method name. (now only one method exist: init). i.e. comment could be
	 * $init{department.name} , so here init is the method name. value is the
	 * comments without tie commands.
	 */
	private Map<String, Map<String, String>> templateCommentMap;

	/**
	 * hold input type for input control widget. key is
	 * sheetName!$rowindex$columnindex value is the control type. e.g. dropdown
	 * radiobox calendar
	 */
	private Map<String, String> cellInputType;

	/**
	 * hold attributes for input control widget. key is
	 * sheetName!$rowindex$columnindex value is the list of attributes.
	 */
	private Map<String, List<CellFormAttributes>> cellInputAttributes;

	/**
	 * hold attributes for selectItems of dropdown list or radio box. key is
	 * sheetName!$rowindex$columnindex value is the map of attributes.
	 */
	private Map<String, Map<String, String>> cellSelectItemsAttributes;

	/**
	 * hold date pattern for calendar control widget. key is
	 * sheetName!$rowindex$columnindex value is the date pattern. date pattern
	 * is necessary for covert date to string
	 */

	private Map<String, String> cellDatePattern;

	/**
	 * hold attributes for validate process. key is
	 * sheetName!$rowindex$columnindex value is the list of attributes.
	 *
	 * which .value = rule .message = error message.
	 */
	private Map<String, List<CellFormAttributes>> cellValidateAttributes;

	/**
	 * constructor.
	 *
	 * @param pTemplateCommentMap
	 *            tempdate comment map.
	 * @param pCellInputType
	 *            cell input type.
	 * @param pCellInputAttributes
	 *            cell input attributes.
	 * @param pCellSelectItemsAttributes
	 *            cell select items attributes.
	 * @param pCellDatePattern
	 *            cell date pattern.
	 * @param pCellValidateAttributes
	 *            cell validate attributes.
	 */
	public CellAttributesMap(
			final Map<String, Map<String, String>> pTemplateCommentMap,
			final Map<String, String> pCellInputType,
			final Map<String, List<CellFormAttributes>> pCellInputAttributes,
			final Map<String, Map<String, String>> pCellSelectItemsAttributes,
			final Map<String, String> pCellDatePattern,
			final Map<String, List<CellFormAttributes>> pCellValidateAttributes) {
		this.templateCommentMap = pTemplateCommentMap;
		this.cellInputType = pCellInputType;
		this.cellInputAttributes = pCellInputAttributes;
		this.cellSelectItemsAttributes = pCellSelectItemsAttributes;
		this.cellDatePattern = pCellDatePattern;
		this.cellValidateAttributes = pCellValidateAttributes;
	}

	/**
	 * clear all the related maps.
	 */
	public final void clear() {
		if (this.templateCommentMap != null) {
			this.templateCommentMap.clear();
		}
		if (this.cellDatePattern != null) {
			this.cellDatePattern.clear();
		}
		if (this.cellInputAttributes != null) {
			this.cellInputAttributes.clear();
		}
		if (this.cellInputType != null) {
			this.cellInputType.clear();
		}
		if (this.cellSelectItemsAttributes != null) {
			this.cellSelectItemsAttributes.clear();
		}

	}

	/**
	 * return templatecommentmap.
	 *
	 * @return templatecommentmap.
	 */
	public final Map<String, Map<String, String>> getTemplateCommentMap() {
		return templateCommentMap;
	}

	/**
	 * return cellinputtype.
	 *
	 * @return cellinputtype.
	 */
	public final Map<String, String> getCellInputType() {
		return cellInputType;
	}

	/**
	 * return cellinputattributes.
	 *
	 * @return cellinputattributes.
	 */
	public final Map<String, List<CellFormAttributes>> getCellInputAttributes() {
		return cellInputAttributes;
	}

	/**
	 * return cellselectitemattributes.
	 *
	 * @return cellselectitemattributes.
	 */
	public final Map<String, Map<String, String>> getCellSelectItemsAttributes() {
		return cellSelectItemsAttributes;
	}

	/**
	 * return celldatepattern.
	 *
	 * @return celldatepattern.
	 */
	public final Map<String, String> getCellDatePattern() {
		return cellDatePattern;
	}

	/**
	 * Gets the cell validate attributes.
	 *
	 * @return the cellValidateAttributes
	 */
	public final Map<String, List<CellFormAttributes>> getCellValidateAttributes() {
		return cellValidateAttributes;
	}

	/**
	 * Sets the cell validate attributes.
	 *
	 * @param pcellValidateAttributes
	 *            the cellValidateAttributes to set
	 */
	public final void setCellValidateAttributes(
			final Map<String, List<CellFormAttributes>> pcellValidateAttributes) {
		this.cellValidateAttributes = pcellValidateAttributes;
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */

package org.tiefaces.components.websheet.service;

import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.primefaces.component.datatable.DataTable;
import org.primefaces.context.RequestContext;
import org.tiefaces.common.FacesUtility;
import org.tiefaces.common.TieConstants;
import org.tiefaces.components.websheet.TieWebSheetBean;
import org.tiefaces.components.websheet.TieWebSheetView.TabModel;
import org.tiefaces.components.websheet.configuration.ConfigBuildRef;
import org.tiefaces.components.websheet.configuration.ConfigurationHandler;
import org.tiefaces.components.websheet.configuration.RangeBuildRef;
import org.tiefaces.components.websheet.configuration.RowsMapping;
import org.tiefaces.components.websheet.configuration.SheetConfiguration;
import org.tiefaces.components.websheet.dataobjects.FacesCell;
import org.tiefaces.components.websheet.dataobjects.FacesRow;
import org.tiefaces.components.websheet.dataobjects.HeaderCell;
import org.tiefaces.components.websheet.dataobjects.TieCell;
import org.tiefaces.components.websheet.utility.CellStyleUtility;
import org.tiefaces.components.websheet.utility.CellUtility;
import org.tiefaces.components.websheet.utility.CommandUtility;
import org.tiefaces.components.websheet.utility.ConfigurationUtility;
import org.tiefaces.components.websheet.utility.SaveAttrsUtility;
import org.tiefaces.components.websheet.utility.WebSheetUtility;
import org.tiefaces.exception.AddRowException;
import org.tiefaces.exception.DeleteRowException;

/**
 * The Class WebSheetLoader.
 */
public class WebSheetLoader implements Serializable {

	/** The parent. */
	private TieWebSheetBean parent = null;

	/** logger. */
	private static final Logger LOG = Logger.getLogger(WebSheetLoader.class.getName());

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;

	/**
	 * Instantiates a new web sheet loader.
	 *
	 * @param pParent
	 *            the parent
	 */
	public WebSheetLoader(final TieWebSheetBean pParent) {
		this.parent = pParent;
		LOG.fine("TieWebSheetLoader Constructor");
	}

	/**
	 * Load header rows.
	 *
	 * @param sheetConfig
	 *            the sheet config
	 * @param cellRangeMap
	 *            the cell range map
	 * @param skippedRegionCells
	 *            the skipped region cells
	 */
	private void loadHeaderRows(final SheetConfiguration sheetConfig, final Map<String, CellRangeAddress> cellRangeMap,
			final List<String> skippedRegionCells) {

		int top = sheetConfig.getHeaderCellRange().getTopRow();
		int bottom = sheetConfig.getHeaderCellRange().getBottomRow();
		int left = sheetConfig.getHeaderCellRange().getLeftCol();
		int right = sheetConfig.getHeaderCellRange().getRightCol();

		String sheetName = sheetConfig.getSheetName();
		Sheet sheet1 = parent.getWb().getSheet(sheetName);

		int totalWidth = CellStyleUtility.calcTotalWidth(sheet1, left, right,
				WebSheetUtility.pixel2WidthUnits(parent.getLineNumberColumnWidth() + parent.getAddRowColumnWidth()));

		RangeBuildRef rangeBuildRef = new RangeBuildRef(left, right, totalWidth, sheet1);

		if (sheetConfig.isFixedWidthStyle()) {
			parent.setTableWidthStyle(
					"table-layout: fixed; width:" + WebSheetUtility.widthUnits2Pixel(totalWidth) + "px;");
		}

		parent.setLineNumberColumnWidthStyle(
				getWidthStyle(WebSheetUtility.pixel2WidthUnits(parent.getLineNumberColumnWidth()), totalWidth));
		parent.setAddRowColumnWidthStyle("width:" + parent.getAddRowColumnWidth() + "px;");

		parent.getHeaderRows().clear();

		if (top < 0) {
			// this is blank configuration. set column letter as header
			parent.getHeaderRows().add(loadHeaderRowWithoutConfigurationTab(rangeBuildRef, true));
			// set showlinenumber to true as default
			parent.setShowLineNumber(true);
		} else {
			parent.getHeaderRows().add(loadHeaderRowWithoutConfigurationTab(rangeBuildRef, false));
			for (int i = top; i <= bottom; i++) {
				parent.getHeaderRows().add(loadHeaderRowWithConfigurationTab(sheetConfig, rangeBuildRef, i,
						cellRangeMap, skippedRegionCells));

			}
			// set showlinenumber to false as default
			parent.setShowLineNumber(false);

		}

	}

	/**
	 * Load header row without configuration tab.
	 *
	 * @param rangeBuildRef
	 *            the range build ref
	 * @param rendered
	 *            the rendered
	 * @return the list
	 */
	private List<HeaderCell> loadHeaderRowWithoutConfigurationTab(final RangeBuildRef rangeBuildRef,
			final boolean rendered) {

		int firstCol = rangeBuildRef.getLeft();
		int lastCol = rangeBuildRef.getRight();
		double totalWidth = (double) rangeBuildRef.getTotalWidth();
		Sheet sheet1 = rangeBuildRef.getSheet();
		List<HeaderCell> headercells = new ArrayList<>();
		for (int i = firstCol; i <= lastCol; i++) {
			if (!sheet1.isColumnHidden(i)) {
				String style = getHeaderColumnStyle(parent.getWb(), null, sheet1.getColumnWidth(i), totalWidth);
				headercells.add(
						new HeaderCell("1", "1", style, style, WebSheetUtility.getExcelColumnName(i), rendered, true));
			}
		}
		fillToMaxColumns(headercells);
		return headercells;

	}

	/**
	 * Fill to max columns.
	 *
	 * @param headercells
	 *            the headercells
	 */
	private void fillToMaxColumns(final List<HeaderCell> headercells) {
		if (headercells.size() < parent.getMaxColCounts()) {
			int fills = parent.getMaxColCounts() - headercells.size();
			for (int s = 0; s < fills; s++) {
				headercells.add(new HeaderCell("1", "1", "", "", "", false, false));
			}
		}
	}

	/**
	 * Gets the header column style.
	 *
	 * @param wb
	 *            the wb
	 * @param cell
	 *            the cell
	 * @param colWidth
	 *            the col width
	 * @param totalWidth
	 *            the total width
	 * @return the header column style
	 */
	private String getHeaderColumnStyle(final Workbook wb, final Cell cell, final double colWidth,
			final double totalWidth) {

		String columnstyle = "";
		if (cell != null) {
			columnstyle += CellStyleUtility.getCellStyle(wb, cell, "") + CellStyleUtility.getCellFontStyle(wb, cell); // +
		}

		columnstyle = columnstyle + getWidthStyle(colWidth, totalWidth);
		return columnstyle;
	}

	/**
	 * Gets the width style.
	 *
	 * @param colWidth
	 *            the col width
	 * @param totalWidth
	 *            the total width
	 * @return the width style
	 */
	private String getWidthStyle(final double colWidth, final double totalWidth) {
		double percentage = FacesUtility.round(TieConstants.CELL_FORMAT_PERCENTAGE_VALUE * colWidth / totalWidth, 2);
		return "width:" + percentage + TieConstants.CELL_FORMAT_PERCENTAGE_SYMBOL + ";";
	}

	/**
	 * Load header row with configuration tab.
	 *
	 * @param sheetConfig
	 *            the sheet config
	 * @param rangeBuildRef
	 *            the range build ref
	 * @param currentRow
	 *            the current row
	 * @param cellRangeMap
	 *            the cell range map
	 * @param skippedRegionCells
	 *            the skipped region cells
	 * @return the list
	 */
	private List<HeaderCell> loadHeaderRowWithConfigurationTab(final SheetConfiguration sheetConfig,
			final RangeBuildRef rangeBuildRef, final int currentRow, final Map<String, CellRangeAddress> cellRangeMap,
			final List<String> skippedRegionCells) {

		Sheet sheet1 = rangeBuildRef.getSheet();
		int left = rangeBuildRef.getLeft();
		int right = rangeBuildRef.getRight();
		double totalWidth = (double) rangeBuildRef.getTotalWidth();
		Row row = sheet1.getRow(currentRow);
		List<HeaderCell> headercells = new ArrayList<>();
		for (int cindex = left; cindex <= right; cindex++) {
			String cellindex = CellUtility.getCellIndexNumberKey(cindex, currentRow);

			if (!skippedRegionCells.contains(cellindex) && !sheet1.isColumnHidden(cindex)) {
				Cell cell = null;
				if (row != null) {
					cell = row.getCell(cindex, MissingCellPolicy.CREATE_NULL_AS_BLANK);
				}
				int originRowIndex = ConfigurationUtility.getOriginalRowNumInHiddenColumn(row);
				if (cell != null) {
					FacesCell fcell = new FacesCell();

					CellUtility.convertCell(sheetConfig, fcell, cell, cellRangeMap, originRowIndex,
							parent.getCellAttributesMap(), null);
					parent.getPicHelper().setupFacesCellPictureCharts(sheet1, fcell, cell,
							WebSheetUtility.getFullCellRefName(sheet1, cell));
					CellStyleUtility.setupCellStyle(parent.getWb(), fcell, cell, row.getHeightInPoints());
					fcell.setColumnStyle(fcell.getColumnStyle()
							+ getColumnWidthStyle(sheet1, cellRangeMap, cellindex, cindex, totalWidth));
					fcell.setColumnIndex(cindex);

					headercells.add(
							new HeaderCell(Integer.toString(fcell.getRowspan()), Integer.toString(fcell.getColspan()),
									fcell.getStyle(), fcell.getColumnStyle(), CellUtility.getCellValueWithFormat(cell,
											parent.getFormulaEvaluator(), parent.getDataFormatter()),
									true, true));
				}
			}

		}
		fillToMaxColumns(headercells);
		return headercells;
	}

	/**
	 * Gets the column width style.
	 *
	 * @param sheet1
	 *            the sheet 1
	 * @param cellRangeMap
	 *            the cell range map
	 * @param cellindex
	 *            the cellindex
	 * @param cindex
	 *            the cindex
	 * @param totalWidth
	 *            the total width
	 * @return the column width style
	 */
	private String getColumnWidthStyle(final Sheet sheet1, final Map<String, CellRangeAddress> cellRangeMap,
			final String cellindex, final int cindex, final double totalWidth) {

		CellRangeAddress caddress = cellRangeMap.get(cellindex);
		double colWidth;
		// check whether the cell has rowspan or colspan
		if (caddress != null) {
			colWidth = CellStyleUtility.calcTotalWidth(sheet1, caddress.getFirstColumn(), caddress.getLastColumn(), 0);
		} else {
			colWidth = sheet1.getColumnWidth(cindex);
		}

		return getWidthStyle(colWidth, totalWidth);

	}

	// return 0 -- No template
	// return -1 -- error in open form
	// return 1 -- success

	/**
	 * Clear workbook.
	 */
	private void clearWorkbook() {
		parent.setFormulaEvaluator(null);
		parent.setDataFormatter(null);
		parent.setSheetConfigMap(null);
		parent.setTabs(null);
		parent.getSerialDataContext().setDataContext(null);
		parent.setPicturesMap(null);
		parent.setHeaderRows(null);
		parent.setBodyRows(null);
		parent.setWb(null);
		parent.getHeaderRows().clear();
		parent.getBodyRows().clear();
		parent.getCharsData().getChartsMap().clear();
		parent.getCharsData().getChartDataMap().clear();
		parent.getCharsData().getChartAnchorsMap().clear();
		parent.getCharsData().getChartPositionMap().clear();
		parent.getCellAttributesMap().clear();
	}

	/**
	 * Load workbook.
	 *
	 * @param fis
	 *            the fis
	 * @param dataContext
	 *            the data context
	 * @return the int
	 */
	public final int loadWorkbook(final InputStream fis, final Map<String, Object> dataContext) {

		try {
			Workbook wb = WorkbookFactory.create(fis);
			int ireturn = loadWorkbook(wb, dataContext);
			fis.close();
			return ireturn;
		} catch (Exception e) {
			LOG.log(Level.SEVERE, "Web Form loadWorkbook Error Exception = " + e.getLocalizedMessage(), e);
			return -1;
		}

	}

	/**
	 * Load workbook.
	 *
	 * @param wb
	 *            the wb
	 * @param dataContext
	 *            the data context
	 * @return the int
	 */
	public final int loadWorkbook(final Workbook wb, final Map<String, Object> dataContext) {

		try {
			clearWorkbook();
			// only support xssf workbook now since 2016 July
			if (!(wb instanceof XSSFWorkbook)) {
				LOG.fine("Error: WebSheet only support xlsx template.");
				return -1;
			}
			LOG.fine("Begin load work book...");
			parent.setWb(wb);
			parent.getSerialDataContext().setDataContext(dataContext);
			parent.setSheetConfigMap(new ConfigurationHandler(parent).buildConfiguration());
			parent.reCalcMaxColCounts();
			parent.getChartHelper().loadChartsMap();
			parent.getPicHelper().loadPicturesMap();
			initSheet();
			initTabs();
			if (!parent.getTabs().isEmpty()) {
				loadWorkSheet(parent.getTabs().get(0).getTitle());
			}

		} catch (Exception e) {
			LOG.log(Level.FINE, "Web Form loadWorkbook Error Exception = " + e.getLocalizedMessage(), e);
			return -1;
		}
		return 1;

	}

	/**
	 * Inits the tabs.
	 */
	private void initTabs() {
		parent.setTabs(new ArrayList<TabModel>());
		if (parent.getSheetConfigMap() != null) {
			for (String key : parent.getSheetConfigMap().keySet()) {
				parent.getTabs().add(new TabModel("form_" + key, key, "form"));
			}
		}
	}

	/**
	 * Inits the sheet.
	 */
	private void initSheet() {
		loadData();
	}

	/**
	 * load data process. unfinished.
	 */
	private void loadData() {

		if (parent.getSerialDataContext().getDataContext() == null) {
			// no data objects available.
			return;
		}

		if (parent.isAdvancedContext()) {
			parent.getSerialDataContext().getDataContext().put("tiecells", new HashMap<String, TieCell>());
		}

		for (SheetConfiguration sheetConfig : parent.getSheetConfigMap().values()) {
			List<RowsMapping> currentRowsMappingList = null;
			ConfigBuildRef configBuildRef = new ConfigBuildRef(parent.getWbWrapper(),
					parent.getWb().getSheet(sheetConfig.getSheetName()), parent.getExpEngine(), parent.getCellHelper(),
					sheetConfig.getCachedCells(), parent.getCellAttributesMap(), sheetConfig.getFinalCommentMap());
			int length = sheetConfig.getFormCommand().buildAt(null, configBuildRef,
					sheetConfig.getFormCommand().getTopRow(), parent.getSerialDataContext().getDataContext(),
					currentRowsMappingList);
			sheetConfig.setShiftMap(configBuildRef.getShiftMap());
			sheetConfig.setCollectionObjNameMap(configBuildRef.getCollectionObjNameMap());
			sheetConfig.setCommandIndexMap(configBuildRef.getCommandIndexMap());
			sheetConfig.setWatchList(configBuildRef.getWatchList());
			sheetConfig.setBodyAllowAddRows(configBuildRef.isBodyAllowAdd());
			sheetConfig.getBodyCellRange().setBottomRow(sheetConfig.getFormCommand().getTopRow() + length - 1);
			sheetConfig.setBodyPopulated(true);
		}
		parent.getCellHelper().reCalc();

	}

	/**
	 * Refresh data.
	 */
	public void refreshData() {

		if (parent.getSerialDataContext().getDataContext() == null) {
			// no data objects available.
			return;
		}

		for (SheetConfiguration sheetConfig : parent.getSheetConfigMap().values()) {
			for (int irow = sheetConfig.getFormCommand().getTopRow(); irow < sheetConfig.getFormCommand()
					.getLastRow(); irow++) {

				refreshDataForRow(parent.getWb().getSheet(sheetConfig.getSheetName()).getRow(irow));
			}

		}
		parent.getCellHelper().reCalc();

	}

	/**
	 * Refresh data for row.
	 *
	 * @param row the row
	 */
	private void refreshDataForRow(Row row) {
		if (row == null) {
			return;
		}
		String saveAttrList = SaveAttrsUtility.getSaveAttrListFromRow(row);
		if (saveAttrList!= null) {
			String[] saveAttrs = saveAttrList.split(",");
			for (String fullSaveAttr : saveAttrs) {
				refreshDataForCell(row, fullSaveAttr);
			}
		}
	}

	/**
	 * refresh data for single cell.
	 *
	 * @param row row.
	 * @param fullSaveAttr fullSaveAttr.
	 */

	private void refreshDataForCell(Row row, String fullSaveAttr) {
		if (fullSaveAttr != null) {
			try {
				String fullName = ConfigurationUtility.getFullNameFromRow(row);
				if (fullName != null) {
					parent.getCellHelper().restoreDataContext(fullName);
					SaveAttrsUtility.refreshSheetRowFromContext(parent.getSerialDataContext().getDataContext(),
							fullSaveAttr, row, parent.getExpEngine());
				}
			}catch (Exception ex) {
				LOG.log(Level.SEVERE, "refreshDataForCell with fullAaveAttr ="+fullSaveAttr+" error = " + ex.getMessage(), ex);
			}

		}
	}

	/**
	 * Find tab index with name.
	 *
	 * @param tabname
	 *            the tabname
	 * @return the int
	 */
	public final int findTabIndexWithName(final String tabname) {

		for (int i = 0; i < parent.getTabs().size(); i++) {
			if (parent.getTabs().get(i).getTitle().equalsIgnoreCase(tabname)) {
				return i;
			}
		}
		return -1;

	}

	/**
	 * Load work sheet.
	 *
	 * @param tabName
	 *            the tab name
	 */
	public final void loadWorkSheet(final String tabName) {

		prepareWorkShee(tabName);
		parent.getValidationHandler().validateCurrentPage();
		createDynamicColumns(tabName);
		// reset datatable current page to 1
		setDataTablePage(0);
		parent.getCurrent().setCurrentDataContextName(null);
		saveObjs();
		if ((RequestContext.getCurrentInstance() != null) && (parent.getClientId() != null)) {
			RequestContext.getCurrentInstance().update(parent.getClientId() + ":websheettab");
		}
	}

	/**
	 * prepare worksheet for loading.
	 * this only load at backend without refresh gui.
	 *
	 * @param tabName the tab name
	 */
	public final void prepareWorkShee(final String tabName) {

		int tabIndex = findTabIndexWithName(tabName);
		if (parent.getWebFormTabView() != null) {
			parent.getWebFormTabView().setActiveIndex(tabIndex);
		}
		parent.getCurrent().setCurrentTabName(tabName);
		String sheetName = parent.getSheetConfigMap().get(tabName).getSheetName();
		Sheet sheet1 = parent.getWb().getSheet(sheetName);
		parent.getWb().setActiveSheet(parent.getWb().getSheetIndex(sheet1));

		SheetConfiguration sheetConfig = parent.getSheetConfigMap().get(tabName);

		parent.setMaxRowsPerPage(parent.getSheetConfigMap().get(tabName).getMaxRowPerPage());
		parent.setBodyAllowAddRows(parent.getSheetConfigMap().get(tabName).isBodyAllowAddRows());

		// populate repeat rows before setup cell range map

		Map<String, CellRangeAddress> cellRangeMap = ConfigurationUtility.indexMergedRegion(sheet1);
		List<String> skippedRegionCells = ConfigurationUtility.skippedRegionCells(sheet1);
		loadHeaderRows(sheetConfig, cellRangeMap, skippedRegionCells);
		loadBodyRows(sheetConfig, cellRangeMap, skippedRegionCells);
	}

	/**
	 * Sets the data table page.
	 *
	 * @param first
	 *            the new data table page
	 */
	private void setDataTablePage(final int first) {
		if (parent.getWebFormClientId() != null) {
			final DataTable d = (DataTable) FacesContext.getCurrentInstance().getViewRoot()
					.findComponent(parent.getWebFormClientId());
			if (d != null) {
				d.setFirst(first);
			}
		}
	}

	/**
	 * Save objs.
	 */
	private void saveObjs() {

		try {
			if (FacesContext.getCurrentInstance() != null) {
				Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
				viewMap.put("currentTabName", parent.getCurrent().getCurrentTabName());
				viewMap.put(TieConstants.SUBMITMODE, parent.getSubmitMode());
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, "saveobjs in viewMap error = " + ex.getMessage(), ex);

		}

	}

	/**
	 * Setup row info.
	 *
	 * @param facesRow
	 *            the faces row
	 * @param sheet1
	 *            the sheet 1
	 * @param row
	 *            the row
	 * @param rowIndex
	 *            the row index
	 * @param allowAdd
	 *            the allow add
	 */
	private void setupRowInfo(final FacesRow facesRow, final Sheet sheet1, final Row row, final int rowIndex,
			final boolean allowAdd) {

		facesRow.setAllowAdd(allowAdd);
		if (row != null) {
			facesRow.setRendered(!row.getZeroHeight());
			facesRow.setRowheight(row.getHeight());
			int rowNum = ConfigurationUtility.getOriginalRowNumInHiddenColumn(row);
			facesRow.setOriginRowIndex(rowNum);
		} else {
			facesRow.setRendered(true);
			facesRow.setRowheight(sheet1.getDefaultRowHeight());
			facesRow.setOriginRowIndex(rowIndex);
		}

	}

	/**
	 * Load body rows.
	 *
	 * @param sheetConfig
	 *            the sheet config
	 * @param cellRangeMap
	 *            the cell range map
	 * @param skippedRegionCells
	 *            the skipped region cells
	 */
	private void loadBodyRows(final SheetConfiguration sheetConfig, final Map<String, CellRangeAddress> cellRangeMap,
			final List<String> skippedRegionCells) {

		int top = sheetConfig.getBodyCellRange().getTopRow();
		int bottom = CellUtility.getBodyBottomFromConfig(sheetConfig);
		int left = sheetConfig.getBodyCellRange().getLeftCol();
		int right = sheetConfig.getBodyCellRange().getRightCol();

		String sheetName = sheetConfig.getSheetName();
		Sheet sheet1 = parent.getWb().getSheet(sheetName);

		parent.getBodyRows().clear();
		clearCache();

		for (int i = top; i <= bottom; i++) {

			parent.getBodyRows()
					.add(assembleFacesBodyRow(i, sheet1, left, right, sheetConfig, cellRangeMap, skippedRegionCells));

		}
		sheetConfig.setBodyPopulated(true);
		parent.getCurrent().setCurrentTopRow(top);
		parent.getCurrent().setCurrentLeftColumn(left);
	}

	/**
	 * Assemble faces body row.
	 *
	 * @param rowIndex
	 *            the row index
	 * @param sheet1
	 *            the sheet 1
	 * @param left
	 *            the left
	 * @param right
	 *            the right
	 * @param sheetConfig
	 *            the sheet config
	 * @param cellRangeMap
	 *            the cell range map
	 * @param skippedRegionCells
	 *            the skipped region cells
	 * @return the faces row
	 */
	private FacesRow assembleFacesBodyRow(final int rowIndex, final Sheet sheet1, final int left, final int right,
			final SheetConfiguration sheetConfig, final Map<String, CellRangeAddress> cellRangeMap,
			final List<String> skippedRegionCells) {

		FacesRow facesRow = new FacesRow(rowIndex);
		Row row = sheet1.getRow(rowIndex);
		setupRowInfo(facesRow, sheet1, row, rowIndex, CommandUtility.isRowAllowAdd(row, sheetConfig));
		String saveAttrList = SaveAttrsUtility.getSaveAttrListFromRow(row);
		List<FacesCell> bodycells = new ArrayList<>();
		for (int cindex = left; cindex <= right; cindex++) {
			String cellindex = CellUtility.getCellIndexNumberKey(cindex, rowIndex);
			if (!skippedRegionCells.contains(cellindex) && !sheet1.isColumnHidden(cindex)) {
				Cell cell = null;
				if (row != null) {
					cell = row.getCell(cindex, MissingCellPolicy.CREATE_NULL_AS_BLANK);
				}
				if (cell != null) {
					FacesCell fcell = new FacesCell();

					CellUtility.convertCell(sheetConfig, fcell, cell, cellRangeMap, facesRow.getOriginRowIndex(),
							parent.getCellAttributesMap(), saveAttrList);
					parent.getPicHelper().setupFacesCellPictureCharts(sheet1, fcell, cell,
							WebSheetUtility.getFullCellRefName(sheet1, cell));
					CellStyleUtility.setupCellStyle(parent.getWb(), fcell, cell, row.getHeightInPoints());
					fcell.setColumnIndex(cindex);
					bodycells.add(fcell);
					addCache(cell);
				} else {
					bodycells.add(null);
				}
			} else {
				bodycells.add(null);
			}
		}
		facesRow.setCells(bodycells);
		return facesRow;
	}

	/**
	 * Adds the cache.
	 *
	 * @param cell
	 *            the cell
	 */
	private void addCache(final Cell cell) {
		parent.getCachedCells().put(cell, CellType.FORMULA);
	}

	/**
	 * Clear cache.
	 */
	private void clearCache() {
		parent.getCachedCells().clear();

	}

	/**
	 * Refresh cached cell.
	 *
	 * @param tblName
	 *            the tbl name
	 * @param i
	 *            the i
	 * @param index
	 *            the index
	 * @param cell
	 *            the cell
	 * @param fcell
	 *            the fcell
	 */
	public final void refreshCachedCell(final String tblName, final int i, final int index, final Cell cell,
			final FacesCell fcell) {

		if ((cell != null) && (cell.getCellTypeEnum() == CellType.FORMULA) && (tblName != null)) {
			try {
				processRefreshCell(tblName, i, index, cell, fcell);
			} catch (Exception ex) {
				LOG.log(Level.SEVERE, "refresh Cached Cell error : " + ex.getLocalizedMessage(), ex);
			}
		}
	}

	/**
	 * Process refresh cell.
	 *
	 * @param tblName
	 *            the tbl name
	 * @param i
	 *            the i
	 * @param index
	 *            the index
	 * @param cell
	 *            the cell
	 * @param fcell
	 *            the fcell
	 */
	private void processRefreshCell(final String tblName, final int i, final int index, final Cell cell,
			final FacesCell fcell) {
		String newValue = CellUtility.getCellValueWithFormat(cell, parent.getFormulaEvaluator(),
				parent.getDataFormatter());
		if (parent.getCachedCells().isValueChanged(cell, newValue)) {
			if (fcell.isHasSaveAttr()) {
				parent.getCellHelper().saveDataInContext(cell, newValue);
			}
			RequestContext.getCurrentInstance().update(tblName + ":" + i + ":cocalc" + index);
			parent.getCachedCells().put(cell, CellType.FORMULA);
		}
	}

	/**
	 * Creates the dynamic columns.
	 *
	 * @param tabName
	 *            the tab name
	 */
	private void createDynamicColumns(final String tabName) {

		SheetConfiguration sheetConfig = parent.getSheetConfigMap().get(tabName);

		int left = sheetConfig.getBodyCellRange().getLeftCol();
		int right = sheetConfig.getBodyCellRange().getRightCol();

		parent.getColumns().clear();

		for (int i = left; i <= right; i++) {
			parent.getColumns().add("column" + (i - left));
		}

	}

	/**
	 * Adds the repeat row.
	 *
	 * @param rowIndex
	 *            the row index
	 */
	public final void addRepeatRow(final int rowIndex) {

		try {
			SheetConfiguration sheetConfig = parent.getSheetConfigMap().get(parent.getCurrent().getCurrentTabName());
			Sheet sheet = parent.getWb().getSheet(sheetConfig.getSheetName());
			ConfigBuildRef configBuildRef = new ConfigBuildRef(parent.getWbWrapper(), sheet, parent.getExpEngine(),
					parent.getCellHelper(), sheetConfig.getCachedCells(), parent.getCellAttributesMap(),
					sheetConfig.getFinalCommentMap());
			// set add mode
			configBuildRef.setAddMode(true);
			configBuildRef.setCollectionObjNameMap(sheetConfig.getCollectionObjNameMap());
			configBuildRef.setCommandIndexMap(sheetConfig.getCommandIndexMap());
			configBuildRef.setShiftMap(sheetConfig.getShiftMap());
			configBuildRef.setWatchList(sheetConfig.getWatchList());
			int length = CommandUtility.addRow(configBuildRef, rowIndex,
					parent.getSerialDataContext().getDataContext());
			refreshBodyRowsInRange(configBuildRef.getInsertPosition(), length, sheet, sheetConfig);
			parent.getCellHelper().reCalc();
		} catch (AddRowException e) {
			FacesContext.getCurrentInstance().addMessage(null,
					new FacesMessage(FacesMessage.SEVERITY_ERROR, "Add Row Error", e.getMessage()));
			LOG.log(Level.SEVERE, "Add row error = " + e.getLocalizedMessage(), e);

		} catch (Exception ex) {
			LOG.log(Level.SEVERE, "Add row error = " + ex.getLocalizedMessage(), ex);

		}

	}

	/**
	 * Refresh body rows in range.
	 *
	 * @param insertPosition
	 *            the insert position
	 * @param length
	 *            the length
	 * @param sheet
	 *            the sheet
	 * @param sheetConfig
	 *            the sheet config
	 */
	private void refreshBodyRowsInRange(final int insertPosition, final int length, final Sheet sheet,
			final SheetConfiguration sheetConfig) {
		Map<String, CellRangeAddress> cellRangeMap = ConfigurationUtility.indexMergedRegion(sheet);
		List<String> skippedRegionCells = ConfigurationUtility.skippedRegionCells(sheet);
		int top = sheetConfig.getBodyCellRange().getTopRow();
		int left = sheetConfig.getBodyCellRange().getLeftCol();
		int right = sheetConfig.getBodyCellRange().getRightCol();
		for (int irow = insertPosition; irow < (insertPosition + length); irow++) {
			parent.getBodyRows().add(irow - top,
					assembleFacesBodyRow(irow, sheet, left, right, sheetConfig, cellRangeMap, skippedRegionCells));
		}
		for (int irow = insertPosition + length - top; irow < parent.getBodyRows().size(); irow++) {
			FacesRow facesrow = parent.getBodyRows().get(irow);
			facesrow.setRowIndex(facesrow.getRowIndex() + length);
		}
	}

	/**
	 * Delete repeat row.
	 *
	 * @param rowIndex
	 *            the row index
	 */
	public final void deleteRepeatRow(final int rowIndex) {
		try {
			SheetConfiguration sheetConfig = parent.getSheetConfigMap().get(parent.getCurrent().getCurrentTabName());
			Sheet sheet = parent.getWb().getSheet(sheetConfig.getSheetName());
			ConfigBuildRef configBuildRef = new ConfigBuildRef(parent.getWbWrapper(), sheet, parent.getExpEngine(),
					parent.getCellHelper(), sheetConfig.getCachedCells(), parent.getCellAttributesMap(),
					sheetConfig.getFinalCommentMap());
			// set delete mode
			configBuildRef.setCollectionObjNameMap(sheetConfig.getCollectionObjNameMap());
			configBuildRef.setCommandIndexMap(sheetConfig.getCommandIndexMap());
			configBuildRef.setShiftMap(sheetConfig.getShiftMap());
			configBuildRef.setWatchList(sheetConfig.getWatchList());
			CommandUtility.deleteRow(configBuildRef, rowIndex, parent.getSerialDataContext().getDataContext(),
					sheetConfig, parent.getBodyRows());
			parent.getCellHelper().reCalc();
		} catch (DeleteRowException e) {
			FacesContext.getCurrentInstance().addMessage(null,
					new FacesMessage(FacesMessage.SEVERITY_ERROR, "Delete row error", e.getMessage()));
			LOG.log(Level.SEVERE, "Delete row error = " + e.getLocalizedMessage(), e);

		} catch (Exception ex) {
			LOG.log(Level.SEVERE, "Delete row error = " + ex.getLocalizedMessage(), ex);

		}

	}

	/**
	 * Sets the unsaved status.
	 *
	 * @param requestContext
	 *            the request context
	 * @param statusFlag
	 *            the status flag
	 */
	public void setUnsavedStatus(final RequestContext requestContext, final Boolean statusFlag) {

		// in client js should have setUnsavedState method
		if (requestContext != null) {
			LOG.log(Level.FINE, "run setUnsavedState( {} )", statusFlag.toString());
			requestContext.execute("setUnsavedState(" + statusFlag.toString() + ")");
		}

	}

	/**
	 * Checks if is unsaved status.
	 *
	 * @return the boolean
	 */
	public final Boolean isUnsavedStatus() {
		Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
		Boolean flag = (Boolean) viewMap.get(TieConstants.UNSAVEDSTATE);
		if (flag == null) {
			return false;
		}
		return flag;
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.configuration;

import java.io.Serializable;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.tiefaces.components.websheet.serializable.SerialCell;
import org.tiefaces.components.websheet.serializable.SerialCellAddress;

/**
 * The Class ConfigRangeAttrs.
 */
public class ConfigRangeAttrs implements Serializable {
	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = 1L;

	/** first cell. */
	private SerialCell serialFirstRowRef;
	/** last cell. */
	private SerialCell serialLastRowPlusRef;
	/** first cell address. This used to calculate relative address. */
	private SerialCellAddress firstRowAddr;
	/** last cell address. This used to calculate relative address. */
	private SerialCellAddress lastRowPlusAddr;
	/** if true then the lastCell is created instead of exist cell. */
	private boolean lastCellCreated;

	/** The final length. */
	private int finalLength = 0;

	/** The allow add. */
	private boolean allowAdd = false;

	/** The unit rows mapping. */
	private RowsMapping unitRowsMapping = null;

	/**
	 * Instantiates a new config range attrs.
	 *
	 * @param plastCellCreated
	 *            the last cell created
	 */
	public ConfigRangeAttrs(final boolean plastCellCreated) {
		this.lastCellCreated = plastCellCreated;
	}

	/**
	 * Gets the first row ref.
	 *
	 * @return the first row ref
	 */
	public final Cell getFirstRowRef() {
		return serialFirstRowRef.getCell();
	}

	/**
	 * Gets the serial first row ref.
	 *
	 * @return the serialFirstRowRef
	 */
	public final SerialCell getSerialFirstRowRef() {
		if (this.serialFirstRowRef == null) {
			this.serialFirstRowRef = new SerialCell();
		}
		return serialFirstRowRef;
	}

	/**
	 * Sets the first row ref.
	 *
	 * @param pfirstRowRef
	 *            the new first row ref
	 */
	public final void setFirstRowRef(final Cell pfirstRowRef) {
		this.getSerialFirstRowRef().setCell(pfirstRowRef);
	}

	/**
	 * Gets the last row plus ref.
	 *
	 * @return the last row plus ref
	 */
	public final Cell getLastRowPlusRef() {
		return serialLastRowPlusRef.getCell();
	}

	/**
	 * Gets the serial last row plus ref.
	 *
	 * @return the serialLastRowPlusRef
	 */
	public final SerialCell getSerialLastRowPlusRef() {
		if (this.serialLastRowPlusRef == null) {
			this.serialLastRowPlusRef = new SerialCell();
		}
		return serialLastRowPlusRef;
	}

	/**
	 * Sets the last row plus ref.
	 *
	 * @param plastRowPlusRef
	 *            the new last row plus ref
	 */
	public final void setLastRowPlusRef(final Cell plastRowPlusRef) {
		this.getSerialLastRowPlusRef().setCell(plastRowPlusRef);
	}

	/**
	 * Gets the first row addr.
	 *
	 * @return the first row addr
	 */
	public final SerialCellAddress getFirstRowAddr() {
		return firstRowAddr;
	}

	/**
	 * Sets the first row addr.
	 *
	 * @param pfirstRowAddr
	 *            the new first row addr
	 */
	public final void setFirstRowAddr(
			final SerialCellAddress pfirstRowAddr) {
		this.firstRowAddr = pfirstRowAddr;
	}

	/**
	 * Gets the last row plus addr.
	 *
	 * @return the last row plus addr
	 */
	public final SerialCellAddress getLastRowPlusAddr() {
		return lastRowPlusAddr;
	}

	/**
	 * Sets the last row plus addr.
	 *
	 * @param plastRowPlusAddr
	 *            the new last row plus addr
	 */
	public final void setLastRowPlusAddr(
			final SerialCellAddress plastRowPlusAddr) {
		this.lastRowPlusAddr = plastRowPlusAddr;
	}

	/**
	 * Checks if is last cell created.
	 *
	 * @return true, if is last cell created
	 */
	public final boolean isLastCellCreated() {
		return lastCellCreated;
	}

	/**
	 * Sets the last cell created.
	 *
	 * @param plastCellCreated
	 *            the new last cell created
	 */
	public final void setLastCellCreated(final boolean plastCellCreated) {
		this.lastCellCreated = plastCellCreated;
	}

	/**
	 * Gets the final length.
	 *
	 * @return the final length
	 */
	public final int getFinalLength() {
		return finalLength;
	}

	/**
	 * Sets the final length.
	 *
	 * @param pfinalLength
	 *            the new final length
	 */
	public final void setFinalLength(final int pfinalLength) {
		this.finalLength = pfinalLength;
	}

	/**
	 * Checks if is allow add.
	 *
	 * @return true, if is allow add
	 */
	public final boolean isAllowAdd() {
		return allowAdd;
	}

	/**
	 * Sets the allow add.
	 *
	 * @param pallowAdd
	 *            the new allow add
	 */
	public final void setAllowAdd(final boolean pallowAdd) {
		this.allowAdd = pallowAdd;
	}

	/**
	 * Gets the unit rows mapping.
	 *
	 * @return the unit rows mapping
	 */
	public final RowsMapping getUnitRowsMapping() {
		return unitRowsMapping;
	}

	/**
	 * Sets the unit rows mapping.
	 *
	 * @param punitRowsMapping
	 *            the new unit rows mapping
	 */
	public final void setUnitRowsMapping(
			final RowsMapping punitRowsMapping) {
		this.unitRowsMapping = punitRowsMapping;
	}

	/**
	 * recover by using it's address.
	 *
	 * @param sheet
	 *            sheet.
	 */
	public final void recover(final Sheet sheet) {

		this.getSerialFirstRowRef().recover(sheet);
		this.getSerialLastRowPlusRef().recover(sheet);
		if (this.getUnitRowsMapping() != null) {
			this.getUnitRowsMapping().recover(sheet);
		}
	}

	/**
	 * Gets the first row index.
	 *
	 * @return the first row index
	 */
	public final int getFirstRowIndex() {

		Cell cell = this.getFirstRowRef();
		if (cell != null) {
			return cell.getRowIndex();
		}
		return -1;
	}

	/**
	 * Gets the last row index.
	 *
	 * @return the last row index
	 */
	public final int getLastRowIndex() {

		Cell cell = this.getLastRowPlusRef();
		if (cell != null) {
			return cell.getRowIndex() - 1;
		}
		return -1;
	}

}


/*
 * (C) Copyright 2016 Fluenda.
 *
 * 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.fluenda.parcefone.parser;

import com.fluenda.parcefone.event.CEFHandlingException;
import com.fluenda.parcefone.event.CefRev23;
import com.fluenda.parcefone.event.CommonEvent;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A Common Event Format (CEF) parser used to convert String or byte array into a Map containing the parsed and
 * validated CEF fields
 * The parser does not make any assertion in regards to thread safety. Proceed with care.
 */
public class CEFParser {
    final static Logger logger = LoggerFactory.getLogger(CEFParser.class);

    final static Pattern extensionPattern = Pattern.compile("(?<!\\\\)" + Pattern.quote("="));

    Validator validator;

    /**
    *  Creates a CEFParser instance utilizing the default Bean Validator.
     */
    public CEFParser() {

    }

    /**
     *  Creates a CEFParser instance utilizing thread-safe Beans Validator. The use of this constructor should result in significantly higher
     *  throughput when performing multiple instatiations of CEFParser.
     * @param validator A JSR-303 complianceValidator such as Hibernate or Apache bVal
     */
    public CEFParser(Validator validator) {
        this.validator = validator;
    }

    /**
     * @return CommonEvent
     * @param cefByteArray byte [] containing the CEF message to be parsed - Array will be converted String using UTF-8
     */
    public CommonEvent parse(byte [] cefByteArray)  {
        String cefString;
        cefString = new String(cefByteArray, Charset.forName("UTF-8"));
        return this.parse(cefString, false);
    }

    /**
     * @return CommonEvent
     * @param cefByteArray byte [] containing the CEF message to be parsed - Array will be converted String using UTF-8
     * @param validate Boolean if parser should validate values beyond type compatibility (e.g. Values within acceptable lengths, value lists, etc)
     */
    public CommonEvent parse(byte [] cefByteArray, boolean validate)  {
        String cefString;
        cefString = new String(cefByteArray, Charset.forName("UTF-8"));
        return this.parse(cefString, validate);
    }

    /**
     * @return CommonEvent
     * @param cefByteArray byte [] containing the CEF message to be parsed - Array will be converted String using UTF-8
     * @param validate Boolean if parser should validate values beyond type compatibility (e.g. Values within acceptable lengths, value lists, etc)
     * @param locale The locale to be used when parsing dates (so that parser can handle both jul (en_US) and juil.(fr_FR)
     */
    public CommonEvent parse(byte [] cefByteArray, boolean validate, Locale locale)  {
        String cefString;
        cefString = new String(cefByteArray, Charset.forName("UTF-8"));
        return this.parse(cefString, validate, locale);
    }

    /**
     *

     * Converts a CEF formatted String into a {@link CommonEvent} object without enforcing strict validation.
     *

     * The use of this method is discouraged and future versions may deprecate its use.
     * @param cefString String containing the CEF message to be parsed
     * @return CommonEvent
     */
    public CommonEvent parse(String cefString)  {
        return this.parse(cefString, false);
    }

    /**
     *

     * Converts a CEF formatted String into a {@link CommonEvent} object with exposed control over strict validation.
     *

     * All CEF extension fields containing Dates are processed with the {@link Locale Locale.ENGLISH}.
     * @param cefString String containing the CEF message to be parsed
     * @param validate Boolean if parser should validate values beyond type compatibility (e.g. Values within acceptable lengths, value lists, etc)
     * @return CommonEvent
     */
    public CommonEvent parse(String cefString, final boolean validate)  {
        return this.parse(cefString, validate, Locale.ENGLISH);
    }

    /**
     * Converts a CEF formatted String into a {@link CommonEvent} object with exposed control over validation and the
     * {@link Locale} used to parse fields containing {@link Date Dates}
     * @param cefString String containing the CEF message to be parsed
     * @param validate Boolean if parser should validate values beyond type compatibility (e.g. Values within acceptable lengths, value lists, etc)
     * @param locale The locale to be used when parsing dates (so that parser can handle both jul (en_US) and juil.(fr_FR)
     * @return CommonEvent
     */
    public CommonEvent parse(String cefString, final boolean validate, Locale locale)  {

        int cefHeaderSize = 7;
        CommonEvent cefEvent = new CefRev23(locale);

        // Note how split number of splits is cefHeaderSize + 1. This is because the final split
        // should be the body of the CEF message
        // Compiled pattern is equivalent to "(?<!\\\\)" + Pattern.quote("|")
        final String[] extractedMessage = cefString.split("(?<!\\\\)" + Pattern.quote("|"), cefHeaderSize + 1);

        // CEF header misses values
        if (extractedMessage.length < cefHeaderSize) {
            if (logger.isDebugEnabled()) {
                logger.debug("CEF message failed validation");
            }
            return null;
        }

        final HashMap<String, Object> headers = new HashMap<String, Object>();
        headers.put("version", Integer.valueOf(extractedMessage[0].substring(extractedMessage[0].length() - 1)));
        headers.put("deviceVendor", extractedMessage[1]);
        headers.put("deviceProduct", extractedMessage[2]);
        headers.put("deviceVersion", extractedMessage[3]);
        headers.put("deviceEventClassId", extractedMessage[4]);
        headers.put("name", extractedMessage[5]);
        headers.put("severity", extractedMessage[6]);

        final HashMap<String, String> extensions = new HashMap<String, String>();

        final String ext = extractedMessage[7];

        // Compiled pattern is equivalent to String extensionRegex = "(?<!\\\\)" + Pattern.quote("=");
        final Matcher matcher = extensionPattern.matcher(ext);
        final Matcher valueMatcher = extensionPattern.matcher(ext);

        int index = 0;

        while (matcher.find()) {
            String key = ext.substring(index, matcher.end() - 1).replace(" ", "");

            // Capture the start of the value (first char after delimiter match);
            int valueStart = matcher.end();

            // Handle all but last extension
            if (valueMatcher.find(valueStart)) {
                // FInd the next match to determine the maximum length of the value
                int nextMatch = valueMatcher.start();
                // Find the last space prior to next match (i.e. last char before next key)
                int lastSpace = ext.lastIndexOf(" ", nextMatch);

                // Copy the value between the value start (i.e. this match)
                // and the lastSpace (i.e. last char prior to next key)
                String value = ext.substring(valueStart, lastSpace);

                // Put to map.
                extensions.put(key, value);

                // Update index to the last character before the next key
                index = lastSpace + 1;

                // Treat the last KV (if match is true)
            } else if (valueMatcher.find(valueStart - 1)) {
                // We are handling the final character, value end if newline at the
                // end of string
                int valueEnd = ext.length();

                String value = ext.substring(valueStart, valueEnd);
                extensions.put(key, value);
                // Update the index to the end of the string so no matches are possible
                index = valueEnd;
            }
        }

        try {
            cefEvent.setHeader(headers);
            cefEvent.setExtension(extensions);

        } catch (CEFHandlingException e) {
            logger.error(e.toString());
            return null;
        }

        if (validate) {
            if (validator == null) {
                // Since the validator wasn't initiated previously, create a new one;
                this.validator = Validation.buildDefaultValidatorFactory().getValidator();;
            }

            Set<ConstraintViolation<CommonEvent>> validationResult = validator.validate(cefEvent);

            if (validationResult.size() > 0) {
            if (logger.isDebugEnabled()) {
                logger.debug("CEF message failed validation");
            }
                return null;
            } else {
                return cefEvent;
            }
        } else {
            return cefEvent;
        }
    }
}


package se.jbee.inject.bind;

import static se.jbee.inject.Name.named;

import java.io.File;

import se.jbee.inject.Dependency;
import se.jbee.inject.Injector;
import se.jbee.inject.Scope;
import se.jbee.inject.UnresolvableDependency;
import se.jbee.inject.config.Config;
import se.jbee.inject.container.Supplier;
import se.jbee.inject.scope.ApplicationScope;
import se.jbee.inject.scope.DependencyScope;
import se.jbee.inject.scope.DiskScope;
import se.jbee.inject.scope.ThreadScope;

public final class DefaultScopes extends BinderModule
		implements Supplier<Scope> {

	@Override
	protected void declare() {
		ScopedBinder inScope = asDefault().per(Scope.container);
		inScope.bind(Scope.injection, Scope.class).to(Scope.INJECTION);
		inScope.bind(Scope.application, Scope.class).to(ApplicationScope.class);
		inScope.bind(Scope.thread, Scope.class).to(ThreadScope.class);
		inScope.bind(Scope.jvm, Scope.class).to(DependencyScope.JVM);
		inScope.bind(Scope.dependency, Scope.class).to(
				() -> new DependencyScope(
						DependencyScope::hierarchicalInstanceName));
		inScope.bind(Scope.dependencyInstance, Scope.class).to(
				() -> new DependencyScope(DependencyScope::instanceName));
		inScope.bind(Scope.dependencyType, Scope.class).to(
				() -> new DependencyScope(DependencyScope::typeName));
		inScope.bind(Scope.targetInstance, Scope.class).to(
				() -> new DependencyScope(DependencyScope::targetInstanceName));
		inScope.bind(named("disk:*"), Scope.class).toSupplier(this);
	}

	/**
	 * By convention {@link Scope#disk(File)} create names starting with
	 * {@code disk:} followed by the path to the folder of the scope. This can
	 * be used to extract the directory in this {@link Supplier} to bind the
	 * particular {@link DiskScope} for that directory.
	 */
	@Override
	public Scope supply(Dependency<? super Scope> dep, Injector injector)
			throws UnresolvableDependency {
		String disk = dep.instance.name.toString();
		File dir = new File(disk.substring(5));
		return new DiskScope(injector.resolve(Config.class), dir,
				DependencyScope::instanceName);
	}

}


/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject;

import static se.jbee.inject.Dependency.dependency;
import static se.jbee.inject.Type.raw;

import java.io.Serializable;

/**
 * Describes WHAT can be injected and WHERE it can be injected.
 *
 * It is an {@link Instance} with added information where the bind applies.
 *
 * @author Jan Bernitt (jan@jbee.se)
 */
public final class Resource<T>
		implements Typed<T>, Qualifying<Resource<?>>, Serializable {

	public static <T> Resource<T> resource(Class<T> type) {
		return new Resource<>(Instance.anyOf(raw(type)));
	}

	public final Instance<T> instance;
	public final Target target;

	public Resource(Instance<T> instance) {
		this(instance, Target.ANY);
	}

	public Resource(Instance<T> instance, Target target) {
		this.instance = instance;
		this.target = target;
	}

	public boolean isMatching(Dependency<? super T> dependency) {
		return isNameCompatibleWith(dependency) // check names first since default goes sorts first but will not match any named
			&& isAvailableFor(dependency)//
			&& isAssignableTo(dependency); // most 'expensive' check so we do it last
	}

	public boolean isCompatibleWith(Dependency<? super T> dependency) {
		return isNameCompatibleWith(dependency) && isAssignableTo(dependency);
	}

	/**
	 * Does the {@link Type} of this a valid argument for the one of the
	 * {@link Dependency} given ?
	 */
	public boolean isAssignableTo(Dependency<? super T> dependency) {
		return instance.type().isAssignableTo(dependency.type());
	}

	/**
	 * Does the given {@link Dependency} occur in the right package and for the
	 * right target ?
	 */
	public boolean isAvailableFor(Dependency<? super T> dependency) {
		return target.isAvailableFor(dependency);
	}

	/**
	 * Does this resource provide the instance wanted by the given
	 * {@link Dependency}'s {@link Name}
	 */
	public boolean isNameCompatibleWith(Dependency<? super T> dependency) {
		return instance.name.isCompatibleWith(dependency.instance.name);
	}

	@Override
	public Type<T> type() {
		return instance.type();
	}

	@Override
	public boolean moreQualiedThan(Resource<?> other) {
		return Qualifying.compareRelated(instance, other.instance, target,
				other.target);
	}

	@Override
	public String toString() {
		return instance + " " + target;
	}

	@Override
	public <E> Resource<E> typed(Type<E> type) {
		return new Resource<>(instance.typed(type), target);
	}

	public boolean equalTo(Resource<?> other) {
		return this == other
			|| instance.equalTo(other.instance) && target.equalTo(other.target);
	}

	@Override
	public boolean equals(Object obj) {
		return obj instanceof Resource && equalTo((Resource<?>) obj);
	}

	@Override
	public int hashCode() {
		return instance.hashCode() ^ target.hashCode();
	}

	/**
	 * @since 19.1
	 * @return a {@link Dependency} that {@link #isMatching(Dependency)} this
	 *         {@link Resource}.
	 */
	public Dependency<T> toDependency() {
		Dependency<T> dep = dependency(instance);
		if (target != Target.ANY) {
			if (!target.parents.isAny()) {
				for (Instance<?> p : target.parents)
					dep = dep.injectingInto(p);
			}
			dep = dep.injectingInto(target.instance);
		}
		return dep;
	}
}


/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject.bootstrap;

import static se.jbee.inject.Utils.arrayOf;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import se.jbee.inject.DeclarationType;
import se.jbee.inject.InconsistentBinding;
import se.jbee.inject.Name;
import se.jbee.inject.Qualifying;
import se.jbee.inject.Resource;
import se.jbee.inject.Source;
import se.jbee.inject.Type;
import se.jbee.inject.Typed;
import se.jbee.inject.UnresolvableDependency;
import se.jbee.inject.container.Injectee;
import se.jbee.inject.container.Supplier;

/**
 * A {@link Binding} is implements the {@link Injectee} created during the
 * bootstrapping process based on {@link Bindings}, {@link Bundle}s and
 * {@link Module}s.
 *
 * @author Jan Bernitt (jan@jbee.se)
 *
 * @param <T> The type of the bound value (instance)
 */
public final class Binding<T>
		implements Comparable<Binding<?>>, Injectee<T>, Module, Typed<T> {

	public static <T> Binding<T> binding(Resource<T> resource, BindingType type,
			Supplier<? extends T> supplier, Name scope, Source source) {
		return new Binding<>(resource, type, supplier, scope, source);
	}

	public final Resource<T> resource;
	public final BindingType type;
	public final Supplier<? extends T> supplier;
	public final Name scope;
	public final Source source;

	private Binding(Resource<T> resource, BindingType type,
			Supplier<? extends T> supplier, Name scope, Source source) {
		this.resource = resource;
		this.type = type;
		this.supplier = supplier;
		this.scope = scope;
		this.source = source;
	}

	@Override
	public Resource<T> resource() {
		return resource;
	}

	@Override
	public Name scope() {
		return scope;
	}

	@Override
	public Source source() {
		return source;
	}

	@Override
	public Supplier<? extends T> supplier() {
		return supplier;
	}

	@Override
	public Type<T> type() {
		return resource.type();
	}

	@SuppressWarnings("unchecked")
	@Override
	public <E> Binding<E> typed(Type<E> type) {
		return new Binding<>(resource.typed(type().toSupertype(type)),
				this.type, (Supplier<? extends E>) supplier, scope, source);
	}

	public boolean isComplete() {
		return supplier != null;
	}

	public Binding<T> complete(BindingType type,
			Supplier<? extends T> supplier) {
		return new Binding<>(resource, type, supplier, scope, source);
	}

	@Override
	public void declare(Bindings bindings) {
		bindings.add(this);
	}

	@Override
	public int compareTo(Binding<?> other) {
		int res = resource.type().rawType.getCanonicalName().compareTo(
				other.resource.type().rawType.getCanonicalName());
		if (res != 0)
			return res;
		res = Qualifying.compare(resource.instance, other.resource.instance);
		if (res != 0)
			return res;
		res = Qualifying.compare(resource.target, other.resource.target);
		if (res != 0)
			return res;
		res = Qualifying.compare(source, other.source);
		if (res != 0)
			return res;
		return -1; // keep order
	}

	@Override
	public String toString() {
		return resource + " / " + scope + " / " + source;
	}

	/**
	 * Removes those bindings that are ambiguous but also do not clash because
	 * of different {@link DeclarationType}s that replace each other.
	 */
	public static Binding<?>[] disambiguate(Binding<?>[] bindings) {
		if (bindings.length <= 1) {
			return bindings;
		}
		List<Binding<?>> uniques = new ArrayList<>(bindings.length);
		Arrays.sort(bindings);
		uniques.add(bindings[0]);
		int lastUniqueIndex = 0;
		Set<Type<?>> required = new HashSet<>();
		List<Binding<?>> dropped = new ArrayList<>();
		for (int i = 1; i < bindings.length; i++) {
			Binding<?> b_d = bindings[lastUniqueIndex];
			Binding<?> b_i = bindings[i];
			final boolean equalResource = b_d.resource.equalTo(b_i.resource);
			DeclarationType t_d = b_d.source.declarationType;
			DeclarationType t_i = b_i.source.declarationType;
			if (equalResource && t_d.clashesWith(t_i)) {
				throw new InconsistentBinding(
						"Duplicate binds:\n" + b_d + "\n" + b_i);
			}
			if (t_i == DeclarationType.REQUIRED) {
				required.add(b_i.resource.type());
			} else if (equalResource && t_d.droppedWith(t_i)) {
				if (i - 1 == lastUniqueIndex) {
					dropped.add(uniques.remove(uniques.size() - 1));
				}
				dropped.add(b_i);
			} else if (!equalResource || !t_i.replacedBy(t_d)) {
				uniques.add(b_i);
				lastUniqueIndex = i;
			}
		}
		return withoutProvidedThatAreNotRequiredIn(uniques, required, dropped);
	}

	private static Binding<?>[] withoutProvidedThatAreNotRequiredIn(
			List<Binding<?>> bindings, Set<Type<?>> required,
			List<Binding<?>> dropped) {
		List<Binding<?>> res = new ArrayList<>(bindings.size());
		for (Binding<?> b : bindings) {
			Type<?> type = b.resource.type();
			if (b.source.declarationType != DeclarationType.PROVIDED
				|| required.contains(type)) {
				res.add(b);
				required.remove(type);
			}
		}
		if (!required.isEmpty()) {
			throw new UnresolvableDependency.NoCaseForDependency(required,
					dropped);
		}
		return arrayOf(res, Binding.class);
	}

}


/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject.event;

import static java.lang.reflect.Proxy.newProxyInstance;
import static se.jbee.inject.Type.returnType;
import static se.jbee.inject.event.EventException.unwrapGet;

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BinaryOperator;

import se.jbee.inject.Type;

/**
 * Default implementation of the {@link EventProcessor} supporting all
 * {@link EventPreferences}.
 *
 * @since 19.1
 */
public class ConcurrentEventProcessor implements EventProcessor {

	private final static class EventHandler extends WeakReference<Object> {

		/**
		 * How many threads are currently calling one of the handlers methods.
		 */
		final AtomicInteger concurrentUsage = new AtomicInteger(0);

		EventHandler(Object handler) {
			super(handler);
		}

		/**
		 * If the use succeeds (result is true) the end of usage should be
		 * marked by calling {@link #free()} so that this handler can continue
		 * to keep track of the concurrent using threads.
		 *
		 * @return true if this handler could be reserved for usage by the
		 *         calling thread, else false.
		 */
		boolean allocateFor(Event<?, ?> e) {
			while (true) {
				int currentUsage = concurrentUsage.get();
				if (currentUsage >= e.prefs.maxConcurrentUsage)
					return false;
				boolean success = concurrentUsage.compareAndSet(currentUsage,
						currentUsage + 1);
				if (success)
					return true;
			}
		}

		void free() {
			concurrentUsage.decrementAndGet();
		}
	}

	static final class EventHandlers
			extends ConcurrentLinkedDeque<EventHandler> {

		/**
		 * Tries to find a handler that can be used to process the event. A
		 * successfully received handler has to be marked
		 * {@link #free(EventHandler)} right after usage ends unless its handler
		 * reference became collected. In that case the handler became
		 * out-dated.
		 *
		 * @return a free handler to use or null if there is no such handler
		 */
		EventHandler allocateFor(Event<?, ?> e) {
			// either we run out of handler to poll
			// or we find a handler to use
			while (true) {
				EventHandler h = pollFirst();
				if (h == null)
					return null;
				if (h.allocateFor(e)) {
					if (h.get() != null)
						return h;
				} else {
					addLast(h);
				}
			}
		}

		void free(EventHandler h) {
			h.free();
			addLast(h);
		}
	}

	private final Map<Class<?>, Object> proxiesByEventType = new ConcurrentHashMap<>();
	private final Map<Class<?>, EventHandlers> handlersByEventType = new ConcurrentHashMap<>();
	private final Map<Class<?>, EventPreferences> prefsByEventType = new ConcurrentHashMap<>();
	private final ExecutorService executor;
	private final EventMirror reflector;

	ConcurrentEventProcessor(EventMirror reflector, ExecutorService executor) {
		this.reflector = reflector;
		this.executor = executor;
	}

	@Override
	public void close() {
		executor.shutdown();
	}

	@Override
	public <E> void await(Class<E> event) throws InterruptedException {
		EventHandlers hs = getHandlers(event);
		if (!hs.isEmpty())
			return;
		synchronized (hs) {
			hs.wait();
		}
	}

	private EventPreferences getPrefs(Class<?> event) {
		return prefsByEventType.computeIfAbsent(event,
				e -> reflector.reflect(e));
	}

	@Override
	public <E> void register(Class<E> event, E handler) {
		if (Proxy.isProxyClass(handler.getClass())
			&& Proxy.getInvocationHandler(
					handler).getClass() == ProxyEventHandler.class) {
			return; // prevent own proxies to be registered as this causes multi-threaded endless loops
		}
		EventHandlers hs = getHandlers(event);
		hs.addFirst(new EventHandler(handler));
		synchronized (hs) {
			hs.notifyAll();
		}
	}

	private <E> EventHandlers getHandlers(Class<E> event) {
		return handlersByEventType.computeIfAbsent(event,
				k -> new EventHandlers());
	}

	@Override
	public <E> void deregister(Class<E> event, E handler) {
		EventHandlers hs = handlersByEventType.get(event);
		if (hs != null && !hs.isEmpty())
			hs.removeIf(h -> h.get() == handler);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <E> E getProxy(Class<E> event) {
		return (E) proxiesByEventType.computeIfAbsent(event,
				e -> newProxyInstance(e.getClassLoader(), new Class[] { e },
						new ProxyEventHandler<>(e, getPrefs(e), this)));
	}

	private <T> Future<T> submit(Event<?, ?> event, Callable<T> f) {
		try {
			return executor.submit(f);
		} catch (RejectedExecutionException e) {
			throw new EventException(event, e);
		}
	}

	@Override
	public <E> void dispatch(Event<E, ?> event) throws Throwable {
		Future<?> res;
		if (event.prefs.isMultiDispatch()) {
			res = submit(event, () -> doDispatch(event));
		} else {
			res = submit(event, () -> doCompute(event));
		}
		if (event.prefs.isSyncMultiDispatch())
			EventException.unwrap(event, () -> res.get());
	}

	// - when should I give up?
	// - what to do when giving up?
	// - how often should I retry?
	// note: these can be programmed as a utility on top with basically the same effect and little extra overhead for a corner case
	//       as long as there is a clear contract: namely that failure is always indicated by a EventEception
	@Override
	public <E, T> T compute(Event<E, T> event) throws Throwable {
		return unwrapGet(event, submit(event, () -> doProcess(event)));
	}

	@Override
	public <E, T extends Future<V>, V> Future<V> computeEventually(
			Event<E, T> event) {
		return new UnboxingFuture<>(event,
				submit(event, () -> doProcess(event)));
	}

	private <E, T> T doProcess(Event<E, T> event) throws EventException {
		return event.prefs.isAggregatedMultiDispatch()
			&& event.aggregator != null ? doDispatch(event) : doCompute(event);
	}

	private <E, T> T doCompute(Event<E, T> event) throws EventException {
		ensureNoTimeout(event);
		EventHandlers hs = handlersByEventType.get(event.type);
		if (hs == null)
			throw new EventException(event, null);
		while (true) {
			EventHandler h = hs.allocateFor(event);
			if (h == null)
				throw new EventException(event, null);
			@SuppressWarnings("unchecked")
			E handler = (E) h.get();
			if (handler != null) {
				T res = invoke(event, handler);
				hs.free(h);
				return res;
			}
		}
	}

	private <E, T> T doDispatch(Event<E, T> event) throws EventException {
		ensureNoTimeout(event);
		EventHandlers hs = handlersByEventType.get(event.type);
		if (hs == null || hs.isEmpty())
			return null;
		Iterator<EventHandler> iter = hs.iterator();
		LinkedList<EventHandler> retry = null;
		final BinaryOperator<T> aggregator = event.aggregator;
		T res = null;
		while (iter.hasNext()) {
			EventHandler h = iter.next();
			@SuppressWarnings("unchecked")
			E handler = (E) h.get();
			if (handler != null) {
				if (h.allocateFor(event)) {
					T res1 = invoke(event, handler);
					if (aggregator != null)
						res = res == null ? res1 : aggregator.apply(res, res1);
					h.free();
				} else {
					if (retry == null)
						retry = new LinkedList<>();
					retry.add(h);
				}
			} else {
				iter.remove();
			}
		}
		if (retry != null) {
			while (!retry.isEmpty()) {
				EventHandler h = retry.pollFirst();
				@SuppressWarnings("unchecked")
				E handler = (E) h.get();
				if (handler != null) {
					if (h.allocateFor(event)) {
						T res1 = invoke(event, handler);
						if (aggregator != null)
							res = res == null
								? res1
								: aggregator.apply(res, res1);
						h.free();
					} else {
						retry.addLast(h);
					}
				}
			}
		}
		return res;
	}

	private static <E, T> void ensureNoTimeout(Event<E, T> event)
			throws EventException {
		if (event.isOutdated())
			throw new EventException(event, new TimeoutException());
	}

	@SuppressWarnings("unchecked")
	private static <E, T> T invoke(Event<E, T> event, E listener)
			throws EventException {
		try {
			return (T) event.handler.invoke(listener, event.args);
		} catch (IllegalAccessException | IllegalArgumentException
				| InvocationTargetException e) {
			throw new EventException(event, e);
		}
	}

	static class ProxyEventHandler<E> implements InvocationHandler {

		final Class<E> event;
		final EventPreferences prefs;
		final EventProcessor processor;

		public ProxyEventHandler(Class<E> event, EventPreferences prefs,
				EventProcessor processor) {
			this.event = event;
			this.prefs = prefs;
			this.processor = processor;
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			return invoke(method, args, returnType(method));
		}

		@SuppressWarnings("unchecked")
		private <T> Object invoke(Method method, Object[] args, Type<T> result)
				throws Throwable {
			Class<T> raw = result.rawType;
			Event<E, T> e = new Event<>(event, prefs, result, method, args,
					defaultAggregator(raw, args));
			if (e.returnsVoid()) {
				processor.dispatch((Event<E, Void>) e);
				return null;
			}
			if (raw == Future.class) {
				return processor.computeEventually(
						(Event<E, Future<Object>>) e);
			}
			return processor.compute(e);
		}

		@SuppressWarnings("unchecked")
		private static <T> BinaryOperator<T> defaultAggregator(Class<T> raw,
				Object[] args) {
			BinaryOperator<T> aggregator = args != null && args.length > 0
				&& args[args.length - 1] != null
				&& BinaryOperator.class.isAssignableFrom(args.getClass())
					? (BinaryOperator<T>) args[args.length - 1]
					: null;
			if (aggregator != null)
				return aggregator;
			if (raw == boolean.class || raw == Boolean.class)
				return (BinaryOperator<T>) ((BinaryOperator<Boolean>) (a,
						b) -> a || b);
			if (raw == int.class || raw == Integer.class)
				return (BinaryOperator<T>) ((BinaryOperator<Integer>) (a,
						b) -> a + b);
			return null;
		}

	}
}


/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject.event;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import se.jbee.inject.bind.BinderModule;
import se.jbee.inject.bootstrap.Module;
import se.jbee.inject.container.Initialiser;

/**
 * Base {@link Module} for modules that want to make known a handler to the
 * event system using {@link #handle(Class)}.
 *
 * @since 19.1
 */
public abstract class EventModule extends BinderModule {

	protected EventModule() {
		super(EventBaseModule.class);
	}

	/**
	 * Registers the given event type so that it is handled by the
	 * {@link EventProcessor} system.
	 *
	 * That means classes implementing the given event interface "automatically"
	 * receive calls to any of the interface methods. When The event interface
	 * should be injected to signal/call one of its methods a
	 * {@link EventProcessor#getProxy(Class)} is injected.
	 *
	 * @param event the type of the event/listener (must be an interface)
	 */
	protected <T> void handle(Class<T> event) {
		if (!event.isInterface())
			throw new IllegalArgumentException(
					"Event type has to be an interface but was: " + event);
		initbind(event).to((Initialiser<T>) (listener,
				injector) -> injector.resolve(EventProcessor.class).register(
						event, listener));
		bind(event).toSupplier((dep,
				injector) -> injector.resolve(EventProcessor.class).getProxy(
						event));
	}

	private static final class EventBaseModule extends BinderModule {

		@Override
		protected void declare() {
			asDefault().bind(EventProcessor.class).to(
					ConcurrentEventProcessor.class);
			asDefault().bind(EventMirror.class).to(
					event -> EventPreferences.DEFAULT);
			asDefault().injectingInto(EventProcessor.class).bind(
					ExecutorService.class).to(
							() -> Executors.newWorkStealingPool());
		}

	}

}


/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject.bootstrap;

import static se.jbee.inject.Utils.arrayOf;
import static se.jbee.inject.Utils.isClassMonomodal;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import se.jbee.inject.InconsistentBinding;
import se.jbee.inject.config.Mirrors;

/**
 * {@link Bindings} accumulate the {@link Binding} 4-tuples.
 *
 * Any builder is just a utility to construct calls to {@link #add(Binding)}
 *
 * @author Jan Bernitt (jan@jbee.se)
 */
public final class Bindings {

	public static Bindings newBindings() {
		return new Bindings(new ArrayList<>(128), Macros.DEFAULT,
				Mirrors.DEFAULT);
	}

	private final List<Binding<?>> bindings;
	public final Macros macros;
	public final Mirrors mirrors;

	private Bindings(List<Binding<?>> bindings, Macros macros,
			Mirrors mirrors) {
		this.bindings = bindings;
		this.macros = macros;
		this.mirrors = mirrors;
	}

	public Bindings with(Mirrors mirrors) {
		return new Bindings(bindings, macros, mirrors);
	}

	public Bindings with(Macros macros) {
		return new Bindings(bindings, macros, mirrors);
	}

	/**
	 * Add (accumulate) a binding described by the 4-tuple given.
	 */
	public <T> void add(Binding<T> complete) {
		if (!complete.isComplete()) {
			throw new InconsistentBinding(
					"Incomplete binding added: " + complete);
		}
		// NB. #64 here we can inform post binding that about the new binding
		bindings.add(complete);
	}

	public void expandInto(Binding<?> binding) {
		macros.expandInto(this, binding, binding);
	}

	public Binding<?>[] toArray() {
		return arrayOf(bindings, Binding.class);
	}

	public Binding<?>[] declareFrom(Module... modules) {
		Set<Class<?>> declared = new HashSet<>();
		Set<Class<?>> multimodals = new HashSet<>();
		for (Module m : modules) {
			Class<? extends Module> ns = m.getClass();
			final boolean hasBeenDeclared = declared.contains(ns);
			if (hasBeenDeclared && !isClassMonomodal(ns))
				multimodals.add(ns);
			if (!hasBeenDeclared || multimodals.contains(ns)) {
				m.declare(this);
				declared.add(ns);
			}
		}
		return toArray();
	}

}