package com.qmth.teachcloud.common.util.excel;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;

import com.qmth.teachcloud.common.util.excel.convertor.ValueConvertor;
import com.qmth.teachcloud.common.util.excel.convertor.ValueConvertors;
import com.qmth.teachcloud.common.util.excel.model.*;

public abstract class ExcelWriterDynamic {


    /**
     * 初始化带表头的Excel生成工具
     *
     * @param type 文件格式，XLSX或XLS
     * @return
     */
    public static ExcelWriterDynamic create(ExcelType type) {
        if (ExcelType.XLSX.equals(type)) {
            return new XlsxWriter();
        } else if (ExcelType.XLS.equals(type)) {
            return new XlsWriter();
        } else {
            throw new IllegalArgumentException("Excel type is required");
        }
    }

    /**
     * 创建sheet并写入String[]内容
     *
     * @param sheetName    sheet名称
     * @param titles       前置的标题，可以为空
     * @param columnNames  数据列名称
     * @param dataIterator String[]迭代器
     */
    public void writeDataArrays(String sheetName, String[] titles, String[] columnNames,
                                Iterator<String[]> dataIterator) {
        Sheet sheet = getWorkbook().createSheet(sheetName);
        AtomicInteger rowIndex = new AtomicInteger(0);
        //生成标题
        writeTitle(sheet, rowIndex, titles, columnNames.length);
        //生成表头
        writeColumnName(sheet, rowIndex, columnNames);
        //生成数据
        while (dataIterator.hasNext()) {
            writeArray(sheet, rowIndex, columnNames, dataIterator.next());
        }
    }

    /**
     * 创建sheet并写入DataMap对象
     *
     * @param sheetName    sheet名称
     * @param titles       前置的标题，可以为空
     * @param columnNames  数据列名称
     * @param dataIterator DataMap对象迭代器
     */
    public void writeDataMaps(String sheetName, String[] titles, String[] columnNames, Iterator<DataMap> dataIterator) {
        Sheet sheet = getWorkbook().createSheet(sheetName);
        AtomicInteger rowIndex = new AtomicInteger(0);
        //生成标题
        writeTitle(sheet, rowIndex, titles, columnNames.length);
        //生成表头
        writeColumnName(sheet, rowIndex, columnNames);
        //生成数据
        while (dataIterator.hasNext()) {
            writeData(sheet, rowIndex, columnNames, dataIterator.next());
        }
    }

    /**
     * 创建sheet并写入自定义对象
     *
     * @param sheetName      sheet名称
     * @param titles         前置标题，可以为空
     * @param objectType     自定义对象类型
     * @param objectIterator 自定义对象迭代器
     * @param <E>
     */
    public <E> void writeObjects(String sheetName, String[] titles, Class<E> objectType, Iterator<E> objectIterator) {
        Sheet sheet = getWorkbook().createSheet(sheetName);
        AtomicInteger rowIndex = new AtomicInteger(0);
        ObjectParam objectParam = ObjectParam.get(objectType);
        //生成标题
        writeTitle(sheet, rowIndex, titles, objectParam.getColumnNames().length);
        //生成表头
        writeColumnName(sheet, rowIndex, objectParam.getColumnNames());
        //生成数据
        while (objectIterator.hasNext()) {
            writeObject(sheet, rowIndex, objectParam, objectIterator.next());
        }
    }

    private void writeTitle(Sheet sheet, AtomicInteger rowIndex, String[] titles, int columnCount) {
        if (titles != null && titles.length > 0) {
            for (String title : titles) {
                Row row = sheet.createRow(rowIndex.getAndIncrement());
                Cell cell = row.createCell(0);
                cell.setCellValue(title);
                CellStyle style = getWorkbook().createCellStyle();
                style.setAlignment(HorizontalAlignment.CENTER);
                cell.setCellStyle(style);
                //根据表头宽度合并单元格
                if (columnCount > 1) {
                    sheet.addMergedRegion(new CellRangeAddress(row.getRowNum(), row.getRowNum(), 0, columnCount - 1));
                }
            }
        }
    }

    private void writeColumnName(Sheet sheet, AtomicInteger rowIndex, String[] names) {
        Row columnNameRow = sheet.createRow(rowIndex.getAndIncrement());
        int columnIndex = 0;
        for (String name : names) {
            Cell cell = columnNameRow.createCell(columnIndex++);
            cell.setCellValue(name);
        }
    }

    private <E> void writeObject(Sheet sheet, AtomicInteger rowIndex, ObjectParam objectParam, E object) {
        Row dataRow = sheet.createRow(rowIndex.getAndIncrement());
        int columnIndex = 0;
        for (String name : objectParam.getColumnNames()) {
            Cell cell = dataRow.createCell(columnIndex++);
            writeCell(cell, object, objectParam.get(name));
        }
    }

    private void writeData(Sheet sheet, AtomicInteger rowIndex, String[] columnNames, DataMap dataMap) {
        Row dataRow = sheet.createRow(rowIndex.getAndIncrement());
        int columnIndex = 0;
        for (String name : columnNames) {
            Cell cell = dataRow.createCell(columnIndex++);
            cell.setCellValue(dataMap.getOrDefault(name, StringUtils.EMPTY));
        }
    }

    private void writeArray(Sheet sheet, AtomicInteger rowIndex, String[] columnNames, String[] data) {
        Row dataRow = sheet.createRow(rowIndex.getAndIncrement());
        for (int columnIndex = 0; columnIndex < columnNames.length; columnIndex++) {
            Cell cell = dataRow.createCell(columnIndex);
            cell.setCellValue(data.length > columnIndex ? data[columnIndex] : StringUtils.EMPTY);
        }
    }

    private <E> void writeCell(Cell cell, E object, FieldParam param) {
        try {
            Object value = param.getField().get(object);
            if (value != null) {
                ValueConvertor formator = ValueConvertors.getConvertor(param.getField().getType());
                if (formator != null) {
                    formator.format(value, param, cell);
                } else {
                    cell.setCellValue(String.valueOf(value));
                }
            } else {
                cell.setCellValue(StringUtils.EMPTY);
            }
        } catch (Exception e) {
            cell.setCellValue(StringUtils.EMPTY);
        }
    }

    protected abstract Workbook getWorkbook();

    private CellStyle createCellStyle() {
        return getWorkbook().createCellStyle();
    }

    public void output(OutputStream ous) throws IOException {
        getWorkbook().write(ous);
    }

}