// LzmaBench.cs

using System;
using System.IO;

namespace SevenZip
{
	/// <summary>
	/// LZMA Benchmark
	/// </summary>
	internal abstract class LzmaBench
	{
		const UInt32 kAdditionalSize = (6 << 20);
		const UInt32 kCompressedAdditionalSize = (1 << 10);
		const UInt32 kMaxLzmaPropSize = 10;

		class CRandomGenerator
		{
			UInt32 A1;
			UInt32 A2;
			public CRandomGenerator() { Init(); }
			public void Init() { A1 = 362436069; A2 = 521288629; }
			public UInt32 GetRnd()
			{
				return
					((A1 = 36969 * (A1 & 0xffff) + (A1 >> 16)) << 16) ^
					((A2 = 18000 * (A2 & 0xffff) + (A2 >> 16)));
			}
		};

		class CBitRandomGenerator
		{
			CRandomGenerator RG = new CRandomGenerator();
			UInt32 Value;
			int NumBits;
			public void Init()
			{
				Value = 0;
				NumBits = 0;
			}
			public UInt32 GetRnd(int numBits)
			{
				UInt32 result;
				if (NumBits > numBits)
				{
					result = Value & (((UInt32)1 << numBits) - 1);
					Value >>= numBits;
					NumBits -= numBits;
					return result;
				}
				numBits -= NumBits;
				result = (Value << numBits);
				Value = RG.GetRnd();
				result |= Value & (((UInt32)1 << numBits) - 1);
				Value >>= numBits;
				NumBits = 32 - numBits;
				return result;
			}
		};

		class CBenchRandomGenerator
		{
			CBitRandomGenerator RG = new CBitRandomGenerator();
			UInt32 Pos;
			public UInt32 BufferSize;
			public Byte[] Buffer = null;
			public CBenchRandomGenerator() { }
			public void Init() { RG.Init(); }
			public void Set(UInt32 bufferSize)
			{
				Buffer = new Byte[bufferSize];
				Pos = 0;
				BufferSize = bufferSize;
			}
			UInt32 GetRndBit() { return RG.GetRnd(1); }
			/*
			UInt32 GetLogRand(int maxLen)
			{
				UInt32 len = GetRnd() % (maxLen + 1);
				return GetRnd() & ((1 << len) - 1);
			}
			*/
			UInt32 GetLogRandBits(int numBits)
			{
				UInt32 len = RG.GetRnd(numBits);
				return RG.GetRnd((int)len);
			}
			UInt32 GetOffset()
			{
				if (GetRndBit() == 0)
					return GetLogRandBits(4);
				return (GetLogRandBits(4) << 10) | RG.GetRnd(10);
			}
			UInt32 GetLen()
			{
				if (GetRndBit() == 0)
					return RG.GetRnd(2);
				if (GetRndBit() == 0)
					return 4 + RG.GetRnd(3);
				return 12 + RG.GetRnd(4);
			}
			public void Generate()
			{
				while (Pos < BufferSize)
				{
					if (GetRndBit() == 0 || Pos < 1)
						Buffer[Pos++] = (Byte)(RG.GetRnd(8));
					else
					{
						UInt32 offset = GetOffset();
						while (offset >= Pos)
							offset >>= 1;
						offset += 1;
						UInt32 len = 2 + GetLen();
						for (UInt32 i = 0; i < len && Pos < BufferSize; i++, Pos++)
							Buffer[Pos] = Buffer[Pos - offset];
					}
				}
			}
		};

		class CrcOutStream : System.IO.Stream
		{
			public CRC CRC = new CRC();
			public void Init() { CRC.Init(); }
			public UInt32 GetDigest() { return CRC.GetDigest(); }

			public override bool CanRead { get { return false; } }
			public override bool CanSeek { get { return false; } }
			public override bool CanWrite { get { return true; } }
			public override Int64 Length { get { return 0; } }
			public override Int64 Position { get { return 0; } set { } }
			public override void Flush() { }
			public override long Seek(long offset, SeekOrigin origin) { return 0; }
			public override void SetLength(long value) { }
			public override int Read(byte[] buffer, int offset, int count) { return 0; }

