// ----------------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ----------------------------------------------------------------------------

#if BOILER_PLATE
namespace Bartok.Marshal
{
    public class CheapState {
#endif // BOILER_PLATE
        private static CheapState latest;

        private const uint Limit = 16;

        public CheapState(int indent)
        {
            used = 0;
            read = 0;
            this.indent = indent;

            loadPtrs = new object[128 * 1024];

            regionSize = 8 * 1024 * 1024;
            region = VirtualAlloc(0, regionSize, MEM_COMMIT, PAGE_READWRITE);
            Console.WriteLine("Region = {0:x8}", region);

            latest = this;
        }

        public CheapState(CheapState other)
        {
            region = other.region;
            regionSize = other.regionSize;

            used = 0;
            read = 0;
            indent = 0;

            loadPtrs = new object[16 * 1024];

            latest = this;
        }

        [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError=true)]
        private static extern uint VirtualAlloc(uint lpAddress,
                                                uint dwSize,
                                                uint flAllocationType,
                                                uint flProtect);
        private const uint MEM_COMMIT = 0x1000;
        private const uint PAGE_READWRITE = 0x04;

        private uint        region;
        private uint        regionSize;

        private uint        used;
        private uint        read;

        private ObjectIDGenerator   oig;

        private object[]    loadPtrs;
        private uint        loadCount;

        private uint        usedTrace;
        private uint        readTrace;
        private int         indent;

        public static System.Collections.Hashtable writeHash;
        public static System.Collections.Hashtable readHash;

        public void WriteOpen()
        {
            oig = new ObjectIDGenerator();
#if false
            unsafe {
                byte *ptr = (byte *)region;
                for (uint i = 0; i < used; i++) {
                    ptr[i] = 0xdd;
                }
            }
#endif

            used = 0;
            usedTrace = 0;
            indent = 0;

            WriteRaw(used);
            TraceWrite("Zero Buffer Size");
        }

        public void WriteClose()
        {
            unsafe {
                uint * p = (uint *)region;
                *p = used;
            }
            Trace(0, 4, "Wrote Buffer Size: {0}", used);
            used = 0;
            read = 0;
        }

        public void ReadOpen()
        {
            read = 0;
            readTrace = 0;
            indent = 0;

#if false
            for (uint i = 0; i < loadCount; i++) {
                loadPtrs[i] = null;
            }
#endif
            loadPtrs[0] = null;
            loadCount = 1;

            used = 4;    // set to 4 so our read don't fail.
            ReadRaw(out used);
            TraceRead("Read Buffer Size: {0}", used);
        }

        public void ReadClose()
        {
            if (used != read) {
                throw new ArgumentOutOfRangeException();
            }
            read = 0;
            used = 0;
        }

        [System.Diagnostics.Conditional("TRACE_CHEAP_STATE")]
        public void Trace(uint offset, uint size, string text)
        {
            if (offset > Limit) {
                return;
            }

#if !USE_DOT_INDENT
            Console.Write("{0,8:d}: {1}{2}", offset, new String('.', indent), text);
            if (indent + text.Length < 32) {
                Console.Write(new String(' ', 32 - (indent + text.Length)));
            }
#else
            Console.Write("{0,8:d}: {1,3} {2,-20} ", offset, indent, text);
#endif
            if (size > 0) {
                if (offset + size > regionSize) {
                    throw new ArgumentOutOfRangeException();
                }
                for (uint i = 0; i < size; i++) {
                    unsafe {
                        byte *p = (byte *)region + offset + i;
                        Console.Write("{0:x2}", *p);
                    }
                }
            }
            Console.WriteLine();
        }

        [System.Diagnostics.Conditional("TRACE_CHEAP_STATE")]
        public void Trace(uint offset, uint size, string text, params object[] args)
        {
            Trace(offset, size, String.Format(text, args));
        }

        [System.Diagnostics.Conditional("TRACE_CHEAP_STATE")]
        public void TraceRead(string text)
        {
            uint offset = readTrace;
            uint size = read - readTrace;
            readTrace = read;

            Trace(offset, size, text);
        }

        [System.Diagnostics.Conditional("TRACE_CHEAP_STATE")]
        public void TraceRead(string text, params object[] args)
        {
            TraceRead(String.Format(text, args));
        }

