package cn.qinys.platform.base.utils.excel;

import cn.qinys.platform.base.exceptions.BizException;
import cn.qinys.platform.base.response.ResultCodeEnum;
import cn.qinys.platform.base.utils.DateUtils;
import cn.qinys.platform.base.utils.StreamUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.util.*;

/**
 * code is far away from bug with the animal protecting
 *
 * @author lsq28657638@gmail.com
 * @description Excel操作类
 * @date Create in 2018/1/24 15:08
 */
@Slf4j
public class ExcelHaveHeadUtils {

    private ExcelHaveHeadUtils() {
    }

    public static <T> void normalExport(String title, List<T> dataList, List<ExcelElement> elements,
                                        HttpServletResponse response) {
        export(title, dataList, elements, null, response);
    }

    public static <T> void normalExport(String title, List<T> dataList, List<ExcelElement> elements,
                                        JSONObject dictionary, HttpServletResponse response) {
        export(title, dataList, elements, dictionary, response);
    }

    private static <T> void export(String title, List<T> dataList, List<ExcelElement> elements,
                                   JSONObject dictionary, HttpServletResponse response) {
        SXSSFWorkbook workbook = new SXSSFWorkbook(1000);
        CellStyle style = getStyle(workbook);
        Sheet sheet = workbook.createSheet();

        Row row = sheet.createRow(0);
        Cell cell = row.createCell(0);
        sheet.addMergedRegion(new CellRangeAddress( 0,0, 0,elements.size() - 1 ));

        CellStyle cellStyle = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setFontHeight((short) 440);
        cellStyle.setFont(font);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setWrapText(true);
        cell.setCellStyle(cellStyle);

        cell.setCellValue(title);

        title += "-"+ LocalDate.now();

        row = sheet.createRow(1);
        // 将列头设置到sheet的单元格中
        for (int colNum = 0; colNum < elements.size(); colNum++) {
            cell = row.createCell(colNum);
            cell.setCellValue(new XSSFRichTextString(elements.get(colNum).getHeader()));
            cell.setCellStyle(style);
        }

        for (int i = 0; i < dataList.size(); i++) {
            JSONObject obj = JSON.parseObject(JSON.toJSONString(dataList.get(i)));
            row = sheet.createRow(i + 2);
            for (int j = 0; j < elements.size(); j++) {
                cell = row.createCell(j);
                // 设置单元格的值
                cell.setCellValue(validAndSetValue(obj, elements.get(j), dictionary));
                // 设置单元格样式
                cell.setCellStyle(style);
            }
            obj.clear();
        }
        try (BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())){
            String fileName = title +".xlsx";
            String trueName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + trueName);
            workbook.write(response.getOutputStream());
            bos.flush();
        } catch (IOException ex) {
            log.error("导出Excel异常: [{}]", ex.getMessage());
            throw new BizException("下载文件异常");
        }
    }

    private static String validAndSetValue(JSONObject obj, ExcelElement excelElement, JSONObject dictionary) {
        String value = "";
        // 如果是0,则全部为字符串,当时null的时候,则转为空字符串""
        if (StringUtils.equals(ExcelElement.STRING, excelElement.getCheckValue())) {
        	value = StringUtils.defaultIfBlank(obj.getString(excelElement.getFieldName()), StringUtils.EMPTY);
            //if(StringUtils.isNotEmpty(excelElement.getFormatter()))value=String.format(excelElement.getFormatter(),value);
        }
        // 如果是1,则是null全部默认为数字0, 输出到excel的0则是字符串
        if (StringUtils.equals(ExcelElement.NUMERIC, excelElement.getCheckValue())) {
            value = StringUtils.defaultIfBlank(obj.getString(excelElement.getFieldName()), "0");
        }
        // 如果是2,则是日期类型
        if (StringUtils.equals(ExcelElement.DATE, excelElement.getCheckValue())) {
            value = DateUtils.dateFormatter(DateUtils.asUtilDate(obj.getString(excelElement.getFieldName())), excelElement.getFormatter());
        }
        // 如果是3,则是字典类型.需要从key值转换成value值.
        if (StringUtils.equals(ExcelElement.DICT, excelElement.getCheckValue())) {
            value = dictionary.getJSONObject(excelElement.getFormatter())
                    .getString(StringUtils.defaultIfBlank(obj.getString(excelElement.getFieldName()), StringUtils.EMPTY));
            // 如果从字典中找不到对应匹配的..则直接返回值
            if (StringUtils.isBlank(value)) {
                value = StringUtils.defaultIfBlank(obj.getString(excelElement.getFieldName()), StringUtils.EMPTY);
            }
        }
        return value;
    }

    public static List<JSONObject> readExcelData(String filePath, List<ExcelElement> elements) {
        return readExcelData(StreamUtils.getFileInputStream(filePath), elements, 0);
    }

    public static List<JSONObject> readExcelData(String filePath, List<ExcelElement> elements, int startRow) {
        return readExcelData(StreamUtils.getFileInputStream(filePath), elements, startRow);
    }

    public static List<JSONObject> readExcelData(File file, List<ExcelElement> elements) {
        return readExcelData(StreamUtils.getFileInputStream(file), elements, 0);
    }

    public static List<JSONObject> readExcelData(MultipartFile file, List<ExcelElement> elements, int startRow) throws Exception {
        return readExcelData(file.getInputStream(), elements, startRow);
    }

    public static List<JSONObject> readExcelData(InputStream stream, List<ExcelElement> elements) {
        return readExcelData(stream, elements, 0);
    }

    public static List<JSONObject> readExcelData(InputStream stream, List<ExcelElement> elements, int startRow) {
        return readExcelData(stream, 0, elements, startRow);
    }

    public static List<JSONObject> readExcelData(InputStream stream, int sheetNum, List<ExcelElement> elements, int startRow) {
        try (Workbook workbook = WorkbookFactory.create(stream)) {
            return readExcelData(workbook, sheetNum, elements, startRow);
        } catch (IOException e) {
            throw new BizException("读取Excel异常");
        }
    }

    private static List<JSONObject> readExcelData(Workbook workbook, int sheetNum, List<ExcelElement> elements, int startRow) {
        List<JSONObject> list = new ArrayList<>();
        Sheet sheet = workbook.getSheetAt(sheetNum);
        Map<Integer, String> headerMap = new HashMap<>(sheet.getRow(0).getLastCellNum());
        if (sheet.getRow(0).getLastCellNum() != elements.size()) {
            throw new BizException(ResultCodeEnum.UPLOAD_EXCEL_TEMPLATE_ERROR);
        }
        for (int i = 0; i < elements.size(); i++) {
            headerMap.put(i, elements.get(i).getFieldName());
        }
        for (int rowNum = startRow; rowNum <= sheet.getPhysicalNumberOfRows(); rowNum++) {
            Row excelRow = sheet.getRow(rowNum);
            if (Objects.isNull(excelRow) || excelRow.getPhysicalNumberOfCells() == 0) {
                continue;
            }
            JSONObject row = new JSONObject(excelRow.getLastCellNum() * 2);
            for (int colNum = 0; colNum < excelRow.getLastCellNum(); colNum++) {
                Cell cell = isMergedRegion(sheet, rowNum, colNum) ?
                        getMergedRegionCell(sheet, rowNum, colNum) : excelRow.getCell(colNum);
                if (cell != null && cell.getCellType() != CellType.BLANK) {
                    row.put(headerMap.get(colNum), getCellValue(cell));
                }
            }
            if (!row.isEmpty()) {
                list.add(row);
            }
        }
        return list;
    }

    public static JSONObject mergedRegion(Sheet sheet, int rowNum, int colNum) {
        int sheetMergeCount = sheet.getNumMergedRegions();
        JSONObject result = new JSONObject(8);
        for (int i = 0; i < sheetMergeCount; i++) {
            CellRangeAddress range = sheet.getMergedRegion(i);
            int firstRowNum = range.getFirstRow();
            int lastRowNum = range.getLastRow();
            int firstColNum = range.getFirstColumn();
            int lastColNum = range.getLastColumn();
            if (rowNum >= firstRowNum && rowNum <= lastRowNum) {
                if (colNum >= firstColNum && colNum <= lastColNum) {
                    result.put("firstRowNum", firstRowNum);
                    result.put("lastRowNum", lastRowNum);
                    result.put("firstColNum", firstColNum);
                    result.put("lastColNum", lastColNum);
                    return result;
                }
            }
        }
        return new JSONObject(4);
    }

    public static boolean isMergedRegion(Sheet sheet, int rowNum, int colNum) {
        JSONObject mergedRegion = mergedRegion(sheet, rowNum, colNum);
        return !isEmptyMap(mergedRegion);
    }

    public static Cell getMergedRegionCell(Sheet sheet, int rowNum, int colNum) {
        JSONObject mergedRegion = mergedRegion(sheet, rowNum, colNum);
        if (!isEmptyMap(mergedRegion)) {
            rowNum = mergedRegion.getIntValue("firstRowNum");
            colNum = mergedRegion.getIntValue("firstColNum");
        }
        return sheet.getRow(rowNum).getCell(colNum);
    }

    public static String getMergedRegionValue(Sheet sheet, int rowNum, int colNum) {
        return getCellValue(getMergedRegionCell(sheet, rowNum, colNum));
    }

    public static String getCellValue(Workbook workbook, int sheetNum, int rowNum, int colNum) {
        return getCellValue(workbook.getSheetAt(sheetNum), rowNum, colNum);
    }

    public static String getCellValue(Sheet sheet, int rowNum, int colNum) {
        return getCellValue(sheet.getRow(rowNum), colNum);
    }

    public static String getCellValue(Sheet sheet, int rowNum, String colNum) {
        return getCellValue(sheet, rowNum, calRowNum(colNum));
    }

    public static String getCellValue(Row row, int colNum) {
        return getCellValue(row.getCell(colNum));
    }

    public static String getCellValue(Cell cell) {
        if (Objects.isNull(cell)) {
            return StringUtils.EMPTY;
        }
        CellType cellType = cell.getCellType();
        if (cellType == CellType.STRING || cellType == CellType.BLANK) {
            return cell.getStringCellValue()
                    .replaceAll("\r", "")
                    .replaceAll("\t", "")
                    .replaceAll("\n", "")
                    .replaceAll(" ", "").trim();
        } else if (cellType == CellType.NUMERIC || cellType == CellType.FORMULA) {
            return new DecimalFormat("0").format(cell.getNumericCellValue());
        } else {
            return "";
        }
    }

    private static int calRowNum(String colNum) {
        StringBuilder builder = new StringBuilder(colNum.toUpperCase());
        char[] charColNum = builder.reverse().toString().toCharArray();
        int sum = -1;
        for (int i = 0, length = charColNum.length; i < length; i++) {
            sum += (charColNum[i] - 64) * Math.pow(26, i);
        }
        return sum;
    }

    private static CellStyle getStyle(Workbook workBook) {
        Font font = workBook.createFont();
        font.setFontHeightInPoints((short) 12);
        font.setFontName("微软雅黑");
        CellStyle style = workBook.createCellStyle();
        style.setBorderBottom(BorderStyle.THIN);
        style.setBottomBorderColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex());
        style.setBorderLeft(BorderStyle.THIN);
        style.setLeftBorderColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex());
        style.setBorderRight(BorderStyle.THIN);
        style.setRightBorderColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex());
        style.setBorderTop(BorderStyle.THIN);
        style.setTopBorderColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex());
        style.setFont(font);
        style.setWrapText(false);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        return style;
    }

    private static <T> boolean isEmptyMap(Map<String, T> map) {
        return Objects.isNull(map) || map.isEmpty();
    }


}
