package net.northking.cctp.common.util.excelUtil;

import com.google.gson.Gson;
import net.northking.cctp.common.util.excelUtil.excelEntity.ExcelHead;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.UnsupportedFileFormatException;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 导入excel
 * Created by chaoyi.zhang
 * 2019/11/29 0029
 */
public class ImportExcel {
    private static final String excel2003 = ".xls";
    private static final String excel2007 = ".xlsx";
    private int rowIndex = 0;
    private int dataNum = 0;
    private boolean isNew = true;
    private static final Logger logger = LoggerFactory.getLogger(ImportExcel.class);
    private static NumberFormat numberFormat = NumberFormat.getNumberInstance();
    static {
        numberFormat.setGroupingUsed(false);
    }

    /**
     * 解析上传excel
     * @param file : 文件流
     * @param heads : 解析表头
     * @return
     */
    public ExcelResult analyseExcelFile(File file,List<ExcelHead> heads){

        ZipSecureFile.setMinInflateRatio(-1.0d);
        Workbook wb = null;
        InputStream fis = null;
        String message = "";
        boolean success = true;
        List<Map<String,Object>> result= new ArrayList<Map<String, Object>>();
        try {
            fis = new FileInputStream(file);

            //判断后缀名
            String fileName = file.getName();
            String suffixName = fileName.substring(fileName.lastIndexOf("."));
            if (excel2003.equals(suffixName)) {
                wb = new HSSFWorkbook(fis);
            } else if (excel2007.equals(suffixName)) {
                wb = new XSSFWorkbook(fis);
            }else{
                throw new Exception("上传文件有误");
            }

            //默认只读第一张sheet页
            Sheet sheet = wb.getSheetAt(0);
            //校验表头
            message = verificationHead(wb,heads);
            if(message.length()>0){
                //表头校验未通过
                throw new Exception(message);
            }
            // 获取合并单元格对象的相关信息
            Map<Integer, List<ExcelMergedRegionBean>> mergedRegionMap = getMergedRegionMap(sheet);
            for(int i = 1;i<sheet.getLastRowNum()+1;i++){
                //循环读取_数据从第二行开始
                int checkB = 0;
                Map<String,Object> dataMap = new HashMap<String,Object>();
                Map<String,Object> expend = new HashMap<String,Object>();
                Row row = sheet.getRow(i);
                for(int k = 0;k<heads.size();k++){
                    ExcelHead head = heads.get(k);
                    Cell cell = row.getCell(k);
                    Object value = getActualCellValue(sheet, mergedRegionMap, cell,(k==(heads.size()-1)));
                    String verRe = verificationData(head,value,i,k);
                    if(!"".equals(verRe) || "".equals(value)){
                        if("".equals(message) && (!"1".equals(head.getIsMerge()) || isNew)){
                            message = verRe;
                        }
                    }else{
                        checkB++;
                    }
                    if("1".equals(head.getIsPublic())){
                        dataMap.put(head.getTemplatePropName(),value);
                    }else{
                        expend.put(head.getTemplatePropName(),value);
                    }
                }
                if(!"".equals(message) && checkB>0){
                    throw new Exception(message);
                }
                Gson gson = new Gson();
                String json =gson.toJson(expend);
                dataMap.put("expands",json);
                if(checkB>0){
                    result.add(dataMap);
                }
                isNew=false;
                message = "";
            }

        } catch (IOException e) {
            logger.error(e.getMessage());
            success = false;
        } catch (Exception e) {
            logger.error(e.getMessage());
            success = false;
        }finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    logger.error(e.getMessage());
                }
            }
        }
        ExcelResult excelResult = new ExcelResult();
        excelResult.setSuccess(success);
        excelResult.setDataList(result);
        excelResult.setMessage(message);
        return excelResult;
    }


    /**
     * 解析上传excel
     * @param file : 文件流
     * @param heads : 解析表头
     * @return
     */
    public ExcelResult analyseExcel(MultipartFile file,List<ExcelHead> heads){
        long time0 = System.currentTimeMillis();
        long time1=0;
        long time2=0;
        long time3=0;
        long getTimeA = 0;
        long getTimeB = 0;
        ZipSecureFile.setMinInflateRatio(-1.0d);
        Workbook wb = null;
        InputStream fis = null;
        String message = "";
        boolean success = true;
        List<Map<String,Object>> result= new ArrayList<Map<String, Object>>();
        try {
            fis = file.getInputStream();

            //判断后缀名
            String fileName = file.getOriginalFilename();
            String suffixName = fileName.substring(fileName.lastIndexOf("."));
            if (excel2003.equals(suffixName)) {
                wb = new HSSFWorkbook(fis);
            } else if (excel2007.equals(suffixName)) {
                wb = new XSSFWorkbook(file.getInputStream());
            }else{
                throw new Exception("上传文件有误");
            }

            //默认只读第一张sheet页
            Sheet sheet = wb.getSheetAt(0);
            time1 = System.currentTimeMillis();
            //校验表头
            message = verificationHead(wb,heads);
            if(message.length()>0){
                //表头校验未通过
                throw new Exception(message);
            }
            time2 = System.currentTimeMillis();
            // 获取合并单元格对象的相关信息
            Map<Integer, List<ExcelMergedRegionBean>> mergedRegionMap = getMergedRegionMap(sheet);
            time3 = System.currentTimeMillis();
            for(int i = 1;i<sheet.getLastRowNum()+1;i++){
                //循环读取_数据从第二行开始
                int checkB = 0;
                Map<String,Object> dataMap = new HashMap();
                Map<String,Object> expend = new HashMap();
                Row row = sheet.getRow(i);
                if(row == null){
                    continue;
                }
                for(int k = 0;k<heads.size();k++){
                    ExcelHead head = heads.get(k);
                    Cell cell = row.getCell(k);
                    long timeA = System.currentTimeMillis();
                    Object value = getActualCellValue(sheet, mergedRegionMap, cell,(k==(heads.size()-1)));
                    long timeB = System.currentTimeMillis();
                    String verRe = verificationData(head,value,i,k);
                    getTimeA += timeB-timeA;
                    getTimeB += System.currentTimeMillis()-timeB;
                    if(!"".equals(verRe) || "".equals(value)){
                        if("".equals(message) && (!"1".equals(head.getIsMerge()) || isNew)){
                            message = verRe;
                        }
                    }else{
                        checkB++;
                    }
                    if("1".equals(head.getIsPublic()+"")){
                        dataMap.put(head.getTemplatePropName(),value);
                    }else{
                        expend.put(head.getTemplatePropName(),value);
                    }
                }
                if(!"".equals(message) && checkB>0){
                    throw new Exception(message);
                }
                Gson gson = new Gson();
                String json =gson.toJson(expend);
                dataMap.put("expands",json);
                if(checkB>0){
                    dataMap.put("id",dataNum+"");
                    result.add(dataMap);
                }
                isNew=false;
                message = "";
            }

        } catch (UnsupportedFileFormatException e) {
            success = false;
            message = "文件解析异常,请确认文件完整性或文件格式是否改动";
        } catch (Exception e) {
            logger.error(e.getMessage());
            if(StringUtils.isEmpty(message))message="文件解析未知异常，请使用系统模板";
            success = false;
        } finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    logger.error(e.getMessage());
                }
            }
        }

        long time4 = System.currentTimeMillis();
        ExcelResult excelResult = new ExcelResult();
        excelResult.setSuccess(success);
        excelResult.setDataList(result);
        excelResult.setMessage(message);
        excelResult.setLog("获取文件耗时:"+(time1-time0)+";\n校验表头耗时:"+(time2-time1)+";" +
                "\n遍历合并单元格耗时:"+(time3-time2)+";\n获取所有数据耗时:"+(time4-time3)+";" +
                "\n其中获单元格数据耗时:"+getTimeA+"\n其中获校验数据耗时:"+getTimeB+";\n总耗时:"+(time4-time0));
        return excelResult;
    }

    //校验表头
    private static String verificationHead(Workbook wb,List<ExcelHead> heads){
        String  message = "";
        Sheet sheet = wb.getSheetAt(0);
        Row headRow = sheet.getRow(0);
        if(headRow==null){
            headRow = sheet.createRow(0);
        }
        for(int i = 0;i<heads.size();i++){
            String temp = ExcelToolUtil.getExcelColumnLabel(i);
            if(headRow.getCell(i)==null){
                message = "第一行第"+temp+"列，表头为空,缺少属性列："+heads.get(i).getTemplatePropDisplay();
                break;
            }
            String cell = headRow.getCell(i).getStringCellValue();
            String head = heads.get(i).getTemplatePropDisplay().trim();
            if (cell ==null || "".equals(cell)){
                message = "第一行第"+temp+"列,表头为空,缺少属性列："+heads.get(i).getTemplatePropDisplay();
                break;
            }else if(!cell.equals(head)){
                message = "第一行第"+temp+"列,表头【"+cell+"】与定义的属性【"+head+"】不匹配，请确认";
                break;
            }
        }
        return message;
    }
    //校验数据
    private static String verificationData(ExcelHead head,Object value,int x,int y){
        String  message = "";
        String temp = ExcelToolUtil.getExcelColumnLabel(y);
        String suffix = "第"+(x+1)+"行第"+temp+"列";
        //必输项校验
        if("".equals(value)){
            if("1".equals(head.getIsFilled())){
                message=suffix+"为必输项，请填写数据再进行导入";
            }
            return message;
        }
        //长度校验
        int len = 0;
        try {
            //len = new String(value.toString().getBytes("GB2312"), "ISO-8859-1").length();
            len = value.toString().length();
        } catch (Exception e) {
            message = suffix+"字符解析出错";
            return message;
        }
        if(head.getValueLength() !=null && len>Integer.parseInt(head.getValueLength())){
            message=suffix+"为字符长度为"+Integer.parseInt(head.getValueLength())+
                    ",当前字符长度"+len+",已超出限制";
            return message;
        }

        //下拉框值校验
        if(head.getAddress()!=null && head.getAddress().length>0){
           String check = ","+String.join(",",head.getAddress())+",";
           if(!check.contains(","+value.toString()+",")){
               message=suffix+"输入值超过限定范围，请重新选取";
               return message;
           }
        }

        //正则表达式校验
        if(head.getRegex()!=null && head.getRegex().length()>0){
            String regex = head.getRegex();
            if(!Pattern.matches(regex,value.toString())){
                message=suffix+"输入值不符合规定,请重新输入";
                return message;
            }
        }
        return message;
    }

    /**
     * 获取真实的单元格的值，主要是处理合并单元格的情况
     * @param sheet
     * @param mergedRegionMap
     * @param myCell
     * @return
     */
    private Object getActualCellValue(Sheet sheet, Map<Integer,List<ExcelMergedRegionBean>> mergedRegionMap, Cell myCell,boolean isEnd) {

        Cell actualCell = myCell;
        if(myCell==null){
            return "";
        }

        int rowNum = actualCell.getRowIndex();
        if(mergedRegionMap.containsKey(rowNum)){
            List<ExcelMergedRegionBean> mergedRegionMapList = mergedRegionMap.get(rowNum);
            // 迭代合并单元格对象，判断myCell该对象是否属于合并单元格
            int rmv = 0;
            for (ExcelMergedRegionBean mb : mergedRegionMapList) {
                // 判断myCell该对象是否属于合并单元格，如果是的话，则直接退出循环
                if (myCell.getColumnIndex() <= mb.getLastCell()
                        && myCell.getColumnIndex() >= mb.getFirstCell()) {

                    Row row = sheet.getRow(mb.getFirstRow());

                    Cell cell = row.getCell(mb.getFirstCell());

                    actualCell = cell;
                    mergedRegionMapList.remove(rmv);
                    break;
                }
                rmv++;
            }
        }
        if (isEnd) {
            mergedRegionMap.remove(rowNum);
        }

        Object re = getCellValue(actualCell);
        //判断单元格的起始行是否是新一轮数据
        if (actualCell != null) {
            if(actualCell.getColumnIndex()<1 && actualCell.getRowIndex()>rowIndex && !"".equals(re)){
                rowIndex = actualCell.getRowIndex();
                dataNum++;
                isNew=true;
            }
        }
        // 返回该单元对应的真实值
        return re;

    }

    /**
     * 对表格中的数据进行格式化
     */
    private Object getCellValue(Cell cell) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Object value = "";
        if (null == cell) {
            return value;
        }
        //以下是判断数据的类型
        switch (cell.getCellType()){
            //数值
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    // 处理日期格式、时间格式
                    value =cell.getDateCellValue();
                } else if (cell.getCellStyle().getDataFormat() == 58) {
                    // 处理自定义日期格式：m月d日(通过判断单元格的格式id解决，id的值是58)

                    double cellValue = cell.getNumericCellValue();
                    value = DateUtil
                            .getJavaDate(cellValue);
                } else {
                    //纯数字格式
                    value = numberFormat.format(cell.getNumericCellValue());
                }
                break;
            case STRING: // 字符串
                value = cell.getStringCellValue().trim();
                break;

            case BOOLEAN: // Boolean
                value = cell.getBooleanCellValue() + "";
                break;

            case FORMULA: // 公式_取公式计算结果
                //value = cell.getCellFormula() + "";
                try {
                    value = numberFormat.format(cell.getNumericCellValue());
                }catch (Exception e){
                    value = cell.getStringCellValue().trim();
                }
                break;

            case BLANK: // 空值
                value = "";
                break;

            case ERROR: // 故障
                value = "非法字符";
                break;
            default:
                value = "未知类型";
                break;
        }
        return value;
    }


    /**
     * 获取合并单元格的信息
     *
     * @param sheet
     * @return
     */
    private static List<ExcelMergedRegionBean> getMergedRegionMapList(Sheet sheet) {

        List<ExcelMergedRegionBean> mergedRegionMapList = new ArrayList<ExcelMergedRegionBean>();

        for(CellRangeAddress ca : sheet.getMergedRegions()){
            // 获得合并单元格的起始行, 结束行, 起始列, 结束列
            int firstC = ca.getFirstColumn();

            int lastC = ca.getLastColumn();

            int firstR = ca.getFirstRow();

            int lastR = ca.getLastRow();

            ExcelMergedRegionBean mb = new ExcelMergedRegionBean();

            mb.setFirstRow(firstR);

            mb.setLastRow(lastR);

            mb.setFirstCell(firstC);

            mb.setLastCell(lastC);

            mergedRegionMapList.add(mb);
        }

        //排序，便于后面循环删除
        //Collections.sort(mergedRegionMapList);

        return mergedRegionMapList;

    }

    /**
     * 获取合并单元格的信息
     *
     * @param sheet
     * @return
     */
    private static Map<Integer,List<ExcelMergedRegionBean>> getMergedRegionMap(Sheet sheet) {
        Map<Integer,List<ExcelMergedRegionBean>> map = new HashMap<>();
        List<ExcelMergedRegionBean> mergedRegionMapList = new ArrayList<ExcelMergedRegionBean>();
        ExcelMergedRegionBean mb = new ExcelMergedRegionBean();
        for(CellRangeAddress ca : sheet.getMergedRegions()){
            int firstC = ca.getFirstColumn();

            int lastC = ca.getLastColumn();

            int firstR = ca.getFirstRow();

            int lastR = ca.getLastRow();

            for (int i = firstR; i < lastR+1; i++) {
                if(map.containsKey(i)){
                    mergedRegionMapList = map.get(i);
                }else{
                    mergedRegionMapList = new ArrayList<ExcelMergedRegionBean>();
                }

                mb = new ExcelMergedRegionBean();

                mb.setFirstRow(firstR);

                mb.setLastRow(lastR);

                mb.setFirstCell(firstC);

                mb.setLastCell(lastC);

                mergedRegionMapList.add(mb);
                map.put(i,mergedRegionMapList);
            }
        }

        return map;

    }
}
