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

import static org.apache.poi.poifs.storage.RawDataUtil.decompress;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.poi.ddf.DefaultEscherRecordFactory;
import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherDggRecord;
import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.ddf.EscherRecordFactory;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.ContinueRecord;
import org.apache.poi.hssf.record.DrawingRecord;
import org.apache.poi.hssf.record.EOFRecord;
import org.apache.poi.hssf.record.EscherAggregate;
import org.apache.poi.hssf.record.NoteRecord;
import org.apache.poi.hssf.record.ObjRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.RecordFactory;
import org.apache.poi.hssf.record.TextObjectRecord;
import org.apache.poi.hssf.record.WindowTwoRecord;
import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate;
import org.apache.poi.hssf.usermodel.HSSFPatriarch;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFTestHelper;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.util.HexDump;
import org.junit.Test;

public class TestDrawingAggregate {
    /**
     *  information about drawing aggregate in a worksheet
     */
    private static class DrawingAggregateInfo {
        /**
         * start and end indices of the aggregate in the worksheet stream
         */
        private int startRecordIndex, endRecordIndex;
        /**
         * the records being aggregated
         */
        private List<RecordBase> aggRecords;

        /**
         * @return aggregate info or null if the sheet does not contain drawing objects
         */
        static DrawingAggregateInfo get(HSSFSheet sheet){
            DrawingAggregateInfo info = null;
            InternalSheet isheet = HSSFTestHelper.getSheetForTest(sheet);
            List<RecordBase> records = isheet.getRecords();
            for(int i = 0; i < records.size(); i++){
                RecordBase rb = records.get(i);
                if((rb instanceof DrawingRecord) && info == null) {
                    info = new DrawingAggregateInfo();
                    info.startRecordIndex = i;
                    info.endRecordIndex = i;
                } else if (info != null && (
                        rb instanceof DrawingRecord
                                || rb instanceof ObjRecord
                                || rb instanceof TextObjectRecord
                                || rb instanceof ContinueRecord
                                || rb instanceof NoteRecord
                )){
                    info.endRecordIndex = i;
                } else {
                    if(rb instanceof EscherAggregate)
                        throw new IllegalStateException("Drawing data already aggregated. " +
                                "You should cal this method before the first invocation of HSSFSheet#getDrawingPatriarch()");
                    if (info != null) break;
                }
            }
            if(info != null){
                info.aggRecords = new ArrayList<>(
                        records.subList(info.startRecordIndex, info.endRecordIndex + 1));
            }
            return info;
        }

        /**
         * @return the raw data being aggregated
         */
        byte[] getRawBytes(){
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            for (RecordBase rb : aggRecords) {
                Record r = (Record) rb;
                try {
                    out.write(r.serialize());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return out.toByteArray();
        }
    }

    /**
     * iterate over all sheets, aggregate drawing records (if there are any)
     * and remember information about the aggregated data.
     * Then serialize the workbook, read back and assert that the aggregated data is preserved.
     *
     * The assertion is strict meaning that the drawing data before and after save must be equal.
     */
    private static void assertWriteAndReadBack(HSSFWorkbook wb) throws IOException {
        // map aggregate info by sheet index
        Map<Integer, DrawingAggregateInfo> aggs = new HashMap<>();
        for(int i = 0; i < wb.getNumberOfSheets(); i++){
            HSSFSheet sheet = wb.getSheetAt(i);
            DrawingAggregateInfo info = DrawingAggregateInfo.get(sheet);
            if(info != null) {
                aggs.put(i, info);
                HSSFPatriarch p = sheet.getDrawingPatriarch();

                // compare aggregate.serialize() with raw bytes from the record stream
                EscherAggregate agg = HSSFTestHelper.getEscherAggregate(p);

                byte[] dgBytes1 = info.getRawBytes();
                byte[] dgBytes2 = agg.serialize();

                assertEquals("different size of raw data ande aggregate.serialize()", dgBytes1.length, dgBytes2.length);
                assertTrue("raw drawing data ("+dgBytes1.length+" bytes) and aggregate.serialize() are different.",
                        Arrays.equals(dgBytes1, dgBytes2));
            }
        }

        if(aggs.size() != 0){
            HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb);
            for(int i = 0; i < wb2.getNumberOfSheets(); i++){
                DrawingAggregateInfo info1 = aggs.get(i);
                if(info1 != null) {
                    HSSFSheet sheet2 = wb2.getSheetAt(i);
                    DrawingAggregateInfo info2 = DrawingAggregateInfo.get(sheet2);
                    byte[] dgBytes1 = info1.getRawBytes();
                    byte[] dgBytes2 = info2.getRawBytes();
                    assertEquals("different size of drawing data before and after save", dgBytes1.length, dgBytes2.length);
                    assertTrue("drawing data ("+dgBytes1.length+" bytes) before and after save is different.",
                            Arrays.equals(dgBytes1, dgBytes2));
                }
            }
            wb2.close();
        }
    }

