///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  Microsoft Research Singularity
//

// Interprets ACPI methods.

using System;
using System.Collections;
using System.Diagnostics;

using Node = Microsoft.Singularity.Hal.Acpi.AcpiNamespace.Node;
using NodePath = Microsoft.Singularity.Hal.Acpi.AcpiNamespace.NodePath;
using AbsoluteNodePath = Microsoft.Singularity.Hal.Acpi.AcpiNamespace.AbsoluteNodePath;

using Microsoft.Singularity.Hal.Acpi;
using Microsoft.Singularity.Hal.Acpi.AcpiObject;
using Microsoft.Singularity.Hal.Acpi.AmlParserUnions;
using Microsoft.Singularity.Hal.Acpi.StackIR;

namespace Microsoft.Singularity.Hal.Acpi
{
    public class InterpretException : Exception
    {
        public InterpretException() : base("Error interpreting AML") { }

        public InterpretException(string s) : base("Error interpreting AML: " + s) { }
    }

    public class MethodNotFoundInterpretException : Exception
    {
        public MethodNotFoundInterpretException() : base("Error interpreting AML: Method not found") { }

        public MethodNotFoundInterpretException(string s) : base("Error interpreting AML: Method not found: " + s) { }
    }

    public abstract class IoLocation
    {
        public abstract AcpiObject.AcpiObject Read();
        public abstract void Write(AcpiObject.AcpiObject value);
    }

    public class ValueIoLocation : IoLocation
    {
        private AcpiObject.AcpiObject value;

        public ValueIoLocation()
        {
            value = new UninitializedObject();
        }

        public ValueIoLocation(AcpiObject.AcpiObject acpiObject)
        {
            this.value = acpiObject;
        }

        public override AcpiObject.AcpiObject Read()
        {
            return value;
        }

        public override void Write(AcpiObject.AcpiObject value)
        {
            throw new InterpretException("Attempt to store to temporary object");
        }
    }

    public class DebugIoLocation : IoLocation
    {
        public override AcpiObject.AcpiObject Read()
        {
            throw new InterpretException("Attempt to read from debug object");
        }

        public override void Write(AcpiObject.AcpiObject value)
        {
#if SINGULARITY_KERNEL
            DebugStub.WriteLine("ACPI DEBUG: " + value.ToString());
#else
            Console.WriteLine("ACPI DEBUG: " + value.ToString());
#endif
        }
    }

    public class VariableIoLocation : ValueIoLocation
    {
        private AcpiObject.AcpiObject value;

        public VariableIoLocation()
        {
            value = new UninitializedObject();
        }

        public VariableIoLocation(AcpiObject.AcpiObject acpiObject)
        {
            value = acpiObject;
        }

        public override AcpiObject.AcpiObject Read()
        {
            return value;
        }

        public override void Write(AcpiObject.AcpiObject value)
        {
            this.value = value;
        }
    }

    public class NodePathIoLocation : ValueIoLocation
    {
        private AcpiNamespace.Node node;

        public NodePathIoLocation(AcpiNamespace.Node node)
        {
            if (node == null) {
                throw new ArgumentException("node should be non-null");
            }
            this.node = node;
        }

        public override AcpiObject.AcpiObject Read()
        {
            return node.Value;
        }

        public override void Write(AcpiObject.AcpiObject value)
        {
            node.Value = value;
        }
    }

    public class AmlStackFrame : IDisposable
    {
        // Local variable and argument objects
        public const int ReservedLocals = 2; // For use by the AmlStackIR compiler
        public const int FirstReservedLocal = 8;
        public const int NumLocals = FirstReservedLocal + ReservedLocals;
        VariableIoLocation[] locals = new VariableIoLocation[NumLocals];
        public const int NumArgs = 7;
        VariableIoLocation[] args = new VariableIoLocation[NumArgs];
        
        StackIRNode[] instructions;
        int currentInstructionIndex;

        private class AcpiNamespaceNodeSet : IEnumerable
        {
            private ArrayList nodeSet = new ArrayList();

            public void Add(AcpiNamespace.Node node)
            {
                if (!nodeSet.Contains(node)) {
                    nodeSet.Add(node);
                }
            }