        [System.Diagnostics.Conditional("TRACE_CHEAP_STATE")]
        public void TraceWrite(string text)
        {
            uint offset = usedTrace;
            uint size = used - usedTrace;
            usedTrace = used;

            Trace(offset, size, text);
        }

        [System.Diagnostics.Conditional("TRACE_CHEAP_STATE")]
        public void TraceWrite(string text, params object[] args)
        {
            TraceWrite(String.Format(text, args));
        }

        [System.Diagnostics.Conditional("TRACE_CHEAP_STATE")]
        public void Trace(string text)
        {
            if (read == 0 && used > Limit) {
                return;
            }
            else if (read > Limit) {
                return;
            }

#if !USE_DOT_INDENT
            Console.WriteLine("        : {0}{1}", new String('.', indent), text);
#else
            Console.WriteLine("        : {0,3} {1}", indent, text);
#endif
        }

        [System.Diagnostics.Conditional("TRACE_CHEAP_STATE")]
        public void Trace(string text, params object[] args)
        {
            Trace(String.Format(text, args));
        }

        [System.Diagnostics.Conditional("TRACE_CHEAP_STATE")]
        public void Trace()
        {
            Console.WriteLine();
        }

        //////////////////////////////////////////////////////////////////////////////
        //
        public bool SavePtr(uint id, object ptr)
        {
            bool first;
            long oid = oig.GetId(ptr, out first);
            if (!first) {
                id |= 0x8000;
                WriteType(id);
                WriteRaw(unchecked((uint)oid));
                TraceWrite("** pointer {0} of 0x{1:x}", oid, id);;
                WriteTypeEnd(id);
                return false;   // already marshaled.
            }
            else {
                TraceWrite("** Save pointer: {0} of 0x{1:x}", oid, id);;
            }
            return true;        // needs to be marshaled.
        }

        // Remember the pointer as we unmarshal an object.
        public uint AddPtr(object ptr, uint id)
        {
            loadPtrs[loadCount] = ptr;
            Trace(read, 0, "** Load pointer: {0} of 0x{1:x}", loadCount, id);
            return loadCount++;
        }

        // Only a pointer was saved because the object was already unmarshaled.
        public object LoadPtr(uint id)
        {
            if ((id & 0x8000) == 0) {
                throw new Exception("Bad id for pointer: " + id);
            }
            uint pi;
            ReadRaw(out pi);
            TraceRead("** pointer {0} of 0x{1:x}", pi, id);
            ReadTypeEnd(id);

            if (pi == 0 || pi > loadCount) {
                throw new Exception("Unexpected object pi: " + pi);
            }
            return loadPtrs[pi];
        }

        public void WriteRaw(ulong u)
        {
            unsafe {
                if (used + sizeof(ulong) > regionSize) {
                    throw new ArgumentOutOfRangeException();
                }
                byte *p = (byte *)region + used;
                *((ulong *)p) = u;
                used += sizeof(ulong);
            }
        }

        public void WriteRaw(uint u)
        {
            unsafe {
                if (used + sizeof(uint) > regionSize) {
                    throw new ArgumentOutOfRangeException();
                }
                byte *p = (byte *)region + used;
                *((uint *)p) = u;
                used += sizeof(uint);
            }
        }

        public void WriteRaw(ushort u)
        {
            unsafe {
                if (used + sizeof(ushort) > regionSize) {
                    throw new ArgumentOutOfRangeException();
                }
                byte *p = (byte *)region + used;
                *((ushort *)p) = u;
                used += sizeof(ushort);
            }
        }

        public void WriteRaw(byte u)
        {
            unsafe {
                if (used + sizeof(byte) > regionSize) {
                    throw new ArgumentOutOfRangeException();
                }
                byte *p = (byte *)region + used;
                *((byte *)p) = u;
                used += sizeof(byte);
            }
        }

        public void ReadRaw(out ulong u)
        {
            unsafe {
                if (read + sizeof(ulong) > used) {
                    throw new ArgumentOutOfRangeException();
                }
                byte *p = (byte *)region + read;
                u = *((ulong *)p);
                read += sizeof(ulong);
            }
        }