			public override void WriteByte(byte b)
			{
				CRC.UpdateByte(b);
			}
			public override void Write(byte[] buffer, int offset, int count)
			{
				CRC.Update(buffer, (uint)offset, (uint)count);
			}
		};

		class CProgressInfo : ICodeProgress
		{
			public Int64 ApprovedStart;
			public Int64 InSize;
			public System.DateTime Time;
			public void Init() { InSize = 0; }
			public void SetProgress(Int64 inSize, Int64 outSize)
			{
				if (inSize >= ApprovedStart && InSize == 0)
				{
					Time = DateTime.UtcNow;
					InSize = inSize;
				}
			}
		}
		const int kSubBits = 8;

		static UInt32 GetLogSize(UInt32 size)
		{
			for (int i = kSubBits; i < 32; i++)
				for (UInt32 j = 0; j < (1 << kSubBits); j++)
					if (size <= (((UInt32)1) << i) + (j << (i - kSubBits)))
						return (UInt32)(i << kSubBits) + j;
			return (32 << kSubBits);
		}

		static UInt64 MyMultDiv64(UInt64 value, UInt64 elapsedTime)
		{
			UInt64 freq = TimeSpan.TicksPerSecond;
			UInt64 elTime = elapsedTime;
			while (freq > 1000000)
			{
				freq >>= 1;
				elTime >>= 1;
			}
			if (elTime == 0)
				elTime = 1;
			return value * freq / elTime;
		}

		static UInt64 GetCompressRating(UInt32 dictionarySize, bool isBT4,
			UInt64 elapsedTime, UInt64 size)
		{
			UInt64 numCommandsForOne;
			if (isBT4)
			{
				UInt64 t = GetLogSize(dictionarySize) - (19 << kSubBits);
				numCommandsForOne = 2000 + ((t * t * 68) >> (2 * kSubBits));
			}
			else
			{
				UInt64 t = GetLogSize(dictionarySize) - (15 << kSubBits);
				numCommandsForOne = 1500 + ((t * t * 41) >> (2 * kSubBits));
			}
			UInt64 numCommands = (UInt64)(size) * numCommandsForOne;
			return MyMultDiv64(numCommands, elapsedTime);
		}

		static UInt64 GetDecompressRating(UInt64 elapsedTime,
			UInt64 outSize, UInt64 inSize)
		{
			UInt64 numCommands = inSize * 250 + outSize * 21;
			return MyMultDiv64(numCommands, elapsedTime);
		}

		static UInt64 GetTotalRating(
			UInt32 dictionarySize,
			bool isBT4,
			UInt64 elapsedTimeEn, UInt64 sizeEn,
			UInt64 elapsedTimeDe,
			UInt64 inSizeDe, UInt64 outSizeDe)
		{
			return (GetCompressRating(dictionarySize, isBT4, elapsedTimeEn, sizeEn) +
				GetDecompressRating(elapsedTimeDe, inSizeDe, outSizeDe)) / 2;
		}

		static void PrintValue(UInt64 v)
		{
			string s = v.ToString();
			for (int i = 0; i + s.Length < 6; i++)
				System.Console.Write(" ");
			System.Console.Write(s);
		}

		static void PrintRating(UInt64 rating)
		{
			PrintValue(rating / 1000000);
			System.Console.Write(" MIPS");
		}

		static void PrintResults(
			UInt32 dictionarySize,
			bool isBT4,
			UInt64 elapsedTime,
			UInt64 size,
			bool decompressMode, UInt64 secondSize)
		{
			UInt64 speed = MyMultDiv64(size, elapsedTime);
			PrintValue(speed / 1024);
			System.Console.Write(" KB/s  ");
			UInt64 rating;
			if (decompressMode)
				rating = GetDecompressRating(elapsedTime, size, secondSize);
			else
				rating = GetCompressRating(dictionarySize, isBT4, elapsedTime, size);
			PrintRating(rating);
		}

		const string bt2 = "BT2";
		const string bt4 = "BT4";