            public IEnumerator GetEnumerator()
            {
                return nodeSet.GetEnumerator();
            }
        }

        AcpiNamespaceNodeSet methodTemporaryNamespaceObjectSet = new AcpiNamespaceNodeSet();

        public AmlStackFrame(StackIRNode[] instructions, AcpiObject.AcpiObject[] args)
        {
            this.instructions = instructions;
            currentInstructionIndex = 0;
            
            int i;
            for (i = 0; i < locals.Length; i++) {
                locals[i] = new VariableIoLocation();
            }
            for (i = 0; i < args.Length; i++) {
                this.args[i] = new VariableIoLocation(args[i]);
            }
            for (; i < this.args.Length; i++) {
                this.args[i] = new VariableIoLocation();
            }
        }

        public void NotifyCreateNodeAt(NodePath nodePath, Node node)
        {
            methodTemporaryNamespaceObjectSet.Add(node);
        }

        public void Dispose()
        {
            foreach (Node node in methodTemporaryNamespaceObjectSet) {
                node.Remove();
            }
        }

        public VariableIoLocation GetArgument(int argNum)
        {
            return args[argNum];
        }

        public void SetArgument(int argNum, AcpiObject.AcpiObject value)
        {
            args[argNum].Write(value);
        }

        public VariableIoLocation GetLocal(int localNum)
        {
            return locals[localNum];
        }

        public void SetLocal(int localNum, AcpiObject.AcpiObject value)
        {
            locals[localNum].Write(value);
        }

        public StackIRNode GetNextInstruction()
        {
            return instructions[currentInstructionIndex];
        }

        public void AdvanceInstruction()
        {
            currentInstructionIndex++;
        }
        
        public void JumpTo(int index)
        {
            currentInstructionIndex = index;
        }
    }

    public delegate void MethodResultCallback(AcpiObject.AcpiObject value);

    public class AmlInterpreterThread
    {
        private class AmlStack
        {
            private Stack stack = new Stack();

            public void Push(AmlStackFrame node)
            {
                stack.Push(node);
            }

            public AmlStackFrame Pop()
            {
                return (AmlStackFrame)stack.Pop();
            }

            public AmlStackFrame Peek()
            {
                return (AmlStackFrame)stack.Peek();
            }

            public bool IsEmpty()
            {
                return stack.Count == 0;
            }
        }

        private class IoLocationStack
        {
            private Stack stack = new Stack();

            public void Push(IoLocation node)
            {
                stack.Push(node);
            }

            public IoLocation Pop()
            {
                return (IoLocation)stack.Pop();
            }

            public IoLocation Peek()
            {
                return (IoLocation)stack.Peek();
            }
        }

        AcpiNamespace acpiNamespace;
        AbsoluteNodePath currentPath;
        AmlStack frameStack = new AmlStack();
        bool exited = false;
        bool blocked = false;
        AcpiObject.AcpiObject exitValue;
        IoLocationStack stack = new IoLocationStack();
        MethodResultCallback callback;
        IOperationRegionAccessor operationRegionAccessor;

        public AmlInterpreterThread(AcpiNamespace acpiNamespace, MethodResultCallback callback,
                                    IOperationRegionAccessor operationRegionAccessor)
        {
            this.acpiNamespace = acpiNamespace;
            this.currentPath = AbsoluteNodePath.CreateRoot();
            this.callback = callback;
            this.operationRegionAccessor = operationRegionAccessor;
        }

        public void InvokeMethod(Method method, AcpiObject.AcpiObject[] parameters)
        {
            method.Invoke(this, parameters, acpiNamespace);
        }

        public void InvokeMethod(AbsoluteNodePath nodePath, AcpiObject.AcpiObject[] parameters)
        {
            Node methodNode = acpiNamespace.LookupNode(nodePath);
            if (methodNode == null) {
                throw new MethodNotFoundInterpretException();
            }
            if (!(methodNode.Value is AcpiObject.BytecodeMethod)) {
                throw new AmlTypeException();
            }
            this.currentPath = methodNode.Path;
            ((AcpiObject.Method)(methodNode.Value)).Invoke(this, parameters, acpiNamespace);
        }

