package de.lcag.common;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.RichTextString;
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.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import de.lcag.common.TableComparisonResult.CellPair;
import routines.LcagGeneral;
import routines.LcagLogger;

/**
 * This class represents a sheet in an Excel Workbook file. By default, it has
 * just one sheet. You add HashTables as additional sheets using the addSheet
 * method. When saving this ExcelTable all its sheets are saved too.
 * 
 * @author U401455
 *
 */
public class ExcelWorkbook implements Table, Storage {
	private static final String EXCEL_DEFAULT_SHEET_NAME = "Sheet1";
	private LcagLogger log = LcagLogger.getLogger(this);;
	private Path filePath = null;
	private XSSFWorkbook workbook = null;
	private ExcelTable firstTable = null;
	private static String dateTimeFormatString = "yyyy-MM-dd HH:mm:ss";
	private static String dateFormatString = "yyyy-MM-dd";
	private FormulaEvaluator evaluator = null;
	private CreationHelper factory = null;
	private String name = null;

	// Map styles as Excel limits to 64000:
	private HashMap<Object, CellStyle> styleMap = new HashMap<>();

	public class ExcelTable extends AbstractTable {
		Sheet sheet;

		public class ExcelColumn implements TableColumn {

			@Override
			public int getPosition() {
				return ExcelTable.this.getColumnPosition(this);
			}

			@Override
			public ExcelTable getTable() {
				return ExcelTable.this;
			}

			@Override
			public void init(TableColumn clientColumn) {
				int len = clientColumn.length();
				ExcelTable thisTable = getTable();
				Table clientTable = clientColumn.getTable();

				for (int myIndex = 1; myIndex <= len; myIndex++) {
					String keyValue = thisTable.getKeyValue(myIndex);
					Integer clientRow = clientTable.getRowByKey(keyValue);

					if (clientRow != null) {
						Object value = clientColumn.getValue(clientRow);
						this.setValue(myIndex, value);
					}
				}
			}

			@Override
			public void init(Object pValue) {
				int len = this.length();
				ExcelTable thisTable = getTable();

				for (int myIndex = 1; myIndex <= len; myIndex++) {
					this.setValue(myIndex, pValue);
				}
			}

			public void colorize(IndexedColors pColor) {
				int len = this.length();
				int colPosition = getPosition();

				for (int i = 0; i <= len; i++) {
					Row row = sheet.getRow(i);
					Cell c = row.getCell(colPosition);

					if (c == null) {
						c = row.createCell(colPosition);
					}

					addColorToCell(c, pColor);
				}
			}

			@Override
			public void addValue(Object v) {
				int rowNo = this.length();

				this.setValue(rowNo, v);
			}

			@Override
			public int shift() {
				int columnPosition = this.getPosition();
				return ExcelTable.this.shiftColumns(columnPosition);
			}

			@Override
			public Object getValue(int pRowNumber) {
				Object result = null;
				Row row = sheet.getRow(pRowNumber);

				if (row == null) {
					return result;
				}

				int columnPosition = this.getPosition();
				Cell cell = row.getCell(columnPosition);

				CellValue value = evaluator.evaluate(cell);

				if (value == null)
					return result;

				switch (value.getCellType()) {
				case STRING:
					result = value.getStringValue();
					break;
				case NUMERIC:
					Double numericCellValue = value.getNumberValue();
					int intValue = numericCellValue.intValue();

					if (intValue == numericCellValue) {
						result = Integer.valueOf(intValue);

						if (DateUtil.isCellDateFormatted(cell)) {
							CellStyle cellStyle = cell.getCellStyle();

							dateFormatString = cellStyle.getDataFormatString();
							result = DateUtil.getJavaDate(numericCellValue);
						}
					} else {
						result = numericCellValue;
					}
					break;
				case BOOLEAN:
					result = value.getBooleanValue();
					break;
				case FORMULA:
					result = cell.getCellFormula();
					break;
				case BLANK:
					result = null;
					break;
				default:
					break;
				}

				return result;
			}