    /**
     * test that we correctly read and write drawing aggregates
     *  in all .xls files in POI test samples
     */
    @Test
    public void testAllTestSamples() throws IOException {
        File[] xls = new File(System.getProperty("POI.testdata.path"), "spreadsheet").listFiles(
                new FilenameFilter() {
                    @Override
                    public boolean accept(File dir, String name) {
                        return name.endsWith(".xls");
                    }
                }
        );
        assertNotNull(
                "Need to find files in test-data path, had path: " + new File(System.getProperty("POI.testdata.path"), "spreadsheet"), 
                xls);
        for(File file : xls) {
            HSSFWorkbook wb;
            try {
                wb = HSSFTestDataSamples.openSampleWorkbook(file.getName());
            } catch (Throwable e){
                // don't bother about files we cannot read - they are different bugs
                // System.out.println("[WARN]  Cannot read " + file.getName());
                continue;
            }
            try {
                assertWriteAndReadBack(wb);
            } catch (Exception e) {
                String filename = file.getName();
                System.out.println("Drawing Aggregate re-write test failed for " + filename);
                e.printStackTrace(System.out);
                
                fail("Error when writing and re-reading workbook " + filename + "\n" + e);
            }
            wb.close();
        }
    }

    /**
     * when reading incomplete data ensure that the serialized bytes match the source
     */
    @Test
    public void testIncompleteData() throws IOException {
        //EscherDgContainer and EscherSpgrContainer length exceeds the actual length of the data
        String data =
            "H4sIAAAAAAAAAGWOOw7CQAxE32YTsSRIWSgQJSUloqSm5g4ICURBg+iBK3APGi6wBWeh9xGYbEps2WON"+
            "P+OWwpYeIsECMFC8S2jxNvMdlrYQ5xha5N8K6ryHdir6+avwOer5l3hq2NPYWuWN0n1dIsgfbgshuSj1"+
            "+2eqbvLdxQ0ndhy5KJ/lc1ZZK9okY5X/gSbrHZTH1vE/ozagTcwAAAA=";
        byte[] dgBytes = decompress(data);

        List<EscherRecord> records = new ArrayList<>();
        EscherRecordFactory recordFactory = new DefaultEscherRecordFactory();
        int pos = 0;
        while (pos < dgBytes.length) {
            EscherRecord r = recordFactory.createRecord(dgBytes, pos);
            int bytesRead = r.fillFields(dgBytes, pos, recordFactory);
            records.add(r);
            pos += bytesRead;
        }
        assertEquals("data was not fully read", dgBytes.length, pos);

        // serialize to byte array
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for(EscherRecord r : records) {
            out.write(r.serialize());
        }
        assertEquals(HexDump.toHex(dgBytes, 10), HexDump.toHex(out.toByteArray(), 10));
    }

    /**
     * TODO: figure out why it fails with "RecordFormatException: 0 bytes written but getRecordSize() reports 80"
     */
    @Test
    public void testFailing() throws IOException {
        HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("15573.xls");
        HSSFSheet sh = wb.getSheetAt(0);
        sh.getDrawingPatriarch();

        HSSFTestDataSamples.writeOutAndReadBack(wb).close();
        wb.close();
    }