        public void PushFrame(AmlStackFrame frame)
        {
            frameStack.Push(frame);
        }

        public void Return(AcpiObject.AcpiObject result)
        {
            frameStack.Pop().Dispose();
            if (frameStack.IsEmpty()) {
                Exit(result);
            }
            else {
                Push(new ValueIoLocation(result));
            }
        }

        public AmlStackFrame CurrentFrame
        {
            get
            {
                return frameStack.Peek();
            }
        }

        public IOperationRegionAccessor OperationRegionAccessor
        {
            get
            {
                return operationRegionAccessor;
            }
        }

        public void Step()
        {
            if (Exited || Blocked) {
                throw new InterpretException("Tried to step exited or blocked thread");
            }
            new InterpretStepVisitor(this).Step();
        }

        public bool Exited
        {
            get
            {
                return exited;
            }
        }

        public void Exit(AcpiObject.AcpiObject exitValue)
        {
            exited = true;
            this.exitValue = exitValue;
            if (callback != null) {
                callback(ExitValue);
            }
        }

        public AcpiObject.AcpiObject ExitValue
        {
            get
            {
                return exitValue;
            }
        }

        public bool Blocked
        {
            get
            {
                return blocked;
            }
        }

        public void Block()
        {
            blocked = true;
        }

        public void Notify()
        {
            blocked = false;
        }

        public Node LookupNode(NodePath nodePath)
        {
            return acpiNamespace.LookupNode(nodePath, currentPath);
        }

        public Node CreateNodeAt(NodePath nodePath)
        {
            Node node = acpiNamespace.CreateNodeAt(nodePath, currentPath);
            frameStack.Peek().NotifyCreateNodeAt(nodePath, node);
            return node;
        }

        public void Push(IoLocation location)
        {
            stack.Push(location);
        }

        public IoLocation Pop()
        {
            return stack.Pop();
        }
    }

    public class InterpretStepVisitor : StackIRNodeVisitor
    {
        AmlInterpreterThread thread;

        public InterpretStepVisitor(AmlInterpreterThread thread)
        {
            this.thread = thread;
        }

        private AmlStackFrame Frame
        {
            get
            {
                return thread.CurrentFrame;
            }
        }

        public void Step()
        {
            AmlStackFrame frame = Frame;
            frame.GetNextInstruction().Accept(this);
            if (!thread.Blocked && !thread.Exited) {
                frame.AdvanceInstruction();
            }
        }

        public override void Visit(Jump node)
        {
            Frame.JumpTo(node.Target - 1); // Minus one because IP will still advance
        }

        public override void Visit(JumpIfNonZero node)
        {
            AcpiObject.AcpiObject predicate = thread.Pop().Read();
            if (predicate.GetAsInt().Value != 0) {
                Frame.JumpTo(node.ThenTarget - 1); // Minus one because IP will still advance
            }
        }

        public override void Visit(JumpIfNodePathExists node)
        {
            if (thread.LookupNode(node.NodePath) != null) {
                Frame.JumpTo(node.ThenTarget - 1); // Minus one because IP will still advance
            }
        }

        public override void Visit(PushArgObj node)
        {
            thread.Push(Frame.GetArgument(node.ArgNum));
        }

        public override void Visit(PushLocalObj node)
        {
            thread.Push(Frame.GetLocal(node.LocalNum));
        }

        public override void Visit(PushDebugObj node)
        {
            thread.Push(new DebugIoLocation());
        }

        public override void Visit(PushNodePath node)
        {
            Node pathNode = thread.LookupNode(node.Value);
            thread.Push(new NodePathIoLocation(pathNode));
            if (pathNode.Value is Method && pathNode.Value.GetAsMethod().ArgCount == 0) {
                Visit(new MethodCall());
            }
        }

        public override void Visit(PushConst node)
        {
            thread.Push(new ValueIoLocation(node.Value));
        }

        public override void Visit(Discard node)
        {
            thread.Pop();
        }

