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

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

import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.NameIdentifier;
import org.apache.poi.ss.formula.SheetIdentifier;
import org.apache.poi.ss.formula.ptg.Area3DPtg;
import org.apache.poi.ss.formula.ptg.Area3DPxg;
import org.apache.poi.ss.formula.ptg.AreaPtg;
import org.apache.poi.ss.formula.ptg.AreaPtgBase;
import org.apache.poi.ss.formula.ptg.AttrPtg;
import org.apache.poi.ss.formula.ptg.FuncVarPtg;
import org.apache.poi.ss.formula.ptg.OperationPtg;
import org.apache.poi.ss.formula.ptg.ParenthesisPtg;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.Ref3DPtg;
import org.apache.poi.ss.formula.ptg.Ref3DPxg;
import org.apache.poi.ss.formula.ptg.RefPtg;
import org.apache.poi.ss.formula.ptg.RefPtgBase;
import org.apache.poi.ss.formula.ptg.AddPtg;
import org.apache.poi.ss.formula.ptg.ValueOperatorPtg;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellReference;
import org.tiefaces.components.websheet.configuration.RowsMapping;
import org.tiefaces.components.websheet.configuration.ShiftFormulaRef;
import org.tiefaces.components.websheet.serializable.SerialRow;

/**
 * The Class ShiftFormula.
 */
public final class ShiftFormulaUtility {

	/** The row wrapping mask. */
	// only support xlsx
	private static int rowWrappingMask = SpreadsheetVersion.EXCEL2007
			.getLastRowIndex();

	/**
	 * hide constructor.
	 */
	private ShiftFormulaUtility() {
		// not called
	}

	/**
	 * Convert shared formulas.
	 *
	 * @param ptgs
	 *            the ptgs
	 * @param shiftFormulaRef
	 *            the shift formula ref
	 * @return the ptg[]
	 */
	public static Ptg[] convertSharedFormulas(final Ptg[] ptgs,
			final ShiftFormulaRef shiftFormulaRef) {

		List<Ptg> newPtgList = new ArrayList<>();
		Object ptg;
		for (int k = 0; k < ptgs.length; ++k) {
			ptg = ptgs[k];
			newPtgList.addAll(Arrays
					.asList(convertPtg(ptgs, k, shiftFormulaRef, ptg)));
		}

		return newPtgList.toArray(new Ptg[newPtgList.size()]);
	}

	/**
	 * 3d (reference to other sheet) is not supported. only 2d (within the
	 * sheet) is supported.
	 *
	 * @param ptg
	 *            input ptg object.
	 * @return -1 if not a supported ptg. Int row number.
	 */
	public static int getFirstSupportedRowNumFromPtg(final Object ptg) {

		int rCode = -1;
		if (ptg instanceof RefPtgBase) {
			if (!(ptg instanceof Ref3DPxg) && !(ptg instanceof Ref3DPtg)) {
				rCode = ((RefPtgBase) ptg).getRow();
			}
		} else if (ptg instanceof AreaPtgBase && !(ptg instanceof Area3DPxg)
				&& !(ptg instanceof Area3DPtg)) {
			rCode = ((AreaPtgBase) ptg).getFirstRow();

		}

		return rCode;
	}

	/**
	 * Convert ptg.
	 *
	 * @param ptgs
	 *            the ptgs
	 * @param position
	 *            the position
	 * @param shiftFormulaRef
	 *            the shift formula ref
	 * @param ptg
	 *            the ptg
	 * @return the ptg[]
	 */
	private static Ptg[] convertPtg(final Ptg[] ptgs, final int position,
			final ShiftFormulaRef shiftFormulaRef, final Object ptg) {

		byte originalOperandClass = -1;

		if (!((Ptg) ptg).isBaseToken()) {
			originalOperandClass = ((Ptg) ptg).getPtgClass();
		}

		int currentRow;
		currentRow = getFirstSupportedRowNumFromPtg(ptg);
		if ((currentRow >= 0)
				&& shiftFormulaRef.getWatchList().contains(currentRow)) {
			return convertPtgForWatchList(ptgs, position, shiftFormulaRef,
					ptg, originalOperandClass, currentRow);

		}
		// no need change ptg
		if ((ptg instanceof AttrPtg)
				&& (shiftFormulaRef.getFormulaChanged() > 1)) {
			AttrPtg newPtg = (AttrPtg) ptg;
			if (newPtg.isSum()) {
				FuncVarPtg fptg = FuncVarPtg.create("sum",
						shiftFormulaRef.getFormulaChanged());
				return singlePtg(fptg, fptg.getPtgClass(),
						shiftFormulaRef.getFormulaChanged());
			}
		}
		return singlePtg(ptg, originalOperandClass,
				shiftFormulaRef.getFormulaChanged());

	}

	/**
	 * Convert ptg for watch list.
	 *
	 * @param ptgs
	 *            the ptgs
	 * @param position
	 *            the position
	 * @param shiftFormulaRef
	 *            the shift formula ref
	 * @param ptg
	 *            the ptg
	 * @param originalOperandClass
	 *            the original operand class
	 * @param currentRow
	 *            the current row
	 * @return the ptg[]
	 */
	private static Ptg[] convertPtgForWatchList(final Ptg[] ptgs,
			final int position, final ShiftFormulaRef shiftFormulaRef,
			final Object ptg, final byte originalOperandClass,
			final int currentRow) {
		List<SerialRow> rowlist = getRowsList(currentRow,
				shiftFormulaRef.getCurrentRowsMappingList());
		if ((rowlist == null) || (rowlist.isEmpty())) {
			// no need change ptg
			return singlePtg(ptg, originalOperandClass, -1);
		}
		shiftFormulaRef.setFormulaChanged(1);
		// one to one or has no round brackets
		if ((rowlist.size() == 1) || ((position + 1) >= ptgs.length)
				|| !(ptgs[position + 1] instanceof ParenthesisPtg)) {
			// change ptg one to one
			// return changed ptg
			return singlePtg(
					fixupRefRelativeRowOneToOne(ptg,
							rowlist.get(0).getRow()),
					originalOperandClass, -1);
		}
		shiftFormulaRef.setFormulaChanged(rowlist.size());
		return fixupRefRelativeRowOneToMany(ptg, originalOperandClass,
				rowlist, ptgs, position);
	}

	/**
	 * Single ptg.
	 *
	 * @param ptg
	 *            the ptg
	 * @param originalOperandClass
	 *            the original operand class
	 * @param formulaChanged
	 *            the formula changed
	 * @return the ptg[]
	 */
	private static Ptg[] singlePtg(final Object ptg,
			final byte originalOperandClass, final int formulaChanged) {
		Ptg[] newPtg = new Ptg[1];
		if (originalOperandClass != (-1)) {
			((Ptg) ptg).setClass(originalOperandClass);
		}
		Object ptgAfter = ptg;
		if (ptg instanceof FuncVarPtg) {
			FuncVarPtg fptg = (FuncVarPtg) ptg;
			if ((formulaChanged > 0)
					&& (fptg.getNumberOfOperands() != formulaChanged)) {
				ptgAfter = FuncVarPtg.create(((FuncVarPtg) ptg).getName(),
						formulaChanged);
			}
		}
		newPtg[0] = (Ptg) ptgAfter;
		return newPtg;
	}

	/**
	 * Gets the rows list.
	 *
	 * @param currentRow
	 *            the current row
	 * @param currentRowsMappingList
	 *            the current rows mapping list
	 * @return the rows list
	 */
	private static List<SerialRow> getRowsList(final int currentRow,
			final List<RowsMapping> currentRowsMappingList) {
		List<SerialRow> all = null;
		int size = currentRowsMappingList.size();
		for (RowsMapping rowsmapping : currentRowsMappingList) {
			List<SerialRow> current = rowsmapping.get(currentRow);
			if (current != null) {
				if (size == 1) {
					return current;
				}
				all = assembleRowsListFromRowsMapping(all, current);
			}
		}
		return all;
	}

	/**
	 * assemble rowslist from rowsmapping.
	 *
	 * @param all
	 *            list all rows.
	 * @param current
	 *            current row list.
	 * @return the list
	 */
	private static List<SerialRow> assembleRowsListFromRowsMapping(
			final List<SerialRow> all, final List<SerialRow> current) {
		List<SerialRow> list;
		if (all == null) {
			list = new ArrayList<>();
			list.addAll(current);
		} else {
			list = all;
			for (SerialRow row : current) {
				if (!all.contains(row)) {
					list.add(row);
				}
			}
		}
		return list;
	}

	/**
	 * Fixup ref relative row one to one.
	 *
	 * @param ptg
	 *            the ptg
	 * @param newRow
	 *            the new row
	 * @return the object
	 */
	protected static Object fixupRefRelativeRowOneToOne(final Object ptg,
			final Row newRow) {
		if (ptg instanceof RefPtgBase) {
			if (ptg instanceof Ref3DPxg) {
				Ref3DPxg ref3dPxg = (Ref3DPxg) ptg;
				Ref3DPxg new3dpxg = new Ref3DPxg(
						ref3dPxg.getExternalWorkbookNumber(),
						new SheetIdentifier(null,
								new NameIdentifier(ref3dPxg.getSheetName(),
										false)),
						new CellReference(newRow.getRowNum(),
								ref3dPxg.getColumn()));
				new3dpxg.setClass(ref3dPxg.getPtgClass());
				new3dpxg.setColRelative(ref3dPxg.isColRelative());
				new3dpxg.setRowRelative(ref3dPxg.isRowRelative());
				new3dpxg.setLastSheetName(ref3dPxg.getLastSheetName());
				return new3dpxg;
			} else {
				RefPtgBase refPtgBase = (RefPtgBase) ptg;
				return new RefPtg(newRow.getRowNum(),
						refPtgBase.getColumn(), refPtgBase.isRowRelative(),
						refPtgBase.isColRelative());

			}
		} else {
			if (ptg instanceof Area3DPxg) {
				Area3DPxg area3dPxg = (Area3DPxg) ptg;
				Area3DPxg new3dpxg = new Area3DPxg(
						area3dPxg.getExternalWorkbookNumber(),
						new SheetIdentifier(null,
								new NameIdentifier(area3dPxg.getSheetName(),
										false)),
						area3dPxg.format2DRefAsString());
				new3dpxg.setClass(area3dPxg.getPtgClass());
				new3dpxg.setFirstColRelative(
						area3dPxg.isFirstColRelative());
				new3dpxg.setLastColRelative(area3dPxg.isLastColRelative());
				int shiftRow = newRow.getRowNum() - area3dPxg.getFirstRow();
				new3dpxg.setFirstRow(area3dPxg.getFirstRow() + shiftRow);
				new3dpxg.setLastRow(area3dPxg.getLastRow() + shiftRow);
				new3dpxg.setFirstRowRelative(
						area3dPxg.isFirstRowRelative());
				new3dpxg.setLastRowRelative(area3dPxg.isLastRowRelative());
				new3dpxg.setLastSheetName(area3dPxg.getLastSheetName());
				return new3dpxg;
			} else {
				AreaPtgBase areaPtgBase = (AreaPtgBase) ptg;
				int shiftRow = newRow.getRowNum()
						- areaPtgBase.getFirstRow();
				return new AreaPtg(areaPtgBase.getFirstRow() + shiftRow,
						areaPtgBase.getLastRow() + shiftRow,
						areaPtgBase.getFirstColumn(),
						areaPtgBase.getLastColumn(),
						areaPtgBase.isFirstRowRelative(),
						areaPtgBase.isLastRowRelative(),
						areaPtgBase.isFirstColRelative(),
						areaPtgBase.isLastColRelative());
			}
		}

	}