			@Override
			public void setValue(int rowNo, Object v) {
				Row row = sheet.getRow(rowNo);
				int position = this.getPosition();
				Cell cell = row.getCell(position);

				if (cell == null) {
					cell = row.createCell(position);
				}

				if (v instanceof String) {
					String stringCellValue = (String) v;
					cell.setCellValue(stringCellValue);
				} else if (v instanceof Integer) {
					double numericCellValue = (Integer) v;
					cell.setCellValue(numericCellValue);
				} else if (v instanceof Double) {
					double numericCellValue = (Double) v;
					cell.setCellValue(numericCellValue);
				} else if (v instanceof Date) {
					Date dateCellValue = (Date) v;
					cell.setCellValue(dateCellValue);
					formatCellAsDate(cell);
				} else if (v instanceof Boolean) {
					Boolean boolCellValue = (Boolean) v;
					cell.setCellValue(boolCellValue);
				} else {
					cell.setBlank();
				}
			}

			@Override
			public int length() {
				return sheet.getLastRowNum();
			}

			@Override
			public int findRow(Object pValue) {
				// TODO Auto-generated method stub
				return 0;
			}

			@Override
			public String getStyle() {
				// TODO Auto-generated method stub
				return null;
			}

			@Override
			public String asCSV(String pSeparator) {
				// TODO Auto-generated method stub
				return null;
			}

			@Override
			public void setStyle(String string) {
				// TODO Auto-generated method stub

			}

			public String getAddressString() {
				int colNumber = this.getPosition();
				int firstRowNo = ExcelWorkbook.this.getFirstRowNo();
				String colLetter = getColumnLetter(colNumber);

				String result = String.format("%s%d:%s%d", colLetter, firstRowNo, colLetter, firstRowNo);

				return result;
			}

			public CellRangeAddress getAddress() {
				int colNumber = this.getPosition();
				int firstRowNo = ExcelWorkbook.this.getFirstRowNo();

				CellRangeAddress result = new CellRangeAddress(firstRowNo, firstRowNo, colNumber, colNumber);

				return result;
			}

			public void enableAutoFilter() {
				CellRangeAddress range = getAddress();

				sheet.setAutoFilter(range);
			}

			public void autoSize() {
				int colNumber = this.getPosition();

				sheet.autoSizeColumn(colNumber);
			}

			public void setComment(String pComment) {
				int colNumber = this.getPosition();
				int headlineRowNo = getHeadlineRowNumber();

				ExcelTable.this.setComment(headlineRowNo, colNumber, pComment);
			}
		}

		public ExcelTable(Sheet pSheet) {
			super(1);
			this.sheet = pSheet;
		}

		@Override
		public ExcelColumn addColumn(String aName) {
			ExcelColumn col = new ExcelColumn();

			this.addColumn(aName, col);

			return col;
		}

		private int getHeadlineRowNumber() {
			return this.getFirstRowNo() - 1;
		}

		@Override
		public String getName() {
			return ExcelWorkbook.this.getName() + "." + sheet.getSheetName();
		}

		@Override
		public void init() {
			List<String> srcColumnNames = new ArrayList<>();
			boolean isHeader = true;
			int rowNo = -1;
			Integer rowNoLimitInt = getOption(OPTION_LIMIT_NUMBER_ROWS);
			int rowNoLimit = rowNoLimitInt == null ? -1 : rowNoLimitInt;

			super.init();

			// Assumption: First row contains column names
			Row nextRow = sheet.getRow(0);

			short minColIx = nextRow.getFirstCellNum();
			short maxColIx = nextRow.getLastCellNum();

			// Find all column names, skip non-String columns:
			for (short colIndex = minColIx; colIndex < maxColIx; colIndex++) {
				Object value = null;
				Cell cell = nextRow.getCell(colIndex);

				if (cell != null) {
					CellType cellType = cell.getCellType();

					switch (cellType) {
					case STRING:
						value = cell.getStringCellValue();
						break;
					default:
						continue;
					}
				}

				String colName = value.toString();
				srcColumnNames.add(colName);

				if (useColumn(colName)) {
					int colDublicateNo = 1;
					String newColname = colName;
					ExcelColumn col = new ExcelColumn();

					// Add the new column to the table. Number column name if the same name already
					// exists.
					while (this.addColumn(newColname, col) < 0) {
						colDublicateNo += 1;
						newColname = colName + "_" + colDublicateNo;
					}
				}
			}

		}