        public override void Visit(Store node)
        {
            IoLocation value = thread.Pop();
            IoLocation destination = thread.Pop();
            destination.Write(value.Read());
            thread.Push(destination);
        }

        public override void Visit(MethodCall node)
        {
            IoLocation methodLocation = thread.Pop();
            Method method = methodLocation.Read().GetAsMethod();
            
            AcpiObject.AcpiObject[] args = new AcpiObject.AcpiObject[method.ArgCount];
            for (int i = 0; i < method.ArgCount; i++) {
                args[i] = thread.Pop().Read();
            }

            thread.InvokeMethod(method, args);
        }

        public override void Visit(Index node)
        {
            AcpiObject.AcpiObject container = thread.Pop().Read();
            ulong index = thread.Pop().Read().GetAsInt().Value;
            thread.Push(new ValueIoLocation(new AcpiObject.BufferField(container, index * 8, 8)));
        }

        public override void Visit(ShiftLeft node)
        {
            ulong left = thread.Pop().Read().GetAsInt().Value;
            ulong right = thread.Pop().Read().GetAsInt().Value;
            PushInteger(left << (int)right);
        }

        public override void Visit(ShiftRight node)
        {
            ulong left = thread.Pop().Read().GetAsInt().Value;
            ulong right = thread.Pop().Read().GetAsInt().Value;
            PushInteger(left >> (int)right);
        }

        public override void Visit(Concatenate node)
        {
            AcpiObject.AcpiObject leftObj = thread.Pop().Read();
            AcpiObject.AcpiObject rightObj = thread.Pop().Read();

            if (leftObj is AcpiObject.String) {
                string leftStr = leftObj.GetAsString().Value;
                string rightStr = rightObj.GetAsString().Value;
                thread.Push(new ValueIoLocation(new AcpiObject.String(leftStr + rightStr)));
            }
            else {
                throw new InterpretException("Concatenate only implemented for strings");
            }
        }

        public override void Visit(Add node)
        {
            ulong left = thread.Pop().Read().GetAsInt().Value;
            ulong right = thread.Pop().Read().GetAsInt().Value;
            PushInteger(left + right);
        }

        public override void Visit(Subtract node)
        {
            ulong left = thread.Pop().Read().GetAsInt().Value;
            ulong right = thread.Pop().Read().GetAsInt().Value;
            PushInteger(left - right);
        }

        public override void Visit(Multiply node)
        {
            ulong left = thread.Pop().Read().GetAsInt().Value;
            ulong right = thread.Pop().Read().GetAsInt().Value;
            PushInteger(left * right);
        }

        public override void Visit(Divide node)
        {
            ulong left = thread.Pop().Read().GetAsInt().Value;
            ulong right = thread.Pop().Read().GetAsInt().Value;
            PushInteger(left / right);
        }

        public override void Visit(Remainder node)
        {
            ulong left = thread.Pop().Read().GetAsInt().Value;
            ulong right = thread.Pop().Read().GetAsInt().Value;
            PushInteger(left % right);
        }

        public override void Visit(LogicalOp node)
        {
            ulong left = thread.Pop().Read().GetAsInt().Value;
            ulong right = 0;

            if (node.Operator != LogicalOp.Op.Not) {
                right = thread.Pop().Read().GetAsInt().Value;
            }

            switch (node.Operator) {
                case LogicalOp.Op.Less:
                    PushInteger(left < right ? 1u : 0u);
                    break;
                case LogicalOp.Op.LessEq:
                    PushInteger(left <= right ? 1u : 0u);
                    break;
                case LogicalOp.Op.Equal:
                    PushInteger(left == right ? 1u : 0u);
                    break;
                case LogicalOp.Op.Greater:
                    PushInteger(left > right ? 1u : 0u);
                    break;
                case LogicalOp.Op.GreaterEq:
                    PushInteger(left >= right ? 1u : 0u);
                    break;
                case LogicalOp.Op.And:
                    PushInteger(left != 0 && right != 0 ? 1u : 0u);
                    break;
                case LogicalOp.Op.Or:
                    PushInteger(left != 0 || right != 0 ? 1u : 0u);
                    break;
                case LogicalOp.Op.Not:
                    PushInteger(left == 0 ? 1u : 0u);
                    break;
            }
        }