	/**
	 * Change formula ptg by replace one ref with multiple ref. We require user
	 * follow the rule to define dynamic formula. e.g. If cell reference in
	 * formula maybe become multiple cells, then should use round brackets
	 * around it.
	 *
	 * Case 1: = (A1) + A2 + A3 Case 2: = SUM((A1)) Case 3: = SUM((A1:A2))
	 *
	 * @param ptg
	 *            the ptg
	 * @param originalOperandClass
	 *            the original operand class
	 * @param rowList
	 *            the row list
	 * @param ptgs
	 *            the ptgs
	 * @param position
	 *            the position
	 * @return the ptg[]
	 */
	protected static Ptg[] fixupRefRelativeRowOneToMany(final Object ptg,
			final byte originalOperandClass, final List<SerialRow> rowList,
			final Ptg[] ptgs, final int position) {
		int size = rowList.size();
		Ptg[] newPtg = null;
		// if followedby valueoperator, then change to multiple ptg plus Add
		// e.g. (A1) --> (A1+A2)
		if (isFollowedByValueOperator(ptgs, position)) {
			if (ptg instanceof RefPtgBase) {
				newPtg = new Ptg[size + 1];
				buildDynamicRowForRefPtgBase(ptg, originalOperandClass,
						rowList, newPtg, false);
				newPtg[rowList.size()] = AddPtg.instance;
			}
		} else {
			// otherwise change to mutiple ptg plus parenth
			// e.g. SUM((A1)) --> SUM((A1),(A2))
			// SUM((A1:B1)) --> SUM((A1:B1),(A2:B2))
			newPtg = new Ptg[(size * 2) - 1];
			if (ptg instanceof RefPtgBase) {
				buildDynamicRowForRefPtgBase(ptg, originalOperandClass,
						rowList, newPtg, true);
			} else {
				buildDynamicRowForAreaPtgBase(ptg, originalOperandClass,
						rowList, newPtg);
			}
		}
		return newPtg;
	}

	/**
	 * Builds the dynamic row for ref ptg base.
	 *
	 * @param ptg
	 *            the ptg
	 * @param originalOperandClass
	 *            the original operand class
	 * @param rowList
	 *            the row list
	 * @param newPtg
	 *            the new ptg
	 * @param includeParenthesis
	 *            the include parenthesis
	 */
	private static void buildDynamicRowForRefPtgBase(final Object ptg,
			final byte originalOperandClass, final List<SerialRow> rowList,
			final Ptg[] newPtg, final boolean includeParenthesis) {
		RefPtgBase refPtg = (RefPtgBase) ptg;
		int unitSize = 1;
		if (includeParenthesis) {
			unitSize = 2;
		}
		for (int i = 0; i < rowList.size(); i++) {
			Row row = rowList.get(i).getRow();
			if (refPtg instanceof Ref3DPxg) {
				Ref3DPxg ref3dPxg = (Ref3DPxg) refPtg;
				Ref3DPxg new3dpxg = new Ref3DPxg(
						ref3dPxg.getExternalWorkbookNumber(),
						new SheetIdentifier(null,
								new NameIdentifier(ref3dPxg.getSheetName(),
										false)),
						new CellReference(row.getRowNum(),
								ref3dPxg.getColumn()));
				new3dpxg.setClass(originalOperandClass);
				new3dpxg.setColRelative(ref3dPxg.isColRelative());
				new3dpxg.setRowRelative(ref3dPxg.isRowRelative());
				new3dpxg.setLastSheetName(ref3dPxg.getLastSheetName());
				newPtg[i * unitSize] = new3dpxg;
			} else {
				RefPtgBase refPtgBase = refPtg;
				newPtg[i * unitSize] = new RefPtg(row.getRowNum(),
						refPtgBase.getColumn(), refPtgBase.isRowRelative(),
						refPtgBase.isColRelative());
			}
			if ((unitSize == 2) && (i < (rowList.size() - 1))) {
				newPtg[i * unitSize + 1] = ParenthesisPtg.instance;
			}
		}
	}

	/**
	 * Builds the dynamic row for area ptg base.
	 *
	 * @param ptg
	 *            the ptg
	 * @param originalOperandClass
	 *            the original operand class
	 * @param rowList
	 *            the row list
	 * @param newPtg
	 *            the new ptg
	 */
	private static void buildDynamicRowForAreaPtgBase(final Object ptg,
			final byte originalOperandClass, final List<SerialRow> rowList,
			final Ptg[] newPtg) {
		AreaPtgBase areaPtg = (AreaPtgBase) ptg;
		int originFirstRow = areaPtg.getFirstRow();
		int originLastRow = areaPtg.getLastRow();
		int unitSize = 2;
		for (int i = 0; i < rowList.size(); i++) {
			Row row = rowList.get(i).getRow();
			int shiftRow = row.getRowNum() - originFirstRow;
			if (ptg instanceof Area3DPxg) {
				Area3DPxg area3dPxg = (Area3DPxg) ptg;
				Area3DPxg new3dpxg = new Area3DPxg(
						area3dPxg.getExternalWorkbookNumber(),
						new SheetIdentifier(null,
								new NameIdentifier(area3dPxg.getSheetName(),
										false)),
						area3dPxg.format2DRefAsString());
				new3dpxg.setClass(originalOperandClass);
				new3dpxg.setFirstColRelative(
						area3dPxg.isFirstColRelative());
				new3dpxg.setLastColRelative(area3dPxg.isLastColRelative());
				new3dpxg.setFirstRow(originFirstRow + shiftRow);
				new3dpxg.setLastRow(originLastRow + shiftRow);
				new3dpxg.setFirstRowRelative(
						area3dPxg.isFirstRowRelative());
				new3dpxg.setLastRowRelative(area3dPxg.isLastRowRelative());
				new3dpxg.setLastSheetName(area3dPxg.getLastSheetName());
				newPtg[i * unitSize] = new3dpxg;
			} else {
				AreaPtgBase areaPtgBase = (AreaPtgBase) ptg;
				newPtg[i * unitSize] = new AreaPtg(
						originFirstRow + shiftRow, originLastRow + shiftRow,
						areaPtgBase.getFirstColumn(),
						areaPtgBase.getLastColumn(),
						areaPtgBase.isFirstRowRelative(),
						areaPtgBase.isLastRowRelative(),
						areaPtgBase.isFirstColRelative(),
						areaPtgBase.isLastColRelative());

			}
			if (i < (rowList.size() - 1)) {
				newPtg[i * unitSize + 1] = ParenthesisPtg.instance;
			}
		}
	}

	/**
	 * check is current ptg followed by valueOperationPtg. valueOperationPtg
	 * include: Add, SubStract, Multiply, Divide etc.
	 *
	 * @param ptgs
	 *            the ptgs
	 * @param position
	 *            the position
	 * @return true, if is followed by value operator
	 */

	private static boolean isFollowedByValueOperator(final Ptg[] ptgs,
			final int position) {

		for (int i = position; i < ptgs.length; i++) {
			Object ptg = ptgs[position];
			if (ptg instanceof OperationPtg) {
				return ptg instanceof ValueOperatorPtg;
			} else if (ptg instanceof AttrPtg) {
				return false;
			}
		}
		return false;
	}

	/**
	 * Fixup relative row.
	 *
	 * @param shift
	 *            the shift
	 * @param row
	 *            the row
	 * @param relative
	 *            the relative
	 * @return the int
	 */

	protected static int fixupRelativeRow(final int shift, final int row,
			final boolean relative) {
		if (relative) {
			return row + shift & rowWrappingMask;
		} else {
			return row;
		}

	}

}


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

package org.tiefaces.components.websheet.utility;

import java.awt.Color;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFPalette;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.model.ThemesTable;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTPlotArea;
import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor;
import org.openxmlformats.schemas.drawingml.x2006.main.CTSchemeColor;
import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties;
import org.tiefaces.common.FacesUtility;
import org.tiefaces.components.websheet.dataobjects.XColor;

/**
 * Utility Class for Color.
 *
 * @author Jason Jiang
 *
 */
public final class ColorUtility {

	/** one million. */
	private static final double MILLION_NUMBERS = 100000.00;
	/** default automatic color numbers. */
	private static final int AUTOCOLORSIZE = 6;
	/** default automatic color names. */
	private static final String AUTOCOLORNAME = "accent";
	/** max rgb color. */
	private static final short RGB8BITS = 256;

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

	/**
	 * prevent initialize.
	 */
	private ColorUtility() {
		// not called
	}

	/**
	 * retrieve background color for plot area.
	 *
	 * @param ctPlot
	 *            plot area instance.
	 * @param themeTable
	 *            theme table contain theme color.
	 * @return xssfcolor with alpha setting.
	 */
	public static XColor getBgColor(final CTPlotArea ctPlot,
			final ThemesTable themeTable) {

		CTSolidColorFillProperties colorFill = null;
		try {
			colorFill = ctPlot.getSpPr().getSolidFill();
		} catch (Exception ex) {
			LOG.log(Level.FINE, "No entry in bgcolor for solidFill", ex);
		}
		XColor xcolor = findAutomaticFillColor(themeTable, colorFill);
		if (xcolor != null) {
			return xcolor;
		} else {
			return new XColor(new XSSFColor(Color.WHITE));
		}
	}

	/**
	 * get line color of line chart from CTLineSer.
	 *
	 * @param index
	 *            line index.
	 * @param ctSpPr
	 *            the ct sp pr
	 * @param themeTable
	 *            themeTable.
	 * @param isLineColor
	 *            is line color.
	 * @return xcolor.
	 */
	public static XColor geColorFromSpPr(final int index,
			final CTShapeProperties ctSpPr, final ThemesTable themeTable,
			final boolean isLineColor) {

		CTSolidColorFillProperties colorFill = null;

		try {
			if (isLineColor) {
				colorFill = ctSpPr.getLn().getSolidFill();
			} else {
				colorFill = ctSpPr.getSolidFill();
			}
		} catch (Exception ex) {
			LOG.log(Level.FINE, "No entry for solidFill", ex);
		}
		XColor xcolor = findAutomaticFillColor(themeTable, colorFill);
		if (xcolor != null) {
			return xcolor;
		} else {
			return getXColorWithAutomaticFill(index, themeTable);
		}

	}

	/**
	 * Find automatic fill color.
	 *
	 * @param themeTable
	 *            the theme table
	 * @param colorFill
	 *            the color fill
	 * @return the x color
	 */
	private static XColor findAutomaticFillColor(final ThemesTable themeTable,
			final CTSolidColorFillProperties colorFill) {
		// if there's no solidFill, then use automaticFill color
		if (colorFill == null) {
			return null;
		}
		CTSchemeColor ctsColor = colorFill.getSchemeClr();
		if (ctsColor != null) {
			return getXColorFromSchemeClr(ctsColor, themeTable);
		} else {
			CTSRgbColor ctrColor = colorFill.getSrgbClr();
			if (ctrColor != null) {
				return getXColorFromRgbClr(ctrColor);
			}
		}
		return null;
	}

