// distribution boxbackup-0.11.1 (svn version: 2821_2827)
// Box Backup, http://www.boxbackup.org/
// 
// Copyright (c) 2003-2010, Ben Summers and contributors.
// All rights reserved.
// 
// Note that this project uses mixed licensing. Any file with this license
// attached, or where the code LICENSE-DUAL appears on the first line, falls
// under this license. See the file COPYING.txt for more information.
// 
// This file is dual licensed. You may use and distribute it providing that you
// comply EITHER with the terms of the BSD license, OR the GPL license. It is
// not necessary to comply with both licenses, only one.
// 
// The BSD license option follows:
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  
// 3. Neither the name of the Box Backup nor the names of its contributors may
//    be used to endorse or promote products derived from this software without
//    specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// [http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29]
// 
// The GPL license option follows:
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
// 
// [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html#SEC4]
// --------------------------------------------------------------------------
//
// File
//		Name:    testcompress.cpp
//		Purpose: Test lib/compress
//		Created: 5/12/03
//
// --------------------------------------------------------------------------

#include "Box.h"

#include <stdio.h>
#include <string.h>

#include "Test.h"
#include "Compress.h"
#include "CompressStream.h"
#include "CollectInBufferStream.h"

#include "MemLeakFindOn.h"

#define DATA_SIZE			(1024*128+103)
#define CHUNK_SIZE			2561
#define DECOMP_CHUNK_SIZE	3

// Stream for testing
class CopyInToOutStream : public IOStream
{
public:
	CopyInToOutStream() : currentBuffer(0) {buffers[currentBuffer].SetForReading();}
	~CopyInToOutStream() {}
	int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite)
	{
		if(buffers[currentBuffer].StreamDataLeft())
		{
			return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout);
		}
		
		// Swap buffers?
		if(buffers[(currentBuffer + 1) & 1].GetSize() > 0)
		{
			buffers[currentBuffer].Reset();
			currentBuffer = (currentBuffer + 1) & 1;
			buffers[currentBuffer].SetForReading();
			return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout);		
		}

		return 0;
	}
	void Write(const void *pBuffer, int NBytes)
	{
		buffers[(currentBuffer + 1) & 1].Write(pBuffer, NBytes);
	}
	bool StreamDataLeft()
	{
		return buffers[currentBuffer].StreamDataLeft() || buffers[(currentBuffer + 1) % 1].GetSize() > 0;
	}
	bool StreamClosed()
	{
		return false;
	}
	int currentBuffer;
	CollectInBufferStream buffers[2];
};

// Test stream based interface
int test_stream()
{
	// Make a load of compressible data to compress
	CollectInBufferStream source;
	uint16_t data[1024];
	for(int x = 0; x < 1024; ++x)
	{
		data[x] = x;
	}
	for(int x = 0; x < (32*1024); ++x)
	{
		source.Write(data, (x % 1024) * 2);
	}
	source.SetForReading();

	// Straight compress from one stream to another
	{
		CollectInBufferStream *poutput = new CollectInBufferStream;
		CompressStream compress(poutput, true /* take ownership */, false /* read */, true /* write */);
		
		source.CopyStreamTo(compress);
		compress.Close();
		poutput->SetForReading();
		
		// Check sizes
		TEST_THAT(poutput->GetSize() < source.GetSize());
		BOX_TRACE("compressed size = " << poutput->GetSize() <<
			", source size = " << source.GetSize());
		
		// Decompress the data
		{
			CollectInBufferStream decompressed;
			CompressStream decompress(poutput, false /* don't take ownership */, true /* read */, false /* write */);
			decompress.CopyStreamTo(decompressed);
			decompress.Close();
			
			TEST_THAT(decompressed.GetSize() == source.GetSize());
			TEST_THAT(::memcmp(decompressed.GetBuffer(), source.GetBuffer(), decompressed.GetSize()) == 0);
		}
		
		// Don't delete poutput, let mem leak testing ensure it's deleted.
	}

	// Set source to the beginning
	source.Seek(0, IOStream::SeekType_Absolute);

	// Test where the same stream compresses and decompresses, should be fun!
	{
		CollectInBufferStream output;
		CopyInToOutStream copyer;
		CompressStream compress(&copyer, false /* no ownership */, true, true);

		bool done = false;
		int count = 0;
		int written = 0;
		while(!done)
		{
			++count;
			bool do_sync = (count % 256) == 0;
			uint8_t buffer[4096];
			int r = source.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite);
			if(r == 0)
			{
				done = true;
				compress.Close();
			}
			else
			{
				compress.Write(buffer, r);
				written += r;
				if(do_sync)
				{
					compress.WriteAllBuffered();
				}
			}

			int r2 = 0;
			do
			{
				r2 = compress.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite);
				if(r2 > 0)
				{
					output.Write(buffer, r2);
				}
			} while(r2 > 0);
			if(do_sync && r != 0)
			{
				// Check that everything is synced
				TEST_THAT(output.GetSize() == written);
				TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0);
			}
		}
		output.SetForReading();
		
		// Test that it's the same
		TEST_THAT(output.GetSize() == source.GetSize());
		TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0);
	}

	return 0;
}

// Test basic interface
int test(int argc, const char *argv[])
{
	// Bad data to compress!
	char *data = (char *)malloc(DATA_SIZE);
	for(int l = 0; l < DATA_SIZE; ++l)
	{
		data[l] = l*23;
	}
	
	// parameters about compression	
	int maxOutput = Compress_MaxSizeForCompressedData(DATA_SIZE);
	TEST_THAT(maxOutput >= DATA_SIZE);

	char *compressed = (char *)malloc(maxOutput);
	int compressedSize = 0;
	
	// Do compression, in small chunks
	{
		Compress<true> compress;
		
		int in_loc = 0;
		while(!compress.OutputHasFinished())
		{
			int ins = DATA_SIZE - in_loc;
			if(ins > CHUNK_SIZE) ins = CHUNK_SIZE;
			
			if(ins == 0)
			{
				compress.FinishInput();
			}
			else
			{
				compress.Input(data + in_loc, ins);
			}
			in_loc += ins;
			
			// Get output data
			int s = 0;
			do
			{
				TEST_THAT(compressedSize < maxOutput);
				s = compress.Output(compressed + compressedSize, maxOutput - compressedSize);
				compressedSize += s;
			} while(s > 0);
		}
	}
	
	// a reasonable test, especially given the compressability of the input data.
	TEST_THAT(compressedSize < DATA_SIZE);

	// decompression
	char *decompressed = (char*)malloc(DATA_SIZE * 2);
	int decomp_size = 0;
	{	
		Compress<false> decompress;
		
		int in_loc = 0;
		while(!decompress.OutputHasFinished())
		{
			int ins = compressedSize - in_loc;
			if(ins > DECOMP_CHUNK_SIZE) ins = DECOMP_CHUNK_SIZE;
			
			if(ins == 0)
			{
				decompress.FinishInput();
			}
			else
			{
				decompress.Input(compressed + in_loc, ins);
			}
			in_loc += ins;
			
			// Get output data
			int s = 0;
			do
			{
				TEST_THAT(decomp_size <= DATA_SIZE);
				s = decompress.Output(decompressed + decomp_size, (DATA_SIZE*2) - decomp_size);
				decomp_size += s;
			} while(s > 0);
		}
	}
	
	TEST_THAT(decomp_size == DATA_SIZE);
	TEST_THAT(::memcmp(data, decompressed, DATA_SIZE) == 0);

	::free(data);
	::free(compressed);
	::free(decompressed);
	
	return test_stream();
}