		@Override
		public int newRow() {
			int rowNo = this.lenght() + getFirstRowNo();

			assert sheet.getRow(rowNo) == null : "Row must not exist yet";

			sheet.createRow(rowNo);
			return rowNo;
		}

		@Override
		public int lenght() {
			return sheet.getLastRowNum();
		}

		private int calcNoRowsNeededForText(String pText, int pFirstCol, int pNoCols) {
			int charsPerLine = 0;
			int textLenght = pText.length();
			int result;

			for (int i = 0; i < pNoCols; i++) {
				int columnWidth = this.getColumnNameByPosition(i).length();
				charsPerLine += columnWidth;
			}

			result = (int) (textLenght / charsPerLine) + 1;
			return result;
		}

		private void setComment(int pRowNo, int pColNo, String pComment) {
			Row row = sheet.getRow(pRowNo);
			Cell cell = row.getCell(pColNo);
			Drawing<?> drawing = sheet.createDrawingPatriarch();
			ClientAnchor anchor = factory.createClientAnchor();
			int noLines = calcNoRowsNeededForText(pComment, pColNo, 3);

			anchor.setCol1(pColNo);
			anchor.setCol2(pColNo + 3);
			anchor.setRow1(pRowNo);
			anchor.setRow2(pRowNo + noLines);
			Comment comment = drawing.createCellComment(anchor);
			RichTextString str = factory.createRichTextString(pComment);

			comment.setString(str);
			cell.setCellComment(comment);
		}

		public int shiftColumns(int pAllAfterPosition) {
			this.sheet.shiftColumns(pAllAfterPosition, this.colNames.size(), 1);

			return super.shiftColumns(pAllAfterPosition);
		}

		public void enableAutoFilter() {
			String firstCol = this.colNames.firstKey();
			int headlineRowNo = this.getHeadlineRowNumber(); // Headline is before first data row number
			int firstColNo = this.getColumnPositionByName(firstCol);
			int lastColNo = this.getLastColumnPosition();

			CellRangeAddress range = new CellRangeAddress(headlineRowNo, headlineRowNo, firstColNo, lastColNo);
			sheet.setAutoFilter(range);
		}

		public void freezeHeadline() {
			int headlineRowNo = this.getFirstRowNo(); // Headline is before first data row number

			sheet.createFreezePane(headlineRowNo, 1);
		}

		public void autoSizeAllColumns() {
			for (String colName : this.colNames.keyList()) {
				ExcelColumn column = (ExcelColumn) this.getColumnByName(colName);

				column.autoSize();
			}
		}

		@Override
		public int addColumn(String pStyledColumnLabel, TableColumn pColumn, String pAfterColumnNamed) {
			int colPos = super.addColumn(pStyledColumnLabel, pColumn, pAfterColumnNamed);

			if (colPos >= 0)
				pColumn.setValue(0, pStyledColumnLabel);
			return colPos;
		}

		@Override
		public void addColumnAtPosition(String pStyledColumnLabel, TableColumn pColumn, int pColumnPosition) {
			super.addColumnAtPosition(pStyledColumnLabel, pColumn, pColumnPosition);

			pColumn.setValue(0, pStyledColumnLabel);
		}

		private void colorizeRow(int pRowNo, int noCells, IndexedColors pColor) {
			Row pRow = sheet.getRow(pRowNo);
			short minColIx = pRow.getFirstCellNum();
			int maxColIx = minColIx + noCells;

			for (short colIndex = minColIx; colIndex < maxColIx; colIndex++) {
				Cell c = pRow.getCell(colIndex);

				if (c == null) {
					c = pRow.createCell(colIndex);
				}

				addColorToCell(c, pColor);
			}
		}

