using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper8.Skyscraper.Math
{
	internal class EntropyCalculatorStream : Stream
	{
		public override void Flush()
		{
			//no actual writes done, so nothing to flush.
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			//this is a sink, so nothing to read here.
			throw new NotSupportedException();
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			if (offset == 0)
				return 0;

			//this is a sink, so we can't do actual seeks
			throw new NotSupportedException();
		}

		public override void SetLength(long value)
		{
			//TODO: allow this later, although I have no idea why one would actually want this.
			throw new NotSupportedException();
		}

		private double _entropy;
		private long[] freq;
		private long internalPosition;
		public override void Write(byte[] buffer, int offset, int count)
		{
			//Count the bytes
			if (freq == null)
				freq = new long[byte.MaxValue + 1];
			for (int i = 0; i < count; i++)
				freq[buffer[i + offset]]++;
			internalPosition += count;


			//Actually calculate the entropy here.
			double newEntropy = 0.0;
			for (int i = 0; i < freq.Length; i++)
			{
				double p_i = (double)freq[i] / (double)internalPosition;
				if (p_i > 0)
					newEntropy -= p_i * System.Math.Log2(p_i);
			}

			//Raise events if we feel like it
			if (newEntropy != Entropy)
			{
				double oldEntropy = Entropy;
				double oldPercentage = Percentage;
				double newPercentage = (newEntropy / 8.0) * 100.0;
				if (newEntropy > Entropy)
					EntropyRising?.Invoke(oldEntropy, newEntropy, oldPercentage, newPercentage);
				else if (newEntropy < Entropy)
					EntropyFalling?.Invoke(oldEntropy, newEntropy, oldPercentage, newPercentage);
			}

			_entropy = newEntropy;
		}

		public override bool CanRead => false;
		public override bool CanSeek => false;
		public override bool CanWrite => true;
		public override long Length => internalPosition;
		public override long Position
		{
			get
			{
				return internalPosition;
			}
			set
			{
				throw new NotSupportedException();
			}
		}
		public double Entropy
		{
			get
			{
				return _entropy;
			}
		}

		public double Percentage
		{
			get
			{
				return (Entropy / 8.0) * 100.0;
			}
		}

		public delegate void EntropyCallback(double oldEntropy, double newEntropy, double oldPercentage, double newPercentage);

		public event EntropyCallback EntropyRising;
		public event EntropyCallback EntropyFalling;
	}
}
