/*
 *  ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one or more
 *    contributor license agreements.  See the NOTICE file distributed with
 *    this work for additional information regarding copyright ownership.
 *    The ASF licenses this file to You under the Apache License, Version 2.0
 *    (the "License"); you may not use this file except in compliance with
 *    the License.  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 * ====================================================================
 */

package org.apache.poi.ss.usermodel;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;

import org.apache.poi.hssf.usermodel.HSSFConditionalFormatting;
import org.apache.poi.hssf.usermodel.HSSFConditionalFormattingRule;
import org.apache.poi.ss.ITestDataProvider;
import org.apache.poi.ss.usermodel.ConditionalFormattingThreshold.RangeType;
import org.apache.poi.ss.usermodel.IconMultiStateFormatting.IconSet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;

/**
 * Base tests for Conditional Formatting, for both HSSF and XSSF
 */
public abstract class BaseTestConditionalFormatting {
    private final ITestDataProvider _testDataProvider;

    protected BaseTestConditionalFormatting(ITestDataProvider testDataProvider) {
        _testDataProvider = testDataProvider;
    }

    protected boolean applyLimitOf3() {
        return true;
    }

    protected abstract void assertColor(String hexExpected, Color actual);

    protected int defaultDataBarMinLength() { return 0; }
    protected int defaultDataBarMaxLength() { return 100; }

    @Test
    void testBasic() throws Throwable {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            Sheet sh = wb.createSheet();
            SheetConditionalFormatting sheetCF = sh.getSheetConditionalFormatting();

            assertEquals(0, sheetCF.getNumConditionalFormattings());
            IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> sheetCF.getConditionalFormattingAt(0));
            assertTrue(e.getMessage().startsWith("Specified CF index 0 is outside the allowable range"));

            e = assertThrows(IllegalArgumentException.class, () -> sheetCF.removeConditionalFormatting(0));
            assertTrue(e.getMessage().startsWith("Specified CF index 0 is outside the allowable range"));

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule("1");
            ConditionalFormattingRule rule2 = sheetCF.createConditionalFormattingRule("2");
            ConditionalFormattingRule rule3 = sheetCF.createConditionalFormattingRule("3");
            ConditionalFormattingRule rule4 = sheetCF.createConditionalFormattingRule("4");

            e = assertThrows(IllegalArgumentException.class, () -> sheetCF.addConditionalFormatting(null, rule1));
            assertTrue(e.getMessage().startsWith("regions must not be null"));

            e = assertThrows(IllegalArgumentException.class, () -> sheetCF.addConditionalFormatting(
                new CellRangeAddress[]{CellRangeAddress.valueOf("A1:A3")}, (ConditionalFormattingRule) null));
            assertTrue(e.getMessage().startsWith("cfRules must not be null"));

            e = assertThrows(IllegalArgumentException.class, () -> sheetCF.addConditionalFormatting(
                new CellRangeAddress[]{CellRangeAddress.valueOf("A1:A3")}, new ConditionalFormattingRule[0]));
            assertTrue(e.getMessage().startsWith("cfRules must not be empty"));

            Executable exec = () ->
                sheetCF.addConditionalFormatting(
                    new CellRangeAddress[]{CellRangeAddress.valueOf("A1:A3")},
                    new ConditionalFormattingRule[]{rule1, rule2, rule3, rule4});

