/* Copyright (c) 2008-2011 - Eric P. Mangold
 * Released under the terms of the MIT/X11 license - see LICENSE.txt */
using System.Net;

namespace AMP.Tests
{
    using System;
    using NUnit.Framework;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;
    using System.Net.Sockets;

    public class MySillyForm : Form
    {
        public int formThreadId;
        public int callbackThreadId;

        public ManualResetEvent waitHandle;
        public IAsyncResult ar;

        public MySillyForm(ManualResetEvent waitHandle)
        {
            this.waitHandle = waitHandle;
            Load += MySillyForm_Load;
        }

        public void MySillyForm_Load(object sender, EventArgs e)
        {
            formThreadId = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("Form thread {0}", formThreadId);
            Console.WriteLine(String.Format("Form ctx: {0}", SynchronizationContext.Current));

            // when we supply any delegates to an AMP method the current
            // synchronization context will be captured. This means that
            // calls to run those delegates will be posted to the synchronization
            // context's message loop, and will thus execute in the same thread
            // that the original AMP call was made in.

            var result = new SimpleAsyncResult();
            result.callback = delegate
            {
                callbackThreadId = Thread.CurrentThread.ManagedThreadId;
                waitHandle.Set();
            };

            // run the callback on a foreign thread, as it would be if this callback were
            // the result of receiving network traffic
            var t = new Thread(result.doCallback);
            t.Start();
        }
    }

    public class B
    {
        public static byte[] b(string foo)
        {
            return Encoding.UTF8.GetBytes(foo);
        }
    }

    public static class ByteArrayExtensions
    {
        public static string ToHexString(this byte[] bytes)
        {
            StringBuilder sb = new StringBuilder();
            foreach (byte b in bytes)
            {
                sb.Append("\\x" + b.ToString("X2"));
            }
            return sb.ToString();
        }
    }

    [TestFixture]
    public class StringDicts
    {
        internal AMP.Msg_Raw a;
        internal AMP.Msg_Raw b;
        [SetUp]
        public void Init()
        {
            a = new AMP.Msg_Raw();
            b = new AMP.Msg_Raw();
        }

        [Test]
        public void empty()
        {
            Assert.That(a, Is.EqualTo(b));
        }

        [Test]
        public void diff_size()
        {
            // a stays empty
            b.Add("key", B.b("value"));
            Assert.That(a, Is.Not.EqualTo(b));
        }

        [Test]
        public void diff_keys()
        {
            a.Add("key", B.b("value"));
            b.Add("key2", B.b("value"));
            Assert.That(a, Is.Not.EqualTo(b));
        }

        [Test]
        public void diff_values()
        {
            a.Add("key", B.b("value"));
            b.Add("key", B.b("value2"));
            Assert.That(a, Is.Not.EqualTo(b));
        }

        [Test]
        public void equal_size_1()
        {
            a.Add("key", B.b("value"));
            b.Add("key", B.b("value"));
            Assert.That(a, Is.EqualTo(b));
        }

        [Test]
        public void equal_size_2()
        {
            a.Add("key", B.b("value"));
            a.Add("key2", B.b("value2"));
            b.Add("key", B.b("value"));
            b.Add("key2", B.b("value2"));
            Assert.That(a, Is.EqualTo(b));
        }
    }

    public class Test_AMP_Protocol : AMP.Protocol
    {
        internal List<AMP.Msg_Raw> messages = new List<AMP.Msg_Raw>();
        internal override void SendMessage(AMP.Msg_Raw raw_msg)
        {
            messages.Add(raw_msg);
        }
    }

