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


namespace skyscraper8.SophtainerIO
{
	public delegate void FileStreamPartSwitchDelegate(int readSoFar, int currentOffset, int remaining);

	internal class SplitFileStream : Stream
	{
		private string baseFileName;
		private uint partSize;
		private int currentPart;
		private FileStream currentPartStream;
		public event FileStreamPartSwitchDelegate PartChangedDuringRead;
		public SplitFileStream(string basename, uint partSize = 4294967295)
		{
			this.baseFileName = basename;
			this.partSize = partSize;
			this.currentPart = -1;
			AcquireCurrentPart();
		}

		private int CountParts()
		{
			int result = 0;
			while (true)
			{
				string filename = baseFileName + "." + (66000 + result++);
				if (!File.Exists(filename))
				{
					break;
				}
			}
			return result - 1;
		}

		private bool AcquireCurrentPart()
		{
			long requiredPart = Position;
			requiredPart /= partSize;
			if (currentPartStream != null)
				currentPartStream.Position = internalPosition % partSize;
			if (requiredPart == currentPart)
				return false;

			if (currentPartStream != null)
			{
				currentPartStream.Flush();
				currentPartStream.Close();
				currentPartStream.Dispose();
			}

			string filename = baseFileName + "." + (66000 + requiredPart).ToString();
			currentPartStream = File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
			currentPartStream.Position = internalPosition % partSize;
			int oldPart = currentPart;
			currentPart = (int)requiredPart;
			return true;
		}

		public override bool CanRead
		{
			get
			{
				AcquireCurrentPart();
				return currentPartStream.CanRead;
			}
		}

		public override bool CanSeek
		{
			get
			{
				AcquireCurrentPart();
				return currentPartStream.CanSeek;
			}
		}

		public override bool CanWrite
		{
			get
			{
				AcquireCurrentPart();
				return currentPartStream.CanWrite;
			}
		}

		public override long Length
		{
			get
			{
				int totalParts = CountParts();
				if (totalParts == 1)
				{
					AcquireCurrentPart();
					return currentPartStream.Length;
				}

				if (totalParts == currentPart)
				{
					long result = 0;
					for (int i = 0; i < totalParts - 1;i++)
					{
						string filename = baseFileName + (66000 + i);
						FileInfo currentFilePart = new FileInfo(filename);
						result += currentFilePart.Length;
					}
					result += currentPartStream.Length;
					return result;
				}

				long result2 = 0;
				for (int i = 0; i < totalParts; i++)
				{
					string filename2 = baseFileName + "." + (66000 + i);
					FileInfo currentFilePart = new FileInfo(filename2);
					result2 += currentFilePart.Length;
				}
				return result2;
			}
		}

		private long internalPosition;
		public override long Position 
		{ 
			get
			{
				return internalPosition;
			}
			set
			{
				if (value == 0)
				{
					if (currentPartStream != null)
					{
						currentPartStream.Flush();
						currentPartStream.Close();
					}
					currentPartStream = null;
					currentPart = -1;
					internalPosition = 0;
					return;
				}

				long maxValue = Length;
				if (value > Length)
				{
					throw new ArgumentOutOfRangeException();
				}
				internalPosition = value;
			}
		}

		public override void Flush()
		{
			if (currentPartStream != null)
			{
				currentPartStream.Flush();
			}
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			int overallResult = 0;
			while (count > 0)
			{
				if (AcquireCurrentPart())
				{
					if (overallResult > 0)
					{
						if (PartChangedDuringRead != null)
						{
							PartChangedDuringRead(overallResult, offset, count);
						}
					}
				}
				long currentPartOffset = currentPartStream.Position;
				long currentPartRemaining = partSize - currentPartOffset;
				long readAmount = Math.Min(currentPartRemaining, count);
				int result = currentPartStream.Read(buffer, offset, (int)readAmount);
				if (result == 0)
					break;
				count -= result;
				offset += result;
				overallResult += result;
				internalPosition += result;
			}
			return overallResult;
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			switch(origin)
			{
				case SeekOrigin.Begin:
					Position = offset; 
					break;
				case SeekOrigin.Current:
					Position += offset; 
					break;
				case SeekOrigin.End:
					long maxPos = Length;
					Position = maxPos + offset;
					break;
				default:
					throw new NotImplementedException(origin.ToString());
			}
			return Position;
		}

		public override void SetLength(long value)
		{
			long currentLength = Length;
			if (value > currentLength)
			{
				Seek(currentLength, SeekOrigin.Begin);
				long neededToWrite = value - currentLength;
				byte[] blankBuffer = new byte[4096];
				while (neededToWrite > 0)
				{
					long partSize = Math.Min(blankBuffer.Length, neededToWrite);
					Write(blankBuffer, 0, (int)partSize);
					neededToWrite -= partSize;
				}
			}
			else
			{
				throw new NotSupportedException("shrink");
			}
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			while (count > 0)
			{
				AcquireCurrentPart();
				long currentPartOffset = currentPartStream.Position;
				long currentPartRemaining = partSize - currentPartOffset;
				long operationSize = Math.Min(count, currentPartRemaining);
				currentPartStream.Write(buffer, offset, (int)operationSize);
				offset += (int)operationSize;
				count -= (int)operationSize;
				internalPosition += operationSize;
			}
		}
	}
}
