//
// System.Security.Cryptography.FromBase64Transform
//
// Authors:
//	Sergey Chaban (serge@wildwestsoftware.com)
//	Sebastien Pouliot  <sebastien@ximian.com>
//
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System.Globalization;
using System.Runtime.InteropServices;

namespace System.Security.Cryptography {

	[Serializable]
#if NET_2_0
	[ComVisible (true)]
#endif
	public enum FromBase64TransformMode : int {
		IgnoreWhiteSpaces,
		DoNotIgnoreWhiteSpaces
	}

#if NET_2_0
	[ComVisible (true)]
#endif
	public class FromBase64Transform : ICryptoTransform {

		private FromBase64TransformMode mode;
		private byte[] accumulator;
		private int accPtr;
		private bool m_disposed;

		private const byte TerminatorByte = ((byte) '=');

		public FromBase64Transform ()
			: this (FromBase64TransformMode.IgnoreWhiteSpaces)
		{
		}

		public FromBase64Transform (FromBase64TransformMode mode)
		{
			this.mode = mode;
			accumulator = new byte [4];
			accPtr = 0;
			m_disposed = false;
		}

		~FromBase64Transform () 
		{
			Dispose (false);
		}

		public bool CanTransformMultipleBlocks {
			get { return false; }
		}

		public virtual bool CanReuseTransform {
			get { return true; }
		}

		public int InputBlockSize {
			get { return 1; }
		}

		public int OutputBlockSize {
			get { return 3; }
		}

		public void Clear() 
		{
			Dispose (true);
		}

		void IDisposable.Dispose () 
		{
			Dispose (true);
			GC.SuppressFinalize (this);  // Finalization is now unnecessary
		}

		protected virtual void Dispose (bool disposing) 
		{
			if (!m_disposed) {
				// zeroize data
				if (accumulator != null)
					Array.Clear (accumulator, 0, accumulator.Length);

				// dispose unmanaged objects
				if (disposing) {
					// dispose managed objects
					accumulator = null;
				}
				m_disposed = true;
			}
		}

		private byte[] lookupTable; 

		private byte lookup (byte input)
		{
			if (input >= lookupTable.Length) {
				throw new FormatException (
					Locale.GetText ("Invalid character in a Base-64 string."));
			}

			byte ret = lookupTable [input];
			if (ret == Byte.MaxValue) {
				throw new FormatException (
					Locale.GetText ("Invalid character in a Base-64 string."));
			}

			return ret;
		}

		private int ProcessBlock (byte[] output, int offset)
		{
			int rem = 0;
			if (accumulator [3] == TerminatorByte)
				rem++;
			if (accumulator [2] == TerminatorByte)
				rem++;

			lookupTable = Base64Constants.DecodeTable;
			int b0,b1,b2,b3;

			switch (rem) {
			case 0:
				b0 = lookup (accumulator [0]);
				b1 = lookup (accumulator [1]);
				b2 = lookup (accumulator [2]);
				b3 = lookup (accumulator [3]);
				output [offset++] = (byte) ((b0 << 2) | (b1 >> 4));
				output [offset++] = (byte) ((b1 << 4) | (b2 >> 2));
				output [offset] = (byte) ((b2 << 6) | b3);
				break;
			case 1:
				b0 = lookup (accumulator [0]);
				b1 = lookup (accumulator [1]);
				b2 = lookup (accumulator [2]);
				output [offset++] = (byte) ((b0 << 2) | (b1 >> 4));
				output [offset] = (byte) ((b1 << 4) | (b2 >> 2));
				break;
			case 2:
				b0 = lookup (accumulator [0]);
				b1 = lookup (accumulator [1]);
				output [offset] = (byte) ((b0 << 2) | (b1 >> 4));
				break;
			}

			return (3 - rem);
		}

		private void CheckInputParameters (byte[] inputBuffer, int inputOffset, int inputCount)
		{
			if (inputBuffer == null)
				throw new ArgumentNullException ("inputBuffer");
			if (inputOffset < 0)
				throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
			if (inputCount > inputBuffer.Length)
				throw new OutOfMemoryException ("inputCount " + Locale.GetText ("Overflow"));
			if (inputOffset > inputBuffer.Length - inputCount)
				throw new ArgumentException ("inputOffset", Locale.GetText ("Overflow"));
			if (inputCount < 0)
				throw new OverflowException ("inputCount < 0");
		}

		public int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
		{
			if (m_disposed)
				throw new ObjectDisposedException ("FromBase64Transform");
			// LAMESPEC: undocumented exceptions
			CheckInputParameters (inputBuffer, inputOffset, inputCount);
			if ((outputBuffer == null) || (outputOffset < 0))
				throw new FormatException ("outputBuffer");

			int res = 0;

			while (inputCount > 0) {
				if (accPtr < 4) {
					byte b = inputBuffer [inputOffset++];
					if (mode == FromBase64TransformMode.IgnoreWhiteSpaces) {
						if (!Char.IsWhiteSpace ((char) b))
							accumulator [accPtr++] = b;
					} else {
						// don't ignore, we'll fail if bad data is provided
						accumulator [accPtr++] = b;
					}
				}
				if (accPtr == 4) {
					res += ProcessBlock (outputBuffer, outputOffset);
					outputOffset += 3;
					accPtr = 0;
				}
				inputCount--;
			}

			return res;
		}

		public byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount)
		{
			if (m_disposed)
				throw new ObjectDisposedException ("FromBase64Transform");
			// LAMESPEC: undocumented exceptions
			CheckInputParameters (inputBuffer, inputOffset, inputCount);

			int ws = 0;
			int terminator = 0;
			if (mode == FromBase64TransformMode.IgnoreWhiteSpaces) {
				// count whitespace inside string
				for (int i=inputOffset, j=0; j < inputCount; i++, j++) {
					if (Char.IsWhiteSpace ((char)inputBuffer [i]))
						ws++;
				}
				// no more (useful) data
				if (ws == inputCount)
					return new byte [0];
				// there may be whitespace after the terminator
				int k = inputOffset + inputCount - 1;
				int n = Math.Min (2, inputCount);
				while (n > 0) {
					char c = (char) inputBuffer [k--];
					if (c == '=') {
						terminator++;
						n--;
					} else if (Char.IsWhiteSpace (c)) {
						continue;
					} else {
						break;
					}
				}						
			} else {
				if (inputBuffer [inputOffset + inputCount - 1] == TerminatorByte)
					terminator++;
				if (inputBuffer [inputOffset + inputCount - 2] == TerminatorByte)
					terminator++;
			}
			// some terminators could already be in the accumulator
			if ((inputCount < 4) && (terminator < 2)) {
				if ((accPtr > 2) && (accumulator [3] == TerminatorByte))
					terminator++;
				if ((accPtr > 1) && (accumulator [2] == TerminatorByte))
					terminator++;
			}

			int count = ((accPtr + inputCount - ws) >> 2) * 3 - terminator;
			if (count <= 0)
				return new byte [0];

			// allocate the "right" ammount (to avoid multiple allocation/copy)
			byte[] result = new byte [count];
			TransformBlock (inputBuffer, inputOffset, inputCount, result, 0);
			return result;
		}
	}
}
