/* ====================================================================
   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.hssf.eventusermodel;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord;
import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord;
import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingRowDummyRecord;
import org.apache.poi.hssf.record.BlankRecord;
import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.DimensionsRecord;
import org.apache.poi.hssf.record.LabelSSTRecord;
import org.apache.poi.hssf.record.MulBlankRecord;
import org.apache.poi.hssf.record.NumberRecord;
import org.apache.poi.hssf.record.RowRecord;
import org.apache.poi.hssf.record.SharedFormulaRecord;
import org.apache.poi.hssf.record.WindowTwoRecord;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.junit.jupiter.api.Test;

/**
 * Tests for MissingRecordAwareHSSFListener
 */
final class TestMissingRecordAwareHSSFListener {

    private final List<org.apache.poi.hssf.record.Record> _records = new ArrayList<>();

    private void readRecords(String sampleFileName) throws IOException {
        _records.clear();
        HSSFRequest req = new HSSFRequest();
        req.addListenerForAllRecords(new MissingRecordAwareHSSFListener(_records::add));

        HSSFEventFactory factory = new HSSFEventFactory();
        try (InputStream is = HSSFTestDataSamples.openSampleFileStream(sampleFileName);
             POIFSFileSystem fs = new POIFSFileSystem(is)) {
            factory.processWorkbookEvents(req, fs);
        }

        assertTrue(_records.size() > 100);
    }

    @Test
    void testMissingRowRecords() throws IOException {
        readRecords("MissingBits.xls");

        // We have rows 0, 1, 2, 20 and 21
        int row0 = lastIndexOf(r -> r instanceof RowRecord && ((RowRecord)r).getRowNumber() == 0);
        assertTrue(row0 > -1);

        // Records: row 0: column 1, 2), then missing rows, rows 20,21,22 each 1 column
        String exp1 =
            "0:rr,1:rr,2:rr,3:mr,4:mr,5:mr,6:mr,7:mr,8:mr,9:mr,10:mr,11:mr,12:mr,13:mr,14:mr," +
            "15:mr,16:mr,17:mr,18:mr,19:mr,20:rr,21:rr,22:rr";
        String act1 = digest(row0, 22);
        assertEquals(exp1, act1);


        // Find the cell at 0,0
        int cell00 = lastIndexOf(r -> r instanceof LabelSSTRecord && ((LabelSSTRecord)r).getRow() == 0 && ((LabelSSTRecord)r).getColumn() == 0);

        String exp2 =
            "0:ls0lc0," +
            "1:nr0/11mc1mc2nr3/23lc3," +
            "2:nr0/45mc1mc2mc3mc4nr5/22lc5," +
            "3:lc,4:lc,5:lc,6:lc,7:lc,8:lc,9:lc,10:lc,11:lc,12:lc,13:lc,14:lc,15:lc,16:lc,17:lc,18:lc,19:lc," +
            "20:nr0/50nr1/51nr2/52nr3/53nr4/54lc4," +
            "21:ls0ls1mc2nr3/12mc4nr5/23nr6/42lc6," +
            "22:ls0mc1mc2ls3ls4mc5mc6mc7mc8mc9mc10ls11lc11";
        String act2 = digest(cell00, 57);
        assertEquals(exp2, act2);
    }

    // Make sure we don't put in any extra new lines that aren't already there
    @Test
    void testNoExtraNewLines() throws IOException {
        // Load a different file
        // This file has has something in lines 1-33
        readRecords("MRExtraLines.xls");

        int rowCount=0;
        for (org.apache.poi.hssf.record.Record rec : _records) {
            if (rec instanceof LastCellOfRowDummyRecord) {
                LastCellOfRowDummyRecord eor = (LastCellOfRowDummyRecord) rec;
                assertEquals(rowCount, eor.getRow());
                rowCount++;
            }
        }
        // Check we got the 33 rows
        assertEquals(33, rowCount);
    }

    /**
     * Make sure that the presence of shared formulas does not cause extra end-of-row records.
     */
    @Test
    void testEndOfRow_bug45672() throws IOException {
        readRecords("ex45672.xls");
        assertEquals(1, matches(r -> r instanceof SharedFormulaRecord));
        assertEquals(1, matches(r -> r instanceof LastCellOfRowDummyRecord));
    }