	/**
	 * assemble xssfcolor with tint/lumoff/lummod/alpha to xcolor.
	 *
	 * @param bcolor
	 *            xssfcolor
	 * @param preTint
	 *            tint
	 * @param lumOff
	 *            lumOff
	 * @param lumMod
	 *            lummod
	 * @param alphaInt
	 *            alpha
	 * @return xcolor
	 */
	private static XColor assembleXcolor(final XSSFColor bcolor,
			final double preTint, final int lumOff, final int lumMod,
			final int alphaInt) {

		if (bcolor == null) {
			return null;
		}

		double tint = preTint;
		if (Double.compare(tint, 0)  == 0) {
			// no preTint
			if (lumOff > 0) {
				tint = lumOff / MILLION_NUMBERS;
			} else {
				if (lumMod > 0) {
					tint = -1 * (lumMod / MILLION_NUMBERS);
				}
			}
		}

		bcolor.setTint(tint);

		double alpha = 0;
		if (alphaInt > 0) {
			alpha = alphaInt / MILLION_NUMBERS;
		}

		return new XColor(bcolor, alpha);

	}

	/**
	 * retrieve xcolor from scheme color.
	 *
	 * @param ctsColor
	 *            scheme color
	 * @param themeTable
	 *            theme table
	 * @return xcolor
	 */
	private static XColor getXColorFromSchemeClr(
			final CTSchemeColor ctsColor, final ThemesTable themeTable) {
		if (ctsColor.getVal() != null) {
			return getXColorWithSchema(ctsColor.getVal().toString(), 0,
					ctsColor, themeTable);
		}
		return null;
	}

	/**
	 * Get xcolor with color schema name. Normally the names are accent1 to 7.
	 * Sometimes also have lumOff/lumMod/alphaInt setting.
	 *
	 * @param colorSchema
	 *            color schema
	 * @param preTint
	 *            tint
	 * @param ctsColor
	 *            ctsColor instance.
	 * @param themeTable
	 *            themeTable.
	 * @return xcolor.
	 */
	private static XColor getXColorWithSchema(final String colorSchema,
			final double preTint, final CTSchemeColor ctsColor,
			final ThemesTable themeTable) {
		int colorIndex = getThemeIndexFromName(colorSchema);
		if (colorIndex < 0) {
			return null;
		}
		XSSFColor bcolor = themeTable.getThemeColor(colorIndex);
		if (bcolor == null) {
			return null;
		}
		int lumOff = 0;
		int lumMod = 0;
		int alphaInt = 0;
		if (ctsColor != null) {
			try {
				lumOff = ctsColor.getLumOffArray(0).getVal();
			} catch (Exception ex) {
				LOG.log(Level.FINE, "No lumOff entry", ex);
			}
			try {
				lumMod = ctsColor.getLumModArray(0).getVal();
			} catch (Exception ex) {
				LOG.log(Level.FINE, "No lumMod entry", ex);
			}
			try {
				alphaInt = ctsColor.getAlphaArray(0).getVal();
			} catch (Exception ex) {
				LOG.log(Level.FINE, "No alpha entry", ex);
			}
		}
		return assembleXcolor(bcolor, preTint, lumOff, lumMod, alphaInt);
	}

	/**
	 * get xcolor from ctsRgbColor.
	 *
	 * @param ctrColor
	 *            ctsRgbColor
	 * @return xcolor
	 */
	private static XColor getXColorFromRgbClr(final CTSRgbColor ctrColor) {
		XSSFColor bcolor = null;
		try {
			byte[] rgb = ctrColor.getVal();
			bcolor = new XSSFColor(rgb);
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, "Cannot get rgb color error = "
					+ ex.getLocalizedMessage(), ex);
			return null;
		}
		int lumOff = 0;
		int lumMod = 0;
		int alphaStr = 0;
		try {
			lumOff = ctrColor.getLumOffArray(0).getVal();
		} catch (Exception ex) {
			LOG.log(Level.FINE, "No lumOff entry", ex);
		}
		try {
			lumMod = ctrColor.getLumModArray(0).getVal();
		} catch (Exception ex) {
			LOG.log(Level.FINE, "No lumMod entry", ex);
		}
		try {
			alphaStr = ctrColor.getAlphaArray(0).getVal();
		} catch (Exception ex) {
			LOG.log(Level.FINE, "No alpha entry", ex);
		}
		return assembleXcolor(bcolor, 0, lumOff, lumMod, alphaStr);
	}

	/**
	 * Get xcolor for automatic fill setting. This is the default setting in
	 * Excel for chart colors. Normally the colors will be accent1 to 7.
	 *
	 * @param index
	 *            line index.
	 * @param themeTable
	 *            theme table.
	 * @return xcolor.
	 */
	private static XColor getXColorWithAutomaticFill(final int index,
			final ThemesTable themeTable) {

		int reminder = (index + 1) % AUTOCOLORSIZE;
		if (reminder == 0) {
			reminder = AUTOCOLORSIZE;
		}
		String schema = AUTOCOLORNAME + reminder;
		double tint = getAutomaticTint(index);
		return getXColorWithSchema(schema, tint, null, themeTable);
	}

	/**
	 * Get automatic tint number for specified index line. The default automatic
	 * color only have 6 colors. If there are more than 6 lines then use tint
	 * for next round, e.g. color7 = color1 + tint (0.25)
	 *
	 * @param index
	 *            line index.
	 * @return tint.
	 */
	private static double getAutomaticTint(final int index) {

		final double[] idxArray = { 0, 0.25, 0.5, -0.25, -0.5, 0.1, 0.3,
				-0.1, -0.3 };
		int i = index / AUTOCOLORSIZE;
		if (i >= idxArray.length) {
			return 0;
		} else {
			return idxArray[i];
		}
	}

	/**
	 * convert xssf color to color.
	 *
	 * @param xssfColor
	 *            xssf color
	 * @return color
	 */
	public static Color xssfClrToClr(final XSSFColor xssfColor) {
		short[] rgb = getTripletFromXSSFColor(xssfColor);
		return new Color(rgb[0], rgb[1], rgb[2]);
	}

	/**
	 * Return index number for giving index name. e.g. tx1 return 1.
	 *
	 * @param idxName
	 *            theme index name.
	 * @return index number.
	 */

	private static int getThemeIndexFromName(final String idxName) {

		final String[] idxArray = { "bg1", "tx1", "bg2", "tx2", "accent1",
				"accent2", "accent3", "accent4", "accent5", "accent6",
				"hlink", "folHlink" };
		try {
			for (int i = 0; i < idxArray.length; i++) {
				if (idxArray[i].equalsIgnoreCase(idxName)) {
					return i;
				}
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, "error in getThemeIndexFromName :"
					+ ex.getLocalizedMessage(), ex);
		}

		return -1;
	}

	/**
	 * Convert xssfcolor to triple let numbers.
	 *
	 * @param xssfColor
	 *            xssf color.
	 * @return triple lets.
	 */
	public static short[] getTripletFromXSSFColor(
			final XSSFColor xssfColor) {

		short[] rgbfix = { RGB8BITS, RGB8BITS, RGB8BITS };
		if (xssfColor != null) {
			byte[] rgb = xssfColor.getRGBWithTint();
			if (rgb == null) {
				rgb = xssfColor.getRGB();
			}
			// Bytes are signed, so values of 128+ are negative!
			// 0: red, 1: green, 2: blue
			rgbfix[0] = (short) ((rgb[0] < 0) ? (rgb[0] + RGB8BITS)
					: rgb[0]);
			rgbfix[1] = (short) ((rgb[1] < 0) ? (rgb[1] + RGB8BITS)
					: rgb[1]);
			rgbfix[2] = (short) ((rgb[2] < 0) ? (rgb[2] + RGB8BITS)
					: rgb[2]);
		}
		return rgbfix;
	}

	/**
	 * Gets the bg color from cell.
	 *
	 * @param wb
	 *            the wb
	 * @param poiCell
	 *            the poi cell
	 * @param cellStyle
	 *            the cell style
	 * @return the bg color from cell
	 */
	static String getBgColorFromCell(final Workbook wb, final Cell poiCell,
			final CellStyle cellStyle) {

		String style = "";
		if (poiCell instanceof HSSFCell) {
			int bkColorIndex = cellStyle.getFillForegroundColor();
			HSSFColor color = HSSFColor.getIndexHash().get(bkColorIndex);
			if (color != null) {
				// correct color for customPalette
				HSSFPalette palette = ((HSSFWorkbook) wb)
						.getCustomPalette();
				HSSFColor color2 = palette.getColor(bkColorIndex);
				if (!color.getHexString()
						.equalsIgnoreCase(color2.getHexString())) {
					color = color2;
				}
				style = "background-color:rgb("
						+ FacesUtility.strJoin(color.getTriplet(), ",")
						+ ");";
			}
		} else if (poiCell instanceof XSSFCell) {
			XSSFColor color = ((XSSFCell) poiCell).getCellStyle()
					.getFillForegroundColorColor();
			if (color != null) {
				style = "background-color:rgb(" + FacesUtility.strJoin(
						getTripletFromXSSFColor(color), ",") + ");";
			}
		}
		return style;
	}
}


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

import java.lang.reflect.Method;
import java.util.Arrays;
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 org.apache.poi.ss.usermodel.Cell;
import org.tiefaces.components.websheet.configuration.SheetConfiguration;
import org.tiefaces.components.websheet.dataobjects.CellAttributesMap;
import org.tiefaces.components.websheet.dataobjects.CellFormAttributes;
import org.tiefaces.components.websheet.dataobjects.FacesCell;

/**
 * Cell controls helper.
 *
 * @author JASON JIANG.
 *
 */
public final class CellControlsUtility {

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

	/** list of supported components. */
	private static List<String> supportComponents = Arrays.asList(
			"Calendar", "SelectOneMenu", "InputNumber", "InputTextarea");

	/**
	 * array list for possible parameter's type.
	 */

	public enum AttributesType {

		/** The string. */
		STRING(String.class) {
			@Override
			public Object parseValue(final String value) {
				return value;
			}
		},

		/** The boolean. */
		BOOLEAN(Boolean.class) {
			@Override
			public Object parseValue(final String value) {
				return Boolean.parseBoolean(value);
			}
		},

		/** The booleantype. */
		BOOLEANTYPE(boolean.class) {
			@Override
			public Object parseValue(final String value) {
				return Boolean.parseBoolean(value);
			}
		},

		/** The integer. */
		INTEGER(Integer.class) {
			@Override
			public Object parseValue(final String value) {
				return Integer.parseInt(value);
			}
		},

		/** The integertype. */
		INTEGERTYPE(int.class) {
			@Override
			public Object parseValue(final String value) {
				return Integer.parseInt(value);
			}
		},

		/** The long. */
		LONG(Long.class) {
			@Override
			public Object parseValue(final String value) {
				return Long.parseLong(value);
			}
		},

