/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

// Distributed under the Thrift Software License
//
// See accompanying file LICENSE or visit the Thrift site at:
// http://developers.facebook.com/thrift/
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using Thrift.Collections;
using Thrift.Test; //generated code
using Thrift.Transport;
using Thrift.Protocol;
using Thrift.Server;
using Thrift;
using System.Threading;
using System.Text;
using System.Security.Authentication;

namespace Test
{
    public class TestServer
    {
        public static int _clientID = -1;
        public delegate void TestLogDelegate(string msg, params object[] values);

        public class TradeServerEventHandler : TServerEventHandler
        {
            public int callCount = 0;
            public void preServe()
            {
                callCount++;
            }
            public Object createContext(Thrift.Protocol.TProtocol input, Thrift.Protocol.TProtocol output)
            {
                callCount++;
                return null;
            }
            public void deleteContext(Object serverContext, Thrift.Protocol.TProtocol input, Thrift.Protocol.TProtocol output)
            {
                callCount++;
            }
            public void processContext(Object serverContext, Thrift.Transport.TTransport transport)
            {
                callCount++;
            }
        };


        public class TestHandler : ThriftTest.Iface, Thrift.TControllingHandler
        {
            public TServer server { get; set; }
            private int handlerID;
            private StringBuilder reusableStringBuilder = new StringBuilder();
            private TestLogDelegate testLogDelegate;

            public TestHandler()
            {
                handlerID = Interlocked.Increment(ref _clientID);
                testLogDelegate += testConsoleLogger;
                testLogDelegate.Invoke("New TestHandler instance created");
            }

            public void testConsoleLogger(string msg, params object[] values)
            {
                reusableStringBuilder.Clear();
                reusableStringBuilder.AppendFormat("handler{0:D3}:",handlerID);
                reusableStringBuilder.AppendFormat(msg, values);
                reusableStringBuilder.AppendLine();
                Console.Write( reusableStringBuilder.ToString() );
            }

            public void testVoid()
            {
                testLogDelegate.Invoke("testVoid()");
            }

            public string testString(string thing)
            {
                testLogDelegate.Invoke("testString({0})", thing);
                return thing;
            }

            public bool testBool(bool thing)
            {
                testLogDelegate.Invoke("testBool({0})", thing);
                return thing;
            }

            public sbyte testByte(sbyte thing)
            {
                testLogDelegate.Invoke("testByte({0})", thing);
                return thing;
            }

            public int testI32(int thing)
            {
                testLogDelegate.Invoke("testI32({0})", thing);
                return thing;
            }

            public long testI64(long thing)
            {
                testLogDelegate.Invoke("testI64({0})", thing);
                return thing;
            }

            public double testDouble(double thing)
            {
                testLogDelegate.Invoke("testDouble({0})", thing);
                return thing;
            }

            public byte[] testBinary(byte[] thing)
            {
                string hex = BitConverter.ToString(thing).Replace("-", string.Empty);
                testLogDelegate.Invoke("testBinary({0:X})", hex);
                return thing;
            }

            public Xtruct testStruct(Xtruct thing)
            {
                testLogDelegate.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.String_thing, thing.Byte_thing, thing.I32_thing, thing.I64_thing);
                return thing;
            }

            public Xtruct2 testNest(Xtruct2 nest)
            {
                Xtruct thing = nest.Struct_thing;
                testLogDelegate.Invoke("testNest({{{0}, {{\"{1}\", {2}, {3}, {4}, {5}}}}})",
                                 nest.Byte_thing,
                                 thing.String_thing,
                                 thing.Byte_thing,
                                 thing.I32_thing,
                                 thing.I64_thing,
                                 nest.I32_thing);
                return nest;
            }

            public Dictionary<int, int> testMap(Dictionary<int, int> thing)
            {
                reusableStringBuilder.Clear();
                reusableStringBuilder.Append("testMap({{");
                bool first = true;
                foreach (int key in thing.Keys)
                {
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        reusableStringBuilder.Append(", ");
                    }
                    reusableStringBuilder.AppendFormat("{0} => {1}", key, thing[key]);
                }
                reusableStringBuilder.Append("}})");
                testLogDelegate.Invoke(reusableStringBuilder.ToString());
                return thing;
            }

