package xyz.wbsite.frame.excel;

import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.NumberToTextConverter;
import xyz.wbsite.frame.excel.annotation.ColumnDescription;
import xyz.wbsite.frame.excel.annotation.ColumnList;
import xyz.wbsite.frame.excel.annotation.ColumnName;
import xyz.wbsite.frame.excel.annotation.Convert;
import xyz.wbsite.frame.excel.annotation.Ignore;
import xyz.wbsite.frame.excel.annotation.ParentFirst;
import xyz.wbsite.frame.excel.annotation.SheetName;
import xyz.wbsite.frame.excel.converter.BooleanConverter;
import xyz.wbsite.frame.excel.converter.ByteConverter;
import xyz.wbsite.frame.excel.converter.CharacterConverter;
import xyz.wbsite.frame.excel.converter.Converter;
import xyz.wbsite.frame.excel.converter.DateConverter;
import xyz.wbsite.frame.excel.converter.DoubleConverter;
import xyz.wbsite.frame.excel.converter.FloatConverter;
import xyz.wbsite.frame.excel.converter.IntegerConverter;
import xyz.wbsite.frame.excel.converter.LongConverter;
import xyz.wbsite.frame.excel.converter.ShortConverter;
import xyz.wbsite.frame.excel.converter.StringConverter;
import xyz.wbsite.frame.excel.exception.ReadErrorException;
import xyz.wbsite.frame.excel.exception.TemplateNotMatchException;
import xyz.wbsite.frame.excel.exception.ValueConverterException;
import xyz.wbsite.frame.excel.style.DataCellStyle;
import xyz.wbsite.frame.excel.style.ErrorCellStyle;
import xyz.wbsite.frame.excel.style.HeadCellStyle;
import xyz.wbsite.frame.excel.style.RedFont;
import xyz.wbsite.frame.excel.style.SuccessCellStyle;
import xyz.wbsite.frame.utils.ClassUtil;
import xyz.wbsite.frame.utils.FileUtil;
import xyz.wbsite.frame.utils.LogUtil;
import xyz.wbsite.frame.utils.ValidationUtil;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * WExcel - 抽象的Excel对象数据集合
 *
 * 导出
 * new WExcel<Dept>(Dept.class).loadData(depts).toFile(file);<br>
 *
 * 导入
 * byte[] bytes = FileUtil.readFileToByteArray(new File("E:\\E.xlsx"));<br>
 * WExcel check = new WExcel<Dept>(Dept.class).loadData(bytes);
 *
 * @author wangbing
 * @version 0.0.1
 * @since 2017-01-01
 */
public class WExcel<T> implements Serializable, Cloneable {

    /**
     * 表名
     */
    private String name;

    /**
     * 是否冻结第一行
     */
    private boolean freezeFirst;

    /**
     * 模板类
     */
    private Class<T> templateClass;

    /**
     * 表头的集合
     */
    private List<WColumn> columnList = new ArrayList<>();

    /**
     * 单元格里存放的对象
     */
    private List<WRow> rowList = new ArrayList<>();

    /**
     * 根据输入的模板类打印下载模板。
     */
    public WExcel(Class<T> tClass) {
        this.templateClass = tClass;
        initColumns(tClass);
    }