    private static byte[] toByteArray(List<RecordBase> records) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for (RecordBase rb : records) {
            Record r = (Record) rb;
            try {
                out.write(r.serialize());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return out.toByteArray();
    }

    @Test
    public void testSolverContainerMustBeSavedDuringSerialization() throws IOException{
        HSSFWorkbook wb1 = HSSFTestDataSamples.openSampleWorkbook("SolverContainerAfterSPGR.xls");
        HSSFSheet sh = wb1.getSheetAt(0);
        InternalSheet ish = HSSFTestHelper.getSheetForTest(sh);
        List<RecordBase> records = ish.getRecords();
        // records to be aggregated
        List<RecordBase> dgRecords = records.subList(19, 22);
        byte[] dgBytes = toByteArray(dgRecords);
        sh.getDrawingPatriarch();
        EscherAggregate agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid);
        assertEquals(agg.getEscherRecords().get(0).getChildRecords().size(), 3);
        assertEquals(agg.getEscherRecords().get(0).getChild(2).getRecordId(), EscherContainerRecord.SOLVER_CONTAINER);
        HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb1);
        wb1.close();
        sh = wb2.getSheetAt(0);
        sh.getDrawingPatriarch();
        ish = HSSFTestHelper.getSheetForTest(sh);
        agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid);
        assertEquals(agg.getEscherRecords().get(0).getChildRecords().size(), 3);
        assertEquals(agg.getEscherRecords().get(0).getChild(2).getRecordId(), EscherContainerRecord.SOLVER_CONTAINER);


