package com.jwipc.order.utils;

import cn.hutool.core.util.ObjectUtil;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;
import com.jwipc.exception.DaoException;
import com.jwipc.order.model.dto.CellModel;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.*;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>
 * POI工具类
 * </p>
 *
 * @author likui
 * @version 1.0
 * @since 2019/8/30
 */
public class PoiExcel {

    private final static Logger log = LoggerFactory.getLogger(PoiExcel.class);

    /**
     * 生成表格（用于生成复杂表头）
     *
     * @param sheetName     sheet名称
     * @param wb            表对象
     * @param cellRowNum    表头总占用行数
     * @param cellListMap   表头数据 {key=cellRowNum-1}
     * @param commentColumn 批注起始列
     * @param exportData    行数据
     * @return SXSSFWorkbook
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static SXSSFWorkbook createCSVUtil(
            String sheetName, SXSSFWorkbook wb, Integer cellRowNum, Map<String, List<CellModel>> cellListMap,
            Integer commentColumn, List<String> columnParam, JSONArray exportData
    ) {
        // 设置表格名称
        SXSSFSheet sheet = wb.createSheet(sheetName);
        // 画批注
        SXSSFDrawing draw =  sheet.createDrawingPatriarch();

        sheet.trackAllColumnsForAutoSizing();
        sheet.autoSizeColumn(1, true);
        XSSFCellStyle cellStyle = (XSSFCellStyle) wb.createCellStyle();
        // 设置单元格内容水平对齐
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        // 设置单元格内容垂直对齐
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 设置自动换行
        cellStyle.setWrapText(true);
        for (int t = 0; t < cellRowNum; t++) {
            SXSSFRow row = sheet.createRow(t);
            List<CellModel> cellNameList = cellListMap.get(String.valueOf(t));
            for (CellModel cellModel : cellNameList) {
                int sr = cellModel.getStartRow();
                int er = cellModel.getEndRow();
                int sc = cellModel.getStartColumn();
                int ec = cellModel.getEndColumn();
                if (sr == er && sc == ec) {
                    continue;
                }
                CellRangeAddress ca = new CellRangeAddress(sr, er, sc, ec);
                sheet.addMergedRegion(ca);
            }

            for (int i = 0; i < cellNameList.size(); i++) {
                // 遍历插入表头
                CellModel cellModel = cellNameList.get(i);
                SXSSFCell cell = row.createCell(cellModel.getStartColumn());
                cell.setCellValue(cellModel.getCellName());
                cell.setCellStyle(cellStyle);

                sheet.setColumnWidth(cellModel.getEndColumn(), cellModel.getColumnWidth() * 256);
            }
        }

        // 创建行及单元格，并设置值
        for (int i = 0; i < exportData.size(); i++) {
            SXSSFRow row = sheet.createRow(cellRowNum);
            JSONObject obj = JSONObject.parseObject(exportData.get(i).toString());
            for (int j = 0; j < columnParam.size(); j++) {
                Object o = obj.get(columnParam.get(j));
                SXSSFCell cellValue = row.createCell(j);
                if (ObjectUtil.isNull(o)) {
                    cellValue.setCellValue("");
                }else {
                    cellValue.setCellValue(o.toString());
                }

                cellValue.setCellStyle(cellStyle);

                // 设置批注信息 为了方便导入是获取需要更新的记录ID
                if (commentColumn != -1 && j > commentColumn - 1) {
                    Comment comment = draw.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, j, cellRowNum, j, cellRowNum));
                    String key = obj.get("resultId").toString() + "-" + columnParam.get(j);
                    comment.setString(new XSSFRichTextString(obj.get(key).toString()));
                    cellValue.setCellComment(comment);
                }
            }
            cellRowNum++;
        }
        return wb;
    }


    /**
     * 下载Excel模板
     *
     * @param titleColumnRow 表头行共占用行数
     * @param titleColumn    Excel表头列
     * @param commentColumn  批注起始列
     * @param fileName       文件名称
     * @param columnParam    列对应数据字段名称
     * @param exportData     导出的数据
     * @param response       响应实体
     */
    public static void downloadExport(
            Integer titleColumnRow, Map<String, List<CellModel>> titleColumn, Integer commentColumn, String fileName,
            List<String> columnParam, JSONArray exportData, HttpServletResponse response
    ) {
        // 向指定的Excel中写入数据
        OutputStream out = null;
        // 设置最大数据行数
        SXSSFWorkbook wb = new SXSSFWorkbook(5000);
        try {
            wb = createCSVUtil("sheet", wb, titleColumnRow, titleColumn, commentColumn, columnParam, exportData);
            // 设置文件名
            String excelName = fileName + ".xlsx";
            // 设定输出文件头
            response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(excelName, "utf-8"));
            // 定义输出类型
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            out = response.getOutputStream();
            wb.write(out);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
            if (wb != null) {
                wb.dispose();
            }
        }
    }


    /**
     * Excel导入时读取Excel数据
     *
     * @param file Excel文件
     * @param fileName Excel文件名
     * @param isComment 是否读取单元格批准信息
     * @param startSheet 开始的sheet
     * @param startRow 开始的行
     * @return 结果
     */
    public static List<Map<String, String>> readExcel(
            MultipartFile file, String fileName, boolean isComment, int startSheet, int startRow
    ) {
        // 初始化excel的集合
        List<Map<String, String>> excelList = Lists.newArrayList();
        // 初始化输入流
        InputStream is = null;
        Workbook wb = null;
        try {
            // 验证文件名是否合格
            if (!validateExcel(fileName)) {
                return null;
            }
            // 根据文件名判断文件是2003版本还是2007版本
            boolean isExcel03 = true;
            if (WDWUtil.isExcel2007(fileName)) {
                isExcel03 = false;
            }
            // 根据新建的文件实例化输入流
            is = file.getInputStream();

            // 当excel是2003时
            if (isExcel03) {
                wb = new HSSFWorkbook(is);
            } else {
                wb = new XSSFWorkbook(is);
            }

            excelList = readExcelValue(wb, isComment, startSheet, startRow);
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
            throw new DaoException(e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }
        return excelList;
    }

    /**
     * Excel导入时读取Excel数据 与readExcel方法唯一不同是数值型保留四位小数（原先是两位）
     *
     * @param file Excel文件
     * @param fileName Excel文件名
     * @param isComment 是否读取单元格批准信息
     * @param startSheet 开始的sheet
     * @param startRow 开始的行
     * @return 结果
     */
    public static List<Map<String, String>> readExcel2(
            MultipartFile file, String fileName, boolean isComment, int startSheet, int startRow
    ) {
        // 初始化excel的集合
        List<Map<String, String>> excelList = Lists.newArrayList();
        // 初始化输入流
        InputStream is = null;
        Workbook wb = null;
        try {
            // 验证文件名是否合格
            if (!validateExcel(fileName)) {
                return null;
            }
            // 根据文件名判断文件是2003版本还是2007版本
            boolean isExcel03 = true;
            if (WDWUtil.isExcel2007(fileName)) {
                isExcel03 = false;
            }
            // 根据新建的文件实例化输入流
            is = file.getInputStream();

            // 当excel是2003时
            if (isExcel03) {
                wb = new HSSFWorkbook(is);
            } else {
                wb = new XSSFWorkbook(is);
            }

            excelList = readExcelValue2(wb, isComment, startSheet, startRow);
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
            throw new DaoException(e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }
        return excelList;
    }

    /**
     * 读取Excel文件数据
     *
     * @param wb 工作薄
     * @param isComment 是否读取单元格批准信息
     * @param startSheet 开始的sheet
     * @param startRow 开始的行
     * @return 结果
     */
    private static List<Map<String, String>> readExcelValue(Workbook wb, boolean isComment, int startSheet, int startRow) {
        List<Map<String, String>> result = Lists.newArrayList();
        // 得到第一个shell
        Sheet sheet = wb.getSheetAt(startSheet);

        // 得到Excel的行数
//        int totalRows = sheet.getPhysicalNumberOfRows();
        int totalRows = sheet.getLastRowNum() + 1;

        int totalCells = 0;
        // 得到Excel的列数(前提是有行数)
        if (totalRows >= startRow && sheet.getRow(startRow) != null) {
            //如果列中有空的话 获取的列数和实际的excel列数不相同
//            totalCells = sheet.getRow(startRow).getPhysicalNumberOfCells();
            totalCells = sheet.getRow(startRow).getLastCellNum();
        }

        // 循环Excel行数
        for (int r = startRow; r < totalRows; r++) {
            Row row = sheet.getRow(r);
            if (row == null) continue;

            Map<String, String> rowData = Maps.newHashMap();
            JSONObject commentData = new JSONObject();
            // 循环Excel的列
            for (int c = 0; c < totalCells; c++) {
                Cell cell = row.getCell(c);

                String s = getCellValue(cell, r + startRow);
                rowData.put("cell" + c, s);
                if (isComment && null != cell) {
                    Comment msg = cell.getCellComment();
                    commentData.put("comment" + c, null == msg ? "" : msg.getString().toString());
                }
            }
            rowData.put("rowComment", commentData.toString());
            result.add(rowData);
        }
        return result;
    }

    /**
     * 读取Excel文件数据 与readExcelValue唯一不同是数值型保留四位小数（原先是两位）
     *
     * @param wb 工作薄
     * @param isComment 是否读取单元格批准信息
     * @param startSheet 开始的sheet
     * @param startRow 开始的行
     * @return 结果
     */
    private static List<Map<String, String>> readExcelValue2(Workbook wb, boolean isComment, int startSheet, int startRow) {
        List<Map<String, String>> result = Lists.newArrayList();
        // 得到第一个shell
        Sheet sheet = wb.getSheetAt(startSheet);

        // 得到Excel的行数
//        int totalRows = sheet.getPhysicalNumberOfRows();
        int totalRows = sheet.getLastRowNum() + 1;

        int totalCells = 0;
        // 得到Excel的列数(前提是有行数)
        if (totalRows >= startRow && sheet.getRow(startRow) != null) {
            //如果列中有空的话 获取的列数和实际的excel列数不相同
//            totalCells = sheet.getRow(startRow).getPhysicalNumberOfCells();
            totalCells = sheet.getRow(startRow).getLastCellNum();
        }

        // 循环Excel行数
        for (int r = startRow; r < totalRows; r++) {
            Row row = sheet.getRow(r);
            if (row == null) continue;

            Map<String, String> rowData = Maps.newHashMap();
            JSONObject commentData = new JSONObject();
            // 循环Excel的列
            for (int c = 0; c < totalCells; c++) {
                Cell cell = row.getCell(c);

                String s = getCellValue2(cell, r + startRow);
                rowData.put("cell" + c, s);
                if (isComment && null != cell) {
                    Comment msg = cell.getCellComment();
                    commentData.put("comment" + c, null == msg ? "" : msg.getString().toString());
                }
            }
            rowData.put("rowComment", commentData.toString());
            result.add(rowData);
        }
        return result;
    }

    /**
     * 获取单元格数据 并做数据格式换
     *
     * @param cell 单元格
     * @param rowIndex 行下标
     * @return 结果
     */
    private static String getCellValue(Cell cell, int rowIndex) {
        String cellValue;
        if (cell == null) return "";
        if (cell.getCellStyle() == null) return "";

        switch (cell.getCellTypeEnum().toString()) {
            case "STRING":
                cellValue = cell.getRichStringCellValue().getString().trim();
                break;
            case "BOOLEAN":
                cellValue = String.valueOf(cell.getBooleanCellValue()).trim();
                break;
            case "FORMULA":
                cellValue = cell.getCellFormula();
                break;
//            case HSSFCell.ENCODING_COMPRESSED_UNICODE:
//                cellValue = cell.toString();
//                break;
            case "NUMERIC":
                cellValue = new DecimalFormat("#.##").format(cell.getNumericCellValue()) + StringPool.EMPTY;
//                    cellValue = new DateTime(cell.getDateCellValue()).toString("yyyy-MM-dd HH:mm:ss")
                break;
            default:
                try {
                    cellValue = cell.getStringCellValue();
                }catch (Exception e){
                    e.printStackTrace();
                    throw new DaoException((rowIndex + 1) + "行数据异常");
                }
                break;
        }
        return cellValue;
    }

    /**
     * 获取单元格数据 并做数据格式换 与getCellValue唯一不同是数值型保留四位小数（原先是两位）
     *
     * @param cell 单元格
     * @param rowIndex 行下标
     * @return 结果
     */
    private static String getCellValue2(Cell cell, int rowIndex) {
        String cellValue;
        if (cell == null) return "";
        if (cell.getCellStyle() == null) return "";

        switch (cell.getCellTypeEnum().toString()) {
            case "STRING":
                cellValue = cell.getRichStringCellValue().getString().trim();
                break;
            case "BOOLEAN":
                cellValue = String.valueOf(cell.getBooleanCellValue()).trim();
                break;
            case "FORMULA":
                cellValue = cell.getCellFormula();
                break;
//            case HSSFCell.ENCODING_COMPRESSED_UNICODE:
//                cellValue = cell.toString();
//                break;
            case "NUMERIC":
                cellValue = new DecimalFormat("#.####").format(cell.getNumericCellValue()) + StringPool.EMPTY;
//                    cellValue = new DateTime(cell.getDateCellValue()).toString("yyyy-MM-dd HH:mm:ss")
                break;
            default:
                try {
                    cellValue = cell.getStringCellValue();
                }catch (Exception e){
                    e.printStackTrace();
                    throw new DaoException((rowIndex + 1) + "行数据异常");
                }
                break;
        }
        return cellValue;
    }

    /**
     * 验证文件是否为EXCEL文件
     *
     * @param fileName 文件名称
     * @return 结果
     */
    private static boolean validateExcel(String fileName) {
        return fileName != null && (WDWUtil.isExcel2003(fileName) || WDWUtil.isExcel2007(fileName));
    }


    /**
     * 判断 并转换时间格式 ditNumber = 43607.4166666667
     *
     * @param ditNumber 程序读取的Excel里面的日期格式单元格的数值
     * @return 结果
     */
    public static Date getTime(String ditNumber) {
        // 如果不是数字
        if(!isNumeric(ditNumber)){
            return null;
        }
        // 如果是数字 小于0则 返回
        BigDecimal bd = new BigDecimal(ditNumber);
        // 天数
        int days = bd.intValue();
        int mills = (int) Math.round(bd.subtract(new BigDecimal(days)).doubleValue() * 24 * 3600);

        // 获取时间
        Calendar c = Calendar.getInstance();
        c.set(1900, Calendar.JANUARY, 1);
        c.add(Calendar.DATE, days - 2);
        int hour = mills / 3600;
        int minute = (mills - hour * 3600) / 60;
        int second = mills - hour * 3600 - minute * 60;
        c.set(Calendar.HOUR_OF_DAY, hour);
        c.set(Calendar.MINUTE, minute);
        c.set(Calendar.SECOND, second);

        return c.getTime();
    }

    /**
     * 校验是否数据含小数点
     *
     * @param str 待判定的字符串
     * @return 结果
     */
    private static boolean isNumeric(String str) {
        Pattern pattern = Pattern.compile("[0-9]+\\.*[0-9]*");
        Matcher isNum = pattern.matcher(str);
        return isNum.matches();
    }


    public static void main(String[] args) {
        final String expression = "k-(x-y)*0.1";
        Map<String, Object> env = Maps.newHashMap();
        env.put("k", 2);
        env.put("x", 10);
        env.put("y", 5);
        Double score = 0d;
        if (!Strings.isNullOrEmpty(expression)) {
            Expression compiledExp = AviatorEvaluator.compile(expression);
            score = (Double) compiledExp.execute(env);
        }
        log.info("结果: {}", score);
    }
}
