using System;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using UnityEngine;

namespace UnityTest
{

    public class DTOFormatter {
    
        private interface ITransferInterface
        {
            void Transfer(ref ResultDTO.MessageType val);
            void Transfer(ref TestResultState val);
            void Transfer(ref byte val);
            void Transfer(ref bool val);
            void Transfer(ref int val);
            void Transfer(ref float val);
            void Transfer(ref double val);
            void Transfer(ref string val);
        }
        
        private class Writer : ITransferInterface
        {
            private readonly Stream _stream;
            public Writer(Stream stream) { _stream = stream; }
        
            private void WriteConvertedNumber(byte[] bytes)
            {
                if(BitConverter.IsLittleEndian)
                    Array.Reverse(bytes);
                _stream.Write(bytes, 0, bytes.Length);
            }
        
            public void Transfer(ref ResultDTO.MessageType val) { _stream.WriteByte((byte)val); }
            public void Transfer(ref TestResultState val) { _stream.WriteByte((byte)val); }
            public void Transfer(ref byte val) { _stream.WriteByte(val); }
            public void Transfer(ref bool val) { _stream.WriteByte((byte)(val ? 0x01 : 0x00)); }
            public void Transfer(ref int val) { WriteConvertedNumber(BitConverter.GetBytes(val)); }
            public void Transfer(ref float val) { WriteConvertedNumber(BitConverter.GetBytes(val)); }
            public void Transfer(ref double val) { WriteConvertedNumber(BitConverter.GetBytes(val)); }
            
            public void Transfer(ref string val) 
            {
                var bytes = Encoding.BigEndianUnicode.GetBytes(val);
                int length = bytes.Length;
                Transfer(ref length);
                _stream.Write(bytes, 0, bytes.Length);
            }
        }
    
        private class Reader : ITransferInterface
        {
            private readonly Stream _stream;
            public Reader(Stream stream) { _stream = stream; }
            
            private byte[] ReadConvertedNumber(int size)
            {
                byte[] buffer = new byte[size];
                _stream.Read (buffer, 0, buffer.Length);
                if(BitConverter.IsLittleEndian)
                    Array.Reverse(buffer);
                return buffer;
            }
            
            public void Transfer(ref ResultDTO.MessageType val) { val = (ResultDTO.MessageType)_stream.ReadByte(); }
            public void Transfer(ref TestResultState val) { val = (TestResultState)_stream.ReadByte(); }
            public void Transfer(ref byte val) { val = (byte)_stream.ReadByte(); }
            public void Transfer(ref bool val) { val = (_stream.ReadByte() != 0); }
            public void Transfer(ref int val) { val = BitConverter.ToInt32(ReadConvertedNumber(4), 0); }
            public void Transfer(ref float val) { val = BitConverter.ToSingle(ReadConvertedNumber(4), 0); }
            public void Transfer(ref double val) { val = BitConverter.ToDouble(ReadConvertedNumber(8), 0); }
            
            public void Transfer(ref string val) 
            {
                int length = 0;
                Transfer (ref length);
                var bytes = new byte[length];
                int remain = length;
                int index = 0;
                do {
                    int bytesRead = _stream.Read(bytes, index, remain);
                    remain -= bytesRead;
                    index += bytesRead;
                } while (remain > 0);
                val = Encoding.BigEndianUnicode.GetString(bytes);
            }
        }
        
        private void Transfer(ResultDTO dto, ITransferInterface transfer)
        {
            transfer.Transfer(ref dto.messageType);
            
            transfer.Transfer(ref dto.levelCount);
            transfer.Transfer(ref dto.loadedLevel);
            transfer.Transfer(ref dto.loadedLevelName);
            
            if(dto.messageType == ResultDTO.MessageType.Ping
               || dto.messageType == ResultDTO.MessageType.RunStarted
               || dto.messageType == ResultDTO.MessageType.RunFinished
               || dto.messageType == ResultDTO.MessageType.RunInterrupted
               || dto.messageType == ResultDTO.MessageType.AllScenesFinished)
                return;
                
            transfer.Transfer(ref dto.testName);
            transfer.Transfer(ref dto.testTimeout);
            
            if(dto.messageType == ResultDTO.MessageType.TestStarted)
                return;
            
            if(transfer is Reader)
                dto.testResult = new SerializableTestResult();
            SerializableTestResult str = (SerializableTestResult)dto.testResult;
            
            transfer.Transfer(ref str.resultState);
            transfer.Transfer(ref str.message);
            transfer.Transfer(ref str.executed);
            transfer.Transfer(ref str.name);
            transfer.Transfer(ref str.fullName);
            transfer.Transfer(ref str.id);
            transfer.Transfer(ref str.isSuccess);
            transfer.Transfer(ref str.duration);
            transfer.Transfer(ref str.stackTrace);
        }
    
        public void Serialize (Stream stream, ResultDTO dto)
        {
            Transfer(dto, new Writer(stream));
        }
        
        public object Deserialize (Stream stream)
        {
            var result = (ResultDTO)FormatterServices.GetSafeUninitializedObject(typeof(ResultDTO));
            Transfer (result, new Reader(stream));
            return result;
        }
    }

}