﻿using FSO.Common.Serialization;
using FSO.Server.Protocol.Electron;
using FSO.Server.Protocol.Gluon;
using FSO.Server.Protocol.Voltron;
using Mina.Core.Buffer;
using Mina.Core.Session;
using Mina.Filter.Codec;
using System;

namespace FSO.Server.Protocol.Aries
{
    public class AriesProtocolEncoder : IProtocolEncoder
    {
        //private static Logger LOG = LogManager.GetCurrentClassLogger();
        private ISerializationContext Context;

        public AriesProtocolEncoder(ISerializationContext context)
        {
            this.Context = context;
        }

        public void Dispose(IoSession session)
        {
        }

        public void Encode(IoSession session, object message, IProtocolEncoderOutput output)
        {
            if (message is IVoltronPacket)
            {
                EncodeVoltron(session, message, output);
            }
            else if (message is IElectronPacket)
            {
                EncodeElectron(session, message, output);
            }else if(message is IGluonPacket)
            {
                EncodeGluon(session, message, output);
            }
            else if (message is IAriesPacket)
            {
                EncodeAries(session, message, output);
            }
            else if (message.GetType().IsArray)
            {
                object[] arr = (object[])message;
                bool allVoltron = true;

                for (var i = 0; i < arr.Length; i++)
                {
                    if (!(arr[i] is IVoltronPacket))
                    {
                        allVoltron = false;
                        break;
                    }
                }

                //TODO: Chunk these into fewer packets
                for (var i = 0; i < arr.Length; i++)
                {
                    Encode(session, arr[i], output);
                }
            }
        }

        private void EncodeAries(IoSession session, object message, IProtocolEncoderOutput output)
        {
            IAriesPacket ariesPacket = (IAriesPacket)message;
            AriesPacketType ariesPacketType = ariesPacket.GetPacketType();

            var payload = IoBuffer.Allocate(128);
            payload.Order = ByteOrder.LittleEndian;
            payload.AutoExpand = true;
            ariesPacket.Serialize(payload, Context);
            payload.Flip();

            int payloadSize = payload.Remaining;
            IoBuffer headers = IoBuffer.Allocate(12);
            headers.Order = ByteOrder.LittleEndian;

            /** 
		     * Aries header
		     * 	uint32	type
		     *  uint32	timestamp
		     *  uint32	payloadSize
		     */
            uint timestamp = (uint)TimeSpan.FromTicks(DateTime.Now.Ticks - session.CreationTime.Ticks).TotalMilliseconds;
            headers.PutUInt32(ariesPacketType.GetPacketCode());
            headers.PutUInt32(timestamp);
            headers.PutUInt32((uint)payloadSize);

            if (payloadSize > 0)
            {
                headers.AutoExpand = true;
                headers.Put(payload);
            }

            headers.Flip();
            output.Write(headers);
            //output.Flush();
        }
        
        private void EncodeVoltron(IoSession session, object message, IProtocolEncoderOutput output)
        {
            IVoltronPacket voltronPacket = (IVoltronPacket)message;
            VoltronPacketType voltronPacketType = voltronPacket.GetPacketType();
            EncodeVoltronStylePackets(session, output, AriesPacketType.Voltron, voltronPacketType.GetPacketCode(), voltronPacket);
        }

        private void EncodeElectron(IoSession session, object message, IProtocolEncoderOutput output)
        {
            IElectronPacket packet = (IElectronPacket)message;
            ElectronPacketType packetType = packet.GetPacketType();
            EncodeVoltronStylePackets(session, output, AriesPacketType.Electron, packetType.GetPacketCode(), packet);
        }

        private void EncodeGluon(IoSession session, object message, IProtocolEncoderOutput output)
        {
            IGluonPacket packet = (IGluonPacket)message;
            GluonPacketType packetType = packet.GetPacketType();
            EncodeVoltronStylePackets(session, output, AriesPacketType.Gluon, packetType.GetPacketCode(), packet);
        }

        private void EncodeVoltronStylePackets(IoSession session, IProtocolEncoderOutput output, AriesPacketType ariesType, ushort packetType, IoBufferSerializable message)
        {
            var payload = IoBuffer.Allocate(512);
            payload.Order = ByteOrder.BigEndian;
            payload.AutoExpand = true;
            message.Serialize(payload, Context);
            payload.Flip();

            int payloadSize = payload.Remaining;
            IoBuffer headers = IoBuffer.Allocate(18);
            headers.Order = ByteOrder.LittleEndian;

            /** 
		     * Aries header
		     * 	uint32	type
		     *  uint32	timestamp
		     *  uint32	payloadSize
		     */
            uint timestamp = (uint)TimeSpan.FromTicks(DateTime.Now.Ticks - session.CreationTime.Ticks).TotalMilliseconds;
            headers.PutUInt32(ariesType.GetPacketCode());
            headers.PutUInt32(timestamp);
            headers.PutUInt32((uint)payloadSize + 6);

            /** 
		     * Voltron header
		     *  uint16	type
		     *  uint32	payloadSize
		     */
            headers.Order = ByteOrder.BigEndian;
            headers.PutUInt16(packetType);
            headers.PutUInt32((uint)payloadSize + 6);
            
            if (payloadSize > 0)
            {
                headers.AutoExpand = true;
                headers.Put(payload);
            }

            headers.Flip();
            output.Write(headers);
            //output.Flush();
        }
    }
}