        public void ReadRaw(out uint u)
        {
            unsafe {
                if (read + sizeof(uint) > used) {
                    throw new ArgumentOutOfRangeException();
                }
                byte *p = (byte *)region + read;
                u = *((uint *)p);
                read += sizeof(uint);
            }
        }

        public void ReadRaw(out ushort u)
        {
            unsafe {
                if (read + sizeof(ushort) > used) {
                    throw new ArgumentOutOfRangeException();
                }
                byte *p = (byte *)region + read;
                u = *((ushort *)p);
                read += sizeof(ushort);
            }
        }

        public void ReadRaw(out byte u)
        {
            unsafe {
                if (read + sizeof(byte) > used) {
                    throw new ArgumentOutOfRangeException();
                }
                byte *p = (byte *)region + read;
                u = *((byte *)p);
                read += sizeof(byte);
            }
        }

        //////////////////////////////////////////////////////////////////////////////
        //
        public void WriteType(uint type)
        {
            WriteRaw(type | 0xbbbb0000);
            TraceWrite("WriteType 0x{0:x}", type);
            if (type != 0) {
                indent++;
            }
        }

        public void WriteTypeEnd(uint type)
        {
            if (type == 0) {
                throw new Exception("Null type can be an end.");
            }
            indent--;
            WriteRaw(type | 0xeeee0000);
            TraceWrite("WriteTypeEnd 0x{0:x}", type);
        }

        public uint ReadType()
        {
            uint type;
            ReadRaw(out type);
            TraceRead("ReadType ~0x{0:x}", (type & 0xffff));
            if ((type & 0xffff0000) != 0xbbbb0000) {
                throw new Exception("Missing beg type");
            }
            type &= 0x0000ffff;

            if (type != 0) {
                indent++;
            }
            return type;
        }

        public uint ReadType(uint id)
        {
            uint type;
            ReadRaw(out type);
            TraceRead("ReadType 0x{0:x}", id);
            if ((type & 0xffff0000) != 0xbbbb0000) {
                throw new Exception("Missing beg type");
            }
            type &= 0x0000ffff;

            if (type != 0) {
                indent++;
            }
            if (type != id) {
                throw new Exception("Invalid beg type");
            }
            return type;
        }

        public uint ReadTypeOrNull(uint id)
        {
            uint type;
            ReadRaw(out type);
            TraceRead("ReadTypeOrNull 0x{0:x}", id);
            if ((type & 0xffff0000) != 0xbbbb0000) {
                throw new Exception("Missing beg type");
            }
            type &= 0x0000ffff;

            if (type == 0) {
                return 0;
            }
            else if (type == id || type == (id | 0x8000)) {
                indent++;
                return type;
            }
            else {
                throw new Exception("Invalid beg type");
            }
        }

        public void ReadTypeEnd(uint id)
        {
            uint type;
            indent--;
            ReadRaw(out type);
            TraceRead("ReadTypeEnd 0x{0:x}", id);
            if ((type & 0xffff0000) != 0xeeee0000) {
                throw new Exception("Missing end type");
            }
            type &= 0x0000ffff;

            if (id != 0 && type != id) {
                throw new Exception("Invalid end type");
            }
        }

        //////////////////////////////////////////////////////////////////////////////
        //
        public void Write(bool value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Bool);
#endif
            if (value) {
                WriteRaw(unchecked((byte)1));
                TraceWrite("true");
            }
            else {
                WriteRaw(unchecked((byte)0));
                TraceWrite("false");
            }
#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Bool);
#endif
        }

        public void Read(out bool value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Bool);
#endif
            byte t;
            ReadRaw(out t);
            TraceRead("bool");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Bool);
#endif
            value = (t != 0);
        }

        public void CheapRead(out bool value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Bool);
#endif
            byte t;
            ReadRaw(out t);
            TraceRead("bool");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Bool);
#endif
            value = (t != 0);
        }

        public void Write(sbyte value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Sbyte);
#endif
            WriteRaw(unchecked((byte)value));
            TraceWrite("sbyte");
#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Sbyte);
#endif
        }

        public void Read(out sbyte value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Sbyte);
#endif
            byte t;
            ReadRaw(out t);
            TraceRead("sbyte");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Sbyte);