		/** The longtype. */
		LONGTYPE(long.class) {
			@Override
			public Object parseValue(final String value) {
				return Long.parseLong(value);
			}
		},

		/** The float. */
		FLOAT(Float.class) {
			@Override
			public Object parseValue(final String value) {
				return Float.parseFloat(value);
			}
		},

		/** The floattype. */
		FLOATTYPE(float.class) {
			@Override
			public Object parseValue(final String value) {
				return Float.parseFloat(value);
			}
		},

		/** The double. */
		DOUBLE(Double.class) {
			@Override
			public Object parseValue(final String value) {
				return Double.parseDouble(value);
			}
		},

		/** The doubletype. */
		DOUBLETYPE(double.class) {
			@Override
			public Object parseValue(final String value) {
				return Double.parseDouble(value);
			}
		},

		/** The byte. */
		BYTE(Byte.class) {
			@Override
			public Object parseValue(final String value) {
				return Byte.parseByte(value);
			}
		},

		/** The bytetype. */
		BYTETYPE(byte.class) {
			@Override
			public Object parseValue(final String value) {
				return Byte.parseByte(value);
			}
		},

		/** The short. */
		SHORT(Short.class) {
			@Override
			public Object parseValue(final String value) {
				return Short.parseShort(value);
			}
		},

		/** The shorttype. */
		SHORTTYPE(short.class) {
			@Override
			public Object parseValue(final String value) {
				return Short.parseShort(value);
			}
		};

		/** The clazz. */
		private final Class clazz; // class name

		/**
		 * Instantiates a new attributes type.
		 *
		 * @param pclazz
		 *            the pclazz
		 */
		AttributesType(final Class pclazz) {
			this.clazz = pclazz;
		}

		/**
		 * Parses the value.
		 *
		 * @param value
		 *            the value
		 * @return the object
		 */
		public abstract Object parseValue(String value);
	}

	/**
	 * hide constructor.
	 */
	private CellControlsUtility() {
		// not called
	}

	/**
	 * find component according it's class.
	 *
	 * @param component
	 *            component.
	 * @return component name.
	 */
	private static String findComponentNameFromClass(
			final UIComponent component) {

		String cname = component.getClass().getSimpleName();
		if (supportComponents.contains(cname)) {
			return cname;
		}
		return null;

	}

	/**
	 * populate attributes.
	 *
	 * @param component
	 *            component obj.
	 * @param fcell
	 *            facescell.
	 * @param defaultControlMap
	 *            default control map.
	 */
	public static void populateAttributes(final UIComponent component,
			final FacesCell fcell,
			final Map<String, Map<String, String>> defaultControlMap) {

		List<CellFormAttributes> inputAttrs = fcell.getInputAttrs();
		String cname = findComponentNameFromClass(component);
		if (cname == null) {
			return;
		}

		Map<String, String> defaultMap = defaultControlMap.get(cname);
		if (defaultMap == null) {
			defaultMap = new HashMap<>();
			defaultControlMap.put(cname, defaultMap);
		}
		for (Map.Entry<String, String> entry : defaultMap.entrySet()) {
			setObjectProperty(component, entry.getKey(), entry.getValue(),
					true);
		}
		for (CellFormAttributes attr : inputAttrs) {
			String propertyName = attr.getType();
			String propertyValue = attr.getValue();
			if (!defaultMap.containsKey(propertyName)) {
				String defaultValue = getObjectPropertyValue(component,
						propertyName, true);
				defaultMap.put(propertyName, defaultValue);
			}
			setObjectProperty(component, propertyName, propertyValue, true);
		}

	}

	/**
	 * match parameter of method.
	 *
	 * @param obj
	 *            object.
	 * @param methodName
	 *            method name.
	 * @return index of paraMatchArray if mached. otherwise return -1.
	 */
	private static AttributesType matchParaMeterOfMethod(final Object obj,
			final String methodName) {

		for (AttributesType attr : AttributesType.values()) {
			try {
				obj.getClass().getMethod(methodName,
						new Class[] { attr.clazz });
				return attr;

			} catch (Exception ex) {
				LOG.log(Level.FINE, " error in matchParaMeterOfMethod = "
						+ ex.getLocalizedMessage(), ex);
			}
		}
		return null;
	}

	/**
	 * convert object.
	 *
	 * @param attr
	 *            the attr
	 * @param value
	 *            value.
	 * @return object according to class.
	 */
	static Object convertToObject(final AttributesType attr, final String value) {

		return attr.parseValue(value);

	}

	/**
	 * set object property.
	 *
	 * @param obj
	 *            object.
	 * @param propertyName
	 *            property name.
	 * @param propertyValue
	 *            value.
	 * @param ignoreNonExisting
	 *            true if ignore non exist property.
	 */

	public static void setObjectProperty(final Object obj,
			final String propertyName, final String propertyValue,
			final boolean ignoreNonExisting) {
		try {

			String methodName = "set"
					+ Character.toUpperCase(propertyName.charAt(0))
					+ propertyName.substring(1);
			AttributesType parameterType = matchParaMeterOfMethod(obj,
					methodName);
			if (parameterType != null) {
				Method method = obj.getClass().getMethod(methodName,
						new Class[] { parameterType.clazz });
				method.invoke(obj,
						convertToObject(parameterType, propertyValue));
			}
		} catch (Exception e) {
			String msg = "failed to set property '" + propertyName
					+ "' to value '" + propertyValue + "' for object "
					+ obj;
			if (ignoreNonExisting) {
				LOG.log(Level.FINE, msg, e);
			} else {
				LOG.warning(msg);
				throw new IllegalArgumentException(e);
			}
		}
	}

	/**
	 * get object property value.
	 *
	 * @param obj
	 *            object.
	 * @param propertyName
	 *            property name.
	 * @param ignoreNonExisting
	 *            true ignore no existing.
	 * @return perperty value.
	 */
	public static String getObjectPropertyValue(final Object obj,
			final String propertyName, final boolean ignoreNonExisting) {
		try {
			Method method = obj.getClass()
					.getMethod("get"
							+ Character.toUpperCase(propertyName.charAt(0))
							+ propertyName.substring(1));
			return (String) method.invoke(obj);
		} catch (Exception e) {
			String msg = "failed to get property '" + propertyName
					+ "' for object " + obj;
			if (ignoreNonExisting) {
				LOG.log(Level.FINE, msg, e);
			} else {
				LOG.warning(msg);
				throw new IllegalArgumentException(e);
			}
		}
		return null;
	}

	/**
	 * Setup control attributes.
	 *
	 * @param originRowIndex
	 *            the origin row index
	 * @param fcell
	 *            the fcell
	 * @param poiCell
	 *            the poi cell
	 * @param sheetConfig
	 *            the sheet config
	 * @param cellAttributesMap
	 *            the cell attributes map
	 */
	public static void setupControlAttributes(final int originRowIndex,
			final FacesCell fcell, final Cell poiCell,
			final SheetConfiguration sheetConfig,
			final CellAttributesMap cellAttributesMap) {
		int rowIndex = originRowIndex;
		if (rowIndex < 0) {
			rowIndex = poiCell.getRowIndex();
		}

		String skey = poiCell.getSheet().getSheetName() + "!" + CellUtility
				.getCellIndexNumberKey(poiCell.getColumnIndex(), rowIndex);

		Map<String, String> commentMap = cellAttributesMap
				.getTemplateCommentMap().get("$$");
		if (commentMap != null) {
			String comment = commentMap.get(skey);
			if (comment != null) {
				CommandUtility.createCellComment(poiCell, comment,
						sheetConfig.getFinalCommentMap());
			}
		}

		String widgetType = cellAttributesMap.getCellInputType().get(skey);
		if (widgetType != null) {
			fcell.setControl(widgetType.toLowerCase());

			fcell.setInputAttrs(
					cellAttributesMap.getCellInputAttributes().get(skey));
			fcell.setSelectItemAttrs(cellAttributesMap
					.getCellSelectItemsAttributes().get(skey));
			fcell.setDatePattern(
					cellAttributesMap.getCellDatePattern().get(skey));
		}

	}

	/**
	 * Find cell validate attributes.
	 *
	 * @param validateMaps
	 *            validateMaps.
	 * @param originRowIndex
	 * 			   original Row Index from facesRow.
	 * @param cell
	 *            cell.
	 * @return list.
	 */
	public static List<CellFormAttributes> findCellValidateAttributes(
			final Map<String, List<CellFormAttributes>> validateMaps,
			final int originRowIndex,
			final Cell cell) {
		String key = cell.getSheet().getSheetName() + "!" + CellUtility
				.getCellIndexNumberKey(cell.getColumnIndex(), originRowIndex);

		return validateMaps.get(key);
	}

}


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

package org.tiefaces.components.websheet.service;

import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Picture;
import org.apache.poi.ss.usermodel.Sheet;
import org.tiefaces.components.websheet.TieWebSheetBean;
import org.tiefaces.components.websheet.dataobjects.FacesCell;
import org.tiefaces.components.websheet.utility.PicturesUtility;

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

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

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

	/**
	 * Instantiates a new pictures helper.
	 *
	 * @param pparent
	 *            the parent
	 */
	public PicturesHelper(final TieWebSheetBean pparent) {
		this.parent = pparent;
	}

	/**
	 * initial pictures map for current workbook.
	 */
	public final void loadPicturesMap() {
		PicturesUtility.getPictruesMap(parent.getWb(),
				parent.getPicturesMap());
	}

	/**
	 * Setup faces cell picture charts.
	 *
	 * @param sheet1
	 *            the sheet 1
	 * @param fcell
	 *            the fcell
	 * @param cell
	 *            the cell
	 * @param fId
	 *            the f id
	 */
	public final void setupFacesCellPictureCharts(final Sheet sheet1,
			final FacesCell fcell, final Cell cell, final String fId) {
		if (parent.getPicturesMap() != null) {
			setupFacesCellPicture(sheet1, fcell, cell, fId);
		}
		if (parent.getCharsData().getChartsMap() != null) {
			setupFacesCellCharts(sheet1, fcell, cell, fId);
		}
	}

	/**
	 * Setup faces cell charts.
	 *
	 * @param sheet1
	 *            the sheet 1
	 * @param fcell
	 *            the fcell
	 * @param cell
	 *            the cell
	 * @param fId
	 *            the f id
	 */
	private void setupFacesCellCharts(final Sheet sheet1,
			final FacesCell fcell, final Cell cell, final String fId) {
		try {
			String chartId = parent.getCharsData().getChartPositionMap()
					.get(fId);
			if (chartId != null) {
				BufferedImage img = parent.getCharsData().getChartsMap()
						.get(chartId);
				if (img != null) {
					fcell.setContainChart(true);
					fcell.setChartId(chartId);
					fcell.setChartStyle(PicturesUtility.generateChartStyle(
							sheet1, fcell, cell, chartId,
							parent.getCharsData().getChartAnchorsMap()));
				}
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE,
					"setupFacesCell Charts error = " + ex.getMessage(), ex);
		}
	}

	/**
	 * Setup faces cell picture.
	 *
	 * @param sheet1
	 *            the sheet 1
	 * @param fcell
	 *            the fcell
	 * @param cell
	 *            the cell
	 * @param fId
	 *            the f id
	 */
	private void setupFacesCellPicture(final Sheet sheet1,
			final FacesCell fcell, final Cell cell, final String fId) {
		try {
			Picture pic = parent.getPicturesMap().get(fId);
			if (pic != null) {
				fcell.setContainPic(true);
				fcell.setPictureId(fId);
				fcell.setPictureStyle(PicturesUtility
						.generatePictureStyle(sheet1, fcell, cell, pic));
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE,
					"setupFacesCell Picture error = " + ex.getMessage(),
					ex);
		}
	}

}


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