		static public int LzmaBenchmark(Int32 numIterations, UInt32 dictionarySize, bool isBT4)
		{
			if (numIterations <= 0)
				return 0;
			if (dictionarySize < (1 << 19) && isBT4 || dictionarySize < (1 << 15))
			{
				System.Console.WriteLine("\nError: dictionary size for benchmark must be >= 19 (512 KB)");
				return 1;
			}
			System.Console.Write("\n       Compressing                Decompressing\n\n");

			Compression.LZMA.Encoder encoder = new Compression.LZMA.Encoder();
			Compression.LZMA.Decoder decoder = new Compression.LZMA.Decoder();


			CoderPropID[] propIDs = 
			{ 
				CoderPropID.DictionarySize,
				CoderPropID.MatchFinder  
			};
			object[] properties = 
			{
				(Int32)(dictionarySize),
				isBT4 ? bt4: bt2
			};

			UInt32 kBufferSize = dictionarySize + kAdditionalSize;
			UInt32 kCompressedBufferSize = (kBufferSize / 2) + kCompressedAdditionalSize;

			encoder.SetCoderProperties(propIDs, properties);
			System.IO.MemoryStream propStream = new System.IO.MemoryStream();
			encoder.WriteCoderProperties(propStream);
			byte[] propArray = propStream.ToArray();

			CBenchRandomGenerator rg = new CBenchRandomGenerator();

			rg.Init();
			rg.Set(kBufferSize);
			rg.Generate();
			CRC crc = new CRC();
			crc.Init();
			crc.Update(rg.Buffer, 0, rg.BufferSize);

			CProgressInfo progressInfo = new CProgressInfo();
			progressInfo.ApprovedStart = dictionarySize;

			UInt64 totalBenchSize = 0;
			UInt64 totalEncodeTime = 0;
			UInt64 totalDecodeTime = 0;
			UInt64 totalCompressedSize = 0;

			MemoryStream inStream = new MemoryStream(rg.Buffer, 0, (int)rg.BufferSize);
			MemoryStream compressedStream = new MemoryStream((int)kCompressedBufferSize);
			CrcOutStream crcOutStream = new CrcOutStream();
			for (Int32 i = 0; i < numIterations; i++)
			{
				progressInfo.Init();
				inStream.Seek(0, SeekOrigin.Begin);
				compressedStream.Seek(0, SeekOrigin.Begin);
				encoder.Code(inStream, compressedStream, -1, -1, progressInfo);
				TimeSpan sp2 = DateTime.UtcNow - progressInfo.Time;
				UInt64 encodeTime = (UInt64)sp2.Ticks;

				long compressedSize = compressedStream.Position;
				if (progressInfo.InSize == 0)
					throw (new Exception("Internal ERROR 1282"));

				UInt64 decodeTime = 0;
				for (int j = 0; j < 2; j++)
				{
					compressedStream.Seek(0, SeekOrigin.Begin);
					crcOutStream.Init();

					decoder.SetDecoderProperties(propArray);
					UInt64 outSize = kBufferSize;
					System.DateTime startTime = DateTime.UtcNow;
					decoder.Code(compressedStream, crcOutStream, 0, (Int64)outSize, null);
					TimeSpan sp = (DateTime.UtcNow - startTime);
					decodeTime = (ulong)sp.Ticks;
					if (crcOutStream.GetDigest() != crc.GetDigest())
						throw (new Exception("CRC Error"));
				}
				UInt64 benchSize = kBufferSize - (UInt64)progressInfo.InSize;
				PrintResults(dictionarySize, isBT4, encodeTime, benchSize, false, 0);
				System.Console.Write("     ");
				PrintResults(dictionarySize, isBT4, decodeTime, kBufferSize, true, (ulong)compressedSize);
				System.Console.WriteLine();

				totalBenchSize += benchSize;
				totalEncodeTime += encodeTime;
				totalDecodeTime += decodeTime;
				totalCompressedSize += (ulong)compressedSize;
			}
			System.Console.WriteLine("---------------------------------------------------");
			PrintResults(dictionarySize, isBT4, totalEncodeTime, totalBenchSize, false, 0);
			System.Console.Write("     ");
			PrintResults(dictionarySize, isBT4, totalDecodeTime,
					kBufferSize * (UInt64)numIterations, true, totalCompressedSize);
			System.Console.WriteLine("    Average");
			return 0;
		}
	}
}