    /**
     * 根据模板类对DataTable添加相应的列。
     *
     * @return WColumn集合
     */
    private List<WColumn> initColumns(Class<?> clazz) {
        //获取工作簿名称，没有则以类名为默认工作簿名称
        if (clazz.isAnnotationPresent(SheetName.class)) {
            SheetName sheetName = clazz.getAnnotation(SheetName.class);
            this.setName(sheetName.value());
            this.setFreezeFirst(sheetName.freezeFirst());
        } else {
            this.setName(clazz.getSimpleName());
        }

        //是否关注父类属性
        boolean parentFirst = clazz.isAnnotationPresent(ParentFirst.class) && clazz.getAnnotation(ParentFirst.class).value();
        Field[] fields = ClassUtil.getFields(clazz, parentFirst);
        for (Field field : fields) {
            if (field.isAnnotationPresent(Ignore.class) && field.getAnnotation(Ignore.class).value()) {
                continue;
            }

            WColumn WColumn = new WColumn();
            WColumn.setField(field);

            Method set = ClassUtil.setMethod(field.getName(), clazz, field.getType());
            WColumn.setSetMethod(set);

            Method get = ClassUtil.getMethod(field.getName(), clazz);
            WColumn.setGetMethod(get);

            //获取列名称
            if (!field.isAnnotationPresent(ColumnName.class)) {
                WColumn.setName(field.getName());
            } else {
                ColumnName columnColumnName = field.getAnnotation(ColumnName.class);
                WColumn.setName(columnColumnName.value());
                WColumn.setCellWidth(columnColumnName.width());
                WColumn.setRequired(columnColumnName.required());
            }
            //获取列填写说明或描述
            if (field.isAnnotationPresent(ColumnDescription.class)) {
                WColumn.setDescription(field.getAnnotation(ColumnDescription.class).value());
            }
            //获取下拉列表
            if (field.isAnnotationPresent(ColumnList.class)) {
                WColumn.setCellList(field.getAnnotation(ColumnList.class).value());
            }

            //获取列类型
            if (field.isAnnotationPresent(Convert.class)) {
                Convert converter = field.getAnnotation(Convert.class);
                Class target = converter.target();
                try {
                    WColumn.setConverter((Converter) target.newInstance());
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            } else {
                if (field.getType() == boolean.class || field.getType() == Boolean.class) {
                    WColumn.setConverter(new BooleanConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_BOOLEAN);
                } else if (field.getType() == byte.class || field.getType() == Byte.class) {
                    WColumn.setConverter(new ByteConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_NUMERIC);
                } else if (field.getType() == char.class || field.getType() == Character.class) {
                    WColumn.setConverter(new CharacterConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_STRING);
                } else if (field.getType() == short.class || field.getType() == Short.class) {
                    WColumn.setConverter(new ShortConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_NUMERIC);
                } else if (field.getType() == int.class || field.getType() == Integer.class) {
                    WColumn.setConverter(new IntegerConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_NUMERIC);
                } else if (field.getType() == long.class || field.getType() == Long.class) {
                    WColumn.setConverter(new LongConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_NUMERIC);
                } else if (field.getType() == float.class || field.getType() == Float.class) {
                    WColumn.setConverter(new FloatConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_NUMERIC);
                } else if (field.getType() == double.class || field.getType() == Double.class) {
                    WColumn.setConverter(new DoubleConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_NUMERIC);
                } else if (field.getType() == Date.class) {
                    WColumn.setConverter(new DateConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_NUMERIC);
                } else if (field.getType() == String.class) {
                    WColumn.setConverter(new StringConverter());
                    WColumn.setCellType(Cell.CELL_TYPE_STRING);
                } else {
                    throw new RuntimeException("Can not find Converter");
                }
            }

            columnList.add(WColumn);
        }
        return columnList;
    }

    public WExcel loadData(List<T> list) {
        return loadData(list, null);
    }

    /**
     * 此构造方法仅用于转换对象列表。<br/>
     * 并不会对数据的格式和合法性进行检验。
     *
     * @param list 需要导出的对象列表
     */
    public WExcel loadData(List<T> list, Processor<T> processor) {
        if (list.size() > 0) {
            for (T t : list) {
                WRow row = new WRow();
                for (WColumn column : columnList) {
                    if (column == null) {
                        continue;
                    }
                    Method method = column.getGetMethod();
                    try {
                        Object value = method.invoke(t);
                        if (null == value) {
                            row.put(column.getName(), new WCell());
                        } else {
                            String string = column.getConverter().string(value);
                            row.put(column.getName(), new WCell(string));
                        }
                    } catch (IllegalAccessException e) {
                        LogUtil.w("can not invoke get method!");
                    } catch (InvocationTargetException e) {
                        LogUtil.w(method.getName() + " unexpected exception!");
                    }
                    // <2层检查>检查模板注解验证是否通过
                    row.addErrors(ValidationUtil.validate(t));
                    // <3层检查>如果没有错误，则可以执行处理器进行业务处理
                    if (!row.hasError() && processor != null) {
                        List<String> exec = processor.exec(t);
                        row.addErrors(exec != null ? exec : Collections.EMPTY_LIST);
                    }
                }
                this.rowList.add(row);
            }
        }
        return this;
    }

    public WExcel loadData(File file) throws TemplateNotMatchException, ReadErrorException, IOException {
        return loadData(FileUtil.readFileToByteArray(file), null);
    }

    public WExcel loadData(File file, Processor<T> processor) throws TemplateNotMatchException, ReadErrorException, IOException {
        return loadData(FileUtil.readFileToByteArray(file), processor);
    }

    public WExcel loadData(byte[] bytes) throws TemplateNotMatchException, ReadErrorException {
        return loadData(bytes, null);
    }

    /**
     * 以Excel文件的字节数组为数据源，为自己赋值的同时，与指定的模板类进行数据校验。<br/>
     * 如果匹配过程中发现了不符合的数据，会在对应的单元中添加错误信息。<br/>
     */
    public WExcel loadData(byte[] bytes, Processor<T> processor) throws TemplateNotMatchException, ReadErrorException {
        Workbook workbook = null;
        InputStream is = null;
        boolean flag;
        try {
            flag = true;
            is = new ByteArrayInputStream(bytes);   //读取文件流
            workbook = new HSSFWorkbook(is);
        } catch (Exception e) {
            flag = false;
        }
        if (!flag) {
            try {
                flag = true;
                is = new ByteArrayInputStream(bytes);   //读取文件流
                workbook = new XSSFWorkbook(is);
            } catch (Exception e) {
                flag = false;
            }
        }
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (!flag) {
            throw new ReadErrorException("读取Excel文件错误");
        }
        //第一张Sheet表
        Sheet sheet = workbook.getSheetAt(0);
        //获取Sheet名称
        if (templateClass.isAnnotationPresent(SheetName.class)) {//如果模板存在注解则使用注解名称
            SheetName sheetName = templateClass.getAnnotation(SheetName.class);
            this.setName(sheetName.value());
        } else {//将类名设为表名，如果类名不存在，将Excel表名设为表名
            this.setName(sheet.getSheetName());
        }

        //读取表头
        Row headRow = sheet.getRow(0);
        //获取Excel列的总数
        int columnSum = headRow.getPhysicalNumberOfCells();

        //检查列数量
        if (columnList.size() != columnSum) {
            throw new TemplateNotMatchException("与模板列数量不同。");
        } else {
            for (int i = 0; i < columnList.size(); i++) {
                WColumn wColumn = columnList.get(i);
                Cell cell = headRow.getCell(i);
                String headValue = getValue(cell);
                headValue = headValue.replace("*", "");
                headValue = headValue.replace(" ", "");

                if (!wColumn.getName().equals(headValue)) {
                    throw new TemplateNotMatchException("第" + (i + 1) + "项，不匹配的列名，" + wColumn.getName() + "和" + headValue);
                }
            }
        }

        //Excel文件的总行数
        int maxRowNumber = sheet.getLastRowNum();
        // 逐行读取导入文件的数据
        for (int i = 0; i < maxRowNumber; i++) {
            //Excel中的一行数据，第0行为表头，所以要加1
            Row inputRow = sheet.getRow(i + 1);
            WRow row = new WRow();
            rowList.add(row);

            if (null != inputRow) {
                for (int j = 0; j < columnList.size(); j++) {
                    WColumn wcolumn = columnList.get(j);

					/* 取得当前格子的值 */
                    Cell excelCell = inputRow.getCell(j);
                    WCell WCell = new WCell();
                    row.put(wcolumn.getName(), WCell);

                    String value = "";
                    if (null != excelCell) {
                        value = getValue(excelCell);
                    }
                    value = value.trim();

                    WCell.setValue(value);
                }
            }

            try {
                T t = templateClass.newInstance();
                // <1层检查>检查数据格式是否正确
                row.addErrors(transferMap(row, t));
                // <2层检查>检查模板注解验证是否通过
                row.addErrors(ValidationUtil.validate(t));
                // <3层检查>如果没有错误，则可以执行处理器进行业务处理
                if (!row.hasError() && processor != null) {
                    List<String> exec = processor.exec(t);
                    row.addErrors(exec != null ? exec : Collections.EMPTY_LIST);
                }
            } catch (InstantiationException | IllegalAccessException e) {
                row.addError("模板对象默认构造函数错误");
            }
        }
        return this;
    }

    /**
     * 转换某行为一个对象
     *
     * @param row    行对象
     * @param target 模板对象
     * @return 模板对象
     */
    public List<String> transferMap(WRow row, T target) {
        List<String> err = new ArrayList<>();
        for (String key : row.keySet()) {
            for (WColumn column : columnList) {
                String name = column.getName();
                Method setMethod = column.getSetMethod();

                if (key.equals(name)) {
                    WCell WCell = row.get(column.getName());
                    if (null != WCell) {
                        String value = WCell.getValue();
                        //获取转换器
                        Converter converter = column.getConverter();
                        try {
                            setMethod.invoke(target, converter.convert(value));
                        } catch (ValueConverterException e) {
                            e.printStackTrace();
                            err.add(e.getMessage());
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                            err.add("[" + setMethod.getName() + "]执行错误");
                        }
                    }
                    break;
                }
            }
        }
        return err;
    }

    /**
     * 获取表名
     *
     * @return 表名
     */
    public String getName() {
        return name;
    }

    /**
     * 设置表名
     *
     * @param name 表名
     */
    public void setName(String name) {
        this.name = name;
    }

    public boolean getFreezeFirst() {
        return freezeFirst;
    }

    public void setFreezeFirst(boolean freezeFirst) {
        this.freezeFirst = freezeFirst;
    }

    public final String toCSV() {
        StringBuilder sb = new StringBuilder();

        for (WColumn WColumn : this.columnList) {
            if (WColumn != null) {
                sb.append(WColumn.getName()).append(",");
            }
        }
        sb.append("\n");

        for (int i = 0; i < this.rowList.size(); i++) {
            for (int j = 0; j < this.columnList.size(); j++) {
                WColumn wColumn = this.columnList.get(j);
                WCell wCell = this.rowList.get(i).get(wColumn.getName());
                sb.append(wCell.getValue()).append(",");
            }
            sb.append("\n");
        }

        return sb.toString();
    }

    /**
     * 判断DataTable是否包含错误信息
     *
     * @return 是否包含错误.
     */
    public final boolean hasError() {
        for (WRow wRow : rowList) {
            if (wRow.hasError()) {
                return true;
            }
        }
        return false;
    }

    public byte[] getBytes() {
        XSSFWorkbook workbook = getExcel();
        if (workbook != null) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            try {
                workbook.write(outputStream);
            } catch (IOException e) {
                return null;
            }
            return outputStream.toByteArray();
        }
        return null;
    }