#endif
            value = unchecked((sbyte)t);
        }

        public void Write(byte value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Byte);
#endif
            WriteRaw(value);
            TraceWrite("byte");
#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Byte);
#endif
        }

        public void Read(out byte value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Byte);
#endif
            ReadRaw(out value);
            TraceRead("byte");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Byte);
#endif
        }

        public void Write(short value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Short);
#endif
            WriteRaw(unchecked((ushort)value));
            TraceWrite("short");
#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Short);
#endif
        }

        public void Read(out short value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Short);
#endif
            ushort t;
            ReadRaw(out t);
            TraceRead("short");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Short);
#endif
            value = unchecked((short)t);
        }

        public void Write(char value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Char);
#endif
            WriteRaw(unchecked((ushort)value));
            TraceWrite("char");
#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Char);
#endif
        }

        public void Read(out char value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Char);
#endif
            ushort t;
            ReadRaw(out t);
            TraceRead("char");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Char);
#endif
            value = unchecked((char)t);
        }

        public void Write(ushort value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Ushort);
#endif
            WriteRaw(value);
            TraceWrite("ushort");
#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Ushort);
#endif
        }

        public void Read(out ushort value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Ushort);
#endif
            ReadRaw(out value);
            TraceRead("ushort");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Ushort);
#endif
        }

        public void Write(int value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Int);
#endif
            WriteRaw(unchecked((uint)value));
            TraceWrite("int");
#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Int);
#endif
        }

        public void Read(out int value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Int);
#endif
            uint t;
            ReadRaw(out t);
            TraceRead("int");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Int);
#endif
            value = unchecked((int)t);
        }

        public void Write(uint value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Uint);
#endif
            WriteRaw(value);
            TraceWrite("uint");
#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Uint);
#endif
        }

        public void Read(out uint value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Uint);
#endif
            ReadRaw(out value);
            TraceRead("uint");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Uint);
#endif
        }

        public void Write(long value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Long);
#endif
            WriteRaw(unchecked((ulong)value));
            TraceWrite("long");

#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Long);
#endif
        }

        public void Read(out long value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Long);
#endif
            ulong t;
            ReadRaw(out t);
            TraceRead("long");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Long);