package org.tiefaces.components.websheet.chart;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.poi.POIXMLDocumentPart.RelationPart;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFChart;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.tiefaces.common.TieConstants;
import org.tiefaces.components.websheet.TieWebSheetBean;
import org.tiefaces.components.websheet.dataobjects.AnchorSize;
import org.tiefaces.components.websheet.dataobjects.ParsedCell;
import org.tiefaces.components.websheet.dataobjects.XColor;
import org.tiefaces.components.websheet.utility.CellUtility;
import org.tiefaces.components.websheet.utility.ChartUtility;
import org.tiefaces.components.websheet.utility.ColorUtility;
import org.tiefaces.components.websheet.utility.PicturesUtility;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.ui.RectangleEdge;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.title.LegendTitle;

/**
 * The Class ChartHelper.
 *
 * @author Jason Jiang
 */
public class ChartHelper {

	/** instance to parent websheet bean. */
	private TieWebSheetBean parent = null;

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

	/**
	 * Instantiates a new chart helper.
	 *
	 * @param pParent
	 *            parent websheet bean
	 */
	public ChartHelper(final TieWebSheetBean pParent) {
		this.parent = pParent;
		LOG.fine("ChartHelper Constructor");
	}

	/**
	 * initial charts map for current workbook.
	 */
	public final void loadChartsMap() {

		this.initChartsMap(parent.getWb());
	}

	/**
	 * initial chart map for specified workbook.
	 *
	 * @param wb
	 *            specified workbook.
	 */
	private void initChartsMap(final Workbook wb) {
		try {
			if (wb instanceof XSSFWorkbook) {
				initXSSFChartsMap((XSSFWorkbook) wb, parent.getCharsData());
			}
		} catch (Exception e) {
			LOG.log(Level.SEVERE, "getChartsMap Error Exception = "
					+ e.getLocalizedMessage(), e);
		}
	}

	/**
	 * return cell Value for paredCell. ParsedCell contain sheetName/row/col
	 * which normally is parsed from String like: Sheet1!B2 .
	 *
	 * @param pCell
	 *            parsed cell.
	 * @return cell string value without format.
	 */
	public final String getParsedCellValue(final ParsedCell pCell) {

		String result = "";
		try {
			Cell poiCell = parent.getWb().getSheet(pCell.getSheetName())
					.getRow(pCell.getRow()).getCell(pCell.getCol());
			result = CellUtility.getCellValueWithoutFormat(poiCell);

		} catch (Exception ex) {
			LOG.log(Level.FINE,
					"error getParsedCellValue :" + ex.getLocalizedMessage(),
					ex);
		}
		return result;

	}

	/**
	 * create default category dataset for JfreeChart with giving chartData.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @return DefaultCategoryDataset for jfreechart.
	 */

	private DefaultPieDataset createPieDataset(final ChartData chartData) {
		DefaultPieDataset dataset = new DefaultPieDataset();
		List<ParsedCell> categoryList = chartData.getCategoryList();
		for (ChartSeries chartSeries : chartData.getSeriesList()) {
			List<ParsedCell> valueList = chartSeries.getValueList();
			for (int i = 0; i < categoryList.size(); i++) {
				try {
					String sCategory = getParsedCellValue(
							categoryList.get(i));
					String sValue = getParsedCellValue(valueList.get(i));
					dataset.setValue(sCategory, Double.parseDouble(sValue));
				} catch (Exception ex) {
					LOG.log(Level.FINE, "error in creatPieDataset : "
							+ ex.getLocalizedMessage(), ex);
				}
			}
		}
		return dataset;

	}

	/**
	 * return pie chart title from chartData.
	 *
	 * @param chartData
	 *            ChartData object.
	 * @return title (String).
	 */
	private String getPieTitle(final ChartData chartData) {
		for (ChartSeries chartSeries : chartData.getSeriesList()) {
			if (chartSeries != null) {
				return getParsedCellValue(chartSeries.getSeriesLabel());
			}
		}
		return "";

	}

	/**
	 * Create jfree chart.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @return jfree line chart.
	 */

	private JFreeChart createChart(final ChartData chartData) {

		return chartData.getType().createChart(this, chartData);

	}

	/**
	 * Set color of series.
	 *
	 * @param chart
	 *            JFreeChart.
	 * @param seriesIndex
	 *            Index of series to set color of (0 = first series)
	 * @param style
	 *            One of STYLE_xxx.
	 */
	public final void setSeriesStyle(final JFreeChart chart,
			final int seriesIndex, final String style) {
		if (chart != null && style != null) {
			BasicStroke stroke = ChartUtility.toStroke(style);

			Plot plot = chart.getPlot();
			if (plot instanceof CategoryPlot) {
				CategoryPlot categoryPlot = chart.getCategoryPlot();
				CategoryItemRenderer cir = categoryPlot.getRenderer();
				try {
					cir.setSeriesStroke(seriesIndex, stroke); // series line
																// style
				} catch (Exception e) {
					LOG.log(Level.SEVERE,
							"Error setting style '" + style
									+ "' for series '"
									+ Integer.toString(seriesIndex)
									+ "' of chart '" + chart.toString()
									+ "': " + e.getLocalizedMessage(),
							e);
				}
			} else if (plot instanceof XYPlot) {
				XYPlot xyPlot = chart.getXYPlot();
				XYItemRenderer xyir = xyPlot.getRenderer();
				try {
					xyir.setSeriesStroke(seriesIndex, stroke); // series line
																// style
				} catch (Exception e) {
					LOG.log(Level.SEVERE,
							"Error setting style '" + style
									+ "' for series '"
									+ Integer.toString(seriesIndex)
									+ "' of chart '" + chart.toString()
									+ "': " + e.getLocalizedMessage(),
							e);
				}
			} else {
				LOG.log(Level.FINE, "setSeriesColor() unsupported plot: {}",
						plot.toString());
			}
		}
	}

	/**
	 * Create jfree line chart.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @return jfree line chart.
	 */
	public JFreeChart createLineChart(final ChartData chartData) {

		// create the chart...
		final JFreeChart chart = ChartFactory.createLineChart(
				chartData.getTitle(), // chart title
				chartData.getCatAx().getTitle(), // x axis label
				chartData.getValAx().getTitle(), // y axis label
				createDataset(chartData), // data
				PlotOrientation.VERTICAL, // orientation
				true, // include legend
				false, // tooltips
				false // urls
		);

		setupStyle(chart, chartData);

		return chart;

	}

	/**
	 * Create jfree bar chart.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @return jfree line chart.
	 */
	public JFreeChart createAreaChart(final ChartData chartData) {
		PlotOrientation orientation = PlotOrientation.VERTICAL;
		// create the chart...
		final JFreeChart chart = ChartFactory.createAreaChart(
				chartData.getTitle(), // chart title
				chartData.getCatAx().getTitle(), // x axis label
				chartData.getValAx().getTitle(), // y axis label
				createDataset(chartData), // data
				orientation, true, // include legend
				false, // tooltips
				false // urls
		);

		setupStyle(chart, chartData);

		return chart;

	}

	/**
	 * create stacked area chart.
	 *
	 * @param chartData
	 *            chart data.
	 * @return jfree chart.
	 */
	public JFreeChart createStackedAreaChart(final ChartData chartData) {
		PlotOrientation orientation = PlotOrientation.VERTICAL;
		// create the chart...
		final JFreeChart chart = ChartFactory.createStackedAreaChart(
				chartData.getTitle(), // chart title
				chartData.getCatAx().getTitle(), // x axis label
				chartData.getValAx().getTitle(), // y axis label
				createDataset(chartData), // data
				orientation, true, // include legend
				false, // tooltips
				false // urls
		);

		setupStyle(chart, chartData);

		return chart;

	}

	/**
	 * Create jfree bar chart.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @param vertical
	 *            chart orientation.
	 * @return jfree line chart.
	 */
	public JFreeChart createBarChart(final ChartData chartData,
			final boolean vertical) {

		PlotOrientation orientation = PlotOrientation.VERTICAL;
		if (!vertical) {
			orientation = PlotOrientation.HORIZONTAL;
		}
		// create the chart...
		final JFreeChart chart = ChartFactory.createBarChart(
				chartData.getTitle(), // chart title
				chartData.getCatAx().getTitle(), // x axis label
				chartData.getValAx().getTitle(), // y axis label
				createDataset(chartData), // data
				orientation, true, // include legend
				false, // tooltips
				false // urls
		);

		setupBarStyle(chart, chartData);

		return chart;

	}

	/**
	 * Create jfree bar chart.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @param vertical
	 *            chart orientation.
	 * @return jfree line chart.
	 */
	public JFreeChart createStackedBarChart(final ChartData chartData,
			final boolean vertical) {

		PlotOrientation orientation = PlotOrientation.VERTICAL;
		if (!vertical) {
			orientation = PlotOrientation.HORIZONTAL;
		}
		// create the chart...
		final JFreeChart chart = ChartFactory.createStackedBarChart(
				chartData.getTitle(), // chart title
				chartData.getCatAx().getTitle(), // x axis label
				chartData.getValAx().getTitle(), // y axis label
				createDataset(chartData), // data
				orientation, true, // include legend
				false, // tooltips
				false // urls
		);

		setupBarStyle(chart, chartData);

		return chart;

	}

	/**
	 * Create jfree bar chart.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @return jfree line chart.
	 */
	public JFreeChart createPieChart(final ChartData chartData) {

		// create the chart...
		final JFreeChart chart = ChartFactory.createPieChart(
				getPieTitle(chartData), // chart title
				createPieDataset(chartData), // data
				true, // include legend
				false, // tooltips
				false // urls
		);

		setupPieStyle(chart, chartData);

		return chart;

	}

	/**
	 * create pie 3d chart.
	 *
	 * @param chartData
	 *            chart data.
	 * @return jfreechart.
	 */
	public JFreeChart createPie3DChart(final ChartData chartData) {

		// create the chart...
		final JFreeChart chart = ChartFactory.createPieChart3D(
				getPieTitle(chartData), // chart title
				createPieDataset(chartData), // data
				true, // include legend
				false, // tooltips
				false // urls
		);

		setupPieStyle(chart, chartData);

		return chart;

	}

	/**
	 * Create jfree bar chart.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @param vertical
	 *            chart orientation.
	 * @return jfree line chart.
	 */
	public JFreeChart createBarChart3D(final ChartData chartData,
			final boolean vertical) {

		PlotOrientation orientation = PlotOrientation.VERTICAL;
		if (!vertical) {
			orientation = PlotOrientation.HORIZONTAL;
		}
		// create the chart...
		final JFreeChart chart = ChartFactory.createBarChart3D(
				chartData.getTitle(), // chart title
				chartData.getCatAx().getTitle(), // x axis label
				chartData.getValAx().getTitle(), // y axis label
				createDataset(chartData), // data
				orientation, true, // include legend
				false, // tooltips
				false // urls
		);

		setupBarStyle(chart, chartData);

		return chart;

	}

