package ${basePackage}.frame.excel;

import ${basePackage}.frame.excel.converter.Converter;
import ${basePackage}.frame.excel.exception.ValueConverterException;
import ${basePackage}.frame.excel.style.DataCellStyle;
import ${basePackage}.frame.excel.style.ErrorCellStyle;
import ${basePackage}.frame.excel.style.HeadCellStyle;
import ${basePackage}.frame.excel.style.RedFont;
import ${basePackage}.frame.utils.ClassUtil;
import ${basePackage}.frame.utils.LogUtil;
import ${basePackage}.frame.utils.ValidationUtil;
import ${basePackage}.frame.excel.annotation.*;
import ${basePackage}.frame.excel.converter.*;
import ${basePackage}.frame.excel.exception.ReadErrorException;
import ${basePackage}.frame.excel.exception.TemplateNotMatchException;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;

import javax.validation.constraints.NotNull;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * WSheet - Excel-Sheet对象，完整的数据集合对象。
 *
 * @author wangbing
 * @version 0.0.1
 * @since 2017-01-01
 */
public class WSheet<T> implements Serializable, Cloneable {

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

    /**
     * 表名
     */
    private boolean freezeFirst;

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

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

    /**
     * 根据输入的模板类打印下载模板。
     */
    public WSheet(Class<T> 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(${basePackage}.frame.excel.annotation.Converter.class)) {
                ${basePackage}.frame.excel.annotation.Converter converter = field.getAnnotation(${basePackage}.frame.excel.annotation.Converter.class);
                Class target = converter.target();
                try {
                    WColumn.setConverter((Converter) target.newInstance());
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (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;
    }

    /**
     * 此构造方法仅用于转换对象列表。<br/>
     * 并不会对数据的格式和合法性进行检验。
     *
     * @param list 需要导出的对象列表
     */
    public WSheet(List<T> list) {
        if (list == null || list.size() == 0) {
            throw new RuntimeException("导出数据为空");
        }
        if (list.size() > 0) {
            List<WColumn> wColumns = initColumns(list.get(0).getClass());

            for (T t : list) {
                WRow row = new WRow();
                for (WColumn column : wColumns) {
                    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!");
                    }
                }
                this.rowList.add(row);
            }
        }
    }

    /**
     * 以Excel文件的字节数组为数据源，为自己赋值的同时，与指定的模板类进行数据校验。<br/>
     * 如果匹配过程中发现了不符合的数据，会在对应的单元中添加错误信息。<br/>
     *
     * @param bytes Excel文件的字节数组
     * @param clazz 类型
     * @throws IOException <br/>1:输入流无法转为Excel文件 - 请检查输入的 bytes 是否正确；<br/>
     *                     2:输入流关闭出错；<br/>
     */
    public WSheet(byte[] bytes, Class<T> clazz) throws TemplateNotMatchException, ReadErrorException {
        this(bytes, clazz, null);
    }

    public WSheet(byte[] bytes, Class<T> clazz, Validator validator) 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 (clazz.isAnnotationPresent(SheetName.class)) {//如果模板存在注解则使用注解名称
            SheetName sheetName = clazz.getAnnotation(SheetName.class);
            this.setName(sheetName.value());
        } else {//将类名设为表名，如果类名不存在，将Excel表名设为表名
            this.setName(sheet.getSheetName());
        }

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

        //检查列数量
        List<WColumn> list = initColumns(clazz);
        if (list.size() != columnSum) {
            throw new TemplateNotMatchException("与模板列数量不同。");
        } else {
            for (int i = 0; i < list.size(); i++) {
                WColumn wColumn = list.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);
                }
            }
        }

        int maxRowNumber = sheet.getLastRowNum();                   //Excel文件的总行数
        // 逐行读取导入文件的数据
        for (int i = 0; i < maxRowNumber; i++) {
            Row inputRow = sheet.getRow(i + 1);                //Excel中的一行数据，第0行为表头，所以要加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 = clazz.newInstance();
                List<String> errs = transferMap(row, t);
                errs.addAll(ValidationUtil.validate(t));
                errs.addAll(validator != null ? validator.validate(t) : Collections.emptyList());
                if (errs.size() > 0) {
                    for (String s : errs) {
                        row.addError(s);
                    }
                }
            } catch (InstantiationException | IllegalAccessException e) {
                row.addError("模板对象默认构造函数错误");
            }
        }
    }

    /**
     * 转换某行为一个对象
     *
     * @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.getWColumns()) {
//            if (WColumn != null) {
//                sb.append(WColumn.getName()).append(",");
//            }
//        }
//        sb.append("\n");
//
//        for (int i = 0; i < this.getRowIndex(); i++) {
//            for (int j = 0; j < this.getColumnIndex(); j++) {
//                WCell WCell = this.getCell(i, j);
//                if (this.columnList.get(j).getExcelType().equals(ExcelType.STRING)
//                        || this.columnList.get(j).getExcelType().equals(ExcelType.DATE)
//                        || this.columnList.get(j).getExcelType().equals(ExcelType.DATETIME)) {
//                    sb.append("\"\t").append(WCell.getValue()).append("\",");
//                } else {
//                    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;
    }

    /**
     * 得到已生成好的Excel文件的字节流信息
     *
     * @return 字节流信息
     * @throws IOException
     */
    public byte[] getBytes() {
        return getBytes(false);
    }

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

    public XSSFWorkbook getExcel(boolean checkResult) {
        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 (checkResult && hasError()) {
            offset++;
            Cell firstCell = firstRow.createCell(0);
            firstCell.setCellStyle(new ErrorCellStyle(workbook).getStyle());
            firstCell.setCellValue(new XSSFRichTextString("检查结果说明"));
            sheet.setColumnWidth(0, 18 * 256);
        }

        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 (freezeFirst) {
            sheet.createFreezePane(255, 1);
        }

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

            if (flag) {
                List<String> errorList = wRow.getErrorList();
                Cell xssfCell = row.createCell(0);
                xssfCell.setCellStyle(new ErrorCellStyle(workbook).getStyle());
                String join = String.join(";", errorList);
                xssfCell.setCellValue(new XSSFRichTextString(join));
            }

            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 {
                    double numericCellValue = cell.getNumericCellValue();
                    String s = String.valueOf(numericCellValue);
                    return s.replaceAll("\\.0$", "");
                }
            default:
                return cell.getStringCellValue();
        }
    }

    public interface Validator<T> {
        List<String> validate(T t);
    }
}
