using System;
using System.Linq;
using System.Security.Cryptography;

namespace FSO.Server.Common
{
    public class PasswordHasher
    {
        public static PasswordHash Hash(string password)
        {
            return Hash(password, "Rfc2898");
        }

        public static PasswordHash Hash(string password, string scheme)
        {
            var schemeImpl = GetScheme(scheme);
            return schemeImpl.Hash(password);
        }

        public static bool Verify(string password, PasswordHash hash)
        {
            return Verify(password, hash, "Rfc2898");
        }

        public static bool Verify(string password, PasswordHash hash, string scheme)
        {
            var schemeImpl = GetScheme(scheme);
            return schemeImpl.Verify(password, hash);
        }

        private static IPasswordHashScheme GetScheme(string scheme)
        {
            switch (scheme)
            {
                case "Rfc2898":
                    return new DefaultPasswordHashScheme();
            }

            throw new Exception("Unknown password hash scheme: " + scheme);
        }
    }

    public class PasswordHash
    {
        public byte[] data;
        public string scheme;
    }

    public class DefaultPasswordHashScheme : IPasswordHashScheme
    {
        public PasswordHash Hash(string password)
        {
            var salt_input = GetStrongRandomBytes(16);
            

            return new PasswordHash()
            {
                scheme = "Rfc2898",
                data = Hash(salt_input, password)
            };
        }

        private byte[] Hash(byte[] salt_input, string password)
        {
            var hasher = new Rfc2898DeriveBytes(System.Text.Encoding.UTF8.GetBytes(password), salt_input, 1000);
            var hash = hasher.GetBytes(64);

            //Encode the salt + hash together
            var result = new byte[1 + 16 + hash.Length];
            result[0] = (byte)16;
            Array.Copy(salt_input, 0, result, 1, salt_input.Length);
            Array.Copy(hash, 0, result, salt_input.Length + 1, hash.Length);

            return result;
        }

        public bool Verify(string password, PasswordHash hash)
        {
            var salt_length = hash.data[0];
            var salt_input = new byte[salt_length];
            Array.Copy(hash.data, 1, salt_input, 0, salt_length);

            var expected = Hash(salt_input, password);
            return expected.SequenceEqual(hash.data);
        }

        private byte[] GetStrongRandomBytes(int numBytes)
        {
            var random_bytes = new byte[numBytes];
            using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider())
            {
                rngCsp.GetBytes(random_bytes);
            }
            return random_bytes;
        }
    }

    public interface IPasswordHashScheme
    {
        PasswordHash Hash(string password);
        bool Verify(string password, PasswordHash hash);
    }
}