        public override void Visit(BitOp node)
        {
            ulong left = thread.Pop().Read().GetAsInt().Value;
            ulong right = 0;

            if (node.Operator != BitOp.Op.Not) {
                right = thread.Pop().Read().GetAsInt().Value;
            }

            switch (node.Operator) {
                case BitOp.Op.And:
                    PushInteger(left & right);
                    break;
                case BitOp.Op.Or:
                    PushInteger(left | right);
                    break;
                case BitOp.Op.NAnd:
                    PushInteger(~(left & right));
                    break;
                case BitOp.Op.NOr:
                    PushInteger(~(left | right));
                    break;
                case BitOp.Op.Not:
                    PushInteger(~left);
                    break;
                case BitOp.Op.XOr:
                    PushInteger(left ^ right);
                    break;
            }
        }

        public override void Visit(Return node)
        {
            thread.Return(thread.Pop().Read());
        }

        public override void Visit(CreateField node)
        {
            AcpiObject.Buffer sourceBuff = thread.Pop().Read().GetAsBuffer();
            ulong startBitIndex = thread.Pop().Read().GetAsInt().Value;
            ulong numBits = thread.Pop().Read().GetAsInt().Value;

            Node destinationNode = thread.CreateNodeAt(node.NodePath);
            destinationNode.Value = new BufferField(sourceBuff, startBitIndex, numBits);
        }

        public override void Visit(DefBuffer node)
        {
            ulong size = thread.Pop().Read().GetAsInt().Value;
            byte[] contents = new byte[size];
            byte[] initializer = node.Initializer;
            Array.Copy(initializer, contents, initializer.Length);
            thread.Push(new ValueIoLocation(new AcpiObject.Buffer(contents)));
        }

        public override void Visit(FindSetLeftBit node)
        {
            // Result: Zero indicates no bit set; k indicates the kth bit from the right is set
            ulong value = thread.Pop().Read().GetAsInt().Value;
            for (int i = 63; i >= 0; i--) {
                if (((value >> i) & 1) != 0) {
                    PushInteger((ulong)(i + 1));
                    return;
                }
            }
            PushInteger(0);
        }

        public override void Visit(FindSetRightBit node)
        {
            // Result: Zero indicates no bit set; k indicates the kth bit from the right is set
            ulong value = thread.Pop().Read().GetAsInt().Value;
            for (ulong i = 1; i <= 64; i++) {
                if ((value & 1) != 0) {
                    PushInteger(i);
                    return;
                }
                value >>= 1;
            }
            PushInteger(0);
        }

        public override void Visit(SizeOf node)
        {
            PushInteger(thread.Pop().Read().Size);
        }

        public override void Visit(DefName node)
        {
            Node namespaceNode = thread.CreateNodeAt(node.NodePath);
            namespaceNode.Value = thread.Pop().Read();
        }

        public override void Visit(Load node)
        {
            throw new InterpretException("TODO");
        }

        public override void Visit(Stall node)
        {
            throw new InterpretException("TODO");
        }

        public override void Visit(Match node)
        {
            throw new InterpretException("TODO");
        }

        public override void Visit(DerefOf node)
        {
            thread.Push(new ValueIoLocation(thread.Pop().Read().Dereference()));
        }

        public override void Visit(StackIR.Package node)
        {
            throw new InterpretException("TODO");
        }

        private void PushInteger(ulong i)
        {
            thread.Push(new ValueIoLocation(new AcpiObject.Integer(i)));
        }

        public override void Visit(Notify node)
        {
            throw new InterpretException("TODO");
        }

        public override void Visit(Sleep node)
        {
            throw new InterpretException("TODO");
        }

        public override void Visit(RefOf node)
        {
            // TODO: This implementation is not strictly correct, as it
            // will not continue to refer to the location if its value is
            // replaced (as can happen if it's currently uninitialized).
            // But currently the result of RefOf isn't used on any of
            // the tested machines, so postponing this.
            thread.Push(new ValueIoLocation(new AcpiObject.ObjectReference(new AcpiObjectCell(thread.Pop().Read()))));
        }