            public Dictionary<string, string> testStringMap(Dictionary<string, string> thing)
            {
                reusableStringBuilder.Clear();
                reusableStringBuilder.Append("testStringMap({{");
                bool first = true;
                foreach (string key in thing.Keys)
                {
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        reusableStringBuilder.Append(", ");
                    }
                    reusableStringBuilder.AppendFormat("{0} => {1}", key, thing[key]);
                }
                reusableStringBuilder.Append("}})");
                testLogDelegate.Invoke(reusableStringBuilder.ToString());
                return thing;
            }

            public THashSet<int> testSet(THashSet<int> thing)
            {
                reusableStringBuilder.Clear();
                reusableStringBuilder.Append("testSet({{");
                bool first = true;
                foreach (int elem in thing)
                {
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        reusableStringBuilder.Append(", ");
                    }
                    reusableStringBuilder.AppendFormat("{0}", elem);
                }
                reusableStringBuilder.Append("}})");
                testLogDelegate.Invoke(reusableStringBuilder.ToString());
                return thing;
            }

            public List<int> testList(List<int> thing)
            {
                reusableStringBuilder.Clear();
                reusableStringBuilder.Append("testList({{");
                bool first = true;
                foreach (int elem in thing)
                {
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        reusableStringBuilder.Append(", ");
                    }
                    reusableStringBuilder.AppendFormat("{0}", elem);
                }
                reusableStringBuilder.Append("}})");
                testLogDelegate.Invoke(reusableStringBuilder.ToString());
                return thing;
            }

            public Numberz testEnum(Numberz thing)
            {
                testLogDelegate.Invoke("testEnum({0})", thing);
                return thing;
            }

            public long testTypedef(long thing)
            {
                testLogDelegate.Invoke("testTypedef({0})", thing);
                return thing;
            }

            public Dictionary<int, Dictionary<int, int>> testMapMap(int hello)
            {
                testLogDelegate.Invoke("testMapMap({0})", hello);
                Dictionary<int, Dictionary<int, int>> mapmap =
                  new Dictionary<int, Dictionary<int, int>>();

                Dictionary<int, int> pos = new Dictionary<int, int>();
                Dictionary<int, int> neg = new Dictionary<int, int>();
                for (int i = 1; i < 5; i++)
                {
                    pos[i] = i;
                    neg[-i] = -i;
                }

                mapmap[4] = pos;
                mapmap[-4] = neg;

                return mapmap;
            }

            // Insanity
            // returns:
            // { 1 => { 2 => argument,
            //          3 => argument,
            //        },
            //   2 => { 6 => <empty Insanity struct>, },
            // }
            public Dictionary<long, Dictionary<Numberz, Insanity>> testInsanity(Insanity argument)
            {
                testLogDelegate.Invoke("testInsanity()");

                Dictionary<Numberz, Insanity> first_map = new Dictionary<Numberz, Insanity>();
                Dictionary<Numberz, Insanity> second_map = new Dictionary<Numberz, Insanity>(); ;

                first_map[Numberz.TWO] = argument;
                first_map[Numberz.THREE] = argument;

                second_map[Numberz.SIX] = new Insanity();

                Dictionary<long, Dictionary<Numberz, Insanity>> insane =
                  new Dictionary<long, Dictionary<Numberz, Insanity>>();
                insane[(long)1] = first_map;
                insane[(long)2] = second_map;

                return insane;
            }

            public Xtruct testMulti(sbyte arg0, int arg1, long arg2, Dictionary<short, string> arg3, Numberz arg4, long arg5)
            {
                testLogDelegate.Invoke("testMulti()");

                Xtruct hello = new Xtruct(); ;
                hello.String_thing = "Hello2";
                hello.Byte_thing = arg0;
                hello.I32_thing = arg1;
                hello.I64_thing = arg2;
                return hello;
            }

            /**
             * Print 'testException(%s)' with arg as '%s'
             * @param string arg - a string indication what type of exception to throw
             * if arg == "Xception" throw Xception with errorCode = 1001 and message = arg
             * elsen if arg == "TException" throw TException
             * else do not throw anything
             */
            public void testException(string arg)
            {
                testLogDelegate.Invoke("testException({0})", arg);
                if (arg == "Xception")
                {
                    Xception x = new Xception();
                    x.ErrorCode = 1001;
                    x.Message = arg;
                    throw x;
                }
                if (arg == "TException")
                {
                    throw new Thrift.TException();
                }
                return;
            }

            public Xtruct testMultiException(string arg0, string arg1)
            {
                testLogDelegate.Invoke("testMultiException({0}, {1})", arg0,arg1);
                if (arg0 == "Xception")
                {
                    Xception x = new Xception();
                    x.ErrorCode = 1001;
                    x.Message = "This is an Xception";
                    throw x;
                }
                else if (arg0 == "Xception2")
                {
                    Xception2 x = new Xception2();
                    x.ErrorCode = 2002;
                    x.Struct_thing = new Xtruct();
                    x.Struct_thing.String_thing = "This is an Xception2";
                    throw x;
                }

                Xtruct result = new Xtruct();
                result.String_thing = arg1;
                return result;
            }

            public void testStop()
            {
                if (server != null)
                {
                    server.Stop();
                }
            }

            public void testOneway(int arg)
            {
                testLogDelegate.Invoke("testOneway({0}), sleeping...", arg);
                System.Threading.Thread.Sleep(arg * 1000);
                testLogDelegate.Invoke("testOneway finished");
            }

        } // class TestHandler

        private enum ServerType
        {
            TSimpleServer,
            TThreadedServer,
            TThreadPoolServer,
        }

        private enum ProcessorFactoryType
        {
            TSingletonProcessorFactory,
            TPrototypeProcessorFactory,
        }

        public static bool Execute(string[] args)
        {
            try
            {
                bool useBufferedSockets = false, useFramed = false, useEncryption = false, compact = false, json = false;
                ServerType serverType = ServerType.TSimpleServer;
                ProcessorFactoryType processorFactoryType = ProcessorFactoryType.TSingletonProcessorFactory;
                int port = 9090;
                string pipe = null;
                for (int i = 0; i < args.Length; i++)
                {
                    if (args[i] == "-pipe")  // -pipe name
                    {
                        pipe = args[++i];
                    }
                    else if (args[i].Contains("--port="))
                    {
                        port = int.Parse(args[i].Substring(args[i].IndexOf("=") + 1));
                    }
                    else if (args[i] == "-b" || args[i] == "--buffered" || args[i] == "--transport=buffered")
                    {
                        useBufferedSockets = true;
                    }
                    else if (args[i] == "-f" || args[i] == "--framed" || args[i] == "--transport=framed")
                    {
                        useFramed = true;
                    }
                    else if (args[i] == "--compact" || args[i] == "--protocol=compact")
                    {
                        compact = true;
                    }
                    else if (args[i] == "--json" || args[i] == "--protocol=json")
                    {
                        json = true;
                    }
                    else if (args[i] == "--threaded" || args[i] == "--server-type=threaded")
                    {
                        serverType = ServerType.TThreadedServer;
                    }
                    else if (args[i] == "--threadpool" || args[i] == "--server-type=threadpool")
                    {
                        serverType = ServerType.TThreadPoolServer;
                    }
                    else if (args[i] == "--prototype" || args[i] == "--processor=prototype")
                    {
                        processorFactoryType = ProcessorFactoryType.TPrototypeProcessorFactory;
                    }
                    else if (args[i] == "--ssl")
                    {
                        useEncryption = true;
                    }
                }

                // Transport
                TServerTransport trans;
                if (pipe != null)
                {
                    trans = new TNamedPipeServerTransport(pipe);
                }
                else
                {
                    if (useEncryption)
                    {
                        string certPath = "../keys/server.p12";
                        trans = new TTLSServerSocket(port, 0, useBufferedSockets, new X509Certificate2(certPath, "thrift"), 
                            null,
                            null, SslProtocols.Tls);
                    }
                    else
                    {
                        trans = new TServerSocket(port, 0, useBufferedSockets);
                    }
                }

                TProtocolFactory proto;
                if (compact)
                    proto = new TCompactProtocol.Factory();
                else if (json)
                    proto = new TJSONProtocol.Factory();
                else
                    proto = new TBinaryProtocol.Factory();

                TProcessorFactory processorFactory;
                if (processorFactoryType == ProcessorFactoryType.TPrototypeProcessorFactory)
                {
                    processorFactory = new TPrototypeProcessorFactory<ThriftTest.Processor, TestHandler>();
                }
                else
                {
                    // Processor
                    TestHandler testHandler = new TestHandler();
                    ThriftTest.Processor testProcessor = new ThriftTest.Processor(testHandler);
                    processorFactory = new TSingletonProcessorFactory(testProcessor);
                }

                TTransportFactory transFactory;
                if (useFramed)
                    transFactory = new TFramedTransport.Factory();
                else
                    transFactory = new TTransportFactory();

                TServer serverEngine;
                switch (serverType)
                {
                    case ServerType.TThreadPoolServer:
                        serverEngine = new TThreadPoolServer(processorFactory, trans, transFactory, proto);
                        break;
                    case ServerType.TThreadedServer:
                        serverEngine = new TThreadedServer(processorFactory, trans, transFactory, proto);
                        break;
                    default:
                        serverEngine = new TSimpleServer(processorFactory, trans, transFactory, proto);
                        break;
                }

                //Server event handler
                TradeServerEventHandler serverEvents = new TradeServerEventHandler();
                serverEngine.setEventHandler(serverEvents);

                // Run it
                string where = (pipe != null ? "on pipe " + pipe : "on port " + port);
                Console.WriteLine("Starting the " + serverType.ToString() + " " + where +
                    (processorFactoryType == ProcessorFactoryType.TPrototypeProcessorFactory ? " with processor prototype factory " : "") +
                    (useBufferedSockets ? " with buffered socket" : "") +
                    (useFramed ? " with framed transport" : "") +
                    (useEncryption ? " with encryption" : "") +
                    (compact ? " with compact protocol" : "") +
                    (json ? " with json protocol" : "") +
                    "...");
                serverEngine.Serve();

            }
            catch (Exception x)
            {
                Console.Error.Write(x);
                return false;
            }
            Console.WriteLine("done.");
            return true;
        }
    }
}