            if (applyLimitOf3()) {
                e = assertThrows(IllegalArgumentException.class, exec);
                assertTrue(e.getMessage().startsWith("Number of rules must not exceed 3"));
            } else {
                exec.execute();
            }
        }
    }

    /**
     * Test format conditions based on a boolean formula
     */
    @Test
    void testBooleanFormulaConditions() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            Sheet sh = wb.createSheet();
            SheetConditionalFormatting sheetCF = sh.getSheetConditionalFormatting();

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule("SUM(A1:A5)>10");
            assertEquals(ConditionType.FORMULA, rule1.getConditionType());
            assertEquals("SUM(A1:A5)>10", rule1.getFormula1());
            int formatIndex1 = sheetCF.addConditionalFormatting(
                    new CellRangeAddress[]{
                            CellRangeAddress.valueOf("B1"),
                            CellRangeAddress.valueOf("C3"),
                    }, rule1);
            assertEquals(0, formatIndex1);
            assertEquals(1, sheetCF.getNumConditionalFormattings());
            CellRangeAddress[] ranges1 = sheetCF.getConditionalFormattingAt(formatIndex1).getFormattingRanges();
            assertEquals(2, ranges1.length);
            assertEquals("B1", ranges1[0].formatAsString());
            assertEquals("C3", ranges1[1].formatAsString());

            // adjacent address are merged
            int formatIndex2 = sheetCF.addConditionalFormatting(
                    new CellRangeAddress[]{
                            CellRangeAddress.valueOf("B1"),
                            CellRangeAddress.valueOf("B2"),
                            CellRangeAddress.valueOf("B3"),
                    }, rule1);
            assertEquals(1, formatIndex2);
            assertEquals(2, sheetCF.getNumConditionalFormattings());
            CellRangeAddress[] ranges2 = sheetCF.getConditionalFormattingAt(formatIndex2).getFormattingRanges();
            assertEquals(1, ranges2.length);
            assertEquals("B1:B3", ranges2[0].formatAsString());
        }
    }

    @Test
    void testSingleFormulaConditions() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            Sheet sh = wb.createSheet();
            SheetConditionalFormatting sheetCF = sh.getSheetConditionalFormatting();

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.EQUAL, "SUM(A1:A5)+10");
            assertEquals(ConditionType.CELL_VALUE_IS, rule1.getConditionType());
            assertEquals("SUM(A1:A5)+10", rule1.getFormula1());
            assertEquals(ComparisonOperator.EQUAL, rule1.getComparisonOperation());

            ConditionalFormattingRule rule2 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.NOT_EQUAL, "15");
            assertEquals(ConditionType.CELL_VALUE_IS, rule2.getConditionType());
            assertEquals("15", rule2.getFormula1());
            assertEquals(ComparisonOperator.NOT_EQUAL, rule2.getComparisonOperation());

            ConditionalFormattingRule rule3 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.NOT_EQUAL, "15");
            assertEquals(ConditionType.CELL_VALUE_IS, rule3.getConditionType());
            assertEquals("15", rule3.getFormula1());
            assertEquals(ComparisonOperator.NOT_EQUAL, rule3.getComparisonOperation());

            ConditionalFormattingRule rule4 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.GT, "0");
            assertEquals(ConditionType.CELL_VALUE_IS, rule4.getConditionType());
            assertEquals("0", rule4.getFormula1());
            assertEquals(ComparisonOperator.GT, rule4.getComparisonOperation());

            ConditionalFormattingRule rule5 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.LT, "0");
            assertEquals(ConditionType.CELL_VALUE_IS, rule5.getConditionType());
            assertEquals("0", rule5.getFormula1());
            assertEquals(ComparisonOperator.LT, rule5.getComparisonOperation());

            ConditionalFormattingRule rule6 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.GE, "0");
            assertEquals(ConditionType.CELL_VALUE_IS, rule6.getConditionType());
            assertEquals("0", rule6.getFormula1());
            assertEquals(ComparisonOperator.GE, rule6.getComparisonOperation());

            ConditionalFormattingRule rule7 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.LE, "0");
            assertEquals(ConditionType.CELL_VALUE_IS, rule7.getConditionType());
            assertEquals("0", rule7.getFormula1());
            assertEquals(ComparisonOperator.LE, rule7.getComparisonOperation());

            ConditionalFormattingRule rule8 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.BETWEEN, "0", "5");
            assertEquals(ConditionType.CELL_VALUE_IS, rule8.getConditionType());
            assertEquals("0", rule8.getFormula1());
            assertEquals("5", rule8.getFormula2());
            assertEquals(ComparisonOperator.BETWEEN, rule8.getComparisonOperation());

            ConditionalFormattingRule rule9 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.NOT_BETWEEN, "0", "5");
            assertEquals(ConditionType.CELL_VALUE_IS, rule9.getConditionType());
            assertEquals("0", rule9.getFormula1());
            assertEquals("5", rule9.getFormula2());
            assertEquals(ComparisonOperator.NOT_BETWEEN, rule9.getComparisonOperation());
        }
    }

    @Test
    void testCopy() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            Sheet sheet1 = wb.createSheet();
            Sheet sheet2 = wb.createSheet();
            SheetConditionalFormatting sheet1CF = sheet1.getSheetConditionalFormatting();
            SheetConditionalFormatting sheet2CF = sheet2.getSheetConditionalFormatting();
            assertEquals(0, sheet1CF.getNumConditionalFormattings());
            assertEquals(0, sheet2CF.getNumConditionalFormattings());

            ConditionalFormattingRule rule1 = sheet1CF.createConditionalFormattingRule(
                    ComparisonOperator.EQUAL, "SUM(A1:A5)+10");

            ConditionalFormattingRule rule2 = sheet1CF.createConditionalFormattingRule(
                    ComparisonOperator.NOT_EQUAL, "15");

            // adjacent address are merged
            int formatIndex = sheet1CF.addConditionalFormatting(
                    new CellRangeAddress[]{
                            CellRangeAddress.valueOf("A1:A5"),
                            CellRangeAddress.valueOf("C1:C5")
                    }, rule1, rule2);
            assertEquals(0, formatIndex);
            assertEquals(1, sheet1CF.getNumConditionalFormattings());

            assertEquals(0, sheet2CF.getNumConditionalFormattings());
            sheet2CF.addConditionalFormatting(sheet1CF.getConditionalFormattingAt(formatIndex));
            assertEquals(1, sheet2CF.getNumConditionalFormattings());

            ConditionalFormatting sheet2cf = sheet2CF.getConditionalFormattingAt(0);
            assertEquals(2, sheet2cf.getNumberOfRules());
            assertEquals("SUM(A1:A5)+10", sheet2cf.getRule(0).getFormula1());
            assertEquals(ComparisonOperator.EQUAL, sheet2cf.getRule(0).getComparisonOperation());
            assertEquals(ConditionType.CELL_VALUE_IS, sheet2cf.getRule(0).getConditionType());
            assertEquals("15", sheet2cf.getRule(1).getFormula1());
            assertEquals(ComparisonOperator.NOT_EQUAL, sheet2cf.getRule(1).getComparisonOperation());
            assertEquals(ConditionType.CELL_VALUE_IS, sheet2cf.getRule(1).getConditionType());
        }
    }

    @Test
    void testRemove() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            Sheet sheet1 = wb.createSheet();
            SheetConditionalFormatting sheetCF = sheet1.getSheetConditionalFormatting();
            assertEquals(0, sheetCF.getNumConditionalFormattings());

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.EQUAL, "SUM(A1:A5)");

            // adjacent address are merged
            int formatIndex = sheetCF.addConditionalFormatting(
                new CellRangeAddress[]{CellRangeAddress.valueOf("A1:A5")}, rule1);
            assertEquals(0, formatIndex);
            assertEquals(1, sheetCF.getNumConditionalFormattings());
            sheetCF.removeConditionalFormatting(0);
            assertEquals(0, sheetCF.getNumConditionalFormattings());

            IllegalArgumentException e;
            e = assertThrows(IllegalArgumentException.class, () -> sheetCF.getConditionalFormattingAt(0));
            assertTrue(e.getMessage().startsWith("Specified CF index 0 is outside the allowable range"));

            formatIndex = sheetCF.addConditionalFormatting(
                new CellRangeAddress[]{CellRangeAddress.valueOf("A1:A5")}, rule1);
            assertEquals(0, formatIndex);
            assertEquals(1, sheetCF.getNumConditionalFormattings());
            sheetCF.removeConditionalFormatting(0);
            assertEquals(0, sheetCF.getNumConditionalFormattings());

            e = assertThrows(IllegalArgumentException.class, () -> sheetCF.getConditionalFormattingAt(0));
            assertTrue(e.getMessage().startsWith("Specified CF index 0 is outside the allowable range"));
        }
    }

    @Test
    void testCreateCF() throws IOException {
        try (Workbook workbook = _testDataProvider.createWorkbook()) {
            Sheet sheet = workbook.createSheet();
            String formula = "7";

            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule(formula);
            FontFormatting fontFmt = rule1.createFontFormatting();
            fontFmt.setFontStyle(true, false);

            BorderFormatting bordFmt = rule1.createBorderFormatting();
            bordFmt.setBorderBottom(BorderStyle.THIN);
            bordFmt.setBorderTop(BorderStyle.THICK);
            bordFmt.setBorderLeft(BorderStyle.DASHED);
            bordFmt.setBorderRight(BorderStyle.DOTTED);

            PatternFormatting patternFmt = rule1.createPatternFormatting();
            patternFmt.setFillBackgroundColor(IndexedColors.YELLOW.index);


            ConditionalFormattingRule rule2 = sheetCF.createConditionalFormattingRule(ComparisonOperator.BETWEEN, "1", "2");
            ConditionalFormattingRule[] cfRules = {rule1, rule2};

            short col = 1;
            CellRangeAddress[] regions = {
                    new CellRangeAddress(0, 65535, col, col)
            };

            sheetCF.addConditionalFormatting(regions, cfRules);
            sheetCF.addConditionalFormatting(regions, cfRules);

            // Verification
            assertEquals(2, sheetCF.getNumConditionalFormattings());
            sheetCF.removeConditionalFormatting(1);
            assertEquals(1, sheetCF.getNumConditionalFormattings());
            ConditionalFormatting cf = sheetCF.getConditionalFormattingAt(0);
            assertNotNull(cf);

            regions = cf.getFormattingRanges();
            assertNotNull(regions);
            assertEquals(1, regions.length);
            CellRangeAddress r = regions[0];
            assertEquals(1, r.getFirstColumn());
            assertEquals(1, r.getLastColumn());
            assertEquals(0, r.getFirstRow());
            assertEquals(65535, r.getLastRow());

            assertEquals(2, cf.getNumberOfRules());

            rule1 = cf.getRule(0);
            assertEquals("7", rule1.getFormula1());
            assertNull(rule1.getFormula2());

            FontFormatting r1fp = rule1.getFontFormatting();
            assertNotNull(r1fp);

            assertTrue(r1fp.isItalic());
            assertFalse(r1fp.isBold());

            BorderFormatting r1bf = rule1.getBorderFormatting();
            assertNotNull(r1bf);
            assertEquals(BorderStyle.THIN, r1bf.getBorderBottom());
            assertEquals(BorderStyle.THICK, r1bf.getBorderTop());
            assertEquals(BorderStyle.DASHED, r1bf.getBorderLeft());
            assertEquals(BorderStyle.DOTTED, r1bf.getBorderRight());

            PatternFormatting r1pf = rule1.getPatternFormatting();
            assertNotNull(r1pf);
//        assertEquals(IndexedColors.YELLOW.index,r1pf.getFillBackgroundColor());

            rule2 = cf.getRule(1);
            assertEquals("2", rule2.getFormula2());
            assertEquals("1", rule2.getFormula1());
        }
    }

    @Test
    void testClone() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            Sheet sheet = wb.createSheet();
            String formula = "7";

            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule(formula);
            FontFormatting fontFmt = rule1.createFontFormatting();
            fontFmt.setFontStyle(true, false);

            PatternFormatting patternFmt = rule1.createPatternFormatting();
            patternFmt.setFillBackgroundColor(IndexedColors.YELLOW.index);


            ConditionalFormattingRule rule2 = sheetCF.createConditionalFormattingRule(ComparisonOperator.BETWEEN, "1", "2");
            ConditionalFormattingRule[] cfRules = {rule1, rule2};

            short col = 1;
            CellRangeAddress[] regions = {
                    new CellRangeAddress(0, 65535, col, col)
            };

            sheetCF.addConditionalFormatting(regions, cfRules);

            wb.cloneSheet(0);
            assertEquals(2, wb.getNumberOfSheets());
            // bug 45682 leads to runtime exception "needs to define a clone method"
        }
    }

    @Test
    void testShiftRows() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            Sheet sheet = wb.createSheet();

            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.BETWEEN, "SUM(A10:A15)", "1+SUM(B16:B30)");
            FontFormatting fontFmt = rule1.createFontFormatting();
            fontFmt.setFontStyle(true, false);

            PatternFormatting patternFmt = rule1.createPatternFormatting();
            patternFmt.setFillBackgroundColor(IndexedColors.YELLOW.index);

            ConditionalFormattingRule rule2 = sheetCF.createConditionalFormattingRule(
                    ComparisonOperator.BETWEEN, "SUM(A10:A15)", "1+SUM(B16:B30)");
            BorderFormatting borderFmt = rule2.createBorderFormatting();
            borderFmt.setBorderDiagonal(BorderStyle.MEDIUM);

            CellRangeAddress[] regions = {
                    new CellRangeAddress(2, 4, 0, 0), // A3:A5
            };
            sheetCF.addConditionalFormatting(regions, rule1);
            sheetCF.addConditionalFormatting(regions, rule2);

            // This row-shift should destroy the CF region
            sheet.shiftRows(10, 20, -9);
            assertEquals(0, sheetCF.getNumConditionalFormattings());

            // re-add the CF
            sheetCF.addConditionalFormatting(regions, rule1);
            sheetCF.addConditionalFormatting(regions, rule2);

            // This row shift should only affect the formulas
            sheet.shiftRows(14, 17, 8);
            ConditionalFormatting cf1 = sheetCF.getConditionalFormattingAt(0);
            assertEquals("SUM(A10:A23)", cf1.getRule(0).getFormula1());
            assertEquals("1+SUM(B24:B30)", cf1.getRule(0).getFormula2());
            ConditionalFormatting cf2 = sheetCF.getConditionalFormattingAt(1);
            assertEquals("SUM(A10:A23)", cf2.getRule(0).getFormula1());
            assertEquals("1+SUM(B24:B30)", cf2.getRule(0).getFormula2());

            sheet.shiftRows(0, 8, 21);
            cf1 = sheetCF.getConditionalFormattingAt(0);
            assertEquals("SUM(A10:A21)", cf1.getRule(0).getFormula1());
            assertEquals("1+SUM(#REF!)", cf1.getRule(0).getFormula2());
            cf2 = sheetCF.getConditionalFormattingAt(1);
            assertEquals("SUM(A10:A21)", cf2.getRule(0).getFormula1());
            assertEquals("1+SUM(#REF!)", cf2.getRule(0).getFormula2());
        }
    }

    protected void testRead(String filename) throws IOException {
        try (Workbook wb = _testDataProvider.openSampleWorkbook(filename)) {
            Sheet sh = wb.getSheet("CF");
            SheetConditionalFormatting sheetCF = sh.getSheetConditionalFormatting();
            assertEquals(3, sheetCF.getNumConditionalFormattings());

            ConditionalFormatting cf1 = sheetCF.getConditionalFormattingAt(0);
            assertEquals(2, cf1.getNumberOfRules());

            CellRangeAddress[] regions1 = cf1.getFormattingRanges();
            assertEquals(1, regions1.length);
            assertEquals("A1:A8", regions1[0].formatAsString());

            // CF1 has two rules: values less than -3 are bold-italic red, values greater than 3 are green
            ConditionalFormattingRule rule1 = cf1.getRule(0);
            assertEquals(ConditionType.CELL_VALUE_IS, rule1.getConditionType());
            assertEquals(ComparisonOperator.GT, rule1.getComparisonOperation());
            assertEquals("3", rule1.getFormula1());
            assertNull(rule1.getFormula2());
            // fills and borders are not set
            assertNull(rule1.getPatternFormatting());
            assertNull(rule1.getBorderFormatting());

            FontFormatting fmt1 = rule1.getFontFormatting();
//        assertEquals(IndexedColors.GREEN.index, fmt1.getFontColorIndex());
            assertTrue(fmt1.isBold());
            assertFalse(fmt1.isItalic());

            ConditionalFormattingRule rule2 = cf1.getRule(1);
            assertEquals(ConditionType.CELL_VALUE_IS, rule2.getConditionType());
            assertEquals(ComparisonOperator.LT, rule2.getComparisonOperation());
            assertEquals("-3", rule2.getFormula1());
            assertNull(rule2.getFormula2());
            assertNull(rule2.getPatternFormatting());
            assertNull(rule2.getBorderFormatting());

            FontFormatting fmt2 = rule2.getFontFormatting();
//        assertEquals(IndexedColors.RED.index, fmt2.getFontColorIndex());
            assertTrue(fmt2.isBold());
            assertTrue(fmt2.isItalic());


            ConditionalFormatting cf2 = sheetCF.getConditionalFormattingAt(1);
            assertEquals(1, cf2.getNumberOfRules());
            CellRangeAddress[] regions2 = cf2.getFormattingRanges();
            assertEquals(1, regions2.length);
            assertEquals("B9", regions2[0].formatAsString());

            ConditionalFormattingRule rule3 = cf2.getRule(0);
            assertEquals(ConditionType.FORMULA, rule3.getConditionType());
            assertEquals(ComparisonOperator.NO_COMPARISON, rule3.getComparisonOperation());
            assertEquals("$A$8>5", rule3.getFormula1());
            assertNull(rule3.getFormula2());

            FontFormatting fmt3 = rule3.getFontFormatting();
//        assertEquals(IndexedColors.RED.index, fmt3.getFontColorIndex());
            assertTrue(fmt3.isBold());
            assertTrue(fmt3.isItalic());

            PatternFormatting fmt4 = rule3.getPatternFormatting();
//        assertEquals(IndexedColors.LIGHT_CORNFLOWER_BLUE.index, fmt4.getFillBackgroundColor());
//        assertEquals(IndexedColors.AUTOMATIC.index, fmt4.getFillForegroundColor());
            assertEquals(PatternFormatting.NO_FILL, fmt4.getFillPattern());
            // borders are not set
            assertNull(rule3.getBorderFormatting());

            ConditionalFormatting cf3 = sheetCF.getConditionalFormattingAt(2);
            CellRangeAddress[] regions3 = cf3.getFormattingRanges();
            assertEquals(1, regions3.length);
            assertEquals("B1:B7", regions3[0].formatAsString());
            assertEquals(2, cf3.getNumberOfRules());

            ConditionalFormattingRule rule4 = cf3.getRule(0);
            assertEquals(ConditionType.CELL_VALUE_IS, rule4.getConditionType());
            assertEquals(ComparisonOperator.LE, rule4.getComparisonOperation());
            assertEquals("\"AAA\"", rule4.getFormula1());
            assertNull(rule4.getFormula2());

            ConditionalFormattingRule rule5 = cf3.getRule(1);
            assertEquals(ConditionType.CELL_VALUE_IS, rule5.getConditionType());
            assertEquals(ComparisonOperator.BETWEEN, rule5.getComparisonOperation());
            assertEquals("\"A\"", rule5.getFormula1());
            assertEquals("\"AAA\"", rule5.getFormula2());
        }
    }

    protected void testReadOffice2007(String filename) throws IOException {
        try (Workbook wb = _testDataProvider.openSampleWorkbook(filename)) {
            Sheet s = wb.getSheet("CF");

            // Sanity check data
            assertEquals("Values", s.getRow(0).getCell(0).toString());
            assertEquals(10.0, s.getRow(2).getCell(0).getNumericCellValue());

            // Check we found all the conditional formatting rules we should have
            SheetConditionalFormatting sheetCF = s.getSheetConditionalFormatting();
            int numCF = 3;
            int numCF12 = 15;
            int numCFEX = 0; // TODO This should be 2, but we don't support CFEX formattings yet, see #58149
            assertEquals(numCF + numCF12 + numCFEX, sheetCF.getNumConditionalFormattings());

            int fCF = 0, fCF12 = 0, fCFEX = 0;
            for (int i = 0; i < sheetCF.getNumConditionalFormattings(); i++) {
                ConditionalFormatting cf = sheetCF.getConditionalFormattingAt(i);
                if (cf instanceof HSSFConditionalFormatting) {
                    String str = cf.toString();
                    if (str.contains("/* CF_RULE */")) fCF++;
                    if (str.contains("/* CF_RULE_12 */")) fCF12++;
                    if (str.contains("[CFEX]")) fCFEX++;
                } else {
                    ConditionType type = cf.getRule(cf.getNumberOfRules() - 1).getConditionType();
                    if (type == ConditionType.CELL_VALUE_IS || type == ConditionType.FORMULA) {
                        fCF++;
                    } else {
                        // TODO Properly detect Ext ones from the xml
                        fCF12++;
                    }
                }
            }
            assertEquals(numCF, fCF);
            assertEquals(numCF12, fCF12);
            assertEquals(numCFEX, fCFEX);


            // Check the rules / values in detail


            // Highlight Positive values - Column C
            ConditionalFormatting cf = sheetCF.getConditionalFormattingAt(0);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("C2:C17", cf.getFormattingRanges()[0].formatAsString());

            assertEquals(1, cf.getNumberOfRules());
            ConditionalFormattingRule cr = cf.getRule(0);
            assertEquals(ConditionType.CELL_VALUE_IS, cr.getConditionType());
            assertEquals(ComparisonOperator.GT, cr.getComparisonOperation());
            assertEquals("0", cr.getFormula1());
            assertNull(cr.getFormula2());
            // When it matches:
            //   Sets the font colour to dark green
            //   Sets the background colour to lighter green
            // TODO Should the colours be slightly different between formats? Would CFEX support help for HSSF?
            if (cr instanceof HSSFConditionalFormattingRule) {
                assertColor("0:8080:0", cr.getFontFormatting().getFontColor());
                assertColor("CCCC:FFFF:CCCC", cr.getPatternFormatting().getFillBackgroundColorColor());
            } else {
                assertColor("006100", cr.getFontFormatting().getFontColor());
                assertColor("C6EFCE", cr.getPatternFormatting().getFillBackgroundColorColor());
            }


            // Highlight 10-30 - Column D
            cf = sheetCF.getConditionalFormattingAt(1);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("D2:D17", cf.getFormattingRanges()[0].formatAsString());

            assertEquals(1, cf.getNumberOfRules());
            cr = cf.getRule(0);
            assertEquals(ConditionType.CELL_VALUE_IS, cr.getConditionType());
            assertEquals(ComparisonOperator.BETWEEN, cr.getComparisonOperation());
            assertEquals("10", cr.getFormula1());
            assertEquals("30", cr.getFormula2());
            // When it matches:
            //   Sets the font colour to dark red
            //   Sets the background colour to lighter red
            // TODO Should the colours be slightly different between formats? Would CFEX support help for HSSF?
            if (cr instanceof HSSFConditionalFormattingRule) {
                assertColor("8080:0:8080", cr.getFontFormatting().getFontColor());
                assertColor("FFFF:9999:CCCC", cr.getPatternFormatting().getFillBackgroundColorColor());
            } else {
                assertColor("9C0006", cr.getFontFormatting().getFontColor());
                assertColor("FFC7CE", cr.getPatternFormatting().getFillBackgroundColorColor());
            }


            // Data Bars - Column E
            cf = sheetCF.getConditionalFormattingAt(2);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("E2:E17", cf.getFormattingRanges()[0].formatAsString());
            assertDataBar(cf, "FF63C384");


            // Colours Red->Yellow->Green - Column F
            cf = sheetCF.getConditionalFormattingAt(3);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("F2:F17", cf.getFormattingRanges()[0].formatAsString());
            assertColorScale(cf, "F8696B", "FFEB84", "63BE7B");


            // Colours Blue->White->Red - Column G
            cf = sheetCF.getConditionalFormattingAt(4);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("G2:G17", cf.getFormattingRanges()[0].formatAsString());
            assertColorScale(cf, "5A8AC6", "FCFCFF", "F8696B");


            // Icons : Default - Column H, percentage thresholds
            cf = sheetCF.getConditionalFormattingAt(5);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("H2:H17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GYR_3_TRAFFIC_LIGHTS, 0d, 33d, 67d);


            // Icons : 3 signs - Column I
            cf = sheetCF.getConditionalFormattingAt(6);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("I2:I17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GYR_3_SHAPES, 0d, 33d, 67d);


            // Icons : 3 traffic lights 2 - Column J
            cf = sheetCF.getConditionalFormattingAt(7);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("J2:J17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GYR_3_TRAFFIC_LIGHTS_BOX, 0d, 33d, 67d);


            // Icons : 4 traffic lights - Column K
            cf = sheetCF.getConditionalFormattingAt(8);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("K2:K17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GYRB_4_TRAFFIC_LIGHTS, 0d, 25d, 50d, 75d);


            // Icons : 3 symbols with backgrounds - Column L
            cf = sheetCF.getConditionalFormattingAt(9);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("L2:L17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GYR_3_SYMBOLS_CIRCLE, 0d, 33d, 67d);


            // Icons : 3 flags - Column M2 Only
            cf = sheetCF.getConditionalFormattingAt(10);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("M2", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GYR_3_FLAGS, 0d, 33d, 67d);

            // Icons : 3 flags - Column M (all)
            cf = sheetCF.getConditionalFormattingAt(11);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("M2:M17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GYR_3_FLAGS, 0d, 33d, 67d);


            // Icons : 3 symbols 2 (no background) - Column N
            cf = sheetCF.getConditionalFormattingAt(12);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("N2:N17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GYR_3_SYMBOLS, 0d, 33d, 67d);


            // Icons : 3 arrows - Column O
            cf = sheetCF.getConditionalFormattingAt(13);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("O2:O17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GYR_3_ARROW, 0d, 33d, 67d);


            // Icons : 5 arrows grey - Column P
            cf = sheetCF.getConditionalFormattingAt(14);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("P2:P17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.GREY_5_ARROWS, 0d, 20d, 40d, 60d, 80d);


            // Icons : 3 stars (ext) - Column Q
            // TODO Support EXT formattings


            // Icons : 4 ratings - Column R
            cf = sheetCF.getConditionalFormattingAt(15);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("R2:R17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.RATINGS_4, 0d, 25d, 50d, 75d);


            // Icons : 5 ratings - Column S
            cf = sheetCF.getConditionalFormattingAt(16);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("S2:S17", cf.getFormattingRanges()[0].formatAsString());
            assertIconSetPercentages(cf, IconSet.RATINGS_5, 0d, 20d, 40d, 60d, 80d);


            // Custom Icon+Format - Column T
            cf = sheetCF.getConditionalFormattingAt(17);
            assertEquals(1, cf.getFormattingRanges().length);
            assertEquals("T2:T17", cf.getFormattingRanges()[0].formatAsString());

            // TODO Support IconSet + Other CFs with 2 rules
//        assertEquals(2, cf.getNumberOfRules());
//        cr = cf.getRule(0);
//        assertIconSetPercentages(cr, IconSet.GYR_3_TRAFFIC_LIGHTS_BOX, 0d, 33d, 67d);
//        cr = cf.getRule(1);
//        assertEquals(ConditionType.FORMULA, cr.getConditionType());
//        assertEquals(ComparisonOperator.NO_COMPARISON, cr.getComparisonOperation());
//        // TODO Why aren't these two the same between formats?
//        if (cr instanceof HSSFConditionalFormattingRule) {
//            assertEquals("MOD(ROW($T1),2)=1", cr.getFormula1());
//        } else {
//            assertEquals("MOD(ROW($T2),2)=1", cr.getFormula1());
//        }
//        assertEquals(null, cr.getFormula2());


            // Mixed icons - Column U
            // TODO Support EXT formattings

        }
    }

    @SuppressWarnings("SameParameterValue")
    private void assertDataBar(ConditionalFormatting cf, String color) {
        assertEquals(1, cf.getNumberOfRules());
        ConditionalFormattingRule cr = cf.getRule(0);
        assertDataBar(cr, color);
    }

    private void assertDataBar(ConditionalFormattingRule cr, String color) {
        assertEquals(ConditionType.DATA_BAR, cr.getConditionType());
        assertEquals(ComparisonOperator.NO_COMPARISON, cr.getComparisonOperation());
        assertNull(cr.getFormula1());
        assertNull(cr.getFormula2());

        DataBarFormatting databar = cr.getDataBarFormatting();
        assertNotNull(databar);
        assertFalse(databar.isIconOnly());
        assertTrue(databar.isLeftToRight());
        assertEquals(defaultDataBarMinLength(), databar.getWidthMin());
        assertEquals(defaultDataBarMaxLength(), databar.getWidthMax());

        assertColor(color, databar.getColor());

        ConditionalFormattingThreshold th1 = databar.getMinThreshold();
        assertEquals(RangeType.MIN, th1.getRangeType());
        checkThreshold(th1);
        ConditionalFormattingThreshold th2 = databar.getMaxThreshold();
        assertEquals(RangeType.MAX, th2.getRangeType());
        checkThreshold(th2);
    }

    protected void checkThreshold(ConditionalFormattingThreshold threshold) {
        assertNull(threshold.getValue());
        assertNull(threshold.getFormula());
    }

    private void assertIconSetPercentages(ConditionalFormatting cf, IconSet iconset, Double...vals) {
        assertEquals(1, cf.getNumberOfRules());
        ConditionalFormattingRule cr = cf.getRule(0);
        assertIconSetPercentages(cr, iconset, vals);
    }

    private void assertIconSetPercentages(ConditionalFormattingRule cr, IconSet iconset, Double...vals) {
        assertEquals(ConditionType.ICON_SET, cr.getConditionType());
        assertEquals(ComparisonOperator.NO_COMPARISON, cr.getComparisonOperation());
        assertNull(cr.getFormula1());
        assertNull(cr.getFormula2());

        IconMultiStateFormatting icon = cr.getMultiStateFormatting();
        assertNotNull(icon);
        assertEquals(iconset, icon.getIconSet());
        assertFalse(icon.isIconOnly());
        assertFalse(icon.isReversed());

        assertNotNull(icon.getThresholds());
        assertEquals(vals.length, icon.getThresholds().length);
        for (int i=0; i<vals.length; i++) {
            Double v = vals[i];
            ConditionalFormattingThreshold th = icon.getThresholds()[i];
            assertEquals(RangeType.PERCENT, th.getRangeType());
            assertEquals(v, th.getValue());
            assertNull(th.getFormula());
        }
    }

    private void assertColorScale(ConditionalFormatting cf, String... colors) {
        assertEquals(1, cf.getNumberOfRules());
        ConditionalFormattingRule cr = cf.getRule(0);
        assertColorScale(cr, colors);
    }

    private void assertColorScale(ConditionalFormattingRule cr, String... colors) {
        assertEquals(ConditionType.COLOR_SCALE, cr.getConditionType());
        assertEquals(ComparisonOperator.NO_COMPARISON, cr.getComparisonOperation());
        assertNull(cr.getFormula1());
        assertNull(cr.getFormula2());

        ColorScaleFormatting color = cr.getColorScaleFormatting();
        assertNotNull(color);
        assertNotNull(color.getColors());
        assertNotNull(color.getThresholds());
        assertEquals(colors.length, color.getNumControlPoints());
        assertEquals(colors.length, color.getColors().length);
        assertEquals(colors.length, color.getThresholds().length);

        // Thresholds should be Min / (evenly spaced) / Max
        int steps = 100 / (colors.length-1);
        for (int i=0; i<colors.length; i++) {
            ConditionalFormattingThreshold th = color.getThresholds()[i];
            if (i == 0) {
                assertEquals(RangeType.MIN, th.getRangeType());
            } else if (i == colors.length-1) {
                assertEquals(RangeType.MAX, th.getRangeType());
            } else {
                assertEquals(RangeType.PERCENTILE, th.getRangeType());
                assertEquals(steps*i, th.getValue().intValue());
            }
            assertNull(th.getFormula());
        }

        // Colors should match
        for (int i=0; i<colors.length; i++) {
            assertColor(colors[i], color.getColors()[i]);
        }
    }

    @Test
    void testCreateFontFormatting() throws IOException {
        try (Workbook workbook = _testDataProvider.createWorkbook()) {
            Sheet sheet = workbook.createSheet();

            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule(ComparisonOperator.EQUAL, "7");
            FontFormatting fontFmt = rule1.createFontFormatting();
            assertFalse(fontFmt.isItalic());
            assertFalse(fontFmt.isBold());
            fontFmt.setFontStyle(true, true);
            assertTrue(fontFmt.isItalic());
            assertTrue(fontFmt.isBold());

            assertEquals(-1, fontFmt.getFontHeight()); // not modified
            fontFmt.setFontHeight(200);
            assertEquals(200, fontFmt.getFontHeight());
            fontFmt.setFontHeight(100);
            assertEquals(100, fontFmt.getFontHeight());

            assertEquals(Font.SS_NONE, fontFmt.getEscapementType());
            fontFmt.setEscapementType(Font.SS_SUB);
            assertEquals(Font.SS_SUB, fontFmt.getEscapementType());
            fontFmt.setEscapementType(Font.SS_NONE);
            assertEquals(Font.SS_NONE, fontFmt.getEscapementType());
            fontFmt.setEscapementType(Font.SS_SUPER);
            assertEquals(Font.SS_SUPER, fontFmt.getEscapementType());

            assertEquals(Font.U_NONE, fontFmt.getUnderlineType());
            fontFmt.setUnderlineType(Font.U_SINGLE);
            assertEquals(Font.U_SINGLE, fontFmt.getUnderlineType());
            fontFmt.setUnderlineType(Font.U_NONE);
            assertEquals(Font.U_NONE, fontFmt.getUnderlineType());
            fontFmt.setUnderlineType(Font.U_DOUBLE);
            assertEquals(Font.U_DOUBLE, fontFmt.getUnderlineType());

            assertEquals(-1, fontFmt.getFontColorIndex());
            fontFmt.setFontColorIndex(IndexedColors.RED.index);
            assertEquals(IndexedColors.RED.index, fontFmt.getFontColorIndex());
            fontFmt.setFontColorIndex(IndexedColors.AUTOMATIC.index);
            assertEquals(IndexedColors.AUTOMATIC.index, fontFmt.getFontColorIndex());
            fontFmt.setFontColorIndex(IndexedColors.BLUE.index);
            assertEquals(IndexedColors.BLUE.index, fontFmt.getFontColorIndex());

            ConditionalFormattingRule[] cfRules = {rule1};

            CellRangeAddress[] regions = {CellRangeAddress.valueOf("A1:A5")};

            sheetCF.addConditionalFormatting(regions, cfRules);

            // Verification
            ConditionalFormatting cf = sheetCF.getConditionalFormattingAt(0);
            assertNotNull(cf);

            assertEquals(1, cf.getNumberOfRules());

            FontFormatting r1fp = cf.getRule(0).getFontFormatting();
            assertNotNull(r1fp);

            assertTrue(r1fp.isItalic());
            assertTrue(r1fp.isBold());
            assertEquals(Font.SS_SUPER, r1fp.getEscapementType());
            assertEquals(Font.U_DOUBLE, r1fp.getUnderlineType());
            assertEquals(IndexedColors.BLUE.index, r1fp.getFontColorIndex());
        }
    }

    @Test
    void testCreatePatternFormatting() throws IOException {
        try (Workbook workbook = _testDataProvider.createWorkbook()) {
            Sheet sheet = workbook.createSheet();

            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule(ComparisonOperator.EQUAL, "7");
            PatternFormatting patternFmt = rule1.createPatternFormatting();

            assertEquals(0, patternFmt.getFillBackgroundColor());
            patternFmt.setFillBackgroundColor(IndexedColors.RED.index);
            assertEquals(IndexedColors.RED.index, patternFmt.getFillBackgroundColor());

            assertEquals(0, patternFmt.getFillForegroundColor());
            patternFmt.setFillForegroundColor(IndexedColors.BLUE.index);
            assertEquals(IndexedColors.BLUE.index, patternFmt.getFillForegroundColor());

            assertEquals(PatternFormatting.NO_FILL, patternFmt.getFillPattern());
            patternFmt.setFillPattern(PatternFormatting.SOLID_FOREGROUND);
            assertEquals(PatternFormatting.SOLID_FOREGROUND, patternFmt.getFillPattern());
            patternFmt.setFillPattern(PatternFormatting.NO_FILL);
            assertEquals(PatternFormatting.NO_FILL, patternFmt.getFillPattern());
            patternFmt.setFillPattern(PatternFormatting.BRICKS);
            assertEquals(PatternFormatting.BRICKS, patternFmt.getFillPattern());

            ConditionalFormattingRule[] cfRules = {rule1};

            CellRangeAddress[] regions = {CellRangeAddress.valueOf("A1:A5")};

            sheetCF.addConditionalFormatting(regions, cfRules);

            // Verification
            ConditionalFormatting cf = sheetCF.getConditionalFormattingAt(0);
            assertNotNull(cf);

            assertEquals(1, cf.getNumberOfRules());

            PatternFormatting r1fp = cf.getRule(0).getPatternFormatting();
            assertNotNull(r1fp);

            assertEquals(IndexedColors.RED.index, r1fp.getFillBackgroundColor());
            assertEquals(IndexedColors.BLUE.index, r1fp.getFillForegroundColor());
            assertEquals(PatternFormatting.BRICKS, r1fp.getFillPattern());
        }
    }

    @Test
    void testAllCreateBorderFormatting() throws IOException {
        // Make sure it is possible to create a conditional formatting rule
        // with every type of Border Style
        try (Workbook workbook = _testDataProvider.createWorkbook()) {
            Sheet sheet = workbook.createSheet();

            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule(ComparisonOperator.EQUAL, "7");
            BorderFormatting borderFmt = rule1.createBorderFormatting();

            for (BorderStyle border : BorderStyle.values()) {
                borderFmt.setBorderTop(border);
                assertEquals(border, borderFmt.getBorderTop());

                borderFmt.setBorderBottom(border);
                assertEquals(border, borderFmt.getBorderBottom());

                borderFmt.setBorderLeft(border);
                assertEquals(border, borderFmt.getBorderLeft());

                borderFmt.setBorderRight(border);
                assertEquals(border, borderFmt.getBorderRight());

                borderFmt.setBorderDiagonal(border);
                assertEquals(border, borderFmt.getBorderDiagonal());
            }
        }
    }

    @Test
    void testCreateBorderFormatting() throws IOException {
        try (Workbook workbook = _testDataProvider.createWorkbook()) {
            Sheet sheet = workbook.createSheet();

            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();

            ConditionalFormattingRule rule1 = sheetCF.createConditionalFormattingRule(ComparisonOperator.EQUAL, "7");
            BorderFormatting borderFmt = rule1.createBorderFormatting();

            assertEquals(BorderStyle.NONE, borderFmt.getBorderBottom());
            borderFmt.setBorderBottom(BorderStyle.DOTTED);
            assertEquals(BorderStyle.DOTTED, borderFmt.getBorderBottom());
            borderFmt.setBorderBottom(BorderStyle.NONE);
            assertEquals(BorderStyle.NONE, borderFmt.getBorderBottom());
            borderFmt.setBorderBottom(BorderStyle.THICK);
            assertEquals(BorderStyle.THICK, borderFmt.getBorderBottom());

            assertEquals(BorderStyle.NONE, borderFmt.getBorderTop());
            borderFmt.setBorderTop(BorderStyle.DOTTED);
            assertEquals(BorderStyle.DOTTED, borderFmt.getBorderTop());
            borderFmt.setBorderTop(BorderStyle.NONE);
            assertEquals(BorderStyle.NONE, borderFmt.getBorderTop());
            borderFmt.setBorderTop(BorderStyle.THICK);
            assertEquals(BorderStyle.THICK, borderFmt.getBorderTop());

            assertEquals(BorderStyle.NONE, borderFmt.getBorderLeft());
            borderFmt.setBorderLeft(BorderStyle.DOTTED);
            assertEquals(BorderStyle.DOTTED, borderFmt.getBorderLeft());
            borderFmt.setBorderLeft(BorderStyle.NONE);
            assertEquals(BorderStyle.NONE, borderFmt.getBorderLeft());
            borderFmt.setBorderLeft(BorderStyle.THIN);
            assertEquals(BorderStyle.THIN, borderFmt.getBorderLeft());

            assertEquals(BorderStyle.NONE, borderFmt.getBorderRight());
            borderFmt.setBorderRight(BorderStyle.DOTTED);
            assertEquals(BorderStyle.DOTTED, borderFmt.getBorderRight());
            borderFmt.setBorderRight(BorderStyle.NONE);
            assertEquals(BorderStyle.NONE, borderFmt.getBorderRight());
            borderFmt.setBorderRight(BorderStyle.HAIR);
            assertEquals(BorderStyle.HAIR, borderFmt.getBorderRight());

            ConditionalFormattingRule[] cfRules = {rule1};

            CellRangeAddress[] regions = {CellRangeAddress.valueOf("A1:A5")};

            sheetCF.addConditionalFormatting(regions, cfRules);

            // Verification
            ConditionalFormatting cf = sheetCF.getConditionalFormattingAt(0);
            assertNotNull(cf);

            assertEquals(1, cf.getNumberOfRules());

            BorderFormatting r1fp = cf.getRule(0).getBorderFormatting();
            assertNotNull(r1fp);
            assertEquals(BorderStyle.THICK, r1fp.getBorderBottom());
            assertEquals(BorderStyle.THICK, r1fp.getBorderTop());
            assertEquals(BorderStyle.THIN, r1fp.getBorderLeft());
            assertEquals(BorderStyle.HAIR, r1fp.getBorderRight());
        }
    }

    @Test
    void testCreateIconFormatting() throws IOException {
        try (Workbook wb1 = _testDataProvider.createWorkbook()) {
            Sheet sheet = wb1.createSheet();

            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();
            ConditionalFormattingRule rule1 =
                    sheetCF.createConditionalFormattingRule(IconSet.GYRB_4_TRAFFIC_LIGHTS);
            IconMultiStateFormatting iconFmt = rule1.getMultiStateFormatting();

            assertEquals(IconSet.GYRB_4_TRAFFIC_LIGHTS, iconFmt.getIconSet());
            assertEquals(4, iconFmt.getThresholds().length);
            assertFalse(iconFmt.isIconOnly());
            assertFalse(iconFmt.isReversed());

            iconFmt.setIconOnly(true);
            iconFmt.getThresholds()[0].setRangeType(RangeType.MIN);
            iconFmt.getThresholds()[1].setRangeType(RangeType.NUMBER);
            iconFmt.getThresholds()[1].setValue(10d);
            iconFmt.getThresholds()[2].setRangeType(RangeType.PERCENT);
            iconFmt.getThresholds()[2].setValue(75d);
            iconFmt.getThresholds()[3].setRangeType(RangeType.MAX);

            CellRangeAddress[] regions = {CellRangeAddress.valueOf("A1:A5")};
            sheetCF.addConditionalFormatting(regions, rule1);

            // Save, re-load and re-check
            try (Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1)) {
                sheet = wb2.getSheetAt(0);
                sheetCF = sheet.getSheetConditionalFormatting();
                assertEquals(1, sheetCF.getNumConditionalFormattings());

                ConditionalFormatting cf = sheetCF.getConditionalFormattingAt(0);
                assertEquals(1, cf.getNumberOfRules());
                rule1 = cf.getRule(0);
                assertEquals(ConditionType.ICON_SET, rule1.getConditionType());
                iconFmt = rule1.getMultiStateFormatting();

                assertEquals(IconSet.GYRB_4_TRAFFIC_LIGHTS, iconFmt.getIconSet());
                assertEquals(4, iconFmt.getThresholds().length);
                assertTrue(iconFmt.isIconOnly());
                assertFalse(iconFmt.isReversed());

                assertEquals(RangeType.MIN, iconFmt.getThresholds()[0].getRangeType());
                assertEquals(RangeType.NUMBER, iconFmt.getThresholds()[1].getRangeType());
                assertEquals(RangeType.PERCENT, iconFmt.getThresholds()[2].getRangeType());
                assertEquals(RangeType.MAX, iconFmt.getThresholds()[3].getRangeType());
                assertNull(iconFmt.getThresholds()[0].getValue());
                assertEquals(10d, iconFmt.getThresholds()[1].getValue(), 0);
                assertEquals(75d, iconFmt.getThresholds()[2].getValue(), 0);
                assertNull(iconFmt.getThresholds()[3].getValue());
            }
        }
    }

    @Test
    void testCreateColorScaleFormatting() throws IOException {
        try (Workbook wb1 = _testDataProvider.createWorkbook()) {
            Sheet sheet = wb1.createSheet();

            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();
            ConditionalFormattingRule rule1 =
                    sheetCF.createConditionalFormattingColorScaleRule();
            ColorScaleFormatting clrFmt = rule1.getColorScaleFormatting();

            assertEquals(3, clrFmt.getNumControlPoints());
            assertEquals(3, clrFmt.getColors().length);
            assertEquals(3, clrFmt.getThresholds().length);

            clrFmt.getThresholds()[0].setRangeType(RangeType.MIN);
            clrFmt.getThresholds()[1].setRangeType(RangeType.NUMBER);
            clrFmt.getThresholds()[1].setValue(10d);
            clrFmt.getThresholds()[2].setRangeType(RangeType.MAX);

            CellRangeAddress[] regions = {CellRangeAddress.valueOf("A1:A5")};
            sheetCF.addConditionalFormatting(regions, rule1);

            // Save, re-load and re-check
            try (Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1)) {
                sheet = wb2.getSheetAt(0);
                sheetCF = sheet.getSheetConditionalFormatting();
                assertEquals(1, sheetCF.getNumConditionalFormattings());

                ConditionalFormatting cf = sheetCF.getConditionalFormattingAt(0);
                assertEquals(1, cf.getNumberOfRules());
                rule1 = cf.getRule(0);
                clrFmt = rule1.getColorScaleFormatting();
                assertEquals(ConditionType.COLOR_SCALE, rule1.getConditionType());

                assertEquals(3, clrFmt.getNumControlPoints());
                assertEquals(3, clrFmt.getColors().length);
                assertEquals(3, clrFmt.getThresholds().length);

                assertEquals(RangeType.MIN, clrFmt.getThresholds()[0].getRangeType());
                assertEquals(RangeType.NUMBER, clrFmt.getThresholds()[1].getRangeType());
                assertEquals(RangeType.MAX, clrFmt.getThresholds()[2].getRangeType());
                assertNull(clrFmt.getThresholds()[0].getValue());
                assertEquals(10d, clrFmt.getThresholds()[1].getValue(), 0);
                assertNull(clrFmt.getThresholds()[2].getValue());
            }
        }
    }

    @Test
    void testCreateDataBarFormatting() throws IOException {
        try (Workbook wb1 = _testDataProvider.createWorkbook()) {
            Sheet sheet = wb1.createSheet();

            String colorHex = "FFFFEB84";
            ExtendedColor color = wb1.getCreationHelper().createExtendedColor();
            color.setARGBHex(colorHex);
            SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();
            ConditionalFormattingRule rule1 =
                    sheetCF.createConditionalFormattingRule(color);
            DataBarFormatting dbFmt = rule1.getDataBarFormatting();

            assertFalse(dbFmt.isIconOnly());
            assertTrue(dbFmt.isLeftToRight());
            assertEquals(defaultDataBarMinLength(), dbFmt.getWidthMin());
            assertEquals(defaultDataBarMaxLength(), dbFmt.getWidthMax());
            assertColor(colorHex, dbFmt.getColor());

            dbFmt.getMinThreshold().setRangeType(RangeType.MIN);
            dbFmt.getMaxThreshold().setRangeType(RangeType.MAX);

            CellRangeAddress[] regions = {CellRangeAddress.valueOf("A1:A5")};
            sheetCF.addConditionalFormatting(regions, rule1);

            // Save, re-load and re-check
            try (Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1)) {
                sheet = wb2.getSheetAt(0);
                sheetCF = sheet.getSheetConditionalFormatting();
                assertEquals(1, sheetCF.getNumConditionalFormattings());

                ConditionalFormatting cf = sheetCF.getConditionalFormattingAt(0);
                assertEquals(1, cf.getNumberOfRules());
                rule1 = cf.getRule(0);
                dbFmt = rule1.getDataBarFormatting();
                assertEquals(ConditionType.DATA_BAR, rule1.getConditionType());

                assertFalse(dbFmt.isIconOnly());
                assertTrue(dbFmt.isLeftToRight());
                assertEquals(defaultDataBarMinLength(), dbFmt.getWidthMin());
                assertEquals(defaultDataBarMaxLength(), dbFmt.getWidthMax());
                assertColor(colorHex, dbFmt.getColor());

                assertEquals(RangeType.MIN, dbFmt.getMinThreshold().getRangeType());
                assertEquals(RangeType.MAX, dbFmt.getMaxThreshold().getRangeType());
                assertNull(dbFmt.getMinThreshold().getValue());
                assertNull(dbFmt.getMaxThreshold().getValue());
            }
        }
    }

    @Test
    void testBug55380() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            Sheet sheet = wb.createSheet();
            CellRangeAddress[] ranges = new CellRangeAddress[]{
                    CellRangeAddress.valueOf("C9:D30"), CellRangeAddress.valueOf("C7:C31")
            };
            ConditionalFormattingRule rule = sheet.getSheetConditionalFormatting().createConditionalFormattingRule("$A$1>0");
            int form = sheet.getSheetConditionalFormatting().addConditionalFormatting(ranges, rule);
            assertEquals(0, form);
        }
    }

    @Test
    void testSetCellRangeAddresswithSingleRange() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            final Sheet sheet = wb.createSheet("S1");
            final SheetConditionalFormatting cf = sheet.getSheetConditionalFormatting();
            assertEquals(0, cf.getNumConditionalFormattings());
            ConditionalFormattingRule rule1 = cf.createConditionalFormattingRule("$A$1>0");
            cf.addConditionalFormatting(new CellRangeAddress[]{
                    CellRangeAddress.valueOf("A1:A5")
            }, rule1);

            assertEquals(1, cf.getNumConditionalFormattings());
            ConditionalFormatting readCf = cf.getConditionalFormattingAt(0);
            CellRangeAddress[] formattingRanges = readCf.getFormattingRanges();
            assertEquals(1, formattingRanges.length);
            CellRangeAddress formattingRange = formattingRanges[0];
            assertEquals("A1:A5", formattingRange.formatAsString());

            readCf.setFormattingRanges(new CellRangeAddress[]{
                    CellRangeAddress.valueOf("A1:A6")
            });

            readCf = cf.getConditionalFormattingAt(0);
            formattingRanges = readCf.getFormattingRanges();
            assertEquals(1, formattingRanges.length);
            formattingRange = formattingRanges[0];
            assertEquals("A1:A6", formattingRange.formatAsString());
        }
    }

    @Test
    void testSetCellRangeAddressWithMultipleRanges() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            final Sheet sheet = wb.createSheet("S1");
            final SheetConditionalFormatting cf = sheet.getSheetConditionalFormatting();
            assertEquals(0, cf.getNumConditionalFormattings());
            ConditionalFormattingRule rule1 = cf.createConditionalFormattingRule("$A$1>0");
            cf.addConditionalFormatting(new CellRangeAddress[]{
                    CellRangeAddress.valueOf("A1:A5")
            }, rule1);

            assertEquals(1, cf.getNumConditionalFormattings());
            ConditionalFormatting readCf = cf.getConditionalFormattingAt(0);
            CellRangeAddress[] formattingRanges = readCf.getFormattingRanges();
            assertEquals(1, formattingRanges.length);
            CellRangeAddress formattingRange = formattingRanges[0];
            assertEquals("A1:A5", formattingRange.formatAsString());

            readCf.setFormattingRanges(new CellRangeAddress[]{
                    CellRangeAddress.valueOf("A1:A6"),
                    CellRangeAddress.valueOf("B1:B6")
            });

            readCf = cf.getConditionalFormattingAt(0);
            formattingRanges = readCf.getFormattingRanges();
            assertEquals(2, formattingRanges.length);
            formattingRange = formattingRanges[0];
            assertEquals("A1:A6", formattingRange.formatAsString());
            formattingRange = formattingRanges[1];
            assertEquals("B1:B6", formattingRange.formatAsString());
        }
    }

    @Test
    void testSetCellRangeAddressWithNullRanges() throws IOException {
        try (Workbook wb = _testDataProvider.createWorkbook()) {
            final Sheet sheet = wb.createSheet("S1");
            final SheetConditionalFormatting cf = sheet.getSheetConditionalFormatting();
            assertEquals(0, cf.getNumConditionalFormattings());
            ConditionalFormattingRule rule1 = cf.createConditionalFormattingRule("$A$1>0");
            cf.addConditionalFormatting(new CellRangeAddress[]{
                    CellRangeAddress.valueOf("A1:A5")
            }, rule1);

            assertEquals(1, cf.getNumConditionalFormattings());
            ConditionalFormatting readCf = cf.getConditionalFormattingAt(0);
            assertThrows(IllegalArgumentException.class, () -> readCf.setFormattingRanges(null));
        }
    }


    @Test
    void test52122() throws IOException {
        try (Workbook workbook = _testDataProvider.createWorkbook()) {
            Sheet sheet = workbook.createSheet("Conditional Formatting Test");
            sheet.setColumnWidth(0, 256 * 10);
            sheet.setColumnWidth(1, 256 * 10);
            sheet.setColumnWidth(2, 256 * 10);

            // Create some content.
            // row 0
            Row row = sheet.createRow(0);

            Cell cell0 = row.createCell(0);
            cell0.setCellValue(100);

            Cell cell1 = row.createCell(1);
            cell1.setCellValue(120);

            Cell cell2 = row.createCell(2);
            cell2.setCellValue(130);

            // row 1
            row = sheet.createRow(1);

            cell0 = row.createCell(0);
            cell0.setCellValue(200);

            cell1 = row.createCell(1);
            cell1.setCellValue(220);

            cell2 = row.createCell(2);
            cell2.setCellValue(230);

            // row 2
            row = sheet.createRow(2);

            cell0 = row.createCell(0);
            cell0.setCellValue(300);

            cell1 = row.createCell(1);
            cell1.setCellValue(320);

            cell2 = row.createCell(2);
            cell2.setCellValue(330);

            // Create conditional formatting, CELL1 should be yellow if CELL0 is not blank.
            SheetConditionalFormatting formatting = sheet.getSheetConditionalFormatting();

            ConditionalFormattingRule rule = formatting.createConditionalFormattingRule("$A$1>75");

            PatternFormatting pattern = rule.createPatternFormatting();
            pattern.setFillBackgroundColor(IndexedColors.BLUE.index);
            pattern.setFillPattern(PatternFormatting.SOLID_FOREGROUND);

            CellRangeAddress[] range = {CellRangeAddress.valueOf("B2:C2")};
            CellRangeAddress[] range2 = {CellRangeAddress.valueOf("B1:C1")};

            formatting.addConditionalFormatting(range, rule);
            formatting.addConditionalFormatting(range2, rule);

            try (Workbook wbBack = _testDataProvider.writeOutAndReadBack(workbook)) {
                Sheet sheetBack = wbBack.getSheetAt(0);
                final SheetConditionalFormatting sheetConditionalFormattingBack = sheetBack.getSheetConditionalFormatting();
                assertNotNull(sheetConditionalFormattingBack);
                final ConditionalFormatting formattingBack = sheetConditionalFormattingBack.getConditionalFormattingAt(0);
                assertNotNull(formattingBack);
                final ConditionalFormattingRule ruleBack = formattingBack.getRule(0);
                assertNotNull(ruleBack);
                final PatternFormatting patternFormattingBack1 = ruleBack.getPatternFormatting();
                assertNotNull(patternFormattingBack1);
                assertEquals(IndexedColors.BLUE.index, patternFormattingBack1.getFillBackgroundColor());
                assertEquals(PatternFormatting.SOLID_FOREGROUND, patternFormattingBack1.getFillPattern());
            }
        }
    }

    @Test
    void testRangeType() {
        for (RangeType rangeType : RangeType.values()) {
            assertEquals(rangeType, RangeType.byName(rangeType.name));
            assertEquals(rangeType, RangeType.byId(rangeType.id));
        }

        assertEquals(RangeType.UNALLOCATED, RangeType.byName(null));
        assertNull(RangeType.byName("some other name"));

        assertNull(RangeType.byId(-1));
        assertNull(RangeType.byId(0));
        assertNull(RangeType.byId(99));
    }
}