        public override void Visit(StackIR.OperationRegion node)
        {
            ulong startIndex = thread.Pop().Read().GetAsInt().Value;
            ulong length = thread.Pop().Read().GetAsInt().Value;

            Node namespaceNode = thread.CreateNodeAt(node.NodePath);
            namespaceNode.Value = new AcpiObject.OperationRegion(thread.OperationRegionAccessor,
                                                                 node.OperationSpace,
                                                                 startIndex, length);
        }

        public override void Visit(Field node)
        {
            Node operationRegionNode = thread.LookupNode(node.NodePath);
            SortedList fields = FieldUnit.CreateFromFieldList((AcpiObject.OperationRegion)(operationRegionNode.Value.GetTarget()),
                                                              node.FieldElements,
                                                              node.FieldFlags.accessType, AccessAttrib.SMBNone,
                                                              node.FieldFlags.lockRule, node.FieldFlags.updateRule);
            foreach (DictionaryEntry entry in fields) {
                Node namespaceNode = thread.CreateNodeAt(
                    new NodePath(false/*isAbsolute*/, 0, new string[] { (string)entry.Key }));
                namespaceNode.Value = (FieldUnit)entry.Value;
            }
        }

        public override void Visit(Acquire node)
        {
            Mutex mutex = thread.Pop().Read().GetAsMutex();
            mutex.Acquire(thread);
        }

        public override void Visit(Release node)
        {
            Mutex mutex = thread.Pop().Read().GetAsMutex();
            mutex.Release(thread);
        }

        public override void Visit(ToBuffer node)
        {
            throw new InterpretException("TODO");
        }

        public override void Visit(ToInteger node)
        {
            throw new InterpretException("TODO");
        }

        public override void Visit(ToString node)
        {
            throw new InterpretException("TODO");
        }
    }

    public class AmlInterpreter
    {
        private class AmlInterpreterThreadList : IEnumerable
        {
            Queue queue = new Queue();

            public void Enqueue(AmlInterpreterThread thread)
            {
                queue.Enqueue(thread);
            }

            public AmlInterpreterThread Dequeue()
            {
                return (AmlInterpreterThread)queue.Dequeue();
            }

            public IEnumerator GetEnumerator()
            {
                return queue.GetEnumerator();
            }

            public int Count
            {
                get
                {
                    return queue.Count;
                }
            }
        }

        AmlInterpreterThreadList threadsReadyToRun = new AmlInterpreterThreadList();
        AmlInterpreterThreadList threadsBlocked = new AmlInterpreterThreadList();
        AcpiNamespace acpiNamespace;
        IOperationRegionAccessor operationRegionAccessor;

        public AmlInterpreter(AcpiNamespace acpiNamespace,
                              IOperationRegionAccessor operationRegionAccessor)
        {
            this.acpiNamespace = acpiNamespace;
            this.operationRegionAccessor = operationRegionAccessor;
        }

        public AmlParser.ParseSuccess ParseMethodBodies()
        {
            foreach (Node node in acpiNamespace.GetAllNodes()) {
                if (!(node.Value is AcpiObject.BytecodeMethod)) {
                    continue;
                }

                AcpiObject.BytecodeMethod method = (AcpiObject.BytecodeMethod)node.Value;

#if SINGULARITY_KERNEL
                DebugStub.WriteLine("Parsing method " + node.Path.ToString());
#else
                Console.WriteLine("Parsing method " + node.Path.ToString());
#endif

                if (method.Parse(acpiNamespace, node.Path) == AmlParser.ParseSuccess.Failure) {
                    return AmlParser.ParseSuccess.Failure;
                }
            }
            return AmlParser.ParseSuccess.Success;
        }

        public AmlInterpreterThread InvokeMethodOnNewThread(MethodResultCallback callback,
                                                            AbsoluteNodePath nodePath, AcpiObject.AcpiObject[] parameters)
        {
            AmlInterpreterThread thread = new AmlInterpreterThread(acpiNamespace, callback, operationRegionAccessor);
            thread.InvokeMethod(nodePath, parameters);
            threadsReadyToRun.Enqueue(thread);
            return thread;
        }

