//
// Copyright (c) ZeroC, Inc. All rights reserved.
//

using System;
using System.Diagnostics;
using System.Net.Sockets;

internal class Transceiver : IceInternal.Transceiver
{
    public Socket fd()
    {
        return _transceiver.fd();
    }

    public int initialize(IceInternal.Buffer readBuffer, IceInternal.Buffer writeBuffer, ref bool hasMoreData)
    {
        _configuration.checkInitializeException();
        if(!_initialized)
        {
            int status = _transceiver.initialize(readBuffer, writeBuffer, ref hasMoreData);
            if(status != IceInternal.SocketOperation.None)
            {
                return status;
            }
            _initialized = true;
        }
        return IceInternal.SocketOperation.None;
    }

    public int closing(bool initiator, Ice.LocalException ex)
    {
        return _transceiver.closing(initiator, ex);
    }

    public void close()
    {
        _transceiver.close();
    }

    public IceInternal.EndpointI bind()
    {
        return _transceiver.bind();
    }

    public int write(IceInternal.Buffer buf)
    {
        if(!_configuration.writeReady() && buf.b.hasRemaining())
        {
            return IceInternal.SocketOperation.Write;
        }

        _configuration.checkWriteException();
        return _transceiver.write(buf);
    }

    public int read(IceInternal.Buffer buf, ref bool hasMoreData)
    {
        if(!_configuration.readReady() && buf.b.hasRemaining())
        {
            return IceInternal.SocketOperation.Read;
        }

        _configuration.checkReadException();

        if(_buffered)
        {
            while(buf.b.hasRemaining())
            {
                if(_readBufferPos == _readBuffer.b.position())
                {
                    _readBufferPos = 0;
                    _readBuffer.b.position(0);
                    _transceiver.read(_readBuffer, ref hasMoreData);
                    if(_readBufferPos == _readBuffer.b.position())
                    {
                        hasMoreData = false;
                        return IceInternal.SocketOperation.Read;
                    }
                }

                int pos = _readBuffer.b.position();
                Debug.Assert(pos > _readBufferPos);
                int requested = buf.b.remaining();
                int available = pos - _readBufferPos;
                Debug.Assert(available > 0);
                if(available >= requested)
                {
                    available = requested;
                }

                byte[] arr = new byte[available];
                _readBuffer.b.position(_readBufferPos);
                _readBuffer.b.get(arr);
                buf.b.put(arr);
                _readBufferPos += available;
                _readBuffer.b.position(pos);
            }
            hasMoreData = _readBufferPos < _readBuffer.b.position();
            return IceInternal.SocketOperation.None;
        }
        else
        {
            return _transceiver.read(buf, ref hasMoreData);
        }
    }

    public bool startRead(IceInternal.Buffer buf, IceInternal.AsyncCallback callback, object state)
    {
        if(_configuration.readReady())
        {
            _configuration.checkReadException(); // Only raise if we're configured to read now.
        }
        if(_buffered)
        {
            int pos = _readBuffer.b.position();
            int available = pos - _readBufferPos;
            if(available > 0)
            {
                int requested = buf.b.remaining();
                if(available >= requested)
                {
                    available = requested;
                }

                byte[] arr = new byte[available];
                _readBuffer.b.position(_readBufferPos);
                _readBuffer.b.get(arr);
                buf.b.put(arr);
                _readBufferPos += available;
                _readBuffer.b.position(pos);
            }

            if(_readBufferPos == _readBuffer.b.position() && buf.b.hasRemaining())
            {
                _readBufferPos = 0;
                _readBuffer.b.position(0);
                return _transceiver.startRead(_readBuffer, callback, state);
            }
            else
            {
                Debug.Assert(!buf.b.hasRemaining());
                return true; // Completed synchronously
            }
        }
        else
        {
            return _transceiver.startRead(buf, callback, state);
        }
    }

    public void finishRead(IceInternal.Buffer buf)
    {
        _configuration.checkReadException();
        if(_buffered)
        {
            if(buf.b.hasRemaining())
            {
                _transceiver.finishRead(_readBuffer);

                int pos = _readBuffer.b.position();
                int requested = buf.b.remaining();
                int available = pos - _readBufferPos;
                if(available > 0)
                {
                    if(available >= requested)
                    {
                        available = requested;
                    }

                    byte[] arr = new byte[available];
                    _readBuffer.b.position(_readBufferPos);
                    _readBuffer.b.get(arr);
                    buf.b.put(arr);
                    _readBufferPos += available;
                    _readBuffer.b.position(pos);
                }
            }
        }
        else
        {
            _transceiver.finishRead(buf);
        }
    }

    public bool startWrite(IceInternal.Buffer buf, IceInternal.AsyncCallback callback, object state, out bool completed)
    {
        _configuration.checkWriteException();
        return _transceiver.startWrite(buf, callback, state, out completed);
    }

    public void finishWrite(IceInternal.Buffer buf)
    {
        _configuration.checkWriteException();
        _transceiver.finishWrite(buf);
    }

    public string protocol()
    {
        return "test-" + _transceiver.protocol();
    }

    public Ice.ConnectionInfo getInfo()
    {
        return _transceiver.getInfo();
    }

    public override string ToString()
    {
        return _transceiver.ToString();
    }

    public string toDetailedString()
    {
        return _transceiver.toDetailedString();
    }

    public void checkSendSize(IceInternal.Buffer buf )
    {
        _transceiver.checkSendSize(buf);
    }

    public void setBufferSize(int rcvSize, int sndSize)
    {
        _transceiver.setBufferSize(rcvSize, sndSize);
    }

    public void destroy()
    {
        _transceiver.destroy();
    }

    public IceInternal.Transceiver getDelegate()
    {
        return _transceiver;
    }

    //
    // Only for use by Connector, Acceptor
    //
    internal Transceiver(IceInternal.Transceiver transceiver)
    {
        _transceiver = transceiver;
        _configuration = Configuration.getInstance();
        _initialized = false;
        _readBuffer = new IceInternal.Buffer();
        _readBuffer.resize(1024 * 8, true); // 8KB buffer
        _readBuffer.b.position(0);
        _readBufferPos = 0;
        _buffered = _configuration.buffered();
    }

    private IceInternal.Transceiver _transceiver;
    private Configuration _configuration;
    private bool _initialized;
    private IceInternal.Buffer _readBuffer;
    private int _readBufferPos;
    private bool _buffered;
}
