package com.wb.tool;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Iterator;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.json.JSONObject;

import com.wb.controls.ExtCombo;
import com.wb.util.StringUtil;
import com.wb.util.WebUtil;

public class ExcelForm {
	/**
	 * 获取Excel 2007+模板文件转换得到的Web脚本字符串。这些Web脚本字符串可应用于前端容器控件用于直接生成表单。
	 * @param params 数据来源的参数对象。
	 * @param excelFile Excel 2007+模板文件。
	 * @param sheetIndex 引用模板文件中的Sheet索引号。
	 * @param excelFormAlign 生成的表单对齐方式，有效值为left, center, right。
	 * @return 生成的Web脚本。
	 */
	public static String getHtml(JSONObject params, File excelFile, int sheetIndex, String excelFormAlign)
			throws Exception {
		if (excelFile.getName().toLowerCase().endsWith(".xls"))
			throw new IllegalArgumentException("Excel file version requires 2007+");
		FileInputStream is = new FileInputStream(excelFile);
		try {
			return getHtml(params, is, sheetIndex, excelFormAlign);
		} finally {
			is.close();
		}
	}

	/**
	 * 获取Excel 2007+模板文件转换得到的Web脚本字符串。这些Web脚本字符串可应用于前端容器控件用于直接生成表单或报表。
	 * @param params 数据来源的参数对象。
	 * @param inputStream Excel 2007+模板文件输入流。
	 * @param sheetIndex 引用模板文件中的Sheet索引号。
	 * @param excelFormAlign 生成的表单对齐方式，有效值为left, center, right。
	 * @return 生成的Web脚本。
	 */
	public static String getHtml(JSONObject params, InputStream inputStream, int sheetIndex, String excelFormAlign)
			throws Exception {
		XSSFWorkbook book;
		XSSFSheet sheet;
		XSSFRow row;
		XSSFCell cell;
		XSSFCellStyle style;
		Iterator<Row> rows;
		Iterator<Cell> cells;
		String value, alignHtml;
		StringBuilder tableHtml = new StringBuilder(), rowsHtml = new StringBuilder();
		int i, lastCol, lastRow, defaultHeight, tableWidth, columnWidth, columnIndex, rowIndex, spanXY[],
				maxColumnIndex;
		boolean requiredField, isNumericCell;
		Object mergeRegion[];

		book = new XSSFWorkbook(inputStream);
		try {
			sheet = book.getSheetAt(sheetIndex);
			com.wb.tool.ExcelObject.executeInstruction(sheet, params);
			maxColumnIndex = getMaxColumnIndex(sheet);
			defaultHeight = getPixcelHeight(sheet.getDefaultRowHeight());
			mergeRegion = getMergeRegion(sheet);
			rows = sheet.rowIterator();
			lastRow = 0;
			while (rows.hasNext()) {
				row = (XSSFRow) rows.next();
				rowIndex = row.getRowNum();
				fillTR(rowsHtml, lastRow, rowIndex, defaultHeight, maxColumnIndex);
				lastRow = rowIndex + 1;
				cells = row.cellIterator();
				rowsHtml.append("<tr height=\"");
				rowsHtml.append(getPixcelHeight(row.getHeight()));
				rowsHtml.append("px\">");
				lastCol = 0;
				requiredField = false;
				while (cells.hasNext()) {
					cell = (XSSFCell) cells.next();
					columnIndex = cell.getColumnIndex();
					spanXY = getSpanXY(mergeRegion, columnIndex, rowIndex);
					fillTD(rowsHtml, lastCol, columnIndex);
					lastCol = columnIndex + 1;
					//位于合并区其他单元格直接返回，不需要生成td
					if (spanXY[0] == -1)
						continue;
					style = cell.getCellStyle();
					if (spanXY[0] > 1)
						lastCol += spanXY[0];
					rowsHtml.append("<td");
					//位于合并区首个单元格需要指定rowspan colspan
					if (spanXY[0] > 1) {
						rowsHtml.append(" colspan=\"");
						rowsHtml.append(spanXY[0]);
						rowsHtml.append("\"");
					}
					if (spanXY[1] > 1) {
						rowsHtml.append(" rowspan=\"");
						rowsHtml.append(spanXY[1]);
						rowsHtml.append("\"");
					}
					rowsHtml.append(" style=\"white-space:nowrap;overflow:hidden;");
					isNumericCell = com.wb.tool.ExcelObject.isNumericCell(cell);
					rowsHtml.append(com.wb.tool.ExcelObject.getCellStyle(style, isNumericCell));
					rowsHtml.append("\">");
					value = com.wb.tool.ExcelObject.getDisplayText(cell);
					if (!isNumericCell) {
						value = WebUtil.replaceParams(params, value);
						if (value.startsWith("{") && value.endsWith("}"))
							value = createExpress(value, requiredField);
						else
							value = StringUtil.toHTML(value);
					}
					rowsHtml.append(value);
					rowsHtml.append("</td>");
					requiredField = value.startsWith("*");
				}
				fillTD(rowsHtml, lastCol, maxColumnIndex);
				rowsHtml.append("</tr>");
			}
			//添加表格和首行列的宽度信息
			tableWidth = 0;
			tableHtml.append("<tr style=\"height:0;\">");
			for (i = 0; i < maxColumnIndex; i++) {
				columnWidth = getPixcelWidth(sheet.getColumnWidth(i));
				tableHtml.append("<td style=\"border:none;padding:0;width:");
				tableHtml.append(columnWidth);
				tableHtml.append("px;\"></td>");
				tableWidth += columnWidth;
			}
			tableHtml.append("</tr>");
			//添加表格头
			if ("left".equals(excelFormAlign))
				alignHtml = "margin-left:8px;margin-right:auto;";
			else if ("right".equals(excelFormAlign))
				alignHtml = "margin-left:auto;margin-right:8px;";
			else
				alignHtml = "margin-left:auto;margin-right:auto;";
			tableHtml.insert(0,
					StringUtil.concat("<table style=\"margin-top:8px;margin-bottom:8px;", alignHtml,
							"table-layout:fixed;border-collapse:collapse;\" cellspacing=\"0\" cellpadding=\"5\" width=\"",
							Integer.toString(tableWidth), "px\">"));
			//添加表格行
			tableHtml.append(rowsHtml);
			tableHtml.append("</table>");
		} finally {
			book.close();
		}
		return tableHtml.toString();
	}

