package cn.lihongjie.coal.common;

import com.google.common.base.Splitter;

import io.vavr.Function2;

import jakarta.servlet.http.HttpServletResponse;

import lombok.experimental.UtilityClass;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Supplier;

@UtilityClass
public class ExcelUtils {

    public static Map<String, Map<String, Object>> wbCtxManager = new ConcurrentHashMap<>();

    public static String getCode(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof String) {
            return Splitter.on("-")
                    .trimResults()
                    .omitEmptyStrings()
                    .splitToList((String) value)
                    .get(0);
        }
        return value.toString();
    }

    public static LocalDate getLocalDate(Object value) {

        if (value == null) {
            return null;
        }
        if (value instanceof LocalDate) {
            return (LocalDate) value;
        }
        if (value instanceof Date) {
            return ((Date) value)
                    .toInstant()
                    .atZone(TimeZone.getDefault().toZoneId())
                    .toLocalDate();
        }
        if (value instanceof LocalDateTime) {
            return ((LocalDateTime) value).toLocalDate();
        }
        if (value instanceof Number) {
            return LocalDate.ofEpochDay(((Number) value).longValue());
        }
        if (value instanceof String) {
            return LocalDate.parse((String) value);
        }
        return null;
    }

    public static <T> List<T> readFirstSheetToObjectList(
            InputStream inputStream,
            Supplier<T> objectCreator,
            CellValueToObjectSetter<T> valueSetter) {
        ArrayList<T> ans = new ArrayList<>();
        readFirstSheet(
                inputStream,
                row -> {
                    return objectCreator.get();
                },
                (headerCell, dataCell, object, value) -> {
                    valueSetter.set(headerCell.getStringCellValue(), (T) object, value);
                });
        return ans;
    }

    public static void readFirstSheet(
            InputStream inputStream,
            Function<Row, Object> rowMapper,
            CellValueHandler cellValueHandler) {

        try (Workbook workbook = WorkbookFactory.create(inputStream)) {
            Sheet sheet = workbook.getSheetAt(0);
            Row headerRow = sheet.getRow(0);

            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);

                if (row == null) {
                    continue;
                }
                Object object = rowMapper.apply(row);

                for (int j = 0; j < headerRow.getLastCellNum(); j++) {
                    Cell cell = row.getCell(j);
                    if (cell == null) {
                        continue;
                    }
                    Object value = null;
                    switch (cell.getCellType()) {
                        case STRING:
                            {
                                value = cell.getStringCellValue();
                                break;
                            }
                        case NUMERIC:
                            {
                                if (DateUtil.isCellDateFormatted(cell)) {
                                    value = cell.getDateCellValue();
                                } else {

                                    value = cell.getNumericCellValue();
                                }
                                break;
                            }
                        case BOOLEAN:
                            {
                                value = cell.getBooleanCellValue();
                                break;
                            }
                        case FORMULA:
                            {
                                switch (cell.getCachedFormulaResultType()) {
                                    case STRING:
                                        {
                                            value = cell.getStringCellValue();
                                            break;
                                        }
                                    case NUMERIC:
                                        {
                                            value = cell.getNumericCellValue();
                                            break;
                                        }
                                    case BOOLEAN:
                                        {
                                            value = cell.getBooleanCellValue();
                                            break;
                                        }
                                    case BLANK, ERROR, _NONE:
                                        {
                                            value = null;
                                            break;
                                        }
                                }
                                break;
                            }
                        case BLANK, ERROR, _NONE:
                            {
                                value = null;
                                break;
                            }
                    }

                    cellValueHandler.handle(headerRow.getCell(j), cell, object, value);
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void genSingleSheetToHttp(
            String wbName,
            List<String> headers,
            HeaderCellCustomizer headerCellCustomizer,
            HttpServletResponse response,
            SheetCustomizer sheetCustomizer) {
        genSingleSheetToHttp(
                wbName,
                null,
                headers,
                headerCellCustomizer,
                Collections.emptyList(),
                null,
                response,
                sheetCustomizer);
    }

    public static void genSingleSheetToBase64(
            String wbName,
            List<String> headers,
            HeaderCellCustomizer headerCellCustomizer,
            AtomicReference<String> ref,
            SheetCustomizer sheetCustomizer) {
        genSingleSheetToBase64(
                wbName,
                null,
                headers,
                headerCellCustomizer,
                Collections.emptyList(),
                null,
                ref,
                sheetCustomizer);
    }

    public static void genSingleSheetToBase64File(
            String wbName,
            List<String> headers,
            HeaderCellCustomizer headerCellCustomizer,
            Base64File ref,
            SheetCustomizer sheetCustomizer) {
        genSingleSheetToBase64File(
                wbName,
                null,
                headers,
                headerCellCustomizer,
                Collections.emptyList(),
                null,
                ref,
                sheetCustomizer);
    }

    public static void genSingleSheetToHttp(
            String wbName,
            String sheetName,
            List<String> headers,
            HeaderCellCustomizer headerCellCustomizer,
            Iterable<?> data,
            Function2<String, Object, Object> valueGetter,
            HttpServletResponse response,
            SheetCustomizer sheetCustomizer) {
        genSingleSheet(
                wbName,
                sheetName,
                headers,
                headerCellCustomizer,
                data,
                valueGetter,
                WorkbookConsumer.httpResponseConsumer(response),
                sheetCustomizer);
    }

    public static void genSingleSheetToBase64(
            String wbName,
            String sheetName,
            List<String> headers,
            HeaderCellCustomizer headerCellCustomizer,
            Iterable<?> data,
            Function2<String, Object, Object> valueGetter,
            AtomicReference<String> ref,
            SheetCustomizer sheetCustomizer) {
        genSingleSheet(
                wbName,
                sheetName,
                headers,
                headerCellCustomizer,
                data,
                valueGetter,
                WorkbookConsumer.base64Consumer(ref),
                sheetCustomizer);
    }

    public static void genSingleSheetToBase64File(
            String wbName,
            String sheetName,
            List<String> headers,
            HeaderCellCustomizer headerCellCustomizer,
            Iterable<?> data,
            Function2<String, Object, Object> valueGetter,
            Base64File ref,
            SheetCustomizer sheetCustomizer) {
        genSingleSheet(
                wbName,
                sheetName,
                headers,
                headerCellCustomizer,
                data,
                valueGetter,
                WorkbookConsumer.base64FileConsumer(ref),
                sheetCustomizer);
    }

    public static void genSingleSheet(
            String wbName,
            String sheetName,
            List<String> headers,
            HeaderCellCustomizer headerCellCustomizer,
            Iterable<?> data,
            Function2<String, Object, Object> valueGetter,
            WorkbookConsumer workbookConsumer,
            SheetCustomizer sheetCustomizer) {
        genSingleSheet(
                wbName,
                sheetName,
                headers,
                headerCellCustomizer,
                data,
                valueGetter,
                null,
                workbookConsumer,
                sheetCustomizer);
    }

    public static void genSingleSheet(
            String wbName,
            String sheetName,
            List<String> headers,
            HeaderCellCustomizer headerCellCustomizer,
            Iterable<?> data,
            Function2<String, Object, Object> valueGetter,
            DataCellCustomizer dataCellCustomizer,
            WorkbookConsumer workbookConsumer,
            SheetCustomizer sheetCustomizer) {
        genSingleSheet(
                wbName,
                sheetName,
                headers,
                headerCellCustomizer,
                data,
                valueGetter,
                sheetCustomizer,
                dataCellCustomizer,
                workbookConsumer);
    }

    public static void genSingleSheet(
            String wbName,
            String sheetName,
            List<String> headers,
            HeaderCellCustomizer headerCellCustomizer,
            Iterable<?> data,
            Function2<String, Object, Object> valueGetter,
            SheetCustomizer sheetCustomizer,
            DataCellCustomizer dataCellCustomizer,
            WorkbookConsumer workbookConsumer) {
        try {
            Workbook workbook = new XSSFWorkbook();
            try {

                HashMap<String, Object> wbCtx = new HashMap<>();
                wbCtxManager.put(workbook.hashCode() + "", wbCtx);
                Sheet sheet =
                        StringUtils.isEmpty(sheetName)
                                ? workbook.createSheet()
                                : workbook.createSheet(sheetName);

                Row headerRow = sheet.createRow(0);
                for (int i = 0, headersSize = headers.size(); i < headersSize; i++) {
                    String header = headers.get(i);

                    Cell cell = headerRow.createCell(i);
                    cell.setCellValue(header);
                    if (headerCellCustomizer != null) {

                        headerCellCustomizer.customize(header, cell);
                    }

                    String cellValue = cell.getStringCellValue();

                    wbCtx.put(
                            "max_column_length_" + i,
                            StringUtils.defaultIfBlank(cellValue, "").length());
                }

                int rowNum = 1;

                for (Object d : data) {
                    Row row = sheet.createRow(rowNum++);
                    for (int i = 0, headersSize = headers.size(); i < headersSize; i++) {
                        String header = headers.get(i);
                        Cell cell = row.createCell(i);
                        Object value = valueGetter == null ? null : valueGetter.apply(header, d);
                        int newLength = 0;
                        String maxColumnLengthKey = "max_column_length_" + i;

                        if (value == null) {

                            cell.setBlank();

                        } else if (value instanceof CharSequence) {
                            cell.setCellValue(value.toString());
                            newLength = value.toString().length();
                        } else if (value instanceof Number) {
                            cell.setCellValue(((Number) value).doubleValue());
                            newLength = value.toString().length();
                        } else if (value instanceof Date) {
                            cell.setCellValue((Date) value);

                        } else if (value instanceof Boolean) {
                            cell.setCellValue((Boolean) value);
                            newLength = 2;
                        } else if (value instanceof LocalDateTime) {

                            cell.setCellValue((LocalDateTime) value);
                            newLength = 12;
                        } else if (value instanceof LocalDate) {

                            cell.setCellValue((LocalDate) value);

                            newLength = 6;
                        } else if (value instanceof Calendar) {
                            cell.setCellValue((Calendar) value);
                            newLength = 12;
                        } else {

                            if (value instanceof Iterable) {
                                List<String> list = new ArrayList<>();
                                for (Object o : (Iterable<?>) value) {
                                    list.add(o.toString());
                                }
                                cell.setCellValue(String.join(",", list));

                                newLength = cell.getStringCellValue().length();
                            } else {
                                cell.setCellValue(value.toString());
                                newLength = value.toString().length();
                            }
                        }

                        if (dataCellCustomizer != null) {

                            dataCellCustomizer.customize(header, d, value, cell);
                        }
                        int oldLen = Integer.parseInt(wbCtx.get(maxColumnLengthKey).toString());

                        wbCtx.put(maxColumnLengthKey, Math.max(oldLen, newLength));
                    }
                }

                if (sheetCustomizer != null) {
                    sheetCustomizer.customize(sheet);
                }

                workbookConsumer.accept(wbName, workbook);

            } finally {
                workbook.close();
                wbCtxManager.remove(workbook.hashCode() + "");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public interface ValidationInfo {

        record ListValidationInfo(List<String> list) implements ValidationInfo {}

        record AnyValidationInfo() implements ValidationInfo {}

        record IntValidationInfo(int opt, String formula1, String formula2)
                implements ValidationInfo {}

        record DecimalValidationInfo(int opt, String formula1, String formula2)
                implements ValidationInfo {}

        record DateValidationInfo(int opt, String formula1, String formula2)
                implements ValidationInfo {

            public DateValidationInfo(int opt, LocalDate d1, LocalDate d2) {

                this(
                        opt,
                        d1 == null ? null : "DATEVALUE(\"" + d1 + "\")",
                        d2 == null ? null : "DATEVALUE(\"" + d2 + "\")");
            }


            public DateValidationInfo() {
                this(DataValidationConstraint.OperatorType.BETWEEN,  LocalDate.of(1900, 1, 1),
                        LocalDate.of(2100, 1, 1));
            }
        }

        record TimeValidationInfo(int opt, String formula1, String formula2)
                implements ValidationInfo {}
    }

    public interface SheetCustomizer {

        static SheetCustomizer centerFirstRowCustomizer() {
            return new SheetCustomizer() {
                @Override
                public void customize(Sheet sheet) {
                    CellStyle style = sheet.getWorkbook().createCellStyle();
                    style.setAlignment(HorizontalAlignment.CENTER);
                    style.setVerticalAlignment(VerticalAlignment.CENTER);
                    Row firstRow = sheet.getRow(0);
                    for (Cell cell : firstRow) {
                        cell.setCellStyle(style);
                    }
                }
            };
        }

        static SheetCustomizer autoSizeColumnCustomizer() {
            return new SheetCustomizer() {
                @Override
                public void customize(Sheet sheet) {

                    Row firstRow = sheet.getRow(0);
                    for (int i = 0; i < firstRow.getLastCellNum(); i++) {
                        try {

                            int maxLen =
                                    Integer.parseInt(
                                            wbCtxManager
                                                    .get(sheet.getWorkbook().hashCode() + "")
                                                    .get("max_column_length_" + i)
                                                    .toString());
                            sheet.setColumnWidth(i, maxLen * 256 * 4);
                        } catch (Exception e) {
                            sheet.autoSizeColumn(i);
                        }
                    }
                }
            };
        }

        static SheetCustomizer headerBasedConstrainCustomizer(
                Function<String, ValidationInfo> headerMap) {

            return new SheetCustomizer() {
                @Override
                public void customize(Sheet sheet) {

                    XSSFDataValidationHelper helper =
                            new XSSFDataValidationHelper((XSSFSheet) sheet);

                    Row headerRow = sheet.getRow(0);

                    for (Cell headerCell : headerRow) {

                        CellRangeAddressList addressList =
                                new CellRangeAddressList(
                                        headerCell.getRowIndex() + 1,
                                        SpreadsheetVersion.EXCEL97.getLastRowIndex(),
                                        headerCell.getColumnIndex(),
                                        headerCell.getColumnIndex());
                        String headerName = headerCell.getStringCellValue();

                        ValidationInfo validationInfo = headerMap.apply(headerName);

                        if (validationInfo instanceof ValidationInfo.ListValidationInfo) {
                            List<String> list =
                                    ((ValidationInfo.ListValidationInfo) validationInfo).list();

                            Map<String, Object> wbCtx =
                                    wbCtxManager.get(sheet.getWorkbook().hashCode() + "");
                            Object o = wbCtx.get("hiddenSheet");

                            Sheet hiddenSheet = null;

                            if (o == null) {
                                hiddenSheet = sheet.getWorkbook().createSheet("hidden");
                                sheet.getWorkbook()
                                        .setSheetHidden(
                                                hiddenSheet
                                                        .getWorkbook()
                                                        .getSheetIndex(hiddenSheet),
                                                true);
                                wbCtx.put("hiddenSheet", hiddenSheet);
                            } else {
                                hiddenSheet = (Sheet) o;
                            }

                            Row row = hiddenSheet.createRow(hiddenSheet.getLastRowNum() + 1);
                            row.createCell(0).setCellValue(headerName + "选项");
                            for (int i = 0; i < list.size(); i++) {

                                row.createCell(i + 1).setCellValue(list.get(i));
                            }

                            Name wbName = hiddenSheet.getWorkbook().createName();
                            int rowIndex = row.getRowNum();
                            wbName.setNameName("validate_" + rowIndex);
                            CellReference rangeStart = new CellReference(rowIndex, 1, true, true);

                            CellReference rangeEnd =
                                    new CellReference(rowIndex, list.size(), true, true);

                            wbName.setRefersToFormula(
                                    hiddenSheet.getSheetName()
                                            + "!"
                                            + rangeStart.formatAsString(false)
                                            + ":"
                                            + rangeEnd.formatAsString(false));

                            XSSFDataValidationConstraint dvConstraint =
                                    (XSSFDataValidationConstraint)
                                            helper.createFormulaListConstraint(
                                                    wbName.getNameName());

                            XSSFDataValidation validation =
                                    (XSSFDataValidation)
                                            helper.createValidation(dvConstraint, addressList);
                            validation.setSuppressDropDownArrow(true);
                            validation.setShowErrorBox(true);
                            sheet.addValidationData(validation);

                        } else if (validationInfo instanceof ValidationInfo.AnyValidationInfo) {

                        } else if (validationInfo instanceof ValidationInfo.IntValidationInfo) {

                            DataValidationConstraint integerConstraint =
                                    helper.createIntegerConstraint(
                                            ((ValidationInfo.IntValidationInfo) validationInfo)
                                                    .opt(),
                                            ((ValidationInfo.IntValidationInfo) validationInfo)
                                                    .formula1(),
                                            ((ValidationInfo.IntValidationInfo) validationInfo)
                                                    .formula2());

                            XSSFDataValidation validation =
                                    (XSSFDataValidation)
                                            helper.createValidation(integerConstraint, addressList);
                            validation.setSuppressDropDownArrow(true);
                            validation.setShowErrorBox(true);

                            sheet.addValidationData(validation);

                        } else if (validationInfo instanceof ValidationInfo.DecimalValidationInfo) {

                            DataValidationConstraint decimalConstraint =
                                    helper.createDecimalConstraint(
                                            ((ValidationInfo.DecimalValidationInfo) validationInfo)
                                                    .opt(),
                                            ((ValidationInfo.DecimalValidationInfo) validationInfo)
                                                    .formula1(),
                                            ((ValidationInfo.DecimalValidationInfo) validationInfo)
                                                    .formula2());

                            XSSFDataValidation validation =
                                    (XSSFDataValidation)
                                            helper.createValidation(decimalConstraint, addressList);
                            validation.setSuppressDropDownArrow(true);
                            validation.setShowErrorBox(true);

                            sheet.addValidationData(validation);

                        } else if (validationInfo instanceof ValidationInfo.DateValidationInfo) {

                            DataValidationConstraint dateConstraint =
                                    helper.createDateConstraint(
                                            ((ValidationInfo.DateValidationInfo) validationInfo)
                                                    .opt(),
                                            ((ValidationInfo.DateValidationInfo) validationInfo)
                                                    .formula1(),
                                            ((ValidationInfo.DateValidationInfo) validationInfo)
                                                    .formula2(),
                                            null);

                            XSSFDataValidation validation =
                                    (XSSFDataValidation)
                                            helper.createValidation(dateConstraint, addressList);
                            validation.setSuppressDropDownArrow(true);
                            validation.setShowErrorBox(true);

                            sheet.addValidationData(validation);

                        } else if (validationInfo instanceof ValidationInfo.TimeValidationInfo) {

                            DataValidationConstraint timeConstraint =
                                    helper.createTimeConstraint(
                                            ((ValidationInfo.TimeValidationInfo) validationInfo)
                                                    .opt(),
                                            ((ValidationInfo.TimeValidationInfo) validationInfo)
                                                    .formula1(),
                                            ((ValidationInfo.TimeValidationInfo) validationInfo)
                                                    .formula2());

                            XSSFDataValidation validation =
                                    (XSSFDataValidation)
                                            helper.createValidation(timeConstraint, addressList);
                            validation.setSuppressDropDownArrow(true);
                            validation.setShowErrorBox(true);

                            sheet.addValidationData(validation);
                        } else {
                            throw new RuntimeException("unknown validation info");
                        }
                    }
                }
            };
        }

        default SheetCustomizer andThen(SheetCustomizer other) {
            return new SheetCustomizer() {
                @Override
                public void customize(Sheet sheet) {
                    SheetCustomizer.this.customize(sheet);
                    other.customize(sheet);
                }
            };
        }

        void customize(Sheet sheet);
    }

    @FunctionalInterface
    public interface CellValueHandler {
        void handle(Cell headerCell, Cell dataCell, Object rowObject, Object value);
    }

    @FunctionalInterface
    public interface CellValueToObjectSetter<T> {
        void set(String header, T object, Object value);
    }

    @FunctionalInterface
    public interface WorkbookConsumer {

        static WorkbookConsumer base64Consumer(AtomicReference<String> strRef) {
            return (name, workbook) -> {
                try (ByteArrayOutputStream bs = new ByteArrayOutputStream()) {
                    workbook.write(bs);
                    strRef.set(Base64.getEncoder().encodeToString(bs.toByteArray()));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            };
        }

        static WorkbookConsumer base64FileConsumer(Base64File file) {
            return (name, workbook) -> {
                try (ByteArrayOutputStream bs = new ByteArrayOutputStream()) {
                    workbook.write(bs);
                    file.setFileName(name);
                    file.setData(bs.toByteArray());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            };
        }

        static WorkbookConsumer byteArrayConsumer(ByteArrayOutputStream bs) {
            return (name, workbook) -> {
                try {
                    workbook.write(bs);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            };
        }

        static WorkbookConsumer httpResponseConsumer(HttpServletResponse response) {
            return (name, workbook) -> {
                try {
                    response.setHeader(
                            "Content-Disposition",
                            "attachment; filename="
                                    + URLEncoder.encode(name, StandardCharsets.UTF_8)
                                    + ".xlsx");
                    workbook.write(response.getOutputStream());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            };
        }

        void accept(String name, org.apache.poi.ss.usermodel.Workbook workbook);
    }

    @FunctionalInterface
    public interface DataCellCustomizer {

        void customize(String header, Object model, Object value, Cell cell);
    }

    @FunctionalInterface
    public interface HeaderCellCustomizer {

        static HeaderCellCustomizer validationCustomizer(
                Function<String, List<String>> validationGetter) {

            return new HeaderCellCustomizer() {

                private Sheet hiddenSheet;

                private int rowIndex = 0;

                @Override
                public void customize(String name, Cell cell) {

                    List<String> list = validationGetter.apply(name);
                    if (!CollectionUtils.isNotEmpty(list)) {
                        return;
                    }

                    if (hiddenSheet == null) {
                        hiddenSheet = cell.getSheet().getWorkbook().createSheet("hidden");
                        cell.getSheet()
                                .getWorkbook()
                                .setSheetHidden(
                                        hiddenSheet.getWorkbook().getSheetIndex(hiddenSheet), true);
                    }

                    Row row = hiddenSheet.createRow(rowIndex);
                    row.createCell(0).setCellValue(name + "选项");
                    for (int i = 0; i < list.size(); i++) {

                        row.createCell(i + 1).setCellValue(list.get(i));
                    }

                    Name wbName = hiddenSheet.getWorkbook().createName();
                    wbName.setNameName("validate_" + (rowIndex));
                    CellReference rangeStart = new CellReference(rowIndex, 1, true, true);

                    CellReference rangeEnd = new CellReference(rowIndex, list.size(), true, true);

                    wbName.setRefersToFormula(
                            hiddenSheet.getSheetName()
                                    + "!"
                                    + rangeStart.formatAsString(false)
                                    + ":"
                                    + rangeEnd.formatAsString(false));

                    XSSFDataValidationHelper dvHelper =
                            new XSSFDataValidationHelper((XSSFSheet) cell.getSheet());

                    XSSFDataValidationConstraint dvConstraint =
                            (XSSFDataValidationConstraint)
                                    dvHelper.createFormulaListConstraint(wbName.getNameName());
                    CellRangeAddressList addressList =
                            new CellRangeAddressList(
                                    cell.getRowIndex() + 1,
                                    cell.getRowIndex() + 100000,
                                    cell.getColumnIndex(),
                                    cell.getColumnIndex());
                    XSSFDataValidation validation =
                            (XSSFDataValidation)
                                    dvHelper.createValidation(dvConstraint, addressList);
                    validation.setSuppressDropDownArrow(true);
                    validation.setShowErrorBox(true);
                    cell.getSheet().addValidationData(validation);

                    rowIndex++;
                }
            };
        }

        void customize(String name, Cell cell);
    }
}