    public XSSFWorkbook getExcel() {
        XSSFWorkbook workbook = new XSSFWorkbook();
        //创建一个Sheet表
        XSSFSheet sheet = workbook.createSheet(name);
        sheet.setDefaultRowHeightInPoints(18);
        Row firstRow = sheet.createRow(0); // 下标为0的行开始
        XSSFDrawing drawing = sheet.createDrawingPatriarch();

        int offset = 0;
        // 添加结果和错误说明列
        if (hasError()) {
            offset++;
            {// 第一栏结果栏
                Cell firstCell = firstRow.createCell(0);
                firstCell.setCellStyle(new HeadCellStyle(workbook).getStyle());
                firstCell.setCellValue(new XSSFRichTextString("执行结果"));
                XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
                XSSFComment comment = drawing.createCellComment(anchor);
                comment.setString("此列为检查结果，导入时请处理完错误，并删除该列!");
                firstCell.setCellComment(comment);
            }
            offset++;
            {// 第二栏错误信息栏
                Cell secondCell = firstRow.createCell(1);
                secondCell.setCellStyle(new HeadCellStyle(workbook).getStyle());
                secondCell.setCellValue(new XSSFRichTextString("结果说明"));
                sheet.setColumnWidth(1, 18 * 256);
                XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
                XSSFComment comment = drawing.createCellComment(anchor);
                comment.setString("此列为检查结果信息列，导入时请处理完错误，并删除该列!");
                secondCell.setCellComment(comment);
            }
        }

        for (int j = 0; j < this.columnList.size(); j++) {
            WColumn column = this.columnList.get(j);
            Cell firstCell = firstRow.createCell(j + offset);
            String columnName = column.getName();
            XSSFRichTextString textString;
            if (column.isRequired()) {
                textString = new XSSFRichTextString("*" + columnName);
                textString.applyFont(0, 1, new RedFont(workbook).getFont());
                textString.applyFont(1, textString.length(), workbook.createFont());
            } else {
                textString = new XSSFRichTextString(columnName);
                textString.applyFont(workbook.createFont());
            }
            StringBuilder sb = new StringBuilder();
            sb.append(column.getDescription()).append("\n");

            // 如果填写了注释信息
            if (sb.length() > 1) {
                XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
                XSSFComment comment = drawing.createCellComment(anchor);
                comment.setString(sb.toString());
                firstCell.setCellComment(comment);
            }
            firstCell.setCellValue(textString);
            firstCell.setCellStyle(new HeadCellStyle(workbook).getStyle());
            sheet.setColumnWidth(j + offset, (4 + column.getCellWidth()) * 256);

            if (column.getCellList() != null){
                CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 65535, j+offset, j+offset);
                DataValidationHelper helper = sheet.getDataValidationHelper();
                DataValidationConstraint constraint = helper.createExplicitListConstraint(column.getCellList());
                DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
                //处理Excel兼容性问题
                if (dataValidation instanceof XSSFDataValidation) {
                    dataValidation.setSuppressDropDownArrow(true);
                    dataValidation.setShowErrorBox(true);
                } else {
                    dataValidation.setSuppressDropDownArrow(false);
                }

                dataValidation.setEmptyCellAllowed(true);
                dataValidation.setShowPromptBox(true);
                dataValidation.createPromptBox("规则", "请选择下拉框里面的数据");
                sheet.addValidationData(dataValidation);
            }
        }