#endif
            value = unchecked((long)t);
        }

        public void Write(ulong value)
        {
#if USE_TYPE_IDS_ON_VALUES
            WriteType((uint)Types.Ulong);
#endif
            WriteRaw(value);
            TraceWrite("ulong");
#if USE_TYPE_IDS_ON_VALUES
            WriteTypeEnd((uint)Types.Ulong);
#endif
        }

        public void Read(out ulong value)
        {
#if USE_TYPE_IDS_ON_VALUES
            ReadType((uint)Types.Ulong);
#endif
            ReadRaw(out value);
            TraceRead("ulong");
#if USE_TYPE_IDS_ON_VALUES
            ReadTypeEnd((uint)Types.Ulong);
#endif
        }

        public void Write(string value)
        {
            if (value == null) {
                WriteType(0);
            }
            else if (SavePtr((uint)Types.String, value)) {
                int len = value.Length;
                WriteType((uint)Types.String);
                WriteRaw(unchecked((uint)len));
                for (int i = 0; i < len; i++) {
                    WriteRaw(unchecked((ushort)value[i]));
                }
                TraceWrite("string");
                WriteTypeEnd((uint)Types.String);
            }
        }

        public void Read(out string value)
        {
            uint id = ReadTypeOrNull((uint)Types.String);
            if ((id & 0x8000) != 0) {
                value = (string)LoadPtr(id);
            }
            else if (id != 0) {
                uint len;
                ReadRaw(out len);
                char[] ta = new char [len];
                for (uint i = 0; i < len; i++) {
                    ushort t;
                    ReadRaw(out t);
                    ta[i] = unchecked((char)t);
                }
                TraceRead("string");
                ReadTypeEnd((uint)Types.String);
                value = new String(ta);
                AddPtr(value, (uint)Types.String);
            }
            else {
                value = null;
            }
        }

        public void Write(object[] value) {
            Trace("Write object[]");
            if (value == null) {
                WriteType(0);
            }
            else if (SavePtr((uint)Types.ObjectArray, value)) {
                WriteType((uint)Types.ObjectArray);
                WriteRaw((uint)value.Length);
                TraceWrite("[].Length");
                for (int i = 0; i < value.Length; i++) {
                    if (value[i] == null) {
                        WriteType(0);
                    }
                    else {
                        Write(value[i]);
                    }
                }
                WriteTypeEnd((uint)Types.ObjectArray);
            }
        }

        public void Read(out object[] value) {
            Trace("Read object[] [outer]");
            uint id = ReadTypeOrNull((uint)Types.ObjectArray);
            if ((id & 0x8000) != 0) {
                value = (object[])LoadPtr(id);
            }
            else if (id != 0) {
                uint len;
                ReadRaw(out len);
                TraceRead("[].Length");
                value = new object [len];
                AddPtr(value, (uint)Types.ObjectArray);
                for (int i = 0; i < len; i++) {
                    Read(out value[i]);
                }
                ReadTypeEnd((uint)Types.ObjectArray);
            }
            else {
                value = null;
            }
        }

        public void Write(int[] value) {
            Trace("Write int[]");
            if (value == null) {
                WriteType(0);
            }
            else if (SavePtr((uint)Types.IntArray, value)) {
                WriteType((uint)Types.IntArray);
                WriteRaw((uint)value.Length);
                for (int i = 0; i < value.Length; i++) {
                    WriteRaw(unchecked((uint)value[i]));
                }
                TraceWrite("[]");
                WriteTypeEnd((uint)Types.IntArray);
            }
        }

        public void Read(out int[] value) {
            Trace("Read int[] [outer]");
            uint id = ReadTypeOrNull((uint)Types.IntArray);
            if ((id & 0x8000) != 0) {
                value = (int[])LoadPtr(id);
            }
            else if (id != 0) {
                uint len;
                ReadRaw(out len);
                value = new int [len];
                AddPtr(value, (uint)Types.IntArray);
                for (int i = 0; i < len; i++) {
                    uint v;
                    ReadRaw(out v);
                    value[i] = unchecked((int)v);
                }
                TraceRead("[]");
                ReadTypeEnd((uint)Types.IntArray);
            }
            else {
                value = null;
            }
        }

        public void Write(uint[] value) {
            Trace("Write uint[]");
            if (value == null) {
                WriteType(0);
            }
            else if (SavePtr((uint)Types.UintArray, value)) {
                WriteType((uint)Types.UintArray);
                WriteRaw((uint)value.Length);
                for (int i = 0; i < value.Length; i++) {
                    WriteRaw(value[i]);
                }
                TraceWrite("[]");
                WriteTypeEnd((uint)Types.UintArray);
            }
        }

        public void Read(out uint[] value) {
            Trace("Read uint[] [outer]");
            uint id = ReadTypeOrNull((uint)Types.UintArray);
            if ((id & 0x8000) != 0) {
                value = (uint[])LoadPtr(id);
            }
            else if (id != 0) {
                uint len;
                ReadRaw(out len);
                value = new uint [len];
                AddPtr(value, (uint)Types.UintArray);
                for (int i = 0; i < len; i++) {
                    ReadRaw(out value[i]);
                }
                TraceRead("[]");
                ReadTypeEnd((uint)Types.UintArray);
            }
            else {
                value = null;
            }
        }

        public void Write(object value)
        {
            Trace("Write object");
            if (value == null) {
                WriteType(0);
            }
            else {
                CheapWriteDelegate wd = writeHash[value.GetType()] as CheapWriteDelegate;
                if (wd != null) {
                    wd(this, value);
                }
                else {
                    throw new Exception("Unknown type " + value.GetType());
                }
            }
        }

        public void Read(out object value)
        {
            Trace("Read object");
            uint __id = ReadType();
            if (__id == 0) {
                value = null;
                return;
            }
            if ((__id & 0x8000) != 0) {
                value = LoadPtr(__id);
                return;
            }

            CheapReadDelegate rd = readHash[(uint)(__id & 0x7fff)] as CheapReadDelegate;
            if (rd != null) {
                value = rd(__id, this);
                return;
            }
            throw new Exception("Unknown type id " + __id);
        }
#if BOILER_PLATE
    } // class
} // namespace
#endif // BOILER_PLATE
