package cn.report.excel.easyexcel.api.read.listener;

import java.util.Map;

import cn.report.excel.easyexcel.api.context.AnalysisContext;
import cn.report.excel.easyexcel.api.enums.CellDataTypeEnum;
import cn.report.excel.easyexcel.api.enums.HeadKindEnum;
import cn.report.excel.easyexcel.api.exception.ExcelDataConvertException;
import cn.report.excel.easyexcel.api.metadata.Head;
import cn.report.excel.easyexcel.api.metadata.data.ReadCellData;
import cn.report.excel.easyexcel.api.read.metadata.holder.ReadSheetHolder;
import cn.report.excel.easyexcel.api.read.metadata.property.ExcelReadHeadProperty;
import cn.report.excel.easyexcel.api.util.BeanMapUtils;
import cn.report.excel.easyexcel.api.util.ClassUtils;
import cn.report.excel.easyexcel.api.util.ConverterUtils;
import cn.report.excel.easyexcel.api.util.MapUtils;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cglib.beans.BeanMap;

/**
 * Convert to the object the user needs
 *
 * @author jipengfei
 */
public class ModelBuildEventListener implements IgnoreExceptionReadListener<Map<Integer, ReadCellData<?>>> {

    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> cellDataMap, AnalysisContext context) {
        if (context.readSheetHolder().getMaxDataHeadSize() == null
            || context.readSheetHolder().getMaxDataHeadSize() < CollectionUtils.size(cellDataMap)) {
            context.readSheetHolder().setMaxDataHeadSize(CollectionUtils.size(cellDataMap));
        }
    }

    @Override
    public void invoke(Map<Integer, ReadCellData<?>> cellDataMap, AnalysisContext context) {
        ReadSheetHolder readSheetHolder = context.readSheetHolder();
        if (HeadKindEnum.CLASS.equals(readSheetHolder.excelReadHeadProperty().getHeadKind())) {
            context.readRowHolder()
                .setCurrentRowAnalysisResult(buildUserModel(cellDataMap, readSheetHolder, context));
            return;
        }
        context.readRowHolder().setCurrentRowAnalysisResult(buildStringList(cellDataMap, readSheetHolder, context));
    }

    private Object buildStringList(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder,
        AnalysisContext context) {
        int index = 0;
        Map<Integer, String> map = MapUtils.newLinkedHashMapWithExpectedSize(cellDataMap.size());
        for (Map.Entry<Integer, ReadCellData<?>> entry : cellDataMap.entrySet()) {
            Integer key = entry.getKey();
            ReadCellData<?> cellData = entry.getValue();
            while (index < key) {
                map.put(index, null);
                index++;
            }
            index++;
            map.put(key,
                (String)ConverterUtils.convertToJavaObject(cellData, null, null, readSheetHolder.converterMap(),
                    context, context.readRowHolder().getRowIndex(), key));
        }
        // fix https://github.com/alibaba/easyexcel/issues/2014
        int headSize = calculateHeadSize(readSheetHolder);
        while (index < headSize) {
            map.put(index, null);
            index++;
        }
        return map;
    }

    private int calculateHeadSize(ReadSheetHolder readSheetHolder) {
        if (readSheetHolder.excelReadHeadProperty().getHeadMap().size() > 0) {
            return readSheetHolder.excelReadHeadProperty().getHeadMap().size();
        }
        if (readSheetHolder.getMaxDataHeadSize() != null) {
            return readSheetHolder.getMaxDataHeadSize();
        }
        return 0;
    }

    private Object buildUserModel(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder,
        AnalysisContext context) {
        ExcelReadHeadProperty excelReadHeadProperty = readSheetHolder.excelReadHeadProperty();
        Object resultModel;
        try {
            resultModel = excelReadHeadProperty.getHeadClazz().newInstance();
        } catch (Exception e) {
            throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 0,
                new ReadCellData<>(CellDataTypeEnum.EMPTY), null,
                "Can not instance class: " + excelReadHeadProperty.getHeadClazz().getName(), e);
        }
        Map<Integer, Head> headMap = excelReadHeadProperty.getHeadMap();
        BeanMap dataMap = BeanMapUtils.create(resultModel);
        for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {
            Integer index = entry.getKey();
            Head head = entry.getValue();
            String fieldName = head.getFieldName();
            if (!cellDataMap.containsKey(index)) {
                continue;
            }
            ReadCellData<?> cellData = cellDataMap.get(index);
            Object value = ConverterUtils.convertToJavaObject(cellData, head.getField(),
                ClassUtils.declaredExcelContentProperty(dataMap, readSheetHolder.excelReadHeadProperty().getHeadClazz(),
                    fieldName), readSheetHolder.converterMap(), context, context.readRowHolder().getRowIndex(), index);
            if (value != null) {
                dataMap.put(fieldName, value);
            }
        }
        return resultModel;
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {}
}