        public void Run()
        {
            while (threadsReadyToRun.Count > 0) {
                while (threadsReadyToRun.Count > 0) {
                    AmlInterpreterThread currentThread = threadsReadyToRun.Dequeue();
                    while (!currentThread.Blocked && !currentThread.Exited) {
                        currentThread.Step();
                    }
                    if (currentThread.Blocked) {
                        threadsBlocked.Enqueue(currentThread);
                    }
                }
                foreach (AmlInterpreterThread thread in threadsBlocked) {
                    if (!thread.Blocked) {
                        threadsReadyToRun.Enqueue(thread);
                    }
                }
            }
            if (threadsBlocked.Count > 0) {
                throw new InterpretException("Deadlock between ACPI AML threads detected");
            }
        }
    }

    internal class ByteBufferAmlStreamAdapter : IAmlStream
    {
        byte[] buffer;

        public ByteBufferAmlStreamAdapter(byte[] buffer)
        {
            this.buffer = buffer;
        }

        public byte ReadByteData(ref int offset)
        {
            try {
                byte result = buffer[offset];
                offset++;
                return result;
            }
            catch (System.IndexOutOfRangeException) {
                throw new EndOfAmlStreamException();
            }
        }

        public bool TryReadByteData(ref int offset, out byte result)
        {
            if (offset >= buffer.Length) {
                result = 0;
                return false;
            }
            else {
                result = buffer[offset];
                offset++;
                return true;
            }
        }

        public char ReadChar(ref int offset)
        {
            try {
                char result = (char)buffer[offset];
                offset++;
                return result;
            }
            catch (System.IndexOutOfRangeException) {
                throw new EndOfAmlStreamException();
            }
        }

        public byte[] ReadByteDataArray(ref int offset, int length)
        {
            try {
                byte[] result = new byte[length];
                System.Array.Copy(buffer, offset, result, 0, length);
                offset += length;
                return result;
            }
            catch (System.ArgumentException) {
                throw new EndOfAmlStreamException();
            }
            catch (System.IndexOutOfRangeException) {
                throw new EndOfAmlStreamException();
            }
        }

        public bool TryReadByteDataArray(ref int offset, int length, out byte[] result)
        {
            if (offset + length > buffer.Length) {
                result = null;
                return false;
            }
            else {
                result = new byte[length];
                System.Array.Copy(buffer, offset, result, 0, length);
                offset += length;
                return true;
            }
        }

        public UInt16 ReadWordData(ref int offset)
        {
            try {
                UInt16 result = (UInt16)(buffer[offset] +
                                             (((UInt16)buffer[offset + 1]) << 8));;
                offset += 2;
                return result;
            }
            catch (System.IndexOutOfRangeException) {
                throw new EndOfAmlStreamException();
            }
        }

        public UInt32 ReadDWordData(ref int offset)
        {
            try {
                UInt32 result = (UInt32)(buffer[offset] +
                                               (((UInt32)buffer[offset + 1]) << 8) +
                                               (((UInt32)buffer[offset + 2]) << 16) +
                                               (((UInt32)buffer[offset + 3]) << 24));
                offset += 4;
                return result;
            }
            catch (System.IndexOutOfRangeException) {
                throw new EndOfAmlStreamException();
            }
        }

        public UInt64 ReadQWordData(ref int offset)
        {
            try {
                UInt64 result = (UInt64)(buffer[offset] +
                                               (((UInt64)buffer[offset + 1]) << 8) +
                                               (((UInt64)buffer[offset + 2]) << 16) +
                                               (((UInt64)buffer[offset + 3]) << 24) +
                                               (((UInt64)buffer[offset + 4]) << 32) +
                                               (((UInt64)buffer[offset + 5]) << 40) +
                                               (((UInt64)buffer[offset + 6]) << 48) +
                                               (((UInt64)buffer[offset + 7]) << 56));
                offset += 8;
                return result;
            }
            catch (System.IndexOutOfRangeException) {
                throw new EndOfAmlStreamException();
            }
        }
    }
}