        // 冻结第一行
        if (freezeFirst) {
            sheet.createFreezePane(255, 1);
        }

        // 填充数据
        for (int i = 0; i < this.rowList.size(); i++) {
            WRow wRow = this.rowList.get(i);
            Row row = sheet.createRow(i + 1);

            if (offset > 0) {
                if (this.rowList.get(i).hasError()) {
                    // 添加结果
                    List<String> errorList = wRow.getErrorList();
                    Cell resultCell = row.createCell(0);
                    resultCell.setCellStyle(new ErrorCellStyle(workbook).getStyle());
                    resultCell.setCellValue("错误");
                    // 添加错误信息详细说明
                    Cell errsCell = row.createCell(1);
                    errsCell.setCellStyle(new ErrorCellStyle(workbook).getStyle());
                    String join = String.join(";\n", errorList);
                    errsCell.setCellValue(new XSSFRichTextString(join));
                } else {
                    // 添加结果
                    Cell resultCell = row.createCell(0);
                    resultCell.setCellStyle(new SuccessCellStyle(workbook).getStyle());
                    resultCell.setCellValue("成功");
                    // 添加错误信息详细说明
                    Cell errsCell = row.createCell(1);
                    errsCell.setCellStyle(new SuccessCellStyle(workbook).getStyle());
                    errsCell.setCellValue("");
                }
            }

            for (int j = 0; j < this.columnList.size(); j++) {
                WColumn column = this.columnList.get(j);

                Cell xssfCell = row.createCell(j + offset);
                WCell WCell = this.rowList.get(i).get(column.getName());
                if (null == WCell) {
                    continue;
                }
                String value = WCell.getValue();
                xssfCell.setCellType(column.getCellType());
                if (column.getCellType() == Cell.CELL_TYPE_NUMERIC || column.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
                    xssfCell.setCellStyle(new DataCellStyle(workbook, CellStyle.ALIGN_RIGHT, wRow.hasError()).getStyle());
                } else {
                    xssfCell.setCellStyle(new DataCellStyle(workbook, CellStyle.ALIGN_LEFT, wRow.hasError()).getStyle());
                }

                xssfCell.setCellValue(value);
            }
        }
        return workbook;
    }

    /**
     * 获取单元格的值
     *
     * @param cell 要获取值的单元格
     * @return 单元格的值
     */
    public static String getValue(Cell cell) {
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case Cell.CELL_TYPE_STRING:
                return cell.getStringCellValue();
            case Cell.CELL_TYPE_BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case Cell.CELL_TYPE_NUMERIC:
                if (HSSFDateUtil.isCellDateFormatted(cell)) {
                    Date value = cell.getDateCellValue();
                    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value);
                } else {
                    return NumberToTextConverter.toText(cell.getNumericCellValue());
                }
            default:
                return cell.getStringCellValue();
        }
    }

    public interface Processor<T> {
        List<String> exec(T t);
    }

    public void toFile(File file) throws IOException {
        File parentFile = file.getParentFile();
        if (!parentFile.exists() && !parentFile.mkdirs()) {
            throw new RuntimeException("路径创建失败");
        }
        if (!file.exists() && !file.createNewFile()) {
            throw new RuntimeException("文件创建失败");
        }
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(getBytes());
        fileOutputStream.close();
    }
}