		public void integrateDiff(TableComparisonResult pDifferences) throws IOException {
			Sheet sheet = workbook.getSheetAt(0);
			int rowNo = -1;
			int shiftedRows = 0;
			Row newRow = null;
			boolean preciseCompare = true;

			Map<String, List<TableComparisonResult.CellPair>> rowsToChangeInMaster = pDifferences
					.getCellsToChangeInMaster();
			Table masterTable = pDifferences.getMasterTable();
			Table clientTable = pDifferences.getClientTable();

			for (int n : pDifferences.getRowsToDeleteFromMaster()) {
				colorizeRow(n, masterTable.getColumnNames().size(), IndexedColors.LIGHT_ORANGE);
			}

			for (String colLabel : pDifferences.getColumnsToAddToMaster()) {
				TableColumn clientColumn = clientTable.getColumnByLabel(colLabel);
				int clientPos = clientTable.getColumnPositionByLabel(colLabel);
				String columnNameBefore = clientTable.getColumnNameByPosition(clientPos - 1);

				ExcelColumn newCol = new ExcelColumn();
				this.addColumn(colLabel, newCol, columnNameBefore);
			}

			int noColsToShift = 2; // coloring mst know how many columns were added at he start:
			ExcelColumn diffCellsKeyCol = new ExcelColumn();
			this.addColumnAsFirstOne("NoDiffCells", diffCellsKeyCol);

			ExcelColumn keyCol = new ExcelColumn();
			this.addColumnAsFirstOne(KEY_COLUMN, keyCol);

			diffCellsKeyCol.setComment("Filter to find all rows with a number of different cell values");
			keyCol.setComment("Key used to compare records. Can be a composite key.");

			ExcelColumn filenameCol = new ExcelColumn();
			this.addColumn("Filename", filenameCol);
			filenameCol.init(masterTable.getFilePath().getFileName().toString());

			for (String colLabel : pDifferences.getColumnsToDeleteFromMaster()) {
				ExcelColumn masterColumn = (ExcelColumn) masterTable.getColumnByLabel(colLabel);

				masterColumn.colorize(IndexedColors.LIGHT_ORANGE);
			}

			int noCells = rowsToChangeInMaster.size();
			int i = 0;

			for (String key : rowsToChangeInMaster.keySet()) {
				int rowNoInMasterTable = masterTable.getRowByKey(key);
				int rowNoToChange = rowNoInMasterTable + shiftedRows;
				List<TableComparisonResult.CellPair> changesInRow = rowsToChangeInMaster.get(key);
				Row sourceRow = sheet.getRow(rowNoToChange);
				short firstCellNum = sourceRow.getFirstCellNum();

				int perCent = (100 * i) / noCells;
				i++;

				if (perCent % 5 == 0)
					log.info("Integrate: %d/%d done = %2d%%, row=%d", i, noCells, perCent, rowNoInMasterTable);

				if (rowNoInMasterTable != rowNo) {
					rowNo = rowNoInMasterTable;

					int lastRow = sheet.getLastRowNum();
					int insertAt = rowNoToChange + 1;

					if (false) {
						// Shift rows starting from insertAt to lastRow down by 1
						log.debug("intergrate: shifting rows %d-%d", insertAt, lastRow);
						sheet.shiftRows(insertAt, lastRow, 1, true, true);
						shiftedRows += 1;
					} else {
						insertAt = this.newRow();
					}

					// Create new row at insertAt
					newRow = sheet.getRow(insertAt);

					copyRowTo(sourceRow, newRow);

					keyCol.setValue(insertAt, key);
					filenameCol.setValue(insertAt, clientTable.getFilePath().getFileName().toString());
				}

				rowNo = newRow.getRowNum();

				// Write a diff key so that a user can filter all rows that have changes in
				// columns or sort by number of changes
				String diffKey = String.format("%02d:%s", changesInRow.size(), key);
				diffCellsKeyCol.setValue(rowNo, diffKey);
				diffCellsKeyCol.setValue(rowNoInMasterTable, diffKey);

				for (CellPair cellDiff : changesInRow) {
					int pos = masterTable.getColumnPositionByLabel(cellDiff.masterColName);

					if (pos >= 0) {
						Object value = clientTable.getCellValue(cellDiff.clientRow, cellDiff.clientColName);
						Cell c = newRow.getCell(pos + firstCellNum - noColsToShift);

						if (preciseCompare) {
							Object masterValue = masterTable.getCellValue(rowNoToChange, cellDiff.masterColName);

							value = LcagGeneral.compareValue(masterValue, value);
						}

						masterTable.setCellValue(rowNo, cellDiff.clientColName, value);

						if (c != null) {
							addColorToCell(c, IndexedColors.YELLOW);
						}
					}
				}
			}

			int noColumns = this.getColumnNames().size();

			for (int n : pDifferences.getRowsToAddFromClient()) {
				int newRowNo = this.newRow();

				for (String colName : this.getColumnNames()) {
					TableColumn myColumn = this.getColumnByName(colName);
					TableColumn clientColumn = clientTable.getColumnByName(colName);

					Object value = (clientColumn == null) ? null : clientTable.getCellValue(n, colName);

					myColumn.setValue(newRowNo, value);
				}

				this.colorizeRow(newRowNo, noColumns, IndexedColors.LIGHT_GREEN);
			}

			// Initialize content of new columns.
			for (String colLabel : pDifferences.getColumnsToAddToMaster()) {
				TableColumn clientColumn = clientTable.getColumnByLabel(colLabel);
				ExcelColumn newColumnInMaster = (ExcelColumn) masterTable.getColumnByLabel(colLabel);

				newColumnInMaster.init(clientColumn);
				newColumnInMaster.colorize(IndexedColors.LIGHT_GREEN);
			}

			int n = keyCol.length();
			int firstRowNo = this.getFirstRowNo();
			for (i = firstRowNo; i < n + firstRowNo; i++) {
				String key = masterTable.setKeyValue(i);
				keyCol.setValue(i, key);
			}

			rowNo = this.newRow();
			this.includeTable(rowNo, pDifferences.getSummary());
			this.enableAutoFilter();
			this.autoSizeAllColumns();
			this.freezeHeadline();
		}