	/**
	 * Create jfree bar chart.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @param vertical
	 *            chart orientation.
	 * @return jfree line chart.
	 */
	public JFreeChart createStackedBarChart3D(final ChartData chartData,
			final boolean vertical) {

		PlotOrientation orientation = PlotOrientation.VERTICAL;
		if (!vertical) {
			orientation = PlotOrientation.HORIZONTAL;
		}
		// create the chart...
		final JFreeChart chart = ChartFactory.createStackedBarChart3D(
				chartData.getTitle(), // chart title
				chartData.getCatAx().getTitle(), // x axis label
				chartData.getValAx().getTitle(), // y axis label
				createDataset(chartData), // data
				orientation, true, // include legend
				false, // tooltips
				false // urls
		);

		setupBarStyle(chart, chartData);

		return chart;

	}

	/**
	 * finalize the style for jfreechart. The default setting is different from
	 * jfreechart and Excel. We try to minimize the difference.
	 *
	 * @param chart
	 *            jfreechart.
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 */

	private void setupStyle(final JFreeChart chart,
			final ChartData chartData) {
		CategoryPlot plot = (CategoryPlot) chart.getPlot();
		List<ChartSeries> seriesList = chartData.getSeriesList();
		BasicStroke bLine = new BasicStroke(2.0f);
		for (int i = 0; i < seriesList.size(); i++) {
			Color cColor = ColorUtility.xssfClrToClr(
					seriesList.get(i).getSeriesColor().getXssfColor());
			plot.getRenderer().setSeriesPaint(i, cColor);
			plot.getRenderer().setSeriesStroke(i, bLine);
		}
		plot.setBackgroundPaint(ColorUtility
				.xssfClrToClr(chartData.getBgColor().getXssfColor()));

		// below are modifications for default setting in excel chart
		// to-do: need read setting from xml in future
		plot.setOutlineVisible(false);
		plot.setRangeGridlinesVisible(true);
		plot.setRangeGridlinePaint(Color.BLACK);
		plot.setRangeGridlineStroke(
				new BasicStroke(TieConstants.DEFAULT_BASIC_STROKE));
		plot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT);
		chart.setBackgroundPaint(Color.WHITE);
		LegendTitle legend = chart.getLegend();
		legend.setPosition(RectangleEdge.RIGHT);
		legend.setFrame(BlockBorder.NONE);

	}

	/**
	 * finalize the style for jfreechart. The default setting is different from
	 * jfreechart and Excel. We try to minimize the difference.
	 *
	 * @param chart
	 *            jfreechart.
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 */

	private void setupPieStyle(final JFreeChart chart,
			final ChartData chartData) {
		PiePlot plot = (PiePlot) chart.getPlot();
		List<ChartSeries> seriesList = chartData.getSeriesList();
		List<ParsedCell> categoryList = chartData.getCategoryList();
		BasicStroke bLine = new BasicStroke(2.0f);
		for (int i = 0; i < seriesList.size(); i++) {
			ChartSeries chartSeries = seriesList.get(i);
			List<XColor> valueColorList = chartSeries.getValueColorList();
			for (int index = 0; index < categoryList.size(); index++) {
				try {
					String sCategory = getParsedCellValue(
							categoryList.get(index));
					Color cColor = ColorUtility.xssfClrToClr(
							valueColorList.get(index).getXssfColor());
					plot.setSectionPaint(sCategory, cColor);
					plot.setSectionOutlineStroke(sCategory, bLine);
				} catch (Exception ex) {
					LOG.log(Level.FINE, "SetupPieStyle error = "
							+ ex.getLocalizedMessage(), ex);
				}
			}

		}
		plot.setBackgroundPaint(ColorUtility
				.xssfClrToClr(chartData.getBgColor().getXssfColor()));

		// below are modifications for default setting in excel chart
		// to-do: need read setting from xml in future

		plot.setOutlineVisible(false);
		plot.setLegendItemShape(
				new Rectangle(TieConstants.DEFAULT_LEGENT_ITEM_SHAPE_WIDTH,
						TieConstants.DEFAULT_LEGENT_ITEM_SHAPE_HEIGHT));
		chart.setBackgroundPaint(Color.WHITE);
		LegendTitle legend = chart.getLegend();
		legend.setPosition(RectangleEdge.RIGHT);
		legend.setFrame(BlockBorder.NONE);

	}

	/**
	 * finalize the style for barchart. This will call setupStyle common first.
	 *
	 * @param chart
	 *            jfreechart.
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 */

	private void setupBarStyle(final JFreeChart chart,
			final ChartData chartData) {
		setupStyle(chart, chartData);
		CategoryPlot plot = (CategoryPlot) chart.getPlot();
		BarRenderer renderer = (BarRenderer) plot.getRenderer();
		renderer.setBarPainter(new StandardBarPainter());
		renderer.setItemMargin(TieConstants.DEFAULT_BAR_STYLE_ITEM_MARGIN);

		plot.setForegroundAlpha(
				TieConstants.DEFAULT_BARSTYLE_FOREGROUND_ALPHA);
	}

	/**
	 * create default category dataset for JfreeChart with giving chartData.
	 *
	 * @param chartData
	 *            contain information gathered from excel chart object.
	 * @return DefaultCategoryDataset for jfreechart.
	 */

	private DefaultCategoryDataset createDataset(
			final ChartData chartData) {
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		List<ParsedCell> categoryList = chartData.getCategoryList();
		for (ChartSeries chartSeries : chartData.getSeriesList()) {
			String seriesLabel = getParsedCellValue(
					chartSeries.getSeriesLabel());
			List<ParsedCell> valueList = chartSeries.getValueList();
			for (int i = 0; i < categoryList.size(); i++) {
				try {
					String sCategory = getParsedCellValue(
							categoryList.get(i));
					String sValue = getParsedCellValue(valueList.get(i));
					dataset.addValue(Double.parseDouble(sValue),
							seriesLabel, sCategory);
				} catch (Exception ex) {
					LOG.log(Level.FINE, "error in creatDataset : "
							+ ex.getLocalizedMessage(), ex);
				}
			}
		}
		return dataset;

	}

	/**
	 * initial chart map for XSSF format file. XSSF file is actually the only
	 * format in POI support chart object.
	 *
	 * @param wb
	 *            xssf workbook.
	 * @param chartsData
	 *            the charts data
	 */
	private void initXSSFChartsMap(final XSSFWorkbook wb,
			final ChartsData chartsData) {

		initAnchorsMap(wb, chartsData);
		Map<String, ClientAnchor> anchorMap = chartsData
				.getChartAnchorsMap();

		Map<String, BufferedImage> chartMap = chartsData.getChartsMap();
		Map<String, ChartData> chartDataMap = chartsData.getChartDataMap();
		chartMap.clear();
		chartDataMap.clear();

		for (int i = 0; i < wb.getNumberOfSheets(); i++) {
			XSSFSheet sheet = wb.getSheetAt(i);
			XSSFDrawing drawing = sheet.createDrawingPatriarch();
			List<XSSFChart> charts = drawing.getCharts();
			if ((charts != null) && (!charts.isEmpty())) {
				for (XSSFChart chart : charts) {
					generateSingleXSSFChart(chart,
							getChartIdFromParent(chart,
									sheet.getSheetName()),
							sheet, anchorMap, chartMap, chartDataMap);
				}
			}
		}

	}

	/**
	 * Gets the chart id from parent.
	 *
	 * @param chart
	 *            the chart
	 * @param sheetName
	 *            the sheet name
	 * @return the chart id from parent
	 */
	private String getChartIdFromParent(final XSSFChart chart,
			final String sheetName) {
		if (chart.getParent() != null) {
			for (RelationPart rp : chart.getParent().getRelationParts()) {
				if (rp.getDocumentPart() == chart) {
					return sheetName + "!" + rp.getRelationship().getId();
				}
			}
		}
		return null;
	}

	/**
	 * initial anchors map for specified workbook. Excel put the chart position
	 * information in draw.xml instead of chart.xml. anchors map contains the
	 * information getting from draw.xml.
	 *
	 * @param wb
	 *            specified workbook.
	 * @param chartsData
	 *            the charts data
	 */
	private void initAnchorsMap(final Workbook wb,
			final ChartsData chartsData) {
		try {
			if (wb instanceof XSSFWorkbook) {
				ChartUtility.initXSSFAnchorsMap((XSSFWorkbook) wb,
						chartsData);
			}
		} catch (Exception e) {
			LOG.log(Level.SEVERE,
					"Web Form getAnchorsMap Error Exception = "
							+ e.getLocalizedMessage(),
					e);
		}
	}

	/**
	 * Generate single XSSF chart.
	 *
	 * @param chart
	 *            the chart
	 * @param chartId
	 *            the chart id
	 * @param sheet
	 *            the sheet
	 * @param anchorMap
	 *            the anchor map
	 * @param chartMap
	 *            the chart map
	 * @param chartDataMap
	 *            the chart data map
	 */
	private void generateSingleXSSFChart(final XSSFChart chart,
			final String chartId, final XSSFSheet sheet,
			final Map<String, ClientAnchor> anchorMap,
			final Map<String, BufferedImage> chartMap,
			final Map<String, ChartData> chartDataMap) {
		ClientAnchor anchor;
		try {
			anchor = anchorMap.get(chartId);
			if (anchor != null) {
				ChartData chartData = ChartUtility
						.initChartDataFromXSSFChart(chartId, chart,
								(XSSFWorkbook) parent.getWb());
				chartDataMap.put(chartId, chartData);
				JFreeChart jchart = createChart(chartData);
				if (jchart != null) {
					AnchorSize anchorSize = PicturesUtility
							.getAnchorSize(sheet, null, null, anchor);
					BufferedImage img = jchart.createBufferedImage(
							anchorSize.getWidth(), anchorSize.getHeight());
					chartMap.put(chartId, img);
				}
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, "generate chart for " + chartId
					+ " error = " + ex.getLocalizedMessage(), ex);
		}
	}

}


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

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;

import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaRenderer;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.formula.ptg.Ptg;
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.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
import org.tiefaces.common.TieConstants;
import org.tiefaces.components.websheet.configuration.Command;
import org.tiefaces.components.websheet.configuration.ConfigBuildRef;
import org.tiefaces.components.websheet.configuration.ConfigRange;
import org.tiefaces.components.websheet.configuration.ConfigRangeAttrs;
import org.tiefaces.components.websheet.configuration.ExpressionEngine;
import org.tiefaces.components.websheet.configuration.RowsMapping;
import org.tiefaces.components.websheet.configuration.ShiftFormulaRef;
import org.tiefaces.components.websheet.dataobjects.TieCommandAlias;
import org.tiefaces.exception.EvaluationException;

/**
 * The Class ConfigurationHelper.
 */
public final class ConfigurationUtility {

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

	/**
	 * hide constructor.
	 */
	private ConfigurationUtility() {
		// not called
	}

