//! \file       adler32.cs
//! \date       Mon Jul 21 11:19:54 2014
//! \brief      compute adler32 checksum
//

using System.IO;
using System;

class Adler
{
    public static void Main (string[] args)
    {
        if (args.Length < 1)
            return;
        try
        {
            uint adler = 1;
            using (var input = File.Open (args[0], FileMode.Open, FileAccess.Read))
            {
                var buf = new byte[65536];
                for (;;)
                {
                    int read = input.Read (buf, 0, buf.Length);
                    if (0 == read)
                        break;
                    adler = Adler32.Update (adler, buf, 0, read);
                }
            }
            Console.WriteLine ("{0} => {1:X8}", args[0], adler);
        }
        catch (Exception X)
        {
            Console.Error.WriteLine (X.Message);
        }
    }

    public sealed class Adler32
    {
        const uint BASE = 65521;      /* largest prime smaller than 65536 */
        const int  NMAX = 5552;

        public static uint Compute (byte[] buf, int pos, int len)
        {
            return Update (1, buf, pos, len);
        }

        public static uint Update (uint adler, byte[] buf, int pos, int len)
        {
            /* split Adler-32 into component sums */
            uint sum2 = (adler >> 16) & 0xffff;
            adler &= 0xffff;

            /* in case user likes doing a byte at a time, keep it fast */
            if (1 == len) {
                adler += buf[pos];
                if (adler >= BASE)
                    adler -= BASE;
                sum2 += adler;
                if (sum2 >= BASE)
                    sum2 -= BASE;
                return adler | (sum2 << 16);
            }

            /* in case short lengths are provided, keep it somewhat fast */
            if (len < 16) {
                while (0 != len--) {
                    adler += buf[pos++];
                    sum2 += adler;
                }
                if (adler >= BASE)
                    adler -= BASE;
                sum2 %= BASE;            /* only added so many BASE's */
                return adler | (sum2 << 16);
            }

            /* do length NMAX blocks -- requires just one modulo operation */
            while (len >= NMAX) {
                len -= NMAX;
                int n = NMAX / 16;          /* NMAX is divisible by 16 */
                do {
                    /* 16 sums unrolled */
                    adler += buf[pos];    sum2 += adler;
                    adler += buf[pos+1];  sum2 += adler;
                    adler += buf[pos+2];  sum2 += adler;
                    adler += buf[pos+3];  sum2 += adler;
                    adler += buf[pos+4];  sum2 += adler;
                    adler += buf[pos+5];  sum2 += adler;
                    adler += buf[pos+6];  sum2 += adler;
                    adler += buf[pos+7];  sum2 += adler;
                    adler += buf[pos+8];  sum2 += adler;
                    adler += buf[pos+9];  sum2 += adler;
                    adler += buf[pos+10]; sum2 += adler;
                    adler += buf[pos+11]; sum2 += adler;
                    adler += buf[pos+12]; sum2 += adler;
                    adler += buf[pos+13]; sum2 += adler;
                    adler += buf[pos+14]; sum2 += adler;
                    adler += buf[pos+15]; sum2 += adler;
                    pos += 16;
                } while (0 != --n);
                adler %= BASE;
                sum2 %= BASE;
            }

            /* do remaining bytes (less than NMAX, still just one modulo) */
            if (0 != len) {                  /* avoid modulos if none remaining */
                while (len >= 16) {
                    len -= 16;
                    adler += buf[pos];    sum2 += adler;
                    adler += buf[pos+1];  sum2 += adler;
                    adler += buf[pos+2];  sum2 += adler;
                    adler += buf[pos+3];  sum2 += adler;
                    adler += buf[pos+4];  sum2 += adler;
                    adler += buf[pos+5];  sum2 += adler;
                    adler += buf[pos+6];  sum2 += adler;
                    adler += buf[pos+7];  sum2 += adler;
                    adler += buf[pos+8];  sum2 += adler;
                    adler += buf[pos+9];  sum2 += adler;
                    adler += buf[pos+10]; sum2 += adler;
                    adler += buf[pos+11]; sum2 += adler;
                    adler += buf[pos+12]; sum2 += adler;
                    adler += buf[pos+13]; sum2 += adler;
                    adler += buf[pos+14]; sum2 += adler;
                    adler += buf[pos+15]; sum2 += adler;
                    pos += 16;
                }
                while (0 != len--) {
                    adler += buf[pos++];
                    sum2 += adler;
                }
                adler %= BASE;
                sum2 %= BASE;
            }

            /* return recombined sums */
            return adler | (sum2 << 16);
        }

        private uint m_adler = 1;
        public  uint Value { get { return m_adler; } }

        public void Update (byte[] buf, int pos, int len)
        {
            m_adler = Update (m_adler, buf, pos, len);
        }
    }
}