		public void includeTable(int rowNo, HashTable summary) {
			Iterator<String> colNameIterator = this.getColumnNames().iterator();

			rowNo = this.newRow();
			for (int i = 0; i <= summary.getLastColumnPosition(); i++) {
				String insertColName = colNameIterator.next();
				String srcTableColName = summary.colNames.get(summary.colNames.get(i));

				this.setCellValue(rowNo, insertColName, srcTableColName);
			}
			this.colorizeRow(rowNo, summary.getLastColumnPosition() + 1, IndexedColors.YELLOW);

			for (int srcRow = 0; srcRow < summary.lenght(); srcRow++) {
				colNameIterator = this.getColumnNames().iterator();
				rowNo = this.newRow();

				for (int i = 0; i <= summary.getLastColumnPosition(); i++) {
					String insertColName = colNameIterator.next();
					String srcTableColName = summary.colNames.get(i);

					Object value = summary.getCellValue(srcRow, srcTableColName);
					this.setCellValue(rowNo, insertColName, value);
				}
			}
		}

		@Override
		public Path getFilePath() {
			return ExcelWorkbook.this.getFilePath();
		}
	}

	Map<String, AbstractTable> tables = new LinkedHashMap<>();

	public ExcelWorkbook(String aFilePath, String... pColumnNameList) throws InvalidFormatException, IOException {
		this.filePath = AbstractTable.resolveFilePath(aFilePath, new HashMap<String, Object>());
		this.name = this.filePath.getFileName().toString();

		workbook = new XSSFWorkbook();
		XSSFSheet sheet = workbook.createSheet("Table1");
		firstTable = new ExcelTable(sheet);
		tables.put("Table1", firstTable);

		if (pColumnNameList.length == 0)
			return;

		int headerRowNo = firstTable.newRow();

		for (String colName : pColumnNameList) {
			firstTable.addCellValue(headerRowNo, colName, colName);
		}
	}