    /**
     * MulBlank records hold multiple blank cells.
     * Check that we don't have any MulBlankRecords, but do have lots of BlankRecords
     */
    @Test
    void testMulBlankHandling() throws IOException {
        readRecords("45672.xls");
        assertEquals(20, matches(r -> r instanceof BlankRecord));
        assertEquals(2, matches(r -> r instanceof LastCellOfRowDummyRecord));
        assertEquals(0, matches(r -> r instanceof MulBlankRecord));
    }

    @Test
    void testStringRecordHandling() throws IOException {
        readRecords("53588.xls");
        assertEquals(1, matches(r -> r instanceof MissingCellDummyRecord));
        assertEquals(1, matches(r -> r instanceof LastCellOfRowDummyRecord));
    }

    @Test
    void testFormulasWithStringResultsHandling() throws IOException {
        readRecords("53433.xls");

        String exp =
            "dr0:mr,1:mr,2:mr,3:rr,4:rr,5:rr,6:rr,7:rr,8:rr,9:rr,10:mr,11:mr,12:mr,13:mr,14:mr,15:rr,16:rr,17:mr,18:rr," +
            "0:lc,1:lc,2:lc,3:mc0ls1ls2ls3ls4lc4,4:mc0ls1cv2urnr3/12cv4urlc4," +
            "5:mc0cv1urnr2/23nr3/23cv4urlc4,6:mc0mc1nr2/25nr3/45nr4/32815lc4," +
            "7:mc0ls1cv2cv3urcv4lc4,8:mc0mc1mc2mc3cv4lc4,9:mc0mc1mc2ls3lc3," +
            "10:lc,11:lc,12:lc,13:lc,14:lc,15:mc0ls1lc1,16:mc0mc1mc2ls3lc3," +
            "17:lc,18:mc0mc1mc2mc3ls4lc4wr";
        String act = digest(95, 89);
        assertEquals(exp, act);
    }

    private int lastIndexOf(Predicate<org.apache.poi.hssf.record.Record> pre) {
        int found = -1;
        int i = 0;
        for (org.apache.poi.hssf.record.Record r : _records) {
            if (pre.test(r)) {
                found = i;
            }
            i++;
        }
        return found;
    }

    private String digest(int start, int len) {
        StringBuilder sb = new StringBuilder(len*10);
        int lastRow = -1;
        for (org.apache.poi.hssf.record.Record r : _records.subList(start, start+len+1)) {
            String dig = null;
            int row = -1;
            if (r instanceof RowRecord) {
                RowRecord rr = (RowRecord)r;
                row = rr.getRowNumber();
                dig = "rr";
            } else if (r instanceof MissingRowDummyRecord) {
                MissingRowDummyRecord mr = (MissingRowDummyRecord)r;
                row = mr.getRowNumber();
                dig = "mr";
            } else if (r instanceof MissingCellDummyRecord) {
                MissingCellDummyRecord mc = (MissingCellDummyRecord)r;
                row = mc.getRow();
                dig = "mc" + mc.getColumn();
            } else if (r instanceof LastCellOfRowDummyRecord) {
                LastCellOfRowDummyRecord lc = (LastCellOfRowDummyRecord)r;
                row = lc.getRow();
                dig = "lc" + (lc.getLastColumnNumber() > -1 ? lc.getLastColumnNumber() : "");
            } else if (r instanceof NumberRecord) {
                NumberRecord nr = (NumberRecord)r;
                row = nr.getRow();
                dig = "nr" + nr.getColumn() + "/" + (int)nr.getValue();
            } else if (r instanceof LabelSSTRecord) {
                LabelSSTRecord ls = (LabelSSTRecord) r;
                row = ls.getRow();
                dig = "ls" + ls.getColumn();
            } else if (r instanceof WindowTwoRecord) {
                dig = "wr";
            } else if (r instanceof DimensionsRecord) {
                dig = "dr";
            } else if (r instanceof CellValueRecordInterface) {
                CellValueRecordInterface cv = (CellValueRecordInterface) r;
                row = cv.getRow();
                dig = "cv" + cv.getColumn();
            } else {
                // unhandled record
                dig = "ur";
            }
            if (lastRow != row && row > -1) {
                sb.append(lastRow > -1 ? "," : "").append(row).append(":");
                lastRow = row;
            }
            sb.append(dig);
        }
        return sb.toString();
    }

    private long matches(Predicate<org.apache.poi.hssf.record.Record> r) {
        return _records.stream().filter(r).count();
    }
}