        // collect drawing records into a byte buffer.
        agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid);
        byte[] dgBytesAfterSave = agg.serialize();
        assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length);
        assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave));
        wb2.close();
    }

    @Test
    public void testFileWithTextbox() throws IOException{
        HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("text.xls");
        HSSFSheet sh = wb.getSheetAt(0);
        InternalSheet ish = HSSFTestHelper.getSheetForTest(sh);
        List<RecordBase> records = ish.getRecords();
        // records to be aggregated
        List<RecordBase> dgRecords = records.subList(19, 23);
        byte[] dgBytes = toByteArray(dgRecords);
        sh.getDrawingPatriarch();

        // collect drawing records into a byte buffer.
        EscherAggregate agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid);
        byte[] dgBytesAfterSave = agg.serialize();
        assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length);
        assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave));
        wb.close();
    }

    @Test
    public void testFileWithCharts() throws IOException {
        HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("49581.xls");
        HSSFSheet sh = wb.getSheetAt(0);
        InternalSheet ish = HSSFTestHelper.getSheetForTest(sh);
        List<RecordBase> records = ish.getRecords();
        // records to be aggregated
        List<RecordBase> dgRecords = records.subList(19, 21);
        byte[] dgBytes = toByteArray(dgRecords);
        sh.getDrawingPatriarch();

        // collect drawing records into a byte buffer.
        EscherAggregate agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid);
        byte[] dgBytesAfterSave = agg.serialize();
        assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length);
        for (int i=0; i< dgBytes.length; i++){
            if (dgBytes[i] != dgBytesAfterSave[i]){
                System.out.println("pos = " + i);
            }
        }
        assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave));
        wb.close();
    }

    /**
     * test reading drawing aggregate from a test file from Bugzilla 45129
     */
    @Test
    public void test45129() throws IOException {
        HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("45129.xls");
        HSSFSheet sh = wb.getSheetAt(0);

        InternalWorkbook iworkbook = HSSFTestHelper.getWorkbookForTest(wb);
        InternalSheet isheet = HSSFTestHelper.getSheetForTest(sh);

        List<RecordBase> records = isheet.getRecords();

        // the sheet's drawing is not aggregated
        assertEquals("wrong size of sheet records stream", 394, records.size());
        // the last record before the drawing block
        assertTrue(
                "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(),
                records.get(18) instanceof RowRecordsAggregate);

        // records to be aggregated
        List<RecordBase> dgRecords = records.subList(19, 389);
        // collect drawing records into a byte buffer.
        byte[] dgBytes = toByteArray(dgRecords);

        for (RecordBase rb : dgRecords) {
            Record r = (Record) rb;
            short sid = r.getSid();
            // we expect that drawing block consists of either
            // DrawingRecord or ContinueRecord or ObjRecord or TextObjectRecord
            assertTrue(
                    sid == DrawingRecord.sid ||
                            sid == ContinueRecord.sid ||
                            sid == ObjRecord.sid ||
                            sid == TextObjectRecord.sid);
        }

        // the first record after the drawing block
        assertTrue(
                "records.get(389) is expected to be Window2",
                records.get(389) instanceof WindowTwoRecord);

        // aggregate drawing records.
        // The subrange [19, 388] is expected to be replaced with a EscherAggregate object
        DrawingManager2 drawingManager = iworkbook.findDrawingGroup();
        int loc = isheet.aggregateDrawingRecords(drawingManager, false);
        EscherAggregate agg = (EscherAggregate) records.get(loc);

        assertEquals("wrong size of the aggregated sheet records stream", 25, records.size());
        assertTrue(
                "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(),
                records.get(18) instanceof RowRecordsAggregate);
        assertTrue("records.get(19) is expected to be EscherAggregate but was " + records.get(19).getClass().getSimpleName(),
                records.get(19) instanceof EscherAggregate);
        assertTrue("records.get(20) is expected to be Window2 but was " + records.get(20).getClass().getSimpleName(),
                records.get(20) instanceof WindowTwoRecord);

        byte[] dgBytesAfterSave = agg.serialize();
        assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length);
        assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave));
        wb.close();
    }

    /**
     * Try to check file with such record sequence
     * ...
     * DrawingRecord
     * ContinueRecord
     * ObjRecord | TextObjRecord
     * ...
     */
    @Test
    public void testSerializeDrawingBigger8k() throws IOException {
        HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("DrawingContinue.xls");
        InternalWorkbook iworkbook = HSSFTestHelper.getWorkbookForTest(wb);
        HSSFSheet sh = wb.getSheetAt(0);
        InternalSheet isheet = HSSFTestHelper.getSheetForTest(sh);


        List<RecordBase> records = isheet.getRecords();

        // the sheet's drawing is not aggregated
        assertEquals("wrong size of sheet records stream", 32, records.size());
        // the last record before the drawing block
        assertTrue(
                "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(),
                records.get(18) instanceof RowRecordsAggregate);

        // records to be aggregated
        List<RecordBase> dgRecords = records.subList(19, 26);
        for (RecordBase rb : dgRecords) {
            Record r = (Record) rb;
            short sid = r.getSid();
            // we expect that drawing block consists of either
            // DrawingRecord or ContinueRecord or ObjRecord or TextObjectRecord
            assertTrue(
                    sid == DrawingRecord.sid ||
                            sid == ContinueRecord.sid ||
                            sid == ObjRecord.sid ||
                            sid == NoteRecord.sid ||
                            sid == TextObjectRecord.sid);
        }
        // collect drawing records into a byte buffer.
        byte[] dgBytes = toByteArray(dgRecords);

        // the first record after the drawing block
        assertTrue(
                "records.get(26) is expected to be Window2",
                records.get(26) instanceof WindowTwoRecord);

        // aggregate drawing records.
        // The subrange [19, 38] is expected to be replaced with a EscherAggregate object
        DrawingManager2 drawingManager = iworkbook.findDrawingGroup();
        int loc = isheet.aggregateDrawingRecords(drawingManager, false);
        EscherAggregate agg = (EscherAggregate) records.get(loc);

        assertEquals("wrong size of the aggregated sheet records stream", 26, records.size());
        assertTrue(
                "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(),
                records.get(18) instanceof RowRecordsAggregate);
        assertTrue("records.get(19) is expected to be EscherAggregate but was " + records.get(19).getClass().getSimpleName(),
                records.get(19) instanceof EscherAggregate);
        assertTrue("records.get(20) is expected to be Window2 but was " + records.get(20).getClass().getSimpleName(),
                records.get(20) instanceof WindowTwoRecord);

        byte[] dgBytesAfterSave = agg.serialize();
        assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length);
        assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave));
        wb.close();
    }


    @Test
    public void testSerializeDrawingBigger8k_noAggregation() throws IOException {
        HSSFWorkbook wb1 = HSSFTestDataSamples.openSampleWorkbook("DrawingContinue.xls");

        InternalSheet isheet = HSSFTestHelper.getSheetForTest(wb1.getSheetAt(0));
        List<RecordBase> records = isheet.getRecords();

        HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb1);
        wb1.close();
        InternalSheet isheet2 = HSSFTestHelper.getSheetForTest(wb2.getSheetAt(0));
        List<RecordBase> records2 = isheet2.getRecords();

        assertEquals(records.size(), records2.size());
        for (int i = 0; i < records.size(); i++) {
            RecordBase r1 = records.get(i);
            RecordBase r2 = records2.get(i);
            assertTrue(r1.getClass() == r2.getClass());
            assertEquals(r1.getRecordSize(), r2.getRecordSize());
            if (r1 instanceof Record) {
                assertEquals(((Record) r1).getSid(), ((Record) r2).getSid());
                assertArrayEquals(((Record) r1).serialize(), ((Record) r2).serialize());
            }
        }
        wb2.close();
    }

    @Test
    public void testSerializeDrawingWithComments() throws IOException {
        HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("DrawingAndComments.xls");
        HSSFSheet sh = wb.getSheetAt(0);
        InternalWorkbook iworkbook = HSSFTestHelper.getWorkbookForTest(wb);
        InternalSheet isheet = HSSFTestHelper.getSheetForTest(sh);

        List<RecordBase> records = isheet.getRecords();

        // the sheet's drawing is not aggregated
        assertEquals("wrong size of sheet records stream", 46, records.size());
        // the last record before the drawing block
        assertTrue(
                "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(),
                records.get(18) instanceof RowRecordsAggregate);

        // records to be aggregated
        List<RecordBase> dgRecords = records.subList(19, 39);
        for (RecordBase rb : dgRecords) {
            Record r = (Record) rb;
            short sid = r.getSid();
            // we expect that drawing block consists of either
            // DrawingRecord or ContinueRecord or ObjRecord or TextObjectRecord
            assertTrue(
                    sid == DrawingRecord.sid ||
                            sid == ContinueRecord.sid ||
                            sid == ObjRecord.sid ||
                            sid == NoteRecord.sid ||
                            sid == TextObjectRecord.sid);
        }
        // collect drawing records into a byte buffer.
        byte[] dgBytes = toByteArray(dgRecords);

        // the first record after the drawing block
        assertTrue(
                "records.get(39) is expected to be Window2",
                records.get(39) instanceof WindowTwoRecord);

        // aggregate drawing records.
        // The subrange [19, 38] is expected to be replaced with a EscherAggregate object
        DrawingManager2 drawingManager = iworkbook.findDrawingGroup();
        int loc = isheet.aggregateDrawingRecords(drawingManager, false);
        EscherAggregate agg = (EscherAggregate) records.get(loc);

        assertEquals("wrong size of the aggregated sheet records stream", 27, records.size());
        assertTrue(
                "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(),
                records.get(18) instanceof RowRecordsAggregate);
        assertTrue("records.get(19) is expected to be EscherAggregate but was " + records.get(19).getClass().getSimpleName(),
                records.get(19) instanceof EscherAggregate);
        assertTrue("records.get(20) is expected to be Window2 but was " + records.get(20).getClass().getSimpleName(),
                records.get(20) instanceof WindowTwoRecord);

        byte[] dgBytesAfterSave = agg.serialize();
        assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length);
        assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave));
        wb.close();
    }


    @Test
    public void testFileWithPictures() throws IOException {
        HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("ContinueRecordProblem.xls");
        HSSFSheet sh = wb.getSheetAt(0);

        InternalWorkbook iworkbook = HSSFTestHelper.getWorkbookForTest(wb);
        InternalSheet isheet = HSSFTestHelper.getSheetForTest(sh);

        List<RecordBase> records = isheet.getRecords();

        // the sheet's drawing is not aggregated
        assertEquals("wrong size of sheet records stream", 315, records.size());
        // the last record before the drawing block
        assertTrue(
                "records.get(21) is expected to be RowRecordsAggregate but was " + records.get(21).getClass().getSimpleName(),
                records.get(21) instanceof RowRecordsAggregate);

        // records to be aggregated
        List<RecordBase> dgRecords = records.subList(22, 300);
        for (RecordBase rb : dgRecords) {
            Record r = (Record) rb;
            short sid = r.getSid();
            // we expect that drawing block consists of either
            // DrawingRecord or ContinueRecord or ObjRecord or TextObjectRecord
            assertTrue(
                    sid == DrawingRecord.sid ||
                            sid == ContinueRecord.sid ||
                            sid == ObjRecord.sid ||
                            sid == TextObjectRecord.sid);
        }
        // collect drawing records into a byte buffer.
        byte[] dgBytes = toByteArray(dgRecords);

        // the first record after the drawing block
        assertTrue(
                "records.get(300) is expected to be Window2",
                records.get(300) instanceof WindowTwoRecord);

        // aggregate drawing records.
        // The subrange [19, 299] is expected to be replaced with a EscherAggregate object
        DrawingManager2 drawingManager = iworkbook.findDrawingGroup();
        int loc = isheet.aggregateDrawingRecords(drawingManager, false);
        EscherAggregate agg = (EscherAggregate) records.get(loc);

        assertEquals("wrong size of the aggregated sheet records stream", 38, records.size());
        assertTrue(
                "records.get(21) is expected to be RowRecordsAggregate but was " + records.get(21).getClass().getSimpleName(),
                records.get(21) instanceof RowRecordsAggregate);
        assertTrue("records.get(22) is expected to be EscherAggregate but was " + records.get(22).getClass().getSimpleName(),
                records.get(22) instanceof EscherAggregate);
        assertTrue("records.get(23) is expected to be Window2 but was " + records.get(23).getClass().getSimpleName(),
                records.get(23) instanceof WindowTwoRecord);

        byte[] dgBytesAfterSave = agg.serialize();
        assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length);
        assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave));
        wb.close();
    }

    @Test
    public void testUnhandledContinue() throws IOException {
        String data =
            "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";

        byte[] dgBytes = decompress(data);
        List<Record> dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes));
        assertEquals(20, dgRecords.size());

        short[] expectedSids = {
                DrawingRecord.sid,
                ObjRecord.sid,
                DrawingRecord.sid,
                TextObjectRecord.sid,
                DrawingRecord.sid,
                ObjRecord.sid,
                DrawingRecord.sid,
                TextObjectRecord.sid,
                DrawingRecord.sid,
                ObjRecord.sid,
                DrawingRecord.sid,
                TextObjectRecord.sid,
                DrawingRecord.sid,
                ObjRecord.sid,
                DrawingRecord.sid,
                TextObjectRecord.sid,
                ContinueRecord.sid,
                ObjRecord.sid,
                ContinueRecord.sid,
                TextObjectRecord.sid
        };
        for (int i = 0; i < expectedSids.length; i++) {
            assertEquals("unexpected record.sid and index[" + i + "]", expectedSids[i], dgRecords.get(i).getSid());
        }
        DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord());

        // create a dummy sheet consisting of our test data
        InternalSheet sheet = InternalSheet.createSheet();
        List<RecordBase> records = sheet.getRecords();
        records.clear();
        records.addAll(dgRecords);
        records.add(EOFRecord.instance);


        sheet.aggregateDrawingRecords(drawingManager, false);
        assertEquals("drawing was not fully aggregated", 2, records.size());
        assertTrue("expected EscherAggregate", records.get(0) instanceof EscherAggregate);
        assertTrue("expected EOFRecord", records.get(1) instanceof EOFRecord);
        EscherAggregate agg = (EscherAggregate) records.get(0);

        byte[] dgBytesAfterSave = agg.serialize();
        assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length);
        assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave));
    }

    @Test
    public void testUnhandledContinue2() throws IOException {
        String data =
            "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";

        byte[] dgBytes = decompress(data);
        List<Record> dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes));
        assertEquals(14, dgRecords.size());

        short[] expectedSids = {
                DrawingRecord.sid,
                ObjRecord.sid,
                DrawingRecord.sid,
                ObjRecord.sid,
                DrawingRecord.sid,
                ObjRecord.sid,
                DrawingRecord.sid,
                ObjRecord.sid,
                ContinueRecord.sid,
                ObjRecord.sid,
                ContinueRecord.sid,
                ObjRecord.sid,
                ContinueRecord.sid,
                ObjRecord.sid
        };

        for (int i = 0; i < expectedSids.length; i++) {
            assertEquals("unexpected record.sid and index[" + i + "]", expectedSids[i], dgRecords.get(i).getSid());
        }
        DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord());

        // create a dummy sheet consisting of our test data
        InternalSheet sheet = InternalSheet.createSheet();
        List<RecordBase> records = sheet.getRecords();
        records.clear();
        records.addAll(dgRecords);
        records.add(EOFRecord.instance);

        sheet.aggregateDrawingRecords(drawingManager, false);
        assertEquals("drawing was not fully aggregated", 2, records.size());
        assertTrue("expected EscherAggregate", records.get(0) instanceof EscherAggregate);
        assertTrue("expected EOFRecord", records.get(1) instanceof EOFRecord);

        EscherAggregate agg = (EscherAggregate) records.get(0);

        byte[] dgBytesAfterSave = agg.serialize();
        assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length);
        assertTrue("drawing data brefpore and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave));
    }
}