	public ExcelWorkbook(String aFilePath, Map<String, Object> pParams) {
		this.filePath = AbstractTable.resolveFilePath(aFilePath, pParams);
		this.name = this.filePath.getFileName().toString();
	}

	public ExcelWorkbook(String aFilePath) {
		this(aFilePath, new HashMap<String, Object>());
	}

	public ExcelWorkbook() {
		this(null);
	}

	@Override
	public Path getFilePath() {
		return filePath;
	}

	public List<String> getSheetNames() {
		List<String> result = new ArrayList<>();
		int n = workbook.getNumberOfSheets();

		for (int i = 0; i < n; i++) {
			result.add(workbook.getSheetName(i));
		}

		return result;
	}

	public AbstractTable getSheet(String pSheetName) {
		return tables.get(pSheetName);
	}

	public ExcelTable addSheet(String pSheetName) {
		Sheet sheet = workbook.createSheet(pSheetName);
		ExcelTable newTable = new ExcelTable(sheet);

		return newTable;
	}

	public AbstractTable addSheet(String pSheetName, AbstractTable pTable) {
		this.tables.put(pSheetName, pTable);

		return pTable;
	}

	public void addSheet(String pSheetName, GenericComparisonResultTable pCmpResult) {
		// TODO Auto-generated method stub, should add the data to a new Excel Sheet
	}

	@Override
	public void saveAs(String pFilePath) throws IOException {
		File aFile = new File(pFilePath);

		FileOutputStream outputStream = new FileOutputStream(aFile);
		workbook.write(outputStream);
		workbook.close();
		outputStream.close();

		log.info("Workbook saved as %s", aFile.getAbsolutePath());
	}

	@Override
	public void save() throws IOException {
		saveAs(this.filePath.toString());
	}

	@Override
	public void read() throws IOException {
		getFirstTable().init();
	}

	private void formatCellAsDate(Cell cell) {
		CellStyle cellStyle = workbook.createCellStyle();

		cellStyle.cloneStyleFrom(cell.getCellStyle());
		CreationHelper creationHelper = workbook.getCreationHelper();
		cellStyle.setDataFormat(creationHelper.createDataFormat().getFormat(dateFormatString));
		cell.setCellStyle(cellStyle);
	}

	private void addColorToCell(Cell pCell, IndexedColors pColor) {
		CellStyle currentStyle = pCell.getCellStyle();
		String index = String.format("%s/%s", pColor.name(), currentStyle.getDataFormatString());
		CellStyle cellStyle = this.styleMap.get(index);

		if (cellStyle == null) {
			short dataFormat = currentStyle.getDataFormat();
			cellStyle = workbook.createCellStyle();

			cellStyle.setFillForegroundColor(pColor.getIndex());
			cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
			cellStyle.setDataFormat(dataFormat);
			this.styleMap.put(index, cellStyle);
		}

		pCell.setCellStyle(cellStyle);
	}

	private static String getColumnLetter(int pColNumber) {
		StringBuilder colLetter = new StringBuilder();

		while (pColNumber > 0) {
			int remainder = (pColNumber - 1) % 26;

			colLetter.insert(0, (char) ('A' + remainder));
			pColNumber = (pColNumber - 1) / 26;
		}

		return colLetter.toString();
	}

	private static void copyRowTo(Row sourceRow, Row newRow) {
		// Copy each cell from source to new row
		for (int i = sourceRow.getFirstCellNum(); i < sourceRow.getLastCellNum(); i++) {
			Cell oldCell = sourceRow.getCell(i);
			if (oldCell == null)
				continue;

			Cell newCell = newRow.createCell(i);
			// Copy style
			newCell.setCellStyle(oldCell.getCellStyle());
			CellType cellType = oldCell.getCellType();

			if (cellType != CellType.FORMULA)
				newCell.setCellType(cellType);
			// Copy cell value

			switch (cellType) {
			case STRING:
				String stringCellValue = oldCell.getStringCellValue();
				newCell.setCellValue(stringCellValue);
				break;
			case NUMERIC:
				double numericCellValue = oldCell.getNumericCellValue();
				newCell.setCellValue(numericCellValue);
				break;
			case BOOLEAN:
				boolean booleanCellValue = oldCell.getBooleanCellValue();
				newCell.setCellValue(booleanCellValue);
				break;
			case FORMULA:
				String cellFormula = oldCell.getCellFormula();
				newCell.setCellFormula(cellFormula);
				break;
			case BLANK:
				newCell.setBlank();
				break;
			default:
				break;
			}
		}
	}