	/**
	 * 根据指定单元格参数值创建控件表达式。
	 * @param value 单元格参数值。
	 * @param required 该控件的值是否为必须项。该属性仅适用于快捷表达式模式。
	 * @return 控件表达式。
	 */
	private static String createExpress(String value, boolean required) {
		String innerText = value.substring(1, value.length() - 1).trim();

		//关联控件：语法{%controlItemId}
		if (innerText.startsWith("%"))
			return "##" + innerText.substring(1);
		else {
			//快捷表达式：语法{itemId [xtype] [vtype]}，默认xtype为textfield，如果xtype前缀为%表示为键值
			if (innerText.indexOf(':') == -1) {
				String items[] = StringUtil.split(innerText, ' ');
				StringBuilder script = new StringBuilder();
				//itemId前加*表示不为空，加!允许为空，默认根据前一单元格标签是否加前缀*号来判断
				if (items[0].startsWith("*")) {
					required = true;
					items[0] = items[0].substring(1);
				} else if (items[0].startsWith("!")) {
					required = false;
					items[0] = items[0].substring(1);
				}
				script.append("##{itemId:\"");
				script.append(items[0]);
				script.append("\"");
				if (required)
					script.append(",allowBlank:false");
				script.append(",xtype:\"");
				if (items.length > 1) {
					if (items[1].startsWith("%")) {
						//绑定键值
						script.append("combobox\",");
						script.append(new ExtCombo().getkeyNameScript(items[1].substring(1)));
						script.append('}');
					} else {
						script.append(items[1]);
						if (items.length > 2) {
							if ("image".equals(items[1]))
								script.append("\",src:\"");
							else
								script.append("\",vtype:\"");
							script.append(items[2]);
						}
						script.append("\"}");
					}
				} else {
					script.append("textfield");
					script.append("\"}");
				}
				return script.toString();
			} else //普通表达式
				return "##" + value;
		}
	}