	/**
	 * Transform to collection object.
	 *
	 * @param engine
	 *            the engine
	 * @param collectionName
	 *            the collection name
	 * @param context
	 *            the context
	 * @return the collection
	 */
	@SuppressWarnings("rawtypes")
	public static Collection transformToCollectionObject(
			final ExpressionEngine engine, final String collectionName,
			final Map<String, Object> context) {
		Object collectionObject = engine.evaluate(collectionName, context);
		if (!(collectionObject instanceof Collection)) {
			throw new EvaluationException(
					collectionName + " expression is not a collection");
		}
		return (Collection) collectionObject;
	}

	/**
	 * Gets the full name from row.
	 *
	 * @param row
	 *            the row
	 * @return the full name from row
	 */
	public static String getFullNameFromRow(final Row row) {
		if (row != null) {
			Cell cell = row.getCell(TieConstants.HIDDEN_FULL_NAME_COLUMN);
			if (cell != null) {
				return cell.getStringCellValue();
			}
		}
		return null;
	}

	/**
	 * Re build upper level formula.
	 *
	 * @param configBuildRef
	 *            the config build ref
	 * @param actionFullName
	 *            the action full name
	 */
	public static void reBuildUpperLevelFormula(
			final ConfigBuildRef configBuildRef,
			final String actionFullName) {
		Map<Cell, String> cachedMap = configBuildRef.getCachedCells();
		Map<String, List<RowsMapping>> rowsMap = new HashMap<>();
		for (Map.Entry<Cell, String> entry : cachedMap.entrySet()) {
			Cell cell = entry.getKey();
			String originFormula = entry.getValue();
			if (originFormula != null) {
				setupUpperLevelFormula(cell, originFormula, actionFullName,
						rowsMap, configBuildRef);
			}
		}

	}

	/**
	 * Setup upper level formula.
	 *
	 * @param cell
	 *            cell.
	 * @param originFormula
	 *            originFormula.
	 * @param actionFullName
	 *            add full name.
	 * @param rowsMap
	 *            rowsmap.
	 * @param configBuildRef
	 *            config build ref.
	 */
	private static void setupUpperLevelFormula(final Cell cell,
			final String originFormula, final String actionFullName,
			final Map<String, List<RowsMapping>> rowsMap,
			final ConfigBuildRef configBuildRef) {
		String fullName = getFullNameFromRow(cell.getRow());
		// check wither it's upper level
		if (actionFullName.startsWith(fullName + ":")) {
			// get rows mapping for upper level row
			List<RowsMapping> currentRowsMappingList = rowsMap
					.get(fullName);
			if (currentRowsMappingList == null) {
				currentRowsMappingList = gatherRowsMappingByFullName(
						configBuildRef, fullName);
				rowsMap.put(fullName, currentRowsMappingList);
			}
			ShiftFormulaRef shiftFormulaRef = new ShiftFormulaRef(
					configBuildRef.getWatchList(), currentRowsMappingList);
			shiftFormulaRef.setFormulaChanged(0);
			buildCellFormulaForShiftedRows(configBuildRef.getSheet(),
					configBuildRef.getWbWrapper(), shiftFormulaRef, cell,
					originFormula);
			if (shiftFormulaRef.getFormulaChanged() > 0) {
				configBuildRef.getCachedCells().put(cell, originFormula);
			}

		}
	}

	/**
	 * Builds the cell formula for shifted rows.
	 *
	 * @param sheet
	 *            the sheet
	 * @param wbWrapper
	 *            the wb wrapper
	 * @param shiftFormulaRef
	 *            the shift formula ref
	 * @param cell
	 *            the cell
	 * @param originFormula
	 *            the origin formula
	 */
	public static void buildCellFormulaForShiftedRows(final Sheet sheet,
			final XSSFEvaluationWorkbook wbWrapper,
			final ShiftFormulaRef shiftFormulaRef, final Cell cell,
			final String originFormula) {
		// only shift when there's watchlist exist.
		if ((shiftFormulaRef.getWatchList() != null)
				&& (!shiftFormulaRef.getWatchList().isEmpty())) {
			Ptg[] ptgs = FormulaParser.parse(originFormula, wbWrapper,
					FormulaType.CELL,
					sheet.getWorkbook().getSheetIndex(sheet));
			Ptg[] convertedFormulaPtg = ShiftFormulaUtility
					.convertSharedFormulas(ptgs, shiftFormulaRef);
			if (shiftFormulaRef.getFormulaChanged() > 0) {
				// only change formula when indicator is true
				cell.setCellFormula(FormulaRenderer
						.toFormulaString(wbWrapper, convertedFormulaPtg));

			}
		}
	}

	/**
	 * Gather rows mapping by full name.
	 *
	 * @param configBuildRef
	 *            the config build ref
	 * @param fullName
	 *            the full name
	 * @return the list
	 */
	public static List<RowsMapping> gatherRowsMappingByFullName(
			final ConfigBuildRef configBuildRef, final String fullName) {
		List<RowsMapping> list = new ArrayList<>();
		Map<String, ConfigRangeAttrs> shiftMap = configBuildRef
				.getShiftMap();
		for (Map.Entry<String, ConfigRangeAttrs> entry : shiftMap
				.entrySet()) {
			String fname = entry.getKey();
			if (fname.startsWith(fullName + ":")
					|| fname.equals(fullName)) {
				ConfigRangeAttrs attrs = entry.getValue();
				list.add(attrs.getUnitRowsMapping());
			}
		}
		return list;
	}

	/**
	 * Increase index number in shift map.
	 *
	 * @param shiftMap
	 *            the shift map
	 * @param changeMap
	 *            the change map
	 */
	public static void changeIndexNumberInShiftMap(
			final Map<String, ConfigRangeAttrs> shiftMap,
			final Map<String, String> changeMap) {
		for (Map.Entry<String, String> entry : changeMap.entrySet()) {
			String key = entry.getKey();
			String newKey = entry.getValue();
			ConfigRangeAttrs attrs = shiftMap.get(key);
			if (attrs != null) {
				shiftMap.remove(key);
				shiftMap.put(newKey, attrs);
			}
		}
	}

	/**
	 * Increase upper level final length.
	 *
	 * @param shiftMap
	 *            the shift map
	 * @param addedFullName
	 *            the added full name
	 * @param increasedLength
	 *            the increased length
	 */
	public static void changeUpperLevelFinalLength(
			final Map<String, ConfigRangeAttrs> shiftMap,
			final String addedFullName, final int increasedLength) {
		String[] parts = addedFullName.split(":");
		StringBuilder fname = new StringBuilder();
		for (int i = 0; i < (parts.length - 1); i++) {
			if (i == 0) {
				fname.append(parts[i]);
			} else {
				fname.append(":").append(parts[i]);
			}
			String sname = fname.toString();
			shiftMap.get(sname).setFinalLength(
					shiftMap.get(sname).getFinalLength() + increasedLength);
		}
	}

	/**
	 * Change index number in hidden column.
	 *
	 * @param configBuildRef
	 *            the config build ref
	 * @param startRowIndex
	 *            the start row index
	 * @param fullName
	 *            the full name
	 * @param changeMap
	 *            the change map
	 * @param steps
	 *            the steps ( 1 add -1 delete ).
	 */
	public static void changeIndexNumberInHiddenColumn(
			final ConfigBuildRef configBuildRef, final int startRowIndex,
			final String fullName, final Map<String, String> changeMap,
			final int steps) {
		String searchName = fullName.substring(0,
				fullName.lastIndexOf('.') + 1);
		Sheet sheet = configBuildRef.getSheet();
		for (int i = startRowIndex; i <= sheet.getLastRowNum(); i++) {
			Row row = sheet.getRow(i);
			String fname = getFullNameFromRow(row);
			if ((fname != null) && (fname.indexOf(searchName) >= 0)) {
				int sindex = fname.indexOf(searchName);
				String snum = fname.substring(sindex + searchName.length());
				int sufindex = snum.indexOf(':');
				String suffix = "";
				if (sufindex > 0) {
					snum = snum.substring(0, sufindex);
					suffix = ":";
				}
				int increaseNum = Integer.parseInt(snum) + steps;
				String realFullName = fname.substring(sindex);
				String changeName = fname.replace(
						searchName + snum + suffix,
						searchName + increaseNum + suffix);
				if (changeMap.get(realFullName) == null) {
					changeMap.put(realFullName,
							changeName.substring(sindex));
				}
				setFullNameInHiddenColumn(row, changeName);
			} else {
				return;
			}
		}
	}

	/**
	 * Decrease index number in hidden column.
	 *
	 * @param configBuildRef
	 *            the config build ref
	 * @param startRowIndex
	 *            the start row index
	 * @param fullName
	 *            the full name
	 * @param changeMap
	 *            the change map
	 */
	public static void decreaseIndexNumberInHiddenColumn(
			final ConfigBuildRef configBuildRef, final int startRowIndex,
			final String fullName, final Map<String, String> changeMap) {
		String searchName = fullName.substring(0,
				fullName.lastIndexOf('.') + 1);
		Sheet sheet = configBuildRef.getSheet();
		for (int i = startRowIndex; i <= sheet.getLastRowNum(); i++) {
			Row row = sheet.getRow(i);
			String fname = getFullNameFromRow(row);
			if ((fname != null) && (fname.indexOf(searchName) >= 0)) {
				int sindex = fname.indexOf(searchName);
				String snum = fname.substring(sindex + searchName.length());
				int sufindex = snum.indexOf(':');
				String suffix = "";
				if (sufindex > 0) {
					snum = snum.substring(0, sufindex);
					suffix = ":";
				}
				int increaseNum = Integer.parseInt(snum) - 1;
				String realFullName = fname.substring(sindex);
				String changeName = fname.replace(
						searchName + snum + suffix,
						searchName + increaseNum + suffix);
				if (changeMap.get(realFullName) == null) {
					changeMap.put(realFullName,
							changeName.substring(sindex));
				}
				setFullNameInHiddenColumn(row, changeName);
			}
		}
	}

	/**
	 * Sets the full name in hidden column.
	 *
	 * @param row
	 *            the row
	 * @param fullName
	 *            the full name
	 */
	public static void setFullNameInHiddenColumn(final Row row,
			final String fullName) {
		Cell cell = row.getCell(TieConstants.HIDDEN_FULL_NAME_COLUMN,
				MissingCellPolicy.CREATE_NULL_AS_BLANK);

		cell.setCellValue(fullName);
	}

	/**
	 * Gets the original row num in hidden column.
	 *
	 * @param row
	 *            the row
	 * @return the original row num in hidden column
	 */
	public static int getOriginalRowNumInHiddenColumn(final Row row) {
		if (row != null) {
			Cell cell = row.getCell(
					TieConstants.HIDDEN_ORIGIN_ROW_NUMBER_COLUMN,
					MissingCellPolicy.CREATE_NULL_AS_BLANK);
			String rowNum = cell.getStringCellValue();
			try {
				if ((rowNum != null) && (!rowNum.isEmpty())
						&& (WebSheetUtility.isNumeric(rowNum))) {
					return Integer.parseInt(rowNum);
				}

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

			}
		}
		return -1;
	}

