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

using NetStack.Common;
using System;
using System.Threading;
using System.Collections;
using System.Diagnostics;

#if !SINGULARITY
using System.Net;
#endif

using System.Net.IP;
using Drivers.Net;

namespace NetStack.Runtime
{
    using Protocols;

    // an ICMP session
    public class IcmpSession : Session
    {
        // the session's transmit q size
        public const int TxQSize = 100;

        // the session's receive q size
        public const int RcvQSize = 100;

        public IcmpSession(IProtocol! protocol)
            : base(protocol, TxQSize, RcvQSize)
        {
        }

        // override the OnReceive
        override internal NetStatus OnReceive(object     sender,
                                              NetPacket! pkt,
                                              object     ctx)
        {
            NetStatus res = base.OnReceive(sender, pkt, ctx);
            // if the packet can be delivered to the user
            if (res != NetStatus.Code.PROTOCOL_PROCESSING) {
                // get the packet and put it on the incoming queue
                // if there is no room, drop it. this method is called
                // by the runtime when a packet destination is this session.
                this.PutPacket(inQueue, pkt, false);
            }
            return res;
        }

        // the user should create an IP+ICMP buffer
        override public int WriteData(byte[]! data)
        {
            if (!IsSessionValidForUserWrite()) {
                return -1;
            }

            byte[] pktData = new byte[EthernetFormat.Size+IPFormat.Size+data.Length];
            Array.Copy(data,0,pktData,EthernetFormat.Size,data.Length);
            NetPacket pkt=new NetPacket(pktData);
            PutPacket(outQueue,pkt,true);
            return data.Length;
        }

        override public bool Close()
        {
            Core.Instance().DeregisterSession(Protocol, this);
            return true;
        }
    }

#if Qos
    // an Qos ICMP session
    public class QosIcmpSession : Session
    {
        // the session's transmit q size
        public const int TxQSize=100;

        // the session's receive q size
        public const int RcvQSize=100;

        public QosIcmpSession(IProtocol protocol)
            : base(protocol, TxQSize, RcvQSize)
        {
        }

        // override the OnReceive (no state machine here, so don't call base)
        override internal NetStatus OnReceive(object sender,NetPacket pkt,object ctx)
        {
#if DEBUG
            Console.Out.WriteLine("QosIcmpSession:OnReceived, flow capacity={0}",capacity);
#endif
            if (!this.PutPacket(inQueue,inQueueMonitor,pkt,false)) {
                pkt.ReleaseRef();
                // increment the session capacity
                capacity++;
                return NetStatus.Code.PROTOCOL_OK;
            }
            // the packet is in the Q so, return it only after the user consumed it!
            return NetStatus.Code.PROTOCOL_PROCESSING;
        }

        override public byte[] ReadData()
        {
            NetPacket pkt = GetPacketFromQ(inQueue,inQueueMonitor,true,Timeout.Infinite,true);
            byte[] toUser=null;
            if (pkt != null)
                toUser = pkt.ToUser();

            // now the packet can be returned!
            pkt.ReleaseRef();
            Core.Instance().TheDemux.TakeFreePacket(pkt);
            // increment the session capacity
            capacity++;
#if DEBUG
            Console.Out.WriteLine("QosIcmpSession::ReadData, session capacity={0}",capacity);
#endif
            return toUser;
        }

        override public byte[] PollCopyData(int timeout)
        {
            NetPacket pkt = GetPacketFromQ(inQueue,inQueueMonitor,true,timeout,true);
            byte[] toUser=null;
            if (pkt != null) {
                toUser = pkt.ToUser();
                // now the packet can be returned!
                pkt.ReleaseRef();
                Core.Instance().TheDemux.TakeFreePacket(pkt);
                // increment the session capacity
                capacity++;
            }
            return toUser;
        }

        // the user should create an IP+ICMP buffer
        override public int WriteData(byte[] data)
        {
            if (!IsSessionValidForUserWrite()) {
                return -1;
            }

            byte[] pktData = new byte[EthernetFormat.Size+IPFormat.Size+data.Length];
            Array.Copy(data,0,pktData,EthernetFormat.Size,data.Length);
            NetPacket pkt=new NetPacket(pktData);
            PutPacket(outQueue,outQueueMonitor,pkt,true);
            return data.Length;
        }

        override public bool Close()
        {
            Core.Instance().DeregisterSession(prot,this);
            return true;
        }
    }

#region PrivateIcmpSession
    // -------------------------------------------------------
    // The internal ICMP Session (owned by the stack)
    // -------------------------------------------------------
    public class PrivateIcmpSession : QosIcmpSession
    {

        public PrivateIcmpSession(IProtocol p) : base(p)
        {
        }

        // override the OnReceive
        override internal NetStatus OnReceive(object sender,NetPacket pkt,object ctx)
        {
#if DEBUG
            Console.Out.WriteLine("PrivateIcmpSession:OnReceived, flow capacity={0}",capacity);
#endif
            Debug.Assert(ctx!=null);
            NetStatus res = NetStatus.Code.PROTOCOL_OK;
            IcmpFormat.IcmpHeader icmpHeader = (IcmpFormat.IcmpHeader)ctx;
            IPFormat.IPHeader ipHeader = pkt.OverlapContext as IPFormat.IPHeader;
            Multiplexer mux = pkt.AdapterContext as Multiplexer;  // source mux
            Debug.Assert(ipHeader!=null && mux!=null);
            bool handled=false;

            // handle various types
            switch (icmpHeader.type) {
                case (byte)IcmpFormat.IcmpType.ECHO_REQUEST:
                    if (icmpHeader.code == 0) {
#if DEBUG_IP
                        Console.WriteLine("PrivateIcmpSession: Handling ECHO_REQUEST From: {0}", ipHeader.srcAddrIP);
#endif
                        // send reply
                        // clone it since some session may use it (it is readonly!)
                        int length = pkt.Length();
                        byte [] pktData = new byte [length];
                        Array.Copy(pkt.GetRawData(), 0, pktData, 0, length);

                        IcmpFormat.CreateFastEchoReply(
                            pktData,
                            ipHeader.totalLength-IPFormat.Size
                            );
                        NetPacket reply = new NetPacket(pktData);
                        reply.SessionContext=this;
                        mux.Send(reply);
                        handled=true;
                    }
                    break;
                default:
                    break;
            }
            // return the original packet (no use for it anymore)
            pkt.ReleaseRef();
            // only increment the session capacity
            // if this is not ours..
            // this way we apply back pressure and only
            // increase the capacity once sending is done!
            if (!handled)
                capacity++;
#if DEBUG
            Console.Out.WriteLine("PrivateIcmpSession:OnReceived FINISHED, flow capacity={0}",capacity);
#endif
            return res;
        }
    }
#endregion
#endif
}