	/**
	 * 添加指定数量的空的单元格，以使后续单元格保持正确位置。
	 * @param html html脚本输出缓冲区。
	 * @param fromIndex 开始列索引号。
	 * @param toIndex 结束列索引号。
	 */
	private static void fillTD(StringBuilder html, int fromIndex, int toIndex) {
		int i;

		for (i = fromIndex; i < toIndex; i++) {
			html.append("<td></td>");
		}
	}

	/**
	 * 添加指定数量的空的行，以使后续行保持正确位置。
	 * @param html html脚本输出缓冲区。
	 * @param fromIndex 开始行索引号。
	 * @param toIndex 结束行索引号。
	 * @param defaultRowHeight 默认行高度。
	 * @param maxColumnIndex 最大列索引号。
	 */
	private static void fillTR(StringBuilder html, int fromIndex, int toIndex, int defaultRowHeight,
			int maxColumnIndex) {
		int i;

		for (i = fromIndex; i < toIndex; i++) {
			html.append("<tr height=\"");
			html.append(defaultRowHeight);
			html.append("px\"><td colspan=\"");
			html.append(maxColumnIndex);
			html.append("\"></tr>");
		}
	}

	/**
	 * 根据行列号指定的单元格是否位于合并单元格区域来返回不同的信息。
	 * 如果位于合并区首个单元格返回该单元格的[colspan,rowspan]信息，
	 * 如果位于其他合并区返回[-1,-1]。如果不在合并区，返回[-2,-2]。
	 * @param mergeRegion 区域的合并信息。
	 * @param columnIndex 列索引号。
	 * @param rowIndex 行索引号。
	 * @return 单元格合并信息。
	 */
	private static int[] getSpanXY(Object[] mergeRegion, int columnIndex, int rowIndex) {
		int info[], result[] = { -2, -2 };
		for (Object obj : mergeRegion) {
			info = (int[]) obj;

			//如果位于区域内首个单元格
			if (columnIndex == info[0] && rowIndex == info[1]) {
				result[0] = info[2] - info[0] + 1;
				result[1] = info[3] - info[1] + 1;
				return result;
			}
			//如果位于区域内其他单元格
			if (!(columnIndex < info[0] || columnIndex > info[2] || rowIndex < info[1] || rowIndex > info[3])) {
				result[0] = -1;
				result[1] = -1;
			}
		}
		return result;
	}

	/**
	 * 获取Excel表格中最大一列的索引号。
	 * @param sheet Sheet对象。
	 * @return 最大一列的索引号。
	 */
	private static int getMaxColumnIndex(XSSFSheet sheet) {
		XSSFRow row;
		Iterator<Row> rows;
		int index = 0;

		rows = sheet.rowIterator();
		while (rows.hasNext()) {
			row = (XSSFRow) rows.next();
			index = Math.max(index, row.getLastCellNum());
		}
		return index;
	}

	/**
	 * 获取列宽度转换为象素宽度。
	 * @param width 列宽度。
	 * @return 象素宽度。
	 */
	private static int getPixcelWidth(int width) {
		return Math.round((width * 0.0281f));
	}

	/**
	 * 获取列高度转换为象素高度。
	 * @param height 列高度。
	 * @return 象素高度。
	 */
	private static int getPixcelHeight(int height) {
		return Math.round(height / 15.12f);
	}

	/**
	 * 获取指定sheet所有的合并单元格信息。
	 * @param sheet Sheet对象。
	 * @return 所有的合并单元格信息。
	 */
	private static Object[] getMergeRegion(XSSFSheet sheet) {
		CellRangeAddress rangeAddress;
		int i, mergeCount, info[];
		Object mergeRegion[];

		mergeCount = sheet.getNumMergedRegions();
		mergeRegion = new Object[mergeCount];
		for (i = 0; i < mergeCount; i++) {
			rangeAddress = sheet.getMergedRegion(i);
			info = new int[4];
			info[0] = rangeAddress.getFirstColumn();
			info[1] = rangeAddress.getFirstRow();
			info[2] = rangeAddress.getLastColumn();
			info[3] = rangeAddress.getLastRow();
			mergeRegion[i] = info;
		}
		return mergeRegion;
	}
}
