using System;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Crypto.Parameters;
using SharpCompress.Converter;

namespace SharpCompress.Crypto
{
    //Gathered from:
    //http://stackoverflow.com/questions/3210795/pbkdf2-in-bouncy-castle-c-sharp and Rfc2898DeriveBytes 
    internal class PBKDF2
    {
        private readonly IMac hMac = new HMac(new Sha1Digest());
        private readonly byte[] state = new byte[20];
        private int endIndex;
        private int startIndex;
        private uint block = 1u;
        private readonly byte[] password;
        private readonly byte[] salt;
        private readonly int iterations;


        public PBKDF2(byte[] password, byte[] salt, int iterations)
        {
            this.password = password;
            this.salt = salt;
            this.iterations = iterations;
        }

        public byte[] GetBytes(int cb)
        {
            if (cb <= 0)
            {
                throw new ArgumentOutOfRangeException("cb");
            }
            byte[] array = new byte[cb];
            int i = 0;
            int num = endIndex - startIndex;
            if (num > 0)
            {
                if (cb < num)
                {
                    Buffer.BlockCopy(state, startIndex, array, 0, cb);
                    startIndex += cb;
                    return array;
                }
                Buffer.BlockCopy(state, startIndex, array, 0, num);
                startIndex = (endIndex = 0);
                i += num;
            }
            while (i < cb)
            {
                byte[] src = Hash();
                int num2 = cb - i;
                if (num2 <= 20)
                {
                    Buffer.BlockCopy(src, 0, array, i, num2);
                    i += num2;
                    Buffer.BlockCopy(src, num2, state, startIndex, 20 - num2);
                    endIndex += 20 - num2;
                    return array;
                }
                Buffer.BlockCopy(src, 0, array, i, 20);
                i += 20;
            }
            return array;
        }

        private byte[] Hash()
        {
            byte[] array = DataConverter.BigEndian.GetBytes(block);
            ICipherParameters param = new KeyParameter(password);

            hMac.Init(param);
            hMac.BlockUpdate(salt, 0, salt.Length);

            hMac.BlockUpdate(array, 0, array.Length);
            byte[] array2 = new byte[20];
            hMac.DoFinal(array2, 0);

            hMac.Init(param);

            byte[] array3 = new byte[20];
            Buffer.BlockCopy(array2, 0, array3, 0, 20);
            int num = 2;
            while (num <= (long)((ulong)iterations))
            {
                hMac.BlockUpdate(array2, 0, array2.Length);
                hMac.DoFinal(array2, 0);
                for (int i = 0; i < 20; i++)
                {
                    array3[i] ^= array2[i];
                }
                num++;
            }
            block += 1u;
            return array3;
        }
    }
}

