//  =================================================
//  The SHA1 class computes a cryptographic hash code
//  for a string or file using the SHA1 algorithm.
//  =================================================

using System;
using System.Text;
using System.IO;
using System.Unicode;

namespace System.Security.Cryptography
{
    public uint LeftRotate(uint x, uint n)
    {
        return (x << n) ^ (x >> (32u - n));
    }

    public class SHA1
    {
        public SHA1()
        {
            this.digest = new uint[5];
            this.block = new byte[64];
            Reset();
        }
        public void Reset()
        {
            digest[0] = 0x67452301u;
            digest[1] = 0xEFCDAB89u;
            digest[2] = 0x98BADCFEu;
            digest[3] = 0x10325476u;
            digest[4] = 0xC3D2E1F0u;
            byteIndex = 0u;
            bitCount = 0u;
        }
        public void Process(byte x)
        {
            ProcessByte(x);
            bitCount = bitCount + 8u;
        }
        public void Process(byte[] bytes)
        {
            if (bytes == null)
            {
                throw new ArgumentNullException("provided byte array is null");
            }
            foreach (byte x in bytes)
            {
                Process(x);
            }
        }
        public string GetDigest()
        {
            ProcessByte(0x80u);
            if (byteIndex > 56u)
            {
                while (byteIndex != 0u)
                {
                    ProcessByte(0u);
                }
                while (byteIndex < 56u)
                {
                    ProcessByte(0u);
                }
            }
            else
            {
                while (byteIndex < 56u)
                {
                    ProcessByte(0u);
                }
            }
            ProcessByte(cast<byte>((bitCount >> 56u) & 0xFFu));
            ProcessByte(cast<byte>((bitCount >> 48u) & 0xFFu));
            ProcessByte(cast<byte>((bitCount >> 40u) & 0xFFu));
            ProcessByte(cast<byte>((bitCount >> 32u) & 0xFFu));
            ProcessByte(cast<byte>((bitCount >> 24u) & 0xFFu));
            ProcessByte(cast<byte>((bitCount >> 16u) & 0xFFu));
            ProcessByte(cast<byte>((bitCount >> 8u) & 0xFFu));
            ProcessByte(cast<byte>(bitCount & 0xFFu));
            StringBuilder s = new StringBuilder();
            s.Append(ToHexString(digest[0]));
            s.Append(ToHexString(digest[1]));
            s.Append(ToHexString(digest[2]));
            s.Append(ToHexString(digest[3]));
            s.Append(ToHexString(digest[4]));
            return s.ToString();
        }
        private void ProcessByte(byte x)
        {
            block[byteIndex] = x;
            ++byteIndex;
            if (byteIndex == 64u)
            {
                byteIndex = 0u;
                ProcessBlock();
            }
        }
        private void ProcessBlock()
        {
            uint[] w = new uint[80];
            for (int i = 0; i < 16; ++i)
            {
                w[i] = cast<uint>(block[4 * i]) << 24u;
                w[i] = w[i] | cast<uint>(block[4 * i + 1]) << 16u;
                w[i] = w[i] | cast<uint>(block[4 * i + 2]) << 8u;
                w[i] = w[i] | cast<uint>(block[4 * i + 3]);
            }
            for (int i = 16; i < 80; ++i)
            {
                w[i] = LeftRotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1u);
            }
            uint a = digest[0];
            uint b = digest[1];
            uint c = digest[2];
            uint d = digest[3];
            uint e = digest[4];
            for (int i = 0; i < 80; ++i)
            {
                uint f;
                uint k;
                if (i < 20)
                {
                    f = (b & c) | (~b & d);
                    k = 0x5A827999u;
                }
                else if (i < 40)
                {
                    f = b ^ c ^ d;
                    k = 0x6ED9EBA1u;
                }
                else if (i < 60)
                {
                    f = (b & c) | (b & d) | (c & d);
                    k = 0x8F1BBCDCu;
                }
                else
                {
                    f = b ^ c ^ d;
                    k = 0xCA62C1D6u;
                }
                uint temp = LeftRotate(a, 5u) + f + e + k + w[i];
                e = d;
                d = c;
                c = LeftRotate(b, 30u);
                b = a;
                a = temp;
            }
            digest[0] = digest[0] + a;
            digest[1] = digest[1] + b;
            digest[2] = digest[2] + c;
            digest[3] = digest[3] + d;
            digest[4] = digest[4] + e;
        }
        private uint[] digest;
        private byte[] block;
        private byte byteIndex;
        private ulong bitCount;
    }

    public string GetSHA1MessageDigest(string message)
    {
        SHA1 sha1 = new SHA1();
        MemoryStream memoryStream = new MemoryStream();
        Utf8Encoder encoder = new Utf8Encoder(memoryStream);
        encoder.Encode(message);
        sha1.Process(memoryStream.Bytes);
        return sha1.GetDigest();
    }
    
    public string GetSHA1FileDigest(string filePath)
    {
        SHA1 sha1 = new SHA1();
        using (BinaryReader reader = File.OpenBinary(filePath))
        {
            int size = reader.StreamSize;
            for (int i = 0; i < size; ++i)
            {
                byte b = reader.ReadByte();
                sha1.Process(b);
            }
        }
        return sha1.GetDigest();
    }
}