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

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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.poifs.common.POIFSConstants;
import org.apache.poi.poifs.filesystem.POIFSStream;
import org.apache.poi.poifs.storage.HeaderBlock;
import org.apache.poi.poifs.storage.RawDataUtil;
import org.junit.Test;

/**
 * Class to test PropertyTable functionality
 */
public final class TestPropertyTable {

	private static class MyPOIFSStream extends POIFSStream {
		final ByteArrayOutputStream bos = new ByteArrayOutputStream();

		MyPOIFSStream() {
			super(null);
		}

		public void write(byte[] b, int off, int len) {
			bos.write(b, off, len);
		}

		@Override
		public OutputStream getOutputStream() {
			return bos;
		}
	}

	private static void confirmBlockEncoding(String expectedDataStr, PropertyTable table) throws IOException {

		byte[] expectedData = RawDataUtil.decompress(expectedDataStr);
		MyPOIFSStream stream = new MyPOIFSStream();
		try {
			table.write(stream);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		byte[] output = stream.bos.toByteArray();

		assertArrayEquals(expectedData, output);
	}

	/**
	 * Test PropertyTable
	 * <p>
	 * Running individual tests of the PropertyTable methods, which is the
	 * traditional way to write unit tests (at least for me), seems somewhat
	 * useless in this case. Of greater relevance: if one follows the normal
	 * steps of creating a PropertyTable, and then checking the output, does it
	 * make sense? In other words, more of an integration test.
	 * <p>
	 * So, the test consists of creating a PropertyTable instance, adding three
	 * DocumentProperty instances to it, and then getting the output (including
	 * the preWrite phase first), and comparing it against a real property table
	 * extracted from a file known to be acceptable to Excel.
	 */
	@Test
	public void testWriterPropertyTable() throws IOException {

		// create the PropertyTable
	   HeaderBlock   headerBlock = new HeaderBlock(POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS);
		PropertyTable table = new PropertyTable(headerBlock);

		// create three DocumentProperty instances and add them to the
		// PropertyTable
		DocumentProperty workbook = new DocumentProperty("Workbook", 0x00046777);

		workbook.setStartBlock(0);
		DocumentProperty summary1 = new DocumentProperty(SummaryInformation.DEFAULT_STREAM_NAME, 0x00001000);

		summary1.setStartBlock(0x00000234);
		DocumentProperty summary2 = new DocumentProperty(DocumentSummaryInformation.DEFAULT_STREAM_NAME, 0x00001000);

		summary2.setStartBlock(0x0000023C);
		table.addProperty(workbook);
		RootProperty root = table.getRoot();

		root.addChild(workbook);
		table.addProperty(summary1);
		root = table.getRoot();
		root.addChild(summary1);
		table.addProperty(summary2);
		root = table.getRoot();
		root.addChild(summary2);
		table.preWrite();

		final String testblock =
			"H4sIAAAAAAAAAAtiyAfCEgYFBleGPCBdxFDJQAoQY2Bl/A8FTETq+QdUC2OHA20vYshmSAK7I5sku0FAiIEJbv9/JHMJgfJ0FjDN" +
			"yhDMUMqQC4SJYL97AkMhDewmkEgJQyaQnYfHHA2g/YxAmhmIibXfBBRQAgxQ+12ANiSD3ZAKjgHS3GNBhv9tkOwHAFGXmbcAAgAA";
		confirmBlockEncoding(testblock, table);

		table.removeProperty(summary1);
		root = table.getRoot();
		root.deleteChild(summary1);
		table.preWrite();

		final String testblock2 =
			"H4sIAAAAAAAAAAtiyAfCEgYFBleGPCBdxFDJQAoQY2Bl/A8FTETq+QdUC2OHA20vYshmSAK7I5sku0FAiIEJbv9/JHMJ" +
			"gfJ0FjDNyuACtDeZoZQhlyEVHALBYHYuQyI4LDyBYmlgN4JEShgygew8JHMsgPYzAmlS7LcBBZQAhA0Ae5Y5UIABAAA=";
		// (N)POIFS only returns 384 bytes here, instead of 512
		confirmBlockEncoding(testblock2, table);

		table.addProperty(summary1);
		root = table.getRoot();
		root.addChild(summary1);
		table.preWrite();

		final String testblock3 =
			"H4sIAAAAAAAAAAtiyAfCEgYFBleGPCBdxFDJQAoQY2Bl/A8FzETq+QdUC2OHA20vYshmSAK7I5sku0FAiIEJbv9/JHMJgfJ0FjDNyu" +
			"ACtDeZoZQhlyEVHALBYHYuQyI4LDyBYmlgN4JEShgygew8JHMsyLDfhglICDBA7SfNPnSgAbSfEUiDjCTWfhMk+wEk2TJjAAIAAA==";
		confirmBlockEncoding(testblock3, table);
	}

	@Test
	public void testReadingConstructor() throws IOException {

		// first, we need the raw data blocks
		String raw_data_array =
			"H4sIAAAAAAAAAO2Z608TQRDA5wqVlreIyFMP5AMaMUAMMcYvQIVoUAivxI9HOeCkpaS9GvjmX67OzO61S1930zOpJJ2muUd" +
			"u9zfv3V4PoIAfH2z4BNd4LMIdSGQCktYfLVbEMf34/dWnzjPgggM55H9G/jnqUoQ83vHBw/Pr0LkWwKrwn4o0V7INx6iDDT" +
			"vI9eBMrMdrSDB/GM/pKOVncPYynKIHXGQH3vCQeKF1OcOrLGtCOtXKmuanhfxefdyCS5w/x5bvI72ILJczwUEN3MrdPD7l4" +
			"8fFJ01Z1/w+Ad+6x3eQRswcfqr+tjEyLvO38c4tc204Ye+U8SqQD5r/SMBPGPxjtOwU7Qv4Nuyy96+ghOcO+5984OB1iT1z" +
			"wf4o6fEfNT+QKHxTwu1vFJWqvNf8pMD+HsN+le0Oz03556FlWc5Jdad19AHeaX6vgN8LkvhvoS5FjhA9V9udAn5KwCdf6fY" +
			"Dezi7jxmgLKZYHyHLgZ+sEXnEYbtLTeZ6o/kDAj7l6rw+30RuiTPO5Qyz4QvfIT+cVyq/eQ96q/k9Aj7ZHjX+9RXX2P4hAT" +
			"9l8PeQcsk5ZnMuFLkPq+tDPGZ13wvzf7+Anzb439A26gCKWEBftKr2egn6/6CA32/wD9m/Lkd+g7PcYU8UMBeb+dwUG/mzm" +
			"h2VPwCN/X+Ax3OjDlzsu37IXIvIfybkDxr8r2jvLUY8z3GgmFOPu6t0Ho890VyWtP/HIbr/h8CMv8t5TrarLhfe8xrxnwj4" +
			"wwa/8Zqr8vA7V0IuEj8h4I+AaT/1lzJnXsA94Tr0Iu3CAv6YgD/Kdiup339lOBvIHyVNb159ik/zPRbwSdfA/ur+M8NVmGU" +
			"9bgT7z4Q1BbL8p1xJ6/MjzLwTrPz2978Ja1LIp1qp5l+RmWqVU50nr/3vt/R8lT8h5JsS8DzuuMHaH7bq3OePCPn0OyGs/0" +
			"SVaeTbQj75K6nPq/nXep/TTGaRPyfkU78O9j9busIoB3yu+erqEx59tf7MCPmm1O9/jtD2m0hrHwnZPy3kU73U17+MG8g48" +
			"l8K+VNgrv9l7v+X3HMv2uLPC/nTBn+DmarWbV4N8iIdJpH/QsifMfjbbLcby//PhfxZuO//U+OXt1TGkL8o5M+B6f9drDdZ" +
			"zZlC9i8I+aaofQ/F4ErMJhmN+fs3rgT7ni6/PX7teKnEHZ/q8Pj4+vfAzuZ+jPFzsLTxanV9eS/rL6+trKzafsE2LkPHP3T" +
			"/Pezx8evH6rj+Kd0H/sVRzp+MaX8Sfjh5t9Tm+M7nrwVhNd56fNz+063/OOPj2t9p+R3zS+9d2hnXlf9DLN27g/+E6L0E/T" +
			"/Rp/t5WseX3hnTe9uhmnh35WHLX544XEIAIAAA";

		// Fake up a header
		HeaderBlock header_block = new HeaderBlock(POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS);
		header_block.setPropertyStart(0);

		List<ByteBuffer> data_blocks = new ArrayList<>();
		try (InputStream is = new ByteArrayInputStream(RawDataUtil.decompress(raw_data_array))) {
			byte[] buf = new byte[header_block.getBigBlockSize().getBigBlockSize()];

			for (int readBytes; (readBytes = is.read(buf)) != -1; ) {
				data_blocks.add(ByteBuffer.wrap(buf.clone(), 0, readBytes));
			}
		}

      
		// get property table from the document
		PropertyTable table = new PropertyTable(header_block, data_blocks);

		assertEquals(30 * 64, table.getRoot().getSize());
		int count = 0;
		Property lastChild = null;
		for (Property p : table.getRoot()) {
			assertNotNull(p);
			lastChild = p;
			++count;
		}
		
		assertNotNull("no children found", lastChild);
		assertEquals(1, count);
		assertTrue(lastChild.isDirectory());
		count = 0;
		for (Property p : (DirectoryProperty) lastChild) {
			assertNotNull(p);
			++count;
		}
		assertEquals(35, count);
	}
}
