package com.eactive.eai.rms.common.excel;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.view.document.AbstractXlsxView;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

public class NewExcelView extends AbstractXlsxView {

	@Autowired
	private ObjectMapper objectMapper;

	@Override
	protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		try {
			String[] headers = objectMapper.readValue(request.getParameter("header"), String[].class);
			String[] headerTitles = objectMapper.readValue(request.getParameter("headerTitle"), String[].class);
			String excelFileName = prepareFileName(request);
			setupResponse(response, excelFileName);

			String[] headersLower = Arrays.stream(headers).map(String::toLowerCase).toArray(String[]::new);

			Collection<Map<String, Object>> datas = readDatas(model);

			Sheet sheet = createSheetWithHeader(workbook, headers, headerTitles);
			fillDataToSheet(workbook, sheet, headers, headersLower, datas);

			merge(headers, sheet, createTitleStyle(workbook), request);
			autoSizeColumns(sheet, datas.size());

			workbook.write(response.getOutputStream());
			response.getOutputStream().flush();
		} catch (Exception e) {
			logger.error("Error building excel document: ", e);
			throw e;
		}
	}

	@SuppressWarnings({ "unchecked" })
	private Collection<Map<String, Object>> readDatas(Map<String, Object> model) {
		Object o = model.get("datas");
		if (o instanceof Map[]) {
			return Arrays.asList(((Map[]) o));
		} else {
			return (Collection<Map<String, Object>>) o;
		}
	}

	private String prepareFileName(HttpServletRequest request) {
		String fileName = request.getParameter("fileName");
		if (fileName == null) {
			String uri = request.getRequestURI();
			fileName = uri.substring(uri.lastIndexOf("/") + 1, uri.lastIndexOf("."));
		}
		return new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
	}

	private void setupResponse(HttpServletResponse response, String excelFileName) {
		response.setContentType("application/x-msdownload");
		response.setHeader("Content-Disposition", "attachment;filename=\"" + excelFileName + ".xlsx\"");
		logger.debug("Excel File Name: " + excelFileName);
	}

	private Sheet createSheetWithHeader(Workbook workbook, String[] headers, String[] headerTitles) {
		Sheet sheet = workbook.createSheet("Sheet1");
		logger.debug("Create WorkbookSheet");

		CellStyle tStyle = createTitleStyle(workbook);
		Row header = sheet.createRow(0);
		for (int column = 0; column < headers.length; column++) {
			Cell cell = header.createCell(column);
			logger.debug("Excel Header Title: " + column + " => " + headerTitles[column]);

			cell.setCellValue(StringUtils.replace(headerTitles[column], "<br/>", ""));
			cell.setCellStyle(tStyle);
		}
		return sheet;
	}

	private void fillDataToSheet(Workbook workbook, Sheet sheet, String[] headers, String[] headersLower,
			Collection<Map<String, Object>> datas) {
		CellStyle dStyle = createDataStyle(workbook);
		int rowNum = 1;
		for (Map<String, Object> rowData : datas) {
			Row row = sheet.createRow(rowNum++);
			fillRowWithData(row, headers, headersLower, rowData, dStyle);
		}
	}

	private void fillRowWithData(Row row, String[] headers, String[] headersLower, Map<String, Object> rowData,
			CellStyle dStyle) {
		for (int column = 0; column < headers.length; column++) {
			Cell cell = row.createCell(column);
			Object cellValue = getCellValue(rowData, headers[column], headersLower[column]);
			setCellValue(cell, cellValue);
			cell.setCellStyle(dStyle);
		}
	}

	private Object getCellValue(Map<String, Object> rowData, String header, String headerLower) {
		Object value = rowData.get(header);
		if (value == null) {
			value = rowData.get(headerLower);
		}
		return value;
	}

	private void setCellValue(Cell cell, Object value) {
		if (value instanceof Number) {
			cell.setCellValue(((Number) value).doubleValue());
		} else {
			cell.setCellValue(value == null ? " " : value.toString());
		}
	}

	private void autoSizeColumns(Sheet sheet, int numberOfColumns) {
		for (int column = 0; column < numberOfColumns; column++) {
			sheet.autoSizeColumn(column);
		}
	}

	private CellStyle createDataStyle(Workbook workbook) {
		CellStyle dStyle = workbook.createCellStyle();
		dStyle.setBorderBottom(BorderStyle.THIN);
		dStyle.setBorderTop(BorderStyle.THIN);
		dStyle.setBorderLeft(BorderStyle.THIN);
		dStyle.setBorderRight(BorderStyle.THIN);
		return dStyle;
	}

	private CellStyle createTitleStyle(Workbook workbook) {
		CellStyle tStyle = workbook.createCellStyle();
		tStyle.setBorderBottom(BorderStyle.THIN);
		tStyle.setBorderTop(BorderStyle.THIN);
		tStyle.setBorderLeft(BorderStyle.THIN);
		tStyle.setBorderRight(BorderStyle.THIN);
		tStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.index);
		tStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
		tStyle.setAlignment(HorizontalAlignment.CENTER);
		tStyle.setVerticalAlignment(VerticalAlignment.CENTER);
		return tStyle;
	}

	private void merge(String[] headers, Sheet sheet, CellStyle tStyle, HttpServletRequest request)
			throws JsonProcessingException {

		HashMap<String, String>[] mergeData = null;

		String smerge = request.getParameter("merge");
		if (smerge == null) {
			return;
		}

		mergeData = objectMapper.readValue(smerge, new TypeReference<HashMap<String, String>[]>() {
		});

		// 헤더 행 추가
		sheet.shiftRows(0, sheet.getLastRowNum(), 1);
		Row headerRow = sheet.createRow(0);
		for (int columnIndex = 0; columnIndex < headers.length; columnIndex++) {
			Cell cell = headerRow.createCell(columnIndex);
			cell.setCellValue("");
			cell.setCellStyle(tStyle);
		}

		// 병합 정보 적용
		for (HashMap<String, String> mergeInfo : mergeData) {
			logger.debug("Row&Col merge data: " + mergeInfo);

			int firstRow = Integer.parseInt(mergeInfo.get("firstRow"));
			int lastRow = Integer.parseInt(mergeInfo.get("endRow"));
			int firstCol = Integer.parseInt(mergeInfo.get("firstCol"));
			int lastCol = Integer.parseInt(mergeInfo.get("endCol"));
			String text = mergeInfo.get("text");

			// 셀 값 설정
			Row row = sheet.getRow(firstRow);
			Cell cell = row.getCell(firstCol);
			if (StringUtils.isNumeric(text.isEmpty() ? "NAN" : text)) {
				cell.setCellValue(Double.parseDouble(text));
			} else {
				cell.setCellValue(new XSSFRichTextString(text));
			}

			// 셀 병합
			sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol));
		}
	}

}