	@Override
	public TableColumn addColumn(String aName) {
		return getFirstTable().addColumn(aName);
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public void init() {
		// TODO Auto-generated method stub

	}

	@Override
	public int newRow() {
		return getFirstTable().newRow();
	}

	public AbstractTable getFirstTable() {
		AbstractTable result = firstTable;

		if (result == null) {
			String filePathString = this.filePath.toString();
			FileInputStream inputStream;
			try {
				inputStream = new FileInputStream(new File(filePathString));
				initWorkbook(new XSSFWorkbook(inputStream));
				// Workbook workbook =
				// StreamingReader.builder().rowCacheSize(100).bufferSize(4096).open(inputStream);

				Sheet sheet = workbook.getSheetAt(0);

				result = firstTable = new ExcelTable(sheet);
				String sheetName = sheet.getSheetName();

				this.tables.put(sheetName, firstTable);

				// workbook.close();
				inputStream.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			Collection<AbstractTable> tableList = this.tables.values();
			result = tableList.iterator().next();
		}

		return result;
	}

	private void initWorkbook(XSSFWorkbook pWorkbook) {
		workbook = pWorkbook;

		evaluator = workbook.getCreationHelper().createFormulaEvaluator();
		factory = workbook.getCreationHelper();
	}

	@Override
	public int lenght() {
		return getFirstTable().lenght();
	}

	@Override
	public TableColumn getColumnByName(String colName) {
		return getFirstTable().getColumnByName(colName);
	}

	@Override
	public TableColumn getColumnByLabel(String pFormattedColName) {
		return getFirstTable().getColumnByLabel(pFormattedColName);
	}

	public <V> V getCellValue(int row, String colName) {
		return getFirstTable().getCellValue(row, colName);
	}

	public void setCellValue(int row, String colName, Object v) {
		getFirstTable().setCellValue(row, colName, v);
	}

	public TableComparisonResult compareTo(AbstractTable pCmpTable) {
		return getFirstTable().compareTo(pCmpTable);
	}

	public void integrateDiff(TableComparisonResult pDiff) throws IOException {
		getFirstTable().integrateDiff(pDiff);

		FileOutputStream outputStream = new FileOutputStream(this.filePath.toString());
		this.workbook.write(outputStream);
		this.workbook.close();
	}

	public void integrateDifferences(WorkbookComparisonResult pDiff) throws IOException {
		for (String tableName : pDiff.getDifferencesPerTable().keySet()) {
			AbstractTable myTable = this.tables.get(tableName);
			AbstractTable cmpTable = pDiff.getClientWorkbook().tables.get(tableName);

			TableComparisonResult diff = pDiff.getDifferencesPerTable().get(tableName);

			myTable.integrateDiff(diff);
		}
	}

	@Override
	public void addColumns(String... aNameList) {
		getFirstTable().addColumns(aNameList);
	}

	@Override
	public void addColumns(Collection<String> pNameList) {
		getFirstTable().addColumns(pNameList);

	}

	@Override
	public String getSeparator() {
		return getFirstTable().getSeparator();
	}

	@Override
	public boolean hasColumn(String pName) {
		return getFirstTable().hasColumn(pName);
	}

	@Override
	public Set<String> getColumnNames() {
		return getFirstTable().getColumnNames();
	}

	@Override
	public int getColumnPositionByLabel(String pLabel) {
		return getFirstTable().getColumnPositionByLabel(pLabel);
	}

	@Override
	public int getColumnPositionByName(String pName) {
		return getFirstTable().getColumnPositionByName(pName);
	}

	@Override
	public Collection<String> getColumnLabels() {
		return getFirstTable().getColumnLabels();
	}

	@Override
	public void setCellValues(int pMyRow, Table pSrcTable, Integer pSrcRow) {
		getFirstTable().setCellValues(pMyRow, pSrcTable, pSrcRow);
	}

	public void setMaxRows(int pMaxRows) {
		for (AbstractTable t : tables.values()) {
			t.setMaxRows(pMaxRows);
		}
	}

	@Override
	public int findRow(String colName, Object v) {
		return getFirstTable().findRow(colName, v);
	}

	@Override
	public Table joinTable(Table pTable, Map<String, String> pFieldMap) {
		return getFirstTable().joinTable(pTable, pFieldMap);
	}

	@Override
	public void joinTable(Table pTargetTable, Table pTableToJoin, Map<String, String> pFieldMap) {
		getFirstTable().joinTable(pTargetTable, pTableToJoin, pFieldMap);

	}

	@Override
	public String asCSV(String pColName, String pSeparator) {
		return getFirstTable().asCSV();
	}

	@Override
	public String asHTML() {
		return getFirstTable().asHTML();
	}

	@Override
	public String asCsvData() {
		return getFirstTable().asCsvData();
	}

	@Override
	public String asCSV() {
		return getFirstTable().asCSV();
	}

	public void setKeyColumns(List<String> keyFields) {
		getFirstTable().setKeyColumns(keyFields);
	}

	private void compareStructureTo(WorkbookComparisonResult pResult, ExcelWorkbook pCmpTable) {
		Collection<String> myTableNames = this.tables.keySet();
		Collection<String> cmpTableNames = pCmpTable.tables.keySet();

		Collection<String> commonTables = CollectionUtils.intersection(myTableNames, cmpTableNames);
		pResult.setCommonTables(commonTables);

		Collection<String> myPrivateTabless = CollectionUtils.subtract(myTableNames, commonTables);
		pResult.setTablesToDelete(myPrivateTabless);

		Collection<String> cmpPrivateTables = CollectionUtils.subtract(cmpTableNames, commonTables);
		pResult.setTablesToAdd(cmpPrivateTables);
	}

	public WorkbookComparisonResult compareTo(ExcelWorkbook pCmpWorkbook) {
		WorkbookComparisonResult result = new WorkbookComparisonResult(this, pCmpWorkbook);

		this.compareStructureTo(result, pCmpWorkbook);

		for (String tableName : result.getCommonTables()) {
			AbstractTable myTable = this.tables.get(tableName);
			AbstractTable cmpTable = pCmpWorkbook.tables.get(tableName);

			TableComparisonResult diff = myTable.compareTo(cmpTable);

			if (!diff.tablesAreEqual()) {
				result.addTableDifference(tableName, diff);
			}
		}

		return result;
	}

	@Override
	public String getColumnNameByPosition(int pPos) {
		return getFirstTable().getColumnNameByPosition(pPos);
	}

	public String getKeyValue(int i) {
		return getFirstTable().getKeyValue(i);
	}

	public void setOption(String optionLimitNumberRows, int noRowsLimit) {
		getFirstTable().setOption(optionLimitNumberRows, noRowsLimit);
	}

	public void setWantedColumns(List<String> wantedColumns) {
		getFirstTable().setWantedColumns(wantedColumns);
	}

	public void append(CsvTable csv) {
		getFirstTable().append(csv);
	}

	@Override
	public Integer getRowByKey(String pKey) {
		return getFirstTable().getRowByKey(pKey);
	}

	public void setKey(int rowNo) {
		getFirstTable().setKey(rowNo);
	}

	public void setOption(String optionUseMapping, MappingTable mappingTable) {
		getFirstTable().setOption(optionUseMapping, mappingTable);

	}

	@Override
	public int getFirstRowNo() {
		return getFirstTable().getFirstRowNo();
	}

	@Override
	public String setUniqueKeyValue(int pRow) {
		return getFirstTable().setUniqueKeyValue(pRow);
	}

	@Override
	public String setKeyValue(int pRow) {
		return getFirstTable().setKeyValue(pRow);
	}

	@Override
	public int getNoColumns() {
		return getFirstTable().getNoColumns();
	}

}
