// <copyright company="APX Labs, Inc.">
//     Copyright (c) APX Labs, Inc. All rights reserved.
// </copyright>
//
//   Licensed 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.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Java.Interop;

namespace ApxLabs.FastAndroidCamera
{
	/// <summary>
	/// A wrapper around a Java array that reads elements directly from the pointer instead of through expensive JNI calls.
	/// </summary>
	public sealed class FastJavaByteArray : IList<byte>, IDisposable
	{
		private JniObjectReference _javaRef;

		#region Constructors

		/// <summary>
		/// Creates a new FastJavaByteArray with the given number of bytes reserved.
		/// </summary>
		/// <param name="length">Number of bytes to reserve</param>
		public FastJavaByteArray(int length)
		{
			if (length <= 0)
				throw new ArgumentOutOfRangeException();

			JniObjectReference localRef = JniEnvironment.Arrays.NewByteArray(length);
			if (!localRef.IsValid)
				throw new OutOfMemoryException();

			// Retain a global reference to the byte array.
			_javaRef = localRef.NewGlobalRef();
			Count = length;

			bool isCopy = false;
			unsafe
			{
				// Get the pointer to the byte array using the global Handle
				Raw = (byte*)JniEnvironment.Arrays.GetByteArrayElements(_javaRef, &isCopy);
			}
		}

		/// <summary>
		/// Creates a FastJavaByteArray wrapper around an existing Java/JNI byte array
		/// </summary>
		/// <param name="handle">Native Java array handle</param>
		/// <param name="readOnly">Whether to consider this byte array read-only</param>
		public FastJavaByteArray(IntPtr handle, bool readOnly = true)
		{
			if (handle == IntPtr.Zero)
				throw new ArgumentNullException("handle");

			IsReadOnly = readOnly;

			// Retain a global reference to the byte array.
			_javaRef = new JniObjectReference(handle).NewGlobalRef();
			Count = JniEnvironment.Arrays.GetArrayLength(_javaRef);

			bool isCopy = false;
			unsafe
			{
				// Get a pinned pointer to the byte array using the global Handle
				Raw = (byte*)JniEnvironment.Arrays.GetByteArrayElements(_javaRef, &isCopy);
			}
		}

		#endregion

		#region Dispose Pattern

		/// <summary>
		/// Releases unmanaged resources and performs other cleanup operations before the
		/// <see cref="T:ApxLabs.FastAndroidCamera.FastJavaByteArray"/> is reclaimed by garbage collection.
		/// </summary>
		~FastJavaByteArray()
		{
			Dispose(false);
		}

		/// <summary>
		/// Releases all resource used by the <see cref="T:ApxLabs.FastAndroidCamera.FastJavaByteArray"/> object.
		/// </summary>
		/// <remarks>Call <see cref="Dispose"/> when you are finished using the
		/// <see cref="T:ApxLabs.FastAndroidCamera.FastJavaByteArray"/>. The <see cref="Dispose"/> method leaves the
		/// <see cref="T:ApxLabs.FastAndroidCamera.FastJavaByteArray"/> in an unusable state. After calling
		/// <see cref="Dispose"/>, you must release all references to the
		/// <see cref="T:ApxLabs.FastAndroidCamera.FastJavaByteArray"/> so the garbage collector can reclaim the memory that
		/// the <see cref="T:ApxLabs.FastAndroidCamera.FastJavaByteArray"/> was occupying.</remarks>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		private void Dispose(bool disposing)
		{
			if (!_javaRef.IsValid)
				return;

			unsafe
			{
				// tell Java that we're done with this array
				JniEnvironment.Arrays.ReleaseByteArrayElements(_javaRef, (sbyte*)Raw, JniReleaseArrayElementsMode.Default);
			}

			if (disposing)
			{
				JniObjectReference.Dispose(ref _javaRef);
			}
		}

		#endregion

		#region IList<byte> Properties

		/// <summary>
		/// Count of bytes
		/// </summary>
		public int Count { get; private set; }

