/*
 * Copyright (c) 2002 Sergey Chaban <serge@wildwestsoftware.com>
 */

// see http://cvs.winehq.com/cvsweb/wine/dlls/imagehlp/modify.c
// starting from Revision 1.8

using System;
using System.IO;

namespace Mono.PEToolkit {

	public sealed class CheckSum {

		private CheckSum()
		{
			// Never instantiated.
		}


		public static uint Calc(string peFile)
		{
			uint res = 0;

			FileInfo pe = new FileInfo(peFile);
			if (!pe.Exists) {
				throw new Exception("CheckSum : Invalid file path.");
			}

			using (BinaryReader reader = new BinaryReader(pe.OpenRead())) {
				if (!reader.BaseStream.CanSeek) {
					throw new Exception("Can't seek.");
				}

				DOSHeader dosHdr = new DOSHeader();
				COFFHeader coffHdr = new COFFHeader();
				PEHeader peHdr = new PEHeader();

				dosHdr.Read (reader);
				reader.BaseStream.Position = dosHdr.Lfanew;
				ExeSignature peSig = (ExeSignature) reader.ReadUInt16();
				if (peSig != ExeSignature.NT) {
					throw new BadImageException("Checksum : Invalid image format, cannot find PE signature.");
				}
	
				peSig = (ExeSignature) reader.ReadUInt16();
				if (peSig != ExeSignature.NT2) {
					throw new BadImageException("Checksum : Invalid image format, cannot find PE signature.");
				}

				coffHdr.Read(reader);
				peHdr.Read(reader);

				uint oldSum = peHdr.CheckSum;
				reader.BaseStream.Position = 0;
				long len = pe.Length;
				long whole = len >> 1;
				uint sum = 0;
				uint hi, lo;
				for (long i = whole; --i >= 0;) {
					sum += reader.ReadUInt16();
					hi = sum >> 16;
					if (hi != 0) {
						sum = hi + (sum & 0xFFFF);
					}
				}
				if ((len & 1L) != 0) {
					sum += (uint) reader.ReadByte();
					hi = sum >> 16;
					if (hi != 0) {
						sum = hi + (sum & 0xFFFF);
					}
				}

				// fix low word of checksum
				lo = oldSum & 0xFFFF;
				if ((sum & 0xFFFF) >= lo) {
					sum -= lo;
				} else {
					sum = (((sum & 0xFFFF) - lo) & 0xFFFF) - 1;
				}

				// fix high word of checksum
				hi = oldSum >> 16;
				if ((sum & 0xFFFF) >= hi) {
					sum -= hi;
				} else {
					sum = (((sum & 0xFFFF) - hi) & 0xFFFF) - 1;
				}
			}

			return res;
		}
	}

}