	/**
	 * Sets the original row num in hidden column.
	 *
	 * @param row
	 *            the row.
	 * @param rowNum
	 *            row number.
	 */
	public static void setOriginalRowNumInHiddenColumn(final Row row,
			final int rowNum) {
		Cell cell = row.getCell(
				TieConstants.HIDDEN_ORIGIN_ROW_NUMBER_COLUMN,
				MissingCellPolicy.CREATE_NULL_AS_BLANK);
		cell.setCellValue(Integer.toString(rowNum));
		cell.setCellType(CellType.STRING);
	}

	/**
	 * Find parent rows mapping from shift map.
	 *
	 * @param parts
	 *            the parts
	 * @param shiftMap
	 *            the shift map
	 * @return the list
	 */
	public static List<RowsMapping> findParentRowsMappingFromShiftMap(
			final String[] parts,
			final Map<String, ConfigRangeAttrs> shiftMap) {

		StringBuilder fullName = new StringBuilder();
		List<RowsMapping> rowsMappingList = new ArrayList<>();
		/**
		 * skip first one and last one. first one is line no. last one is it's
		 * self.
		 */
		for (int i = 1; i < parts.length - 1; i++) {
			String part = parts[i];
			if (fullName.length() == 0) {
				fullName.append(part);
			} else {
				fullName.append(":" + part);
			}
			if (fullName.length() > 0) {
				ConfigRangeAttrs rangeAttrs = shiftMap
						.get(fullName.toString());
				if (rangeAttrs != null) {
					rowsMappingList.add(rangeAttrs.getUnitRowsMapping());
				}
			}
		}
		return rowsMappingList;
	}

	/**
	 * Find child rows mapping from shift map.
	 *
	 * @param fullName
	 *            the full name
	 * @param shiftMap
	 *            the shift map
	 * @return the list
	 */
	public static List<RowsMapping> findChildRowsMappingFromShiftMap(
			final String fullName,
			final NavigableMap<String, ConfigRangeAttrs> shiftMap) {

		List<RowsMapping> rowsMappingList = new ArrayList<>();
		NavigableMap<String, ConfigRangeAttrs> tailmap = shiftMap
				.tailMap(fullName, false);
		for (Map.Entry<String, ConfigRangeAttrs> entry : tailmap
				.entrySet()) {
			String key = entry.getKey();
			// check it's children
			if (key.startsWith(fullName)) {
				rowsMappingList.add(entry.getValue().getUnitRowsMapping());
			} else {
				break;
			}
		}
		return rowsMappingList;
	}

	/**
	 * Find item in collection.
	 *
	 * @param collection
	 *            the collection
	 * @param index
	 *            the index
	 * @return the object
	 */
	@SuppressWarnings("rawtypes")
	public static Object findItemInCollection(final Collection collection,
			final int index) {
		if (index >= 0) {
			if (collection instanceof List) {
				List list = (List) collection;
				return list.get(index);
			}
			int i = 0;
			for (Object object : collection) {
				if (i == index) {
					return object;
				}
				i++;
			}

		}
		return null;
	}

	/**
	 * Builds the current range.
	 *
	 * @param sourceConfigRange
	 *            the source config range
	 * @param sheet
	 *            the sheet
	 * @param insertPosition
	 *            the insert position
	 * @return the config range
	 */
	public static ConfigRange buildCurrentRange(
			final ConfigRange sourceConfigRange, final Sheet sheet,
			final int insertPosition) {
		ConfigRange current = new ConfigRange(sourceConfigRange);
		int shiftNum = insertPosition
				- sourceConfigRange.getFirstRowAddr().getRow();
		current.shiftRowRef(sheet, shiftNum);
		return current;
	}

	/**
	 * Whether the row is static. This only check rowIndex against original
	 * template.
	 *
	 * @param sourceConfigRange
	 *            the source config range
	 * @param rowIndex
	 *            the row index
	 * @return true is static false is not.
	 */
	public static boolean isStaticRow(final ConfigRange sourceConfigRange,
			final int rowIndex) {
		if (sourceConfigRange.getCommandList() != null) {
			for (int i = 0; i < sourceConfigRange.getCommandList()
					.size(); i++) {
				Command command = sourceConfigRange.getCommandList().get(i);
				if ((rowIndex >= command.getConfigRange().getFirstRowAddr()
						.getRow())
						&& (rowIndex < (command.getConfigRange()
								.getLastRowPlusAddr().getRow()))) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Whether the row is static.This check row after shifted.
	 *
	 * @param sourceConfigRange
	 *            the source config range
	 * @param row
	 *            the row for check.
	 * @return true is static false is not.
	 */
	public static boolean isStaticRowRef(
			final ConfigRange sourceConfigRange, final Row row) {
		if (sourceConfigRange.getCommandList() != null) {
			for (int i = 0; i < sourceConfigRange.getCommandList()
					.size(); i++) {
				Command command = sourceConfigRange.getCommandList().get(i);
				int rowIndex = row.getRowNum();
				if ((rowIndex >= command.getTopRow())
						&& (rowIndex < (command.getTopRow()
								+ command.getFinalLength()))) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Remove last each command index from full name. e.g. for
	 * F.departments:E.department.1:E.employee.2 will return
	 * F.departments:E.department.1:E.employee
	 *
	 * Reason for this is for last layer collection. e.g. E.employee.1 and
	 * E.employee.2 The E.emloyee is the same. *
	 *
	 * @param fullName
	 *            the full name
	 * @return the full data collect name from full name
	 */
	public static String getFullDataCollectNameFromFullName(
			final String fullName) {
		if (fullName == null) {
			return "";
		}

		int lastEachCommandPos = fullName
				.lastIndexOf(TieConstants.EACH_COMMAND_FULL_NAME_PREFIX);
		if (lastEachCommandPos < 0) {
			return "";
		}
		int lastEachCommandIndexPos = fullName.indexOf('.',
				lastEachCommandPos
						+ TieConstants.EACH_COMMAND_FULL_NAME_PREFIX
								.length());
		if (lastEachCommandIndexPos < 0) {
			return fullName;
		}
		return fullName.substring(0, lastEachCommandIndexPos);
	}

	// This method mainly doing 2 things
	// 1. covert $A to $A$rowIndex
	// 2. Get value of $A$rowIndex and replace it in the string
	// i.e. $value >= $E
	// first $value has been taken cared before to actual value like 100
	// Here change $E to $E$8, Then get $E$8 value. Replace it in string like
	/**
	 * Replace expression with cell value.
	 *
	 * @param attrValue
	 *            the attr value
	 * @param rowIndex
	 *            the row index
	 * @param sheet
	 *            the sheet
	 * @return the string
	 */
	// 100 >= 80
	public static String replaceExpressionWithCellValue(
			final String attrValue, final int rowIndex, final Sheet sheet) {

		int ibegin = 0;
		int ifind;
		int inameEnd;
		String tempStr;
		String findStr;
		String replaceStr;
		String returnStr = attrValue;
		while ((ifind = attrValue.indexOf(TieConstants.CELL_ADDR_PRE_FIX,
				ibegin)) > 0) {
			inameEnd = ParserUtility.findFirstNonCellNamePosition(attrValue, ifind);
			if (inameEnd > 0) {
				findStr = attrValue.substring(ifind, inameEnd);
			} else {
				findStr = attrValue.substring(ifind);
			}
			if (findStr.indexOf(TieConstants.CELL_ADDR_PRE_FIX, 1) < 0) {
				// only $A
				tempStr = findStr + TieConstants.CELL_ADDR_PRE_FIX
						+ (rowIndex + 1);
			} else {
				tempStr = findStr;
			}
			replaceStr = CellUtility.getCellValueWithoutFormat(
					WebSheetUtility.getCellByReference(tempStr, sheet));
			if (replaceStr == null) {
				replaceStr = "";
			}
			returnStr = attrValue.replace(findStr, replaceStr);

			ibegin = ifind + 1;

		}
		return returnStr;
	}

	/**
	 * Index merged region.
	 *
	 * @param sheet1
	 *            the sheet 1
	 * @return the map
	 */
	public static Map<String, CellRangeAddress> indexMergedRegion(
			final Sheet sheet1) {

		int numRegions = sheet1.getNumMergedRegions();
		Map<String, CellRangeAddress> cellRangeMap = new HashMap<>();
		for (int i = 0; i < numRegions; i++) {

			CellRangeAddress caddress = sheet1.getMergedRegion(i);
			if (caddress != null) {
				cellRangeMap.put(CellUtility.getCellIndexNumberKey(
						caddress.getFirstColumn(), caddress.getFirstRow()),
						caddress);
			}
		}
		return cellRangeMap;
	}

	/**
	 * Skipped region cells.
	 *
	 * @param sheet1
	 *            the sheet 1
	 * @return the list
	 */
	public static List<String> skippedRegionCells(final Sheet sheet1) {
		int numRegions = sheet1.getNumMergedRegions();
		List<String> skipCellList = new ArrayList<>();
		for (int i = 0; i < numRegions; i++) {

			CellRangeAddress caddress = sheet1.getMergedRegion(i);
			if (caddress != null) {
				addSkipCellToListInTheRegion(skipCellList, caddress);
			}
		}
		return skipCellList;
	}

	/**
	 * Add skipped cell into the list of a region.
	 *
	 * @param skipCellList
	 *            list.
	 * @param caddress
	 *            region.
	 */
	private static void addSkipCellToListInTheRegion(
			final List<String> skipCellList,
			final CellRangeAddress caddress) {
		for (int col = caddress.getFirstColumn(); col <= caddress
				.getLastColumn(); col++) {
			for (int row = caddress.getFirstRow(); row <= caddress
					.getLastRow(); row++) {
				if ((col == caddress.getFirstColumn())
						&& (row == caddress.getFirstRow())) {
					continue;
				}
				skipCellList
						.add(CellUtility.getCellIndexNumberKey(col, row));
			}
		}
	}

	/**
	 * Build Sheet Comment From command alias.
	 *
	 * @param sheet sheet.
	 * @param tieCommandAliasList 			list of ommand alias
	 */
	public static void buildSheetCommentFromAlias(Sheet sheet, List<TieCommandAlias> tieCommandAliasList) {

		if ((tieCommandAliasList == null)||(tieCommandAliasList.isEmpty())) {
			return;
		}
        for (Row row : sheet) {
            for (Cell cell : row) {
                buildCellCommentFromalias(tieCommandAliasList, cell);
            }
        }

	}

	/**
	 * Builds the cell comment fromalias.
	 *
	 * @param tieCommandAliasList the tie command alias list
	 * @param cell the cell
	 */
	private static void buildCellCommentFromalias(List<TieCommandAlias> tieCommandAliasList, Cell cell) {
		String value = CellUtility.getCellValueWithoutFormat(cell);
		if ((value!=null)&&(!value.isEmpty())) {
			for (TieCommandAlias alias : tieCommandAliasList) {
				Matcher matcher = alias.getPattern().matcher(value);
				if (matcher.find()) {
					CellUtility.createOrInsertComment(cell, alias.getCommand());
					if (alias.isRemove()) {
						CellUtility.setCellValue(cell, ParserUtility.removeCharsFromString(value, matcher.start(), matcher.end()));
					}
				}
			}
		}
	}

}