    namespace Protocol
    {
        [TestFixture]
        public class BuildAMPWireCommand
        {
            [Test]
            [ExpectedException(typeof(AMP.Error.MessageEmptyError))]
            public void build_msg_empty()
            {
                AMP.Protocol.BuildAMPWireCommand(new AMP.Msg_Raw());
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.InvalidKeySizeError))]
            public void build_msg_null_key()
            {
                var amp_msg = new AMP.Msg_Raw { { "", B.b("ignored") } };
                AMP.Protocol.BuildAMPWireCommand(amp_msg);
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.InvalidKeySizeError))]
            public void build_msg_key_too_long()
            {
                var amp_msg = new AMP.Msg_Raw();
                const string chunk = "0123456789ABCDEF";
                string key = "";
                for (int i = 0; i < 16; i++)
                { // 16 bytes 16 times is 256 bytes which is 1 byte too long
                    key += chunk;
                }
                Assert.That(key.Length, Is.EqualTo(256));
                amp_msg.Add(key, B.b("ignored"));
                AMP.Protocol.BuildAMPWireCommand(amp_msg);
            }

            [Test]
            public void build_msg_max_key_len()
            // this also doubles as a test for checking that empty
            // values in the StringDict are handled OK
            {
                var amp_msg = new AMP.Msg_Raw();
                const string chunk = "0123456789ABCDEF";
                string key = "";
                for (int i = 0; i < 15; i++)
                { // 16 bytes 15 times is 240 bytes
                    key += chunk;
                }
                key += "0123456789ABCDE"; // 15 more bytes gives us 255
                Assert.That(key.Length, Is.EqualTo(255));

                amp_msg.Add(key, B.b(""));
                byte[] result = AMP.Protocol.BuildAMPWireCommand(amp_msg);
                var expected_list = new ArrayList { (byte)0x00, (byte)0xff };
                expected_list.AddRange(Encoding.UTF8.GetBytes(key));
                expected_list.Add((byte)0x00);
                expected_list.Add((byte)0x00);
                expected_list.Add((byte)0x00);
                expected_list.Add((byte)0x00);
                var expected = (byte[])expected_list.ToArray(typeof(byte));
                Assert.That(expected, Is.EqualTo(result));
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.InvalidValueSizeError))]
            public void build_msg_value_too_long()
            {
                var amp_msg = new AMP.Msg_Raw();
                const string chunk = "0123456789ABCDEF";
                string value = "";
                for (int i = 0; i < 4096; i++)
                { // 16 bytes 4096 times is 65536 bytes (1 byte too long)
                    value += chunk;
                }
                Assert.That(value.Length, Is.EqualTo(65536));
                amp_msg.Add("key", B.b(value));
                AMP.Protocol.BuildAMPWireCommand(amp_msg);
            }

            [Test]
            public void build_msg_max_value_len()
            {
                var amp_msg = new AMP.Msg_Raw();
                const string chunk = "0123456789ABCDEF";
                string value = "";
                for (int i = 0; i < 4095; i++)
                { // 16 bytes 15 times is 65520 bytes
                    value += chunk;
                }
                value += "0123456789ABCDE"; // 15 more bytes gives us 65535
                Assert.That(value.Length, Is.EqualTo(65535));

                amp_msg.Add("k", B.b(value));
                byte[] result = AMP.Protocol.BuildAMPWireCommand(amp_msg);
                var expected_list = new ArrayList { (byte)0x00, (byte)0x01, (byte)0x6B, (byte)0xff, (byte)0xff };
                expected_list.AddRange(Encoding.UTF8.GetBytes(value));
                expected_list.Add((byte)0x00);
                expected_list.Add((byte)0x00);
                var expected = (byte[])expected_list.ToArray(typeof(byte));
                Assert.That(expected, Is.EqualTo(result));
            }
        }

        /// <summary>
        /// Test the actual AMP wire-protcol parsing and dispatching.
        /// XXX TODO need to test that processCommand() processAnswer() and processError() is called
        /// correctly... we probably want to override these method and simply capture the arguments
        /// they are called with.
        /// </summary>
        [TestFixture]
        public class Parser
        {
            internal AMP.Protocol proto;
            [SetUp]
            public void Init()
            {
                proto = new Test_AMP_Protocol();
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.ProtocolError))]
            public void parse_initial_null()
            {
                byte[] chunk = { 0, 0 };
                proto.DataReceived(chunk);
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.ProtocolError))]
            public void parse_key_too_big()
            {
                byte[] chunk = { 1, 0 };
                proto.DataReceived(chunk);
            }

            [Test]
            public void parse_one_byte_key_value()
            {
                var amp_msg = new AMP.Msg_Raw { { "x", B.b("y") } };
                byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg);
                proto.DataReceived(bytes);
                Assert.That(amp_msg, Is.EqualTo(proto.msg_raw));
            }

            [Test]
            public void parse_one_byte_key_empty_value()
            {
                var amp_msg = new AMP.Msg_Raw { { "x", B.b("") } };
                byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg);
                proto.DataReceived(bytes);
                foreach (byte b in bytes)
                {
                    Console.Write(String.Format("\\x{0:x2}", b));
                }
                Console.WriteLine();

                // NUnit kinda sucks a lot. This doesn't work at all for no apparent reason.
                Assert.That(amp_msg, Is.EqualTo(proto.msg_raw));
            }

            [Test]
            public void parse_simple_msg()
            {
                var amp_msg = new AMP.Msg_Raw
                              {
                                  {"_ask", B.b("14253")},
                                  {"_command", B.b("Sum")},
                                  {"x", B.b("15")},
                                  {"y", B.b("45")}
                              };
                byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg);
                proto.DataReceived(bytes);
                Assert.That(amp_msg, Is.EqualTo(proto.msg_raw));
            }

            [Test]
            public void parse_simple_msg_byte_at_a_time()
            {
                var amp_msg = new AMP.Msg_Raw
                              {
                                  {"_ask", B.b("14253")},
                                  {"_command", B.b("Sum")},
                                  {"x", B.b("15")},
                                  {"y", B.b("45")}
                              };
                byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg);
                var buf = new byte[1];
                foreach (byte b in bytes)
                {
                    buf[0] = b;
                    proto.DataReceived(buf);
                }
                Assert.That(amp_msg, Is.EqualTo(proto.msg_raw));
            }

            [Test]
            public void parse_double_msg()
            {
                var amp_msg = new AMP.Msg_Raw
                              {
                                  {"_ask", B.b("14253")},
                                  {"_command", B.b("Sum")},
                                  {"x", B.b("15")},
                                  {"y", B.b("45")}
                              };

                var amp_msg2 = new AMP.Msg_Raw
                               {
                                   {"_ask", B.b("321")},
                                   {"_command", B.b("Power")},
                                   {"x", B.b("51")},
                                   {"y", B.b("54")}
                               };

                byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg);
                byte[] bytes2 = AMP.Protocol.BuildAMPWireCommand(amp_msg2);

                proto.DataReceived(bytes);
                Assert.That(amp_msg, Is.EqualTo(proto.msg_raw));
                proto.DataReceived(bytes2);
                Assert.That(amp_msg2, Is.EqualTo(proto.msg_raw));

                var bytes3 = new byte[bytes.Length + bytes2.Length];
                bytes2.CopyTo(bytes3, 0);
                bytes.CopyTo(bytes3, bytes2.Length);

                proto.DataReceived(bytes3);
                Assert.That(amp_msg, Is.EqualTo(proto.msg_raw));
            }
        }
    }

    internal class AMP_TEST_Command : AMP.Command
    {
        public AMP_TEST_Command()
            : base("test")
        {
            AddArgument("one", new AMP.Type.String());
            AddArgument("two", new AMP.Type.String());

            AddResponse("result", new AMP.Type.String());
        }
    }

    [TestFixture]
    public class Command
    {
        internal AMP.Command cmd;
        internal AMP.Command test_cmd;
        [SetUp]
        public void Init()
        {
            cmd = new AMP.Command("cmd");
            test_cmd = new AMP_TEST_Command();
        }
        [Test]
        public void toRawMessage_request_empty()
        {
            var amp_typed_msg = new AMP.Msg();
            AMP.Msg_Raw result = cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.REQUEST);
            Assert.That(result.Count, Is.EqualTo(0));
        }
        [Test]
        public void toRawMessage_response_empty()
        {
            var amp_typed_msg = new AMP.Msg();
            AMP.Msg_Raw result = cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.RESPONSE);
            Assert.That(result.Count, Is.EqualTo(0));
        }

        [Test]
        [ExpectedException(typeof(AMP.Error.ParameterNotFound))]
        public void toRawMessage_request_missing_parameter()
        {
            var amp_typed_msg = new AMP.Msg();
            test_cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.REQUEST);
        }
        [Test]
        [ExpectedException(typeof(AMP.Error.ParameterNotFound))]
        public void toRawMessage_response_missing_parameter()
        {
            var amp_typed_msg = new AMP.Msg();
            test_cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.RESPONSE);
        }
        [Test]
        public void toRawMessage_normal()
        {
            var amp_typed_msg = new AMP.Msg { { "one", (object)"one_value" }, { "two", (object)"two_value" } };

            AMP.Msg_Raw result = test_cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.REQUEST);
            Assert.That(result.Count, Is.EqualTo(2));
            Assert.That(B.b("one_value"), Is.EqualTo(result["one"]));
            Assert.That(B.b("two_value"), Is.EqualTo(result["two"]));

            amp_typed_msg = new AMP.Msg { { "result", (object)"one_valuetwo_value" } };

            result = test_cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.RESPONSE);
            Assert.That(result.Count, Is.EqualTo(1));
            Assert.That(B.b("one_valuetwo_value"), Is.EqualTo(result["result"]));
        }

        [Test]
        public void toTypedMessage_request_empty()
        {
            var amp_raw_msg = new AMP.Msg_Raw();
            AMP.Msg result = cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.REQUEST);
            Assert.That(result.Count, Is.EqualTo(0));
        }
        [Test]
        public void toTypedMessage_response_empty()
        {
            var amp_raw_msg = new AMP.Msg_Raw();
            AMP.Msg result = cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.RESPONSE);
            Assert.That(result.Count, Is.EqualTo(0));
        }

        [Test]
        [ExpectedException(typeof(AMP.Error.ParameterNotFound))]
        public void toTypedMessage_request_missing_parameter()
        {
            var amp_raw_msg = new AMP.Msg_Raw();
            test_cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.REQUEST);
        }
        [Test]
        [ExpectedException(typeof(AMP.Error.ParameterNotFound))]
        public void toTypedMessage_response_missing_parameter()
        {
            var amp_raw_msg = new AMP.Msg_Raw();
            test_cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.RESPONSE);
        }
        [Test]
        public void toTypedMessage_normal()
        {
            var amp_raw_msg = new AMP.Msg_Raw { { "one", B.b("one_value") }, { "two", B.b("two_value") } };

            AMP.Msg result = test_cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.REQUEST);
            Assert.That(result.Count, Is.EqualTo(2));
            Assert.That("one_value", Is.EqualTo(result["one"]));
            Assert.That("two_value", Is.EqualTo(result["two"]));

            amp_raw_msg = new AMP.Msg_Raw { { "result", B.b("one_valuetwo_value") } };

            result = test_cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.RESPONSE);
            Assert.That(result.Count, Is.EqualTo(1));
            Assert.That("one_valuetwo_value", Is.EqualTo(result["result"]));
        }

    }

    [TestFixture]
    public class Callbacks
    {
        public MySillyForm theForm;

        [SetUp]
        public void Init()
        {
        }

        // Test that the SimpleAsyncResult class captures the current synchronization context
        // when it is created, and dispatches to the context's thread at callback-time
        [Test]
        public void form_synchronize()
        {
            int testThreadId = Thread.CurrentThread.ManagedThreadId;
            int callbackThreadId = 0; // assigned in callback

            var result = new SimpleAsyncResult();
            result.callback = delegate { callbackThreadId = Thread.CurrentThread.ManagedThreadId; };

            // run the callback on a foreign thread, as it would be if this callback were
            // the result of receiving network traffic
            var tmpWaitHandle = new ManualResetEvent(false);
            var t = new Thread(() => { result.doCallback(); tmpWaitHandle.Set(); });
            t.Start();

            tmpWaitHandle.WaitOne();

            Console.WriteLine("Test thread     : {0}", testThreadId);
            Console.WriteLine("Callback thread : {0}", callbackThreadId);

            // Assert our callback actually ran and saved its thread ID
            Assert.That(callbackThreadId, Is.Not.EqualTo(0));

            // Assert the callback did happen on a foreign thread
            Assert.That(callbackThreadId, Is.Not.EqualTo(testThreadId));

            // NOW, we're going to run a Form on another thread, and have that Form's code create the
            // SimpleAsyncResult. We should see that the login callback happens ON THE FORM'S THREAD, which
            // is quite different than the semantics we see above.

            //System.Console.WriteLine("Test thread {0}", Thread.CurrentThread.ManagedThreadId);
            Assert.That(SynchronizationContext.Current, Is.Null);
            var waitHandle = new ManualResetEvent(false);
            var t2 = new Thread(() => _doForm(waitHandle));
            t2.Start();
            // The form will Set() the waitHandle once it is done.
            waitHandle.WaitOne();

            Assert.That(theForm.formThreadId, Is.EqualTo(theForm.callbackThreadId));

            theForm.Invoke(new MethodInvoker(theForm.Close));
        }

        public void _doForm(ManualResetEvent waitHandle)
        {
            theForm = new MySillyForm(waitHandle);
            theForm.ShowDialog();
        }
    }

    /*
    public class DummyTransport: IAMPTransport
    {
        public void RegisterProtocol(IAMPProtocol proto)
        {
        }
        public void Start()
        {
        }
        public void Write(byte[] bytes)
        {
        }
    }

    [TestFixture]
    public class AMP_Responder_Form_Tests
    {
        public ResponderTestForm theForm;

        public static byte[] B.b(string foo)
        {
            return Encoding.UTF8.GetBytes(foo);
        }

        /// <summary>
        /// Test that registering a responder captures the current
        /// synchronization context and that subsequent calls to the Responder
        /// are posted to context's message loop
        /// </summary>
        [Test]
        public void test_responder_form_synchronize()
        {
            var proto = new AMP.Protocol(new DummyTransport());
            theForm = new ResponderTestForm(proto);
            var t = new Thread(() => theForm.ShowDialog());
            t.Start();

            // wait till the Form has loaded and registered the responder
            theForm.onLoadHandle.WaitOne();

            // give the protocol some commands to process, so that it
            // calls the responders that the Form has registered
            var raw_msg = new AMP.Msg_Raw
                              {
                                  {"_command", B.b("SimpleCmd")},
                                  {"_ask", B.b("1")},
                                  {"test_val", B.b("test")},
                              };
            proto.DataReceived(AMP.Protocol.buildAMPWireCommand(raw_msg));
            raw_msg["_command"] = B.b("TestCmd");
            proto.DataReceived(AMP.Protocol.buildAMPWireCommand(raw_msg));

            // The form will Set() these wait handles when the responders runs.
            theForm.formRespondedHandle.WaitOne();
            theForm.syncCtxRespondedHandle.WaitOne();

            // verify that the responders ran on the Form's thread, not this one
            Assert.That(theForm.formThreadId, Is.EqualTo(theForm.formResponderThreadId));
            Assert.That(theForm.formThreadId, Is.EqualTo(theForm.syncCtxResponderThreadId));
            // close the Form
            theForm.Invoke(new MethodInvoker(theForm.Close));
        }

        public void _doForm(ManualResetEvent waitHandle)
        {
        }
    }

    public class ResponderTestForm : Form
    {
        public int formThreadId;
        public int formResponderThreadId;
        public int syncCtxResponderThreadId;

        public ManualResetEvent onLoadHandle = new ManualResetEvent(false);
        public ManualResetEvent formRespondedHandle = new ManualResetEvent(false);
        public ManualResetEvent syncCtxRespondedHandle = new ManualResetEvent(false);
        private AMP.Protocol proto;

        public ResponderTestForm(AMP.Protocol proto)
        {
            this.proto = proto;
            Load += ResponderTestForm_Load;
        }

        public void ResponderTestForm_Load(object sender, EventArgs e)
        {
            formThreadId = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("Form thread {0}", formThreadId);
            Console.WriteLine(String.Format("Form ctx: {0}", SynchronizationContext.Current));

            // passing 'this' causes calls to the responder use this.Invoke(...)
            proto.RegisterResponder(new SimpleCmd(), SimpleCmdResponder, this);

            // passing the current Synchronization Contextx causes calls to the responder
            // ctx.Send(...)
            proto.RegisterResponder(new TestCmd(), SimpleCmdResponder2, SynchronizationContext.Current);

            onLoadHandle.Set();
        }

        private object SimpleCmdResponder(AMP.Msg msg, object state)
        {
            formResponderThreadId = Thread.CurrentThread.ManagedThreadId;
            formRespondedHandle.Set();
            return new AMP.Msg {{"ret_val", "hello"}};
        }

        private object SimpleCmdResponder2(AMP.Msg msg, object state)
        {
            syncCtxResponderThreadId = Thread.CurrentThread.ManagedThreadId;
            syncCtxRespondedHandle.Set();
            return new AMP.Msg {{"ret_val", "hello"}};
        }
    }
     */

    public class TestCmd : AMP.Command
    {
        public TestCmd()
            : base("TestCmd")
        {
            AddArgument("test_val", new AMP.Type.String());
        }
    }

    /// <summary>
    /// Test that processFullMessage() does the Right Thing under all possible conditions.
    /// processFullMessage() is the processing function that handles every AMP "box" that 
    /// comes in off the wire. The "boxes" (represented by the AMP.Msg_Raw class) can 
    /// represent 3 major catagories of things:
    ///     - An AMP Command that the peer wishes to run.
    ///     - A response to an AMP Command we called on the peer.
    ///     - An error response to an AMP Command we called on the peer.
    /// </summary>
    public class AMP_Message_Dispatch_Base
    {
        // Set up a command that is used by several test cases
        public static string test_cmdName = "TestCmd";
        public static string test_cmdNameNoAnswer = "TestCmdNoAnswer";
        public class MyAMPError : AMP.Error.RemoteAmpError
        {
            public MyAMPError() { }
            public MyAMPError(byte[] errorCode, string description)
                : base(errorCode, description)
            {
            }
        }
        public class TestCmd : AMP.Command
        {
            public TestCmd()
                : base(test_cmdName)
            {
                AddArgument("test_val", new AMP.Type.String());
                AddResponse("ret_val", new AMP.Type.String());
                AddError(typeof(MyAMPError), B.b("MY_ERROR"));
                AddError(typeof(DivideByZeroException), B.b("ZERO_DIVISION"));
            }
        }
        /// <summary>
        /// TODO: write some tests that use this class. requiresAnswer is only a *hint*
        /// for the client-side portion of the protocol...
        /// </summary>
        public class TestCmdNoAnswer : AMP.Command
        {
            public TestCmdNoAnswer()
                : base(test_cmdNameNoAnswer)
            {
                AddArgument("test_val", new AMP.Type.String());
                AddResponse("ret_val", new AMP.Type.String());
                AddError(typeof(MyAMPError), B.b("MY_ERROR"));
                AddError(typeof(DivideByZeroException), B.b("ZERO_DIVISION"));
                RequiresAnswer = false;
            }
        }
        public static TestCmd test_cmd = new TestCmd();
        public static TestCmdNoAnswer test_cmdNoAnswer = new TestCmdNoAnswer();
        internal AMP.Msg_Raw test_msg;
        internal AMP.Msg_Raw test_msg_no_ask;
        internal AMP.Msg_Raw test_msgNoAnswer;
        public Test_AMP_Protocol proto;
        public AMP_Message_Dispatch_Base()
        {
            test_msg = new AMP.Msg_Raw();
            test_msg["_command"] = B.b("TestCmd");
            test_msg["_ask"] = B.b("25");
            test_msg["test_val"] = B.b("Hello World!");

            test_msg_no_ask = new AMP.Msg_Raw();
            test_msg_no_ask["_command"] = B.b("TestCmd");
            test_msg_no_ask["test_val"] = B.b("Hello World!");

            test_msgNoAnswer = new AMP.Msg_Raw();
            test_msgNoAnswer["_command"] = B.b("TestCmdNoAnswer");
            test_msgNoAnswer["_ask"] = B.b("25");
            test_msgNoAnswer["test_val"] = B.b("Hello World!");
        }

        [SetUp]
        public void Init()
        {
            proto = new Test_AMP_Protocol();
        }

    }

    [TestFixture]
    public class Command_Dispatch : AMP_Message_Dispatch_Base
    {
        /// <summary>
        /// Test what happens where there is no responder for the given command
        /// </summary>
        [Test]
        public void no_responder()
        {
            // dispatch a command for which we have not registered any responder
            var reqMsg = new AMP.Msg_Raw { { "_command", B.b("FOO") } };
            proto.ProcessFullMessage(reqMsg);

            // no error should be written, because the request didn't contain an _ask key,
            // so it is not expecting any reply to this command.
            Assert.That(0, Is.EqualTo(proto.messages.Count));

            //ok lets add an _ask key and do it again
            reqMsg.Add("_ask", B.b("77"));
            proto.ProcessFullMessage(reqMsg);

            // now we should find an error message has been sent out
            Assert.That(1, Is.EqualTo(proto.messages.Count));

            AMP.Msg_Raw errMsg = proto.messages[0];

            Assert.That(3, Is.EqualTo(errMsg.Count));
            Assert.That(B.b("77"), Is.EqualTo(errMsg["_error"]));
            Assert.That(AMP.Error.Codes.UNHANDLED_ERROR_CODE, Is.EqualTo(errMsg["_error_code"]));
            Assert.That(errMsg.ContainsKey("_error_description"));
        }

        [Test]
        [ExpectedException(typeof(AMP.Error.ParameterNotFound))]
        public void deserialization_fails()
        {
            // that what happens when the command we receive doesn't match the schema of our local command definition.
            var oddball_msg = new AMP.Msg_Raw
                                  {
                                      {"_ask", B.b("1")},
                                      {"_command", B.b("TestCmd")},
                                      {"fubar", B.b("undefined field")}
                                  };

            // register TestCmd w/ protocol
            proto.RegisterResponder(test_cmd, delegate { return new Object(); });

            // OK, process the command.. this should raise an exception, which this test case expects.
            proto.ProcessFullMessage(oddball_msg);
        }

        [Test]
        public void responder_returns_amp_msg()
        {
            // Test a successful command request that results in a call to a responder that returns an AMP.Msg
            var responseMsg = new AMP.Msg { { "ret_val", "Return Value" } };

            var requestMsg = new AMP.Msg();
            Object responderState = "value_before";
            proto.RegisterResponder(test_cmd, delegate(AMP.Msg reqMsg, Object state)
            {
                requestMsg = reqMsg; responderState = state; return responseMsg;
            }, "test_state");

            // process request.. this should result in a successful response being written out
            proto.ProcessFullMessage(test_msg);

            Assert.That(1, Is.EqualTo(requestMsg.Count));
            Assert.That(requestMsg["test_val"], Is.EqualTo("Hello World!"));
            Assert.That(responderState, Is.EqualTo("test_state"));

            Assert.That(1, Is.EqualTo(proto.messages.Count));

            AMP.Msg_Raw respMsgRaw = proto.messages[0];

            Assert.That(2, Is.EqualTo(respMsgRaw.Count));
            Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_answer"]));
            Assert.That(B.b("Return Value"), Is.EqualTo(respMsgRaw["ret_val"]));
        }

        [Test]
        public void responder_returns_malformed_amp_msg()
        {
            // Test a responder that returns a malformed AMP.Msg
            var responseMsg = new AMP.Msg { { "wrong_key", "foo" } };

            proto.RegisterResponder(test_cmd, delegate { return responseMsg; });

            // process request.. this should result in an error being written out
            // because the responder returns an invalid value
            proto.ProcessFullMessage(test_msg);

            Assert.That(1, Is.EqualTo(proto.messages.Count));

            AMP.Msg_Raw respMsgRaw = proto.messages[0];

            Assert.That(3, Is.EqualTo(respMsgRaw.Count));
            Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"]));
            Assert.That(B.b("UNKNOWN"), Is.EqualTo(respMsgRaw["_error_code"]));
            Assert.That(respMsgRaw.ContainsKey("_error_description"));
        }

        [Test]
        public void responder_returns_deferred_response()
        {
            // Test a successful command request that results in a call to a responder that returns a DeferredResponse
            var responseMsg = new AMP.Msg { { "ret_val", "Return Value" } };

            var requestMsg = new AMP.Msg();
            var deferred = new DeferredResponse();
            // return DeferredResponse from responder
            proto.RegisterResponder(test_cmd, delegate(AMP.Msg reqMsg, Object state) { requestMsg = reqMsg; return deferred; });

            // process request.. the proto should attach a callback to the DeferredResponse
            proto.ProcessFullMessage(test_msg);

            Assert.That(0, Is.EqualTo(proto.messages.Count));

            Assert.That(1, Is.EqualTo(requestMsg.Count));
            Assert.That(requestMsg["test_val"], Is.EqualTo("Hello World!"));

            // OK, lets fire the DeferredResponse callback, which should result in the AMP response being written out
            deferred.respond(responseMsg);

            Assert.That(1, Is.EqualTo(proto.messages.Count));

            AMP.Msg_Raw respMsgRaw = proto.messages[0];

            Assert.That(2, Is.EqualTo(respMsgRaw.Count));
            Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_answer"]));
            Assert.That(B.b("Return Value"), Is.EqualTo(respMsgRaw["ret_val"]));
        }

        [Test]
        public void responder_returns_deferred_response_which_fires_with_exception()
        {
            var requestMsg = new AMP.Msg();
            var deferred = new DeferredResponse();
            // return DeferredResponse from responder
            proto.RegisterResponder(test_cmd, delegate(AMP.Msg reqMsg, Object state) { requestMsg = reqMsg; return deferred; });

            // process request.. the proto should attach a callback to the DeferredResponse
            proto.ProcessFullMessage(test_msg);

            Assert.That(0, Is.EqualTo(proto.messages.Count));

            Assert.That(1, Is.EqualTo(requestMsg.Count));
            Assert.That(requestMsg["test_val"], Is.EqualTo("Hello World!"));

            // OK, lets fire the DeferredResponse callback with an exception,
            // which should result in an AMP error being written out
            deferred.respond(new Exception());

            Assert.That(1, Is.EqualTo(proto.messages.Count));

            AMP.Msg_Raw respMsgRaw = proto.messages[0];

            Assert.That(3, Is.EqualTo(respMsgRaw.Count));
            Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"]));
            Assert.That(B.b("UNKNOWN"), Is.EqualTo(respMsgRaw["_error_code"]));
            Assert.That(respMsgRaw.ContainsKey("_error_description"));
        }

        [Test]
        public void no_ask_key()
        {
            // test that we ignore the responder's return value if no _ask key was given
            var responseMsg = new AMP.Msg { { "ret_val", "Return Value" } };

            proto.RegisterResponder(test_cmd, delegate { return responseMsg; });

            // process request.. no response should be written because the peer didn't ask for one
            proto.ProcessFullMessage(test_msg_no_ask);

            Assert.That(0, Is.EqualTo(proto.messages.Count));
        }

        [Test]
        public void responder_throws()
        {
            proto.RegisterResponder(test_cmd, delegate { throw new Exception(); });

            proto.ProcessFullMessage(test_msg);

            Assert.That(1, Is.EqualTo(proto.messages.Count));

            AMP.Msg_Raw respMsgRaw = proto.messages[0];

            Assert.That(3, Is.EqualTo(respMsgRaw.Count));
            Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"]));
            Assert.That(B.b("UNKNOWN"), Is.EqualTo(respMsgRaw["_error_code"]));
            Assert.That(respMsgRaw.ContainsKey("_error_description"));
        }


        [Test]
        public void responder_throws_but_no_ask_key_given()
        {
            // test that we don't send an error upon an exception in the responder if no _ask key is given
            proto.RegisterResponder(test_cmd, delegate { throw new Exception(); });
            proto.ProcessFullMessage(test_msg_no_ask);

            // responder threw an exception, but no messages written out. Good.
            Assert.That(0, Is.EqualTo(proto.messages.Count));
        }

        [Test]
        public void responder_throws_custom_exception()
        {
            // OK, now test having the responder throw a known exception that has a custom AMP error code

            proto.RegisterResponder(test_cmd, delegate { throw new MyAMPError(); });
            proto.ProcessFullMessage(test_msg);

            Assert.That(1, Is.EqualTo(proto.messages.Count));

            AMP.Msg_Raw respMsgRaw = proto.messages[0];

            Assert.That(3, Is.EqualTo(respMsgRaw.Count));
            Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"]));
            Assert.That(B.b("MY_ERROR"), Is.EqualTo(respMsgRaw["_error_code"]));
            Assert.That(Encoding.UTF8.GetString(respMsgRaw["_error_description"]).Contains("MyAMPError"));
        }

        [Test]
        public void responder_throws_normal_exception()
        {
            // Test having the responder throw a known exception that is just a normal
            // Exception subclass, not a RemoteAmpError

            proto.RegisterResponder(test_cmd, delegate { throw new DivideByZeroException(); });
            proto.ProcessFullMessage(test_msg);

            Assert.That(1, Is.EqualTo(proto.messages.Count));

            AMP.Msg_Raw respMsgRaw = proto.messages[0];

            Assert.That(3, Is.EqualTo(respMsgRaw.Count));
            Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"]));
            Assert.That(B.b("ZERO_DIVISION"), Is.EqualTo(respMsgRaw["_error_code"]));
            Assert.That(Encoding.UTF8.GetString(respMsgRaw["_error_description"]).Contains("DivideByZeroException"));
        }

        [Test]
        public void responder_returns_object_of_invalid_type()
        {
            proto.RegisterResponder(test_cmd, delegate { return new Object(); });

            proto.ProcessFullMessage(test_msg);

            Assert.That(1, Is.EqualTo(proto.messages.Count));
            AMP.Msg_Raw respMsgRaw = proto.messages[0];

            Assert.That(3, Is.EqualTo(respMsgRaw.Count));
            Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"]));
            Assert.That(B.b("UNKNOWN"), Is.EqualTo(respMsgRaw["_error_code"]));
            Assert.That(respMsgRaw.ContainsKey("_error_description"));
        }
    }

    public class SimpleCmd : AMP.Command
    {
        public SimpleCmd()
            : base("SimpleCmd")
        {
            AddArgument("test_val", new AMP.Type.String());
            AddResponse("ret_val", new AMP.Type.String());
        }
    }

    /// <summary>
    /// Test full end-to-end AMP functionality over a loopback socket
    /// </summary>
    [TestFixture]
    public class Loopback
    {
        private int port = 45343;
        private Socket listenSock;
        private Socket serverSock;
        private Socket clientSock;
        private NetworkStream serverStream;
        private AMP.Protocol server;
        private NetworkStream clientStream;
        private AMP.Protocol client;

        [SetUp]
        public void SetUp()
        {
            listenSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listenSock.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), port));
            listenSock.Listen(5);

            //Socket serverSock = null;
            var t = new Thread(() => { serverSock = listenSock.Accept(); });
            t.Start();

            clientSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            clientSock.Connect("localhost", port);
            t.Join();
            //System.Console.WriteLine("serverSock: {0}", serverSock);
            //System.Console.WriteLine("clientSock: {0}", clientSock);
            serverStream = new NetworkStream(serverSock);
            server = new AMP.Protocol(serverStream);
            server.StartReading();

            clientStream = new NetworkStream(clientSock);
            client = new AMP.Protocol(clientStream);
            client.StartReading();
        }

        [TearDown]
        public void TearDown()
        {
            clientSock.Close();
            serverSock.Close();
            listenSock.Close();
        }

        [Test]
        public void BeginCallRemote_loopback()
        {
            Console.WriteLine("Test thread      : {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("test case syn ctx: {0}", SynchronizationContext.Current);
            AMP.Msg reqMsg = null;
            var _ret = new AMP.Msg {{"ret_val", "hello"}};
            server.RegisterResponder(new SimpleCmd(), (msg, state) =>
                                                          {
                                                              reqMsg = msg;
                                                              return _ret;
                                                          });
            var _msg = new AMP.Msg {{"test_val", "value!"}};
            AMP.Msg respMsg = null;
            IAsyncResult _ar = client.BeginCallRemote(new SimpleCmd(), _msg,
                                                      delegate(IAsyncResult ar)
                                                          {
                                                              respMsg = client.EndCallRemote(ar);
                                                          }, "state");
            _ar.AsyncWaitHandle.WaitOne();

            Assert.That("state", Is.EqualTo((string)_ar.AsyncState));

            Assert.That("value!", Is.EqualTo((string)reqMsg["test_val"]));
            Assert.That(1, Is.EqualTo(reqMsg.Count));

            Assert.That("hello", Is.EqualTo((string)respMsg["ret_val"]));
            Assert.That(1, Is.EqualTo(respMsg.Count));

            // Also test that BeginCallRemote is callable without a state argument
            reqMsg = null;
            respMsg = null;
            _ar = client.BeginCallRemote(new SimpleCmd(), _msg,
                                         delegate(IAsyncResult ar)
                                         {
                                             respMsg = client.EndCallRemote(ar);
                                         });
            _ar.AsyncWaitHandle.WaitOne();

            Assert.That(_ar.AsyncState, Is.Null);
                     
            Assert.That("value!", Is.EqualTo((string)reqMsg["test_val"]));
            Assert.That(1, Is.EqualTo(reqMsg.Count));

            Assert.That("hello", Is.EqualTo((string)respMsg["ret_val"]));
            Assert.That(1, Is.EqualTo(respMsg.Count));
        }

        [Test]
        public void CallRemote_loopback()
        {
            AMP.Msg reqMsg = null;
            var _ret = new AMP.Msg {{"ret_val", "hello"}};
            server.RegisterResponder(new SimpleCmd(), (msg, state) =>
                                                          {
                                                              reqMsg = msg;
                                                              return _ret;
                                                          });
            var _msg = new AMP.Msg {{"test_val", "value!"}};
            AMP.Msg respMsg = client.CallRemote(new SimpleCmd(), _msg);

            Assert.That("value!", Is.EqualTo((string)reqMsg["test_val"]));
            Assert.That(1, Is.EqualTo(reqMsg.Count));

            Assert.That("hello", Is.EqualTo((string)respMsg["ret_val"]));
            Assert.That(1, Is.EqualTo(respMsg.Count));
        }

        [Test]
        public void CallRemote_loopback_no_answer()
        {
                        
            AMP.Command cmd = new SimpleCmd();
            cmd.RequiresAnswer = false;

            AMP.Msg reqMsg = null;
            server.RegisterResponder(cmd, (msg, state) =>
                                                          {
                                                              reqMsg = msg;
                                                              return null;
                                                          });
            var _msg = new AMP.Msg {{"test_val", "value!"}};

            // Since we're not requesting an answer, this call
            // does not block and instead returns null immediately.
            AMP.Msg respMsg = client.CallRemote(cmd, _msg);

            // Since the call above does not block, it is quite likely that we'll
            // reach these assertions before the server-side responder (above)
            // will have been called. Lets just busy-wait up to a second for it...
            double maxWaitSeconds = 1.0;
            int waitMs = 10;
            int waitCount = 0;
            while ((waitCount * waitMs / 1000.0) < maxWaitSeconds)
            {
                waitCount++;
                Thread.Sleep(waitMs);
                if (reqMsg != null)
                    break;
            }

            Assert.That("value!", Is.EqualTo((string)reqMsg["test_val"]));
            Assert.That(1, Is.EqualTo(reqMsg.Count));

            Assert.That(respMsg, Is.Null);
        }

        /// <summary>
        /// Test that registering a responder along with a Control, will actually work,
        /// and actually use that Control to run the responder in the correct thread.
        /// </summary>
        [Test]
        [Ignore("Intermittent failures. Not sure why yet. Seems to work if run by itself, but not in combination with other tests.")]
        public void responderWithStateAndForm()
        {
            // fire up a Form in a new thread and start it running
            Form theForm = null;
            var handle = new ManualResetEvent(false);
            var t = new Thread(new ThreadStart(delegate { theForm = new Form(); handle.Set(); theForm.ShowDialog(); }));
            t.Start();
            handle.WaitOne(); // block until the Thread has started and theForm has been assigned

            var _ret = new AMP.Msg {{"ret_val", "hello"}};
            Object _state = null;
            AMP.Msg _msg = null;
            int _threadID = 0;
            server.RegisterResponder(new SimpleCmd(), (msg, state) =>
                                                          {
                                                              _msg = msg;
                                                              _state = state;
                                                              _threadID = Thread.CurrentThread.ManagedThreadId;
                                                              return _ret;
                                                          },
                                     "state", theForm); // Responder should be invoked on theForm's thread

            AMP.Msg respMsg = client.CallRemote(new SimpleCmd(), new AMP.Msg {{"test_val", "value!"}});

            Assert.That("value!", Is.EqualTo((string)_msg["test_val"]));
            Assert.That(1, Is.EqualTo(_msg.Count));

            Assert.That("hello", Is.EqualTo((string)respMsg["ret_val"]));
            Assert.That(1, Is.EqualTo(respMsg.Count));

            Assert.That("state", Is.EqualTo((string)_state));
            Assert.That(t.ManagedThreadId, Is.EqualTo(_threadID));
            theForm.Invoke(new MethodInvoker(theForm.Close));
        }

        /// <summary>
        /// Ensure that the synchronous CallRemote API works OK when called from a form.
        /// </summary>
        [Test]
        public void CallRemoteFromForm()
        {
            var _ret = new AMP.Msg {{"ret_val", "hello"}};
            server.RegisterResponder(new SimpleCmd(), (msg, state) => { return _ret; });

            CallRemoteForm theForm = null;
            var t = new Thread(new ThreadStart(delegate
                                                   {
                                                       theForm = new CallRemoteForm(client);
                                                       theForm.ShowDialog();
                                                   }));
            t.Start();
            t.Join();

            Assert.That("hello", Is.EqualTo((string)theForm.respMsg["ret_val"]));
            Assert.That(1, Is.EqualTo(theForm.respMsg.Count));
        }
        // TODO also test registering a responder with an explicit SynchronizationContext
    }

    public class CallRemoteForm: Form
    {
        public AMP.Msg respMsg;
        private AMP.Protocol client;
         public CallRemoteForm(AMP.Protocol client)
         {
             this.client = client;
             Load += CallRemoteForm_OnLoad;
         }
        public void CallRemoteForm_OnLoad(Object sender, EventArgs e)
        {
            respMsg = client.CallRemote(new SimpleCmd(), new AMP.Msg{{"test_val", "value!"}});
            Close();
        }
    }

    namespace Types
    {
        [TestFixture]
        public class Int
        {
            [Test]
            public void Int32()
            {
                int i = System.Int32.MaxValue;
                AMP.IAmpType intType = new AMP.Type.Int32();
                byte[] got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b(System.Int32.MaxValue.ToString())));
                Assert.That((int)intType.FromAmpBytes(got), Is.EqualTo(System.Int32.MaxValue));

                i = System.Int32.MinValue;
                got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b(System.Int32.MinValue.ToString())));
                Assert.That((int)intType.FromAmpBytes(got), Is.EqualTo(System.Int32.MinValue));

                i = 0;
                got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b("0")));
                Assert.That((int)intType.FromAmpBytes(got), Is.EqualTo(0));
            }

            [Test]
            public void UInt32()
            {
                uint i = System.UInt32.MaxValue;
                AMP.IAmpType intType = new AMP.Type.UInt32();
                byte[] got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b(System.UInt32.MaxValue.ToString())));
                Assert.That((uint)intType.FromAmpBytes(got), Is.EqualTo(System.UInt32.MaxValue));

                i = 0;
                got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b("0")));
                Assert.That((uint)intType.FromAmpBytes(got), Is.EqualTo(0));
            }

            [Test]
            public void Int64()
            {
                long i = System.Int64.MaxValue;
                AMP.IAmpType intType = new AMP.Type.Int64();
                byte[] got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b(System.Int64.MaxValue.ToString())));
                Assert.That((long)intType.FromAmpBytes(got), Is.EqualTo(System.Int64.MaxValue));

                i = System.Int64.MinValue;
                got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b(System.Int64.MinValue.ToString())));
                Assert.That((long)intType.FromAmpBytes(got), Is.EqualTo(System.Int64.MinValue));

                i = 0;
                got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b("0")));
                Assert.That((long)intType.FromAmpBytes(got), Is.EqualTo(0));
            }

            [Test]
            public void UInt64()
            {
                ulong i = System.UInt64.MaxValue;
                AMP.IAmpType intType = new AMP.Type.UInt64();
                byte[] got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b(System.UInt64.MaxValue.ToString())));
                Assert.That((ulong)intType.FromAmpBytes(got), Is.EqualTo(System.UInt64.MaxValue));

                i = 0;
                got = intType.ToAmpBytes(i);

                Assert.That(got, Is.EqualTo(B.b("0")));
                Assert.That((ulong)intType.FromAmpBytes(got), Is.EqualTo(0));
            }

            [Test]
            [ExpectedException(typeof(OverflowException))]
            public void Int32Overflow()
            {
                AMP.IAmpType intType = new AMP.Type.Int32();
                intType.FromAmpBytes(B.b("999999999999999999999999"));
            }

            [Test]
            [ExpectedException(typeof(OverflowException))]
            public void UInt32Overflow()
            {
                AMP.IAmpType intType = new AMP.Type.UInt32();
                intType.FromAmpBytes(B.b("999999999999999999999999"));
            }

            [Test]
            [ExpectedException(typeof(OverflowException))]
            public void Int64Overflow()
            {
                AMP.IAmpType intType = new AMP.Type.Int64();
                intType.FromAmpBytes(B.b("999999999999999999999999"));
            }

            [Test]
            [ExpectedException(typeof(OverflowException))]
            public void UInt64Overflow()
            {
                AMP.IAmpType intType = new AMP.Type.UInt64();
                intType.FromAmpBytes(B.b("999999999999999999999999"));
            }
        }

        [TestFixture]
        public class Decimal
        {
            [Test]
            public void Decimal_ToBytes_1()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                byte[] got = d.ToAmpBytes(1.5m);
                Assert.That(got, Is.EqualTo(B.b("1.5")));
            }

            [Test]
            public void Decimal_ToBytes_2()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                byte[] got = d.ToAmpBytes(-0.050m);
                Assert.That(got, Is.EqualTo(B.b("-0.050")));
            }

            [Test]
            public void Decimal_FromBytes_1()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                decimal got = (decimal)d.FromAmpBytes(B.b("1.5"));
                Assert.That(got, Is.EqualTo(1.5m));
            }

            [Test]
            public void Decimal_FromBytes_2()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                decimal got = (decimal)d.FromAmpBytes(B.b("-0.050"));
                Assert.That(got, Is.EqualTo(-0.050m));
            }

            [Test]
            public void Decimal_FromBytes_3()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                decimal got = (decimal)d.FromAmpBytes(B.b("1E+2"));
                Assert.That(got, Is.EqualTo(100m));
            }

            [Test]
            public void Decimal_FromBytes_4()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                decimal got = (decimal)d.FromAmpBytes(B.b("1E-1"));
                Assert.That(got, Is.EqualTo(0.1m));
            }

            [Test]
            public void Decimal_FromBytes_5()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                decimal got = (decimal)d.FromAmpBytes(B.b("10E+0"));
                Assert.That(got, Is.EqualTo(10m));
            }

            [Test]
            public void Decimal_FromBytes_6()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                decimal got = (decimal)d.FromAmpBytes(B.b("1.5E+2"));
                Assert.That(got, Is.EqualTo(150m));
            }

            [Test]
            [ExpectedException(typeof(FormatException))]
            public void Decimal_FromBytes_error_1()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                d.FromAmpBytes(B.b("1E"));
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.TypeDecodeError))]
            public void Decimal_FromBytes_error_2()
            {
                AMP.IAmpType d = new AMP.Type.Decimal();
                d.FromAmpBytes(B.b("1EE+1"));
            }
        }

        [TestFixture]
        public class ListOf
        {
            [Test]
            public void empty_ToBytes()
            {
                AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32());
                var empty = new List<int>();
                byte[] got = lst.ToAmpBytes(empty);
                byte[] expected = { };
                Assert.That(got, Is.EqualTo(expected));
            }

            [Test]
            public void int32_ToBytes()
            {
                AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32());

                var oneint = new List<int> { 7 };
                var ints = new List<int> { 1, 2, 3 };

                byte[] got = lst.ToAmpBytes(oneint);
                byte[] expected = new byte[] { 0, 1, 0x37 }; // 0x37 is hex for "7" in ASCII/UTF-8
                Assert.That(got, Is.EqualTo(expected));

                got = lst.ToAmpBytes(ints);
                expected = new byte[] { 0, 1, 0x31, 0, 1, 0x32, 0, 1, 0x33 }; // 0x31 is hex for "1" in ASCII/UTF-8
                Assert.That(got, Is.EqualTo(expected));
            }

            [Test]
            public void strings_ToBytes()
            {
                AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.String());

                string test = "0123456789ABCDEF";
                string chunk = "";
                int i;
                for (i = 0; i < 15; i++)
                {
                    chunk += test;
                }
                // chunk is now 16*15 == 240 chars long

                chunk += "0123456789ABCDE"; // chunk is now 255 chars long

                var strings = new List<string> { chunk, chunk, chunk };

                byte[] got = lst.ToAmpBytes(strings);

                Assert.That(got.Length, Is.EqualTo((2 + 255) * 3));

                System.Console.WriteLine("{0} {1}", got[0], got[1]);

                var _tmp = new byte[2] { got[0], got[1] };

                Assert.That(_tmp, Is.EqualTo(new byte[] { 0x00, 0xff }));

                chunk += "Z";
                strings = new List<string> { chunk };

                got = lst.ToAmpBytes(strings);
                System.Console.WriteLine("{0} {1}", got[0], got[1]);
                _tmp = new byte[2] { got[0], got[1] };

                Assert.That(_tmp, Is.EqualTo(new byte[] { 0x01, 0x00 }));
            }

            [Test]
            public void empty_FromBytes()
            {
                AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32());
                var empty = (List<Object>)lst.FromAmpBytes(new byte[] { });

                Assert.That(empty.Count, Is.EqualTo(0));
            }

            [Test]
            public void int32_FromBytes()
            {
                AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32());
                var res = (List<Object>)lst.FromAmpBytes(new byte[] { 0x00, 0x03, 0x31, 0x32, 0x33 });

                Assert.That(res.Count, Is.EqualTo(1));
                Assert.That((int)res[0], Is.EqualTo(123));
            }

            [Test]
            public void empty_string_FromBytes()
            {
                AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.String());
                var res = (List<Object>)lst.FromAmpBytes(new byte[] { 0x00, 0x00, 0x00, 0x00 });

                Assert.That(res.Count, Is.EqualTo(2));
                Assert.That((string)res[0], Is.EqualTo(""));
                Assert.That((string)res[1], Is.EqualTo(""));
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.TypeDecodeError))]
            public void int32_error()
            {
                AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32());
                lst.FromAmpBytes(new byte[] { 0x00 });
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.TypeDecodeError))]
            public void int32_error2()
            {
                AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32());
                lst.FromAmpBytes(new byte[] { 0x00, 0x01 });
            }
        }

        [TestFixture]
        public class AmpList
        {
            [Test]
            public void empty_ToBytes()
            {
                var alist = new AMP.Type.AmpList();
                alist.AddParameter("a", new AMP.Type.String());
                alist.AddParameter("b", new AMP.Type.Int32());

                byte[] got = alist.ToAmpBytes(new List<Msg>());

                Assert.That(got.Length, Is.EqualTo(0));
            }

            [Test]
            public void ToBytes()
            {
                var alist = new AMP.Type.AmpList();
                alist.AddParameter("a", new AMP.Type.String());
                alist.AddParameter("b", new AMP.Type.Int32());

                var msgs = new List<AMP.Msg> { new AMP.Msg { { "a", "AAA" }, { "b", 7 } } };

                byte[] got = alist.ToAmpBytes(msgs);

                //System.Console.WriteLine("got {0}", got.ToHexString());

                var expected = new byte[] { 0x00, 0x01,       // key of length 1 follows
										0x61,             // 'a'
										0x00, 0x03,       // value of length 3 follows
										0x41, 0x41, 0x41, // "AAA"
										0x00, 0x01,       // key of length 1 follows
										0x62,             // 'b'
										0x00, 0x01,       // value of length 1 follows
										0x37,             // '7'
										0x00, 0x00        // key of length 0 follows. end of message.
									   };

                Assert.That(got, Is.EqualTo(expected));

                msgs.Add(new AMP.Msg { { "a", "aaa" }, { "b", 3 } });
                got = alist.ToAmpBytes(msgs);

                expected = new byte[] { 0x00, 0x01,       // key of length 1 follows
									0x61,             // 'a'
									0x00, 0x03,       // value of length 3 follows
									0x41, 0x41, 0x41, // "AAA"
									0x00, 0x01,       // key of length 1 follows
									0x62,             // 'b'
									0x00, 0x01,       // value of length 1 follows
									0x37,             // '7'
									0x00, 0x00,       // null key. end of message.
									0x00, 0x01,       // key of length 1 follows
									0x61,             // 'a'
									0x00, 0x03,       // value of length 3 follows
									0x61, 0x61, 0x61, // "aaa"
									0x00, 0x01,       // key of length 1 follows
									0x62,             // 'b'
									0x00, 0x01,       // value of length 1 follows
									0x33,             // '3'
									0x00, 0x00        // null key. end of message.
								   };

                Assert.That(got, Is.EqualTo(expected));
            }

            [Test]
            public void empty_FromBytes()
            {
                var alist = new AMP.Type.AmpList();
                alist.AddParameter("a", new AMP.Type.String());
                alist.AddParameter("b", new AMP.Type.Int32());

                var msgs = (List<AMP.Msg>)alist.FromAmpBytes(new byte[] { });

                Assert.That(msgs.Count, Is.EqualTo(0));
            }

            [Test]
            public void FromBytes()
            {
                var alist = new AMP.Type.AmpList();
                alist.AddParameter("a", new AMP.Type.String());
                alist.AddParameter("b", new AMP.Type.Int32());

                var bytes = new byte[] { 0x00, 0x01,       // key of length 1 follows
									 0x61,             // 'a'
									 0x00, 0x03,       // value of length 3 follows
									 0x41, 0x41, 0x41, // "AAA"
									 0x00, 0x01,       // key of length 1 follows
									 0x62,             // 'b'
									 0x00, 0x01,       // value of length 1 follows
									 0x37,             // '7'
									 0x00, 0x00        // key of length 0 follows. end of message.
									};

                var msgs = (List<AMP.Msg>)alist.FromAmpBytes(bytes);

                Assert.That(msgs.Count, Is.EqualTo(1));
                Assert.That(msgs[0].Count, Is.EqualTo(2));

                Assert.That((string)msgs[0]["a"], Is.EqualTo("AAA"));
                Assert.That((int)msgs[0]["b"], Is.EqualTo(7));

                bytes = new byte[] { 0x00, 0x01,       // key of length 1 follows
								 0x61,             // 'a'
								 0x00, 0x03,       // value of length 3 follows
								 0x41, 0x41, 0x41, // "AAA"
								 0x00, 0x01,       // key of length 1 follows
								 0x62,             // 'b'
								 0x00, 0x01,       // value of length 1 follows
								 0x37,             // '7'
								 0x00, 0x00,       // null key. end of message.
								 0x00, 0x01,       // key of length 1 follows
								 0x61,             // 'a'
								 0x00, 0x03,       // value of length 3 follows
								 0x61, 0x61, 0x61, // "aaa"
								 0x00, 0x01,       // key of length 1 follows
								 0x62,             // 'b'
								 0x00, 0x01,       // value of length 1 follows
								 0x33,             // '3'
								 0x00, 0x00        // null key. end of message.
								};

                msgs = (List<AMP.Msg>)alist.FromAmpBytes(bytes);

                Assert.That(msgs.Count, Is.EqualTo(2));

                Assert.That(msgs[0].Count, Is.EqualTo(2));
                Assert.That((string)msgs[0]["a"], Is.EqualTo("AAA"));
                Assert.That((int)msgs[0]["b"], Is.EqualTo(7));

                Assert.That(msgs[1].Count, Is.EqualTo(2));
                Assert.That((string)msgs[1]["a"], Is.EqualTo("aaa"));
                Assert.That((int)msgs[1]["b"], Is.EqualTo(3));
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.ProtocolError))]
            public void FromBytes_error()
            {
                var alist = new AMP.Type.AmpList();

                var bytes = new byte[] { 0x66, 0x01 };       // bogus key length
                alist.FromAmpBytes(bytes);
            }

            [Test]
            [ExpectedException(typeof(AMP.Error.ParameterNotFound))]
            public void FromBytes_missing_keys()
            {
                var alist = new AMP.Type.AmpList();
                alist.AddParameter("a", new AMP.Type.String());
                alist.AddParameter("b", new AMP.Type.Int32());

                var bytes = new byte[] { 0x00, 0x01,       // key of length 1 follows
									 0x61,             // 'a'
									 0x00, 0x03,       // value of length 3 follows
									 0x41, 0x41, 0x41, // "AAA"
													   // NOTE missing 'b' key
									 0x00, 0x00        // key of length 0 follows. end of message.
									};

                alist.FromAmpBytes(bytes);
            }

            [Test]
            public void FromBytes_extra_keys()
            {
                var alist = new AMP.Type.AmpList();
                alist.AddParameter("a", new AMP.Type.String());
                alist.AddParameter("b", new AMP.Type.Int32());

                var bytes = new byte[] { 0x00, 0x01,       // key of length 1 follows
									 0x61,             // 'a'
									 0x00, 0x03,       // value of length 3 follows
									 0x41, 0x41, 0x41, // "AAA"
									 0x00, 0x01,       // key of length 1 follows
									 0x62,             // 'b'
									 0x00, 0x01,       // value of length 1 follows
									 0x37,             // '7'
									 0x00, 0x01,       // key of length 1 follows
									 0x63,             // 'c' NOTE 'c' key is not part of the AmpList definition
									 0x00, 0x01,       // value of length 1 follows
									 0x43,             // 'C'
									 0x00, 0x00        // key of length 0 follows. end of message.
									};

                var msgs = (List<AMP.Msg>)alist.FromAmpBytes(bytes);

                // result should only contain keys defined in the AmpList
                Assert.That(msgs.Count, Is.EqualTo(1));

                Assert.That(msgs[0].Count, Is.EqualTo(2)); // only "a" and "b"
                Assert.That((string)msgs[0]["a"], Is.EqualTo("AAA"));
                Assert.That((int)msgs[0]["b"], Is.EqualTo(7));

            }
        }

        [TestFixture]
        public class TimeArgument
        {
            [Test]
            public void ToBytes_from_utc()
            {
                var atr = new AMP.Type.TimeArgument();
                var dt = new DateTime(2008, 04, 16, 12, 30, 15, 200, DateTimeKind.Utc);
                byte[] value = atr.ToAmpBytes(dt);

                Assert.That(value, Is.EqualTo(B.b("2008/4/16 12:30:15")));
            }

            [Test]
            public void ToBytes_from_local()
            {
                var atr = new AMP.Type.TimeArgument();
                var dt = new DateTime(2008, 04, 16, 12, 30, 15, 200, DateTimeKind.Local);
                byte[] value = atr.ToAmpBytes(dt);

                byte[] expected = B.b(dt.ToUniversalTime().ToString("yyyy/M/d HH:mm:ss"));

                Assert.That(value, Is.EqualTo(expected));
            }

            // TODO FromBytes test for TimeArgument
        }

        [TestFixture]
        public class Double
        {
        	AMP.IAmpType d;
        	
        	[SetUp]
        	public void SetUp()
			{
                d = new AMP.Type.Double();
			}
			
            [Test]
            public void ToBytes_normal()
            {
                var s = Encoding.UTF8.GetString(d.ToAmpBytes(3.141592654));
                Assert.That(s, Is.StringStarting("3.141592654"));

                s = Encoding.UTF8.GetString(d.ToAmpBytes(-2.718281828));
                Assert.That(s, Is.StringStarting("-2.718281828"));
			}
                
            [Test]
            public void ToBytes_NaN()
            {
				// Not-a-number
                var s = Encoding.UTF8.GetString(d.ToAmpBytes(double.NaN));
                Assert.That(s, Is.EqualTo("nan"));
			}
                
            [Test]
            public void ToBytes_pos_infinity()
            {
				// Positive Infinity
                var s = Encoding.UTF8.GetString(d.ToAmpBytes(double.PositiveInfinity));
                Assert.That(s, Is.EqualTo("inf"));
			}
                
            [Test]
            public void ToBytes_neg_infinity()
            {
				// Negative Infinity
                var s = Encoding.UTF8.GetString(d.ToAmpBytes(double.NegativeInfinity));
                Assert.That(s, Is.EqualTo("-inf"));
            }

            [Test]
            public void FromBytes_normal()
            {
                var num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("3.141592654"));
                Assert.That(num, Is.InRange(3.1415926, 3.1415927));

                num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("-2.718281828"));
                Assert.That(num, Is.InRange(-2.7182819, -2.7182818));
			}
                
            [Test]
            public void FromBytes_NaN()
            {
				// Not-a-number
                var num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("nan"));
                Assert.That(num, Is.EqualTo(double.NaN));
			}
                
            [Test]
            public void FromBytes_pos_infinity()
            {
				// Positive Infinity
                var num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("inf"));
                Assert.That(num, Is.EqualTo(double.PositiveInfinity));
			}
                
            [Test]
            public void FromBytes_neg_infinity()
            {
				// Negative Infinity
                var num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("-inf"));
                Assert.That(num, Is.EqualTo(double.NegativeInfinity));
            }
        }

        [TestFixture]
        public class DateTimeOffset
        {
            AMP.Type.DateTimeOffset d;
            [SetUp]
            public void SetUp()
            {
                d = new AMP.Type.DateTimeOffset();
            }

            [Test]
            public void ToBytes_normal()
            {
                // set the current culture to use a TimeSeperator that is different than the
                // seperator we expect in the AMP DateTime string. This ensures that we
                // aren't relying on the current culture to encode the DateTimeOffset properly.
                var ci = new System.Globalization.CultureInfo("en-US");
                var dtfi = new System.Globalization.DateTimeFormatInfo();
                dtfi.TimeSeparator = "JUNK";
                ci.DateTimeFormat = dtfi;
                Thread.CurrentThread.CurrentCulture = ci;
                
                var dt = new System.DateTimeOffset(2012, 01, 23, 12, 34, 56, 054, new TimeSpan(1, 23, 0));
                var s = Encoding.UTF8.GetString(d.ToAmpBytes(dt));
                Assert.That(s, Is.EqualTo("2012-01-23T12:34:56.054000+01:23"));

                dt = new System.DateTimeOffset(2012, 01, 23, 18, 34, 56, 123, new TimeSpan(1, 23, 0).Negate());
                s = Encoding.UTF8.GetString(d.ToAmpBytes(dt));
                Assert.That(s, Is.EqualTo("2012-01-23T18:34:56.123000-01:23"));
            }

            [Test]
            public void FromBytes_normal()
            {
                byte[] bytes = Encoding.UTF8.GetBytes("2012-01-23T12:34:56.054000+01:23");
                var dt = (System.DateTimeOffset)d.FromAmpBytes(bytes);
                Assert.That(dt, Is.EqualTo(new System.DateTimeOffset(2012, 01, 23, 12, 34, 56, 054, new TimeSpan(1, 23, 0))));

                bytes = Encoding.UTF8.GetBytes("2012-01-23T18:34:56.123000-01:23");
                dt = (System.DateTimeOffset)d.FromAmpBytes(bytes);
                Assert.That(dt, Is.EqualTo(new System.DateTimeOffset(2012, 01, 23, 18, 34, 56, 123, new TimeSpan(1, 23, 0).Negate())));
         
            }
        }
    }
}