		/// <summary>
		/// Gets a value indicating whether this byte array is read only.
		/// </summary>
		/// <value><c>true</c> if read only; otherwise, <c>false</c>.</value>
		public bool IsReadOnly
		{
			get;
			private set;
		}

		/// <summary>
		/// Indexer
		/// </summary>
		/// <param name="index">Index of byte</param>
		/// <returns>Byte at the given index</returns>
		public byte this[int index]
		{
			get
			{
				if (index < 0 || index >= Count)
				{
					throw new ArgumentOutOfRangeException();
				}
				byte retval;
				unsafe
				{
					retval = Raw[index];
				}
				return retval;
			}
			set
			{
				if (IsReadOnly)
				{
					throw new NotSupportedException("This FastJavaByteArray is read-only");
				}

				if (index < 0 || index >= Count)
				{
					throw new ArgumentOutOfRangeException();
				}
				unsafe
				{
					Raw[index] = value;
				}
			}
		}

		#endregion

		#region IList<byte> Methods

		/// <summary>
		/// Adds a single byte to the list. Not supported
		/// </summary>
		/// <param name="item">byte to add</param>
		public void Add(byte item)
		{
			throw new NotSupportedException("FastJavaByteArray is fixed length");
		}

		/// <summary>
		/// Not supported
		/// </summary>
		public void Clear()
		{
			throw new NotSupportedException("FastJavaByteArray is fixed length");
		}

		/// <summary>
		/// Returns true if the item is found int he array
		/// </summary>
		/// <param name="item">Item to find</param>
		/// <returns>True if the item is found</returns>
		public bool Contains(byte item)
		{
			return IndexOf(item) >= 0;
		}

		/// <summary>
		/// Copies the contents of the FastJavaByteArray into a byte array
		/// </summary>
		/// <param name="array">The array to copy to.</param>
		/// <param name="arrayIndex">The zero-based index into the destination array where CopyTo should start.</param>
		public void CopyTo(byte[] array, int arrayIndex)
		{
			unsafe
			{
				Marshal.Copy(new IntPtr(Raw), array, arrayIndex, Math.Min(Count, array.Length - arrayIndex));
			}
		}

		/// <summary>
		/// Retreives enumerator
		/// </summary>
		/// <returns>Enumerator</returns>
		[DebuggerHidden]
		public IEnumerator<byte> GetEnumerator()
		{
			return new FastJavaByteArrayEnumerator(this);
		}

		/// <summary>
		/// Retreives enumerator
		/// </summary>
		/// <returns>Enumerator</returns>
		[DebuggerHidden]
		IEnumerator IEnumerable.GetEnumerator()
		{
			return new FastJavaByteArrayEnumerator(this);
		}

		/// <summary>
		/// Gets the first index of the given value
		/// </summary>
		/// <param name="item">Item to search for</param>
		/// <returns>Index of found item</returns>
		public int IndexOf(byte item)
		{
			for (int i = 0; i < Count; ++i)
			{
				byte current;
				unsafe
				{
					current = Raw[i];
				}
				if (current == item)
					return i;
			}
			return -1;
		}

		/// <summary>
		/// Not supported
		/// </summary>
		/// <param name="index"></param>
		/// <param name="item"></param>
		public void Insert(int index, byte item)
		{
			throw new NotSupportedException("FastJavaByteArray is fixed length");
		}

		/// <summary>
		/// Not supported
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		public bool Remove(byte item)
		{
			throw new NotSupportedException("FastJavaByteArray is fixed length");
		}

		/// <summary>
		/// Not supported
		/// </summary>
		/// <param name="index"></param>
		public void RemoveAt(int index)
		{
			throw new NotSupportedException("FastJavaByteArray is fixed length");
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Gets the raw pointer to the underlying data.
		/// </summary>
		public unsafe byte* Raw { get; private set; }

		/// <summary>
		/// Gets the handle of the Java reference to the array.
		/// </summary>
		/// <value>The handle.</value>
		public IntPtr Handle
		{
			get { return _javaRef.Handle; }
		}

		#endregion